mirror of https://github.com/halo-dev/halo
chore: clean code for next major version
Signed-off-by: Ryan Wang <i@ryanc.cc>pull/1840/head
parent
68d263bef3
commit
641264ba5c
|
@ -1,3 +0,0 @@
|
|||
[submodule "default-theme"]
|
||||
path = src/main/resources/templates/themes/anatole
|
||||
url = https://github.com/halo-dev/halo-theme-anatole
|
148
build.gradle
148
build.gradle
|
@ -1,148 +0,0 @@
|
|||
plugins {
|
||||
id "org.springframework.boot" version "2.5.10"
|
||||
id "io.spring.dependency-management" version "1.0.11.RELEASE"
|
||||
id "checkstyle"
|
||||
id "java"
|
||||
}
|
||||
|
||||
group = "run.halo.app"
|
||||
description = "Halo, An excellent open source blog publishing application."
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
|
||||
checkstyle {
|
||||
toolVersion = "8.39"
|
||||
showViolations = false
|
||||
ignoreFailures = false
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven { url 'https://maven.aliyun.com/repository/public/' }
|
||||
maven { url 'https://maven.aliyun.com/repository/spring/' }
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
configurations {
|
||||
implementation {
|
||||
exclude module: "spring-boot-starter-tomcat"
|
||||
exclude module: "slf4j-log4j12"
|
||||
exclude module: 'junit'
|
||||
}
|
||||
compileOnly {
|
||||
extendsFrom annotationProcessor
|
||||
}
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
// Aligning log4j dependency versions to 2.17.1
|
||||
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
|
||||
if (details.requested.group == 'org.apache.logging.log4j') {
|
||||
details.useVersion '2.17.1'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bootJar {
|
||||
manifest {
|
||||
attributes "Implementation-Title": "Halo Application",
|
||||
"Implementation-Version": archiveVersion
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
guavaVersion = '31.0.1-jre'
|
||||
upyunSdkVersion = '4.2.3'
|
||||
qiniuSdkVersion = '7.8.0'
|
||||
aliyunSdkVersion = '3.13.2'
|
||||
baiduSdkVersion = '0.10.36'
|
||||
qcloudSdkVersion = '5.6.61'
|
||||
minioSdkVersion = '7.1.4'
|
||||
swaggerVersion = "3.0.0"
|
||||
commonsFileUploadVersion = "1.4"
|
||||
commonsLangVersion = '3.12.0'
|
||||
httpclientVersion = '4.5.13'
|
||||
jgitVersion = "5.9.0.202009080501-r"
|
||||
flexmarkVersion = "0.62.2"
|
||||
thumbnailatorVersion = '0.4.14'
|
||||
image4jVersion = "0.7zensight1"
|
||||
flywayVersion = "7.15.0"
|
||||
h2Version = "1.4.199"
|
||||
levelDbVersion = "0.12"
|
||||
annotationsVersion = "3.0.1u2"
|
||||
zxingVersion = '3.4.1'
|
||||
huaweiObsVersion = '3.21.8.1'
|
||||
templateInheritanceVersion = "0.4.RELEASE"
|
||||
jsoupVersion = '1.14.3'
|
||||
diffUtilsVersion = '4.11'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.springframework.boot:spring-boot-starter-actuator"
|
||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa"
|
||||
implementation "org.springframework.boot:spring-boot-starter-web"
|
||||
implementation "org.springframework.boot:spring-boot-starter-jetty"
|
||||
implementation "org.springframework.boot:spring-boot-starter-freemarker"
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
|
||||
implementation "com.sun.mail:jakarta.mail"
|
||||
implementation "com.google.guava:guava:$guavaVersion"
|
||||
implementation "com.upyun:java-sdk:$upyunSdkVersion"
|
||||
implementation "com.qiniu:qiniu-java-sdk:$qiniuSdkVersion"
|
||||
implementation "com.aliyun.oss:aliyun-sdk-oss:$aliyunSdkVersion"
|
||||
implementation "com.baidubce:bce-java-sdk:$baiduSdkVersion"
|
||||
implementation "com.qcloud:cos_api:$qcloudSdkVersion"
|
||||
// TODO Upgrade huaweicloud sdk dependence to fix log4j 0-day vulnerability
|
||||
implementation("com.huaweicloud:esdk-obs-java:$huaweiObsVersion")
|
||||
implementation "io.minio:minio:$minioSdkVersion"
|
||||
implementation "io.springfox:springfox-boot-starter:$swaggerVersion"
|
||||
implementation "commons-fileupload:commons-fileupload:$commonsFileUploadVersion"
|
||||
implementation "org.apache.commons:commons-lang3:$commonsLangVersion"
|
||||
implementation "org.apache.httpcomponents:httpclient:$httpclientVersion"
|
||||
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml"
|
||||
implementation "org.eclipse.jgit:org.eclipse.jgit:$jgitVersion"
|
||||
implementation "com.google.code.findbugs:annotations:$annotationsVersion"
|
||||
|
||||
implementation "com.vladsch.flexmark:flexmark:$flexmarkVersion"
|
||||
implementation "com.vladsch.flexmark:flexmark-ext-attributes:$flexmarkVersion"
|
||||
implementation "com.vladsch.flexmark:flexmark-ext-autolink:$flexmarkVersion"
|
||||
implementation "com.vladsch.flexmark:flexmark-ext-emoji:$flexmarkVersion"
|
||||
implementation "com.vladsch.flexmark:flexmark-ext-escaped-character:$flexmarkVersion"
|
||||
implementation "com.vladsch.flexmark:flexmark-ext-gfm-strikethrough:$flexmarkVersion"
|
||||
implementation "com.vladsch.flexmark:flexmark-ext-gfm-tasklist:$flexmarkVersion"
|
||||
implementation "com.vladsch.flexmark:flexmark-ext-ins:$flexmarkVersion"
|
||||
implementation "com.vladsch.flexmark:flexmark-ext-media-tags:$flexmarkVersion"
|
||||
implementation "com.vladsch.flexmark:flexmark-ext-tables:$flexmarkVersion"
|
||||
implementation "com.vladsch.flexmark:flexmark-ext-toc:$flexmarkVersion"
|
||||
implementation "com.vladsch.flexmark:flexmark-ext-superscript:$flexmarkVersion"
|
||||
implementation "com.vladsch.flexmark:flexmark-ext-yaml-front-matter:$flexmarkVersion"
|
||||
implementation "com.vladsch.flexmark:flexmark-ext-gitlab:$flexmarkVersion"
|
||||
|
||||
implementation "kr.pe.kwonnam.freemarker:freemarker-template-inheritance:$templateInheritanceVersion"
|
||||
implementation "net.coobird:thumbnailator:$thumbnailatorVersion"
|
||||
implementation "net.sf.image4j:image4j:$image4jVersion"
|
||||
implementation "org.flywaydb:flyway-core:$flywayVersion"
|
||||
implementation "com.google.zxing:core:$zxingVersion"
|
||||
implementation "io.github.java-diff-utils:java-diff-utils:$diffUtilsVersion"
|
||||
|
||||
implementation "org.iq80.leveldb:leveldb:$levelDbVersion"
|
||||
runtimeOnly "com.h2database:h2:$h2Version"
|
||||
runtimeOnly "mysql:mysql-connector-java"
|
||||
|
||||
compileOnly "org.projectlombok:lombok"
|
||||
annotationProcessor "org.projectlombok:lombok"
|
||||
|
||||
testCompileOnly "org.projectlombok:lombok"
|
||||
testAnnotationProcessor "org.projectlombok:lombok"
|
||||
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test") {
|
||||
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
|
||||
}
|
||||
testImplementation "org.jsoup:jsoup:$jsoupVersion"
|
||||
|
||||
developmentOnly "org.springframework.boot:spring-boot-devtools"
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
testLogging.showStandardStreams = true
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
version=1.5.0-SNAPSHOT
|
Binary file not shown.
|
@ -1,5 +0,0 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
|
@ -1,185 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 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.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
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
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
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
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
|
@ -1,89 +0,0 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem 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, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
|
@ -1,10 +0,0 @@
|
|||
pluginManagement {
|
||||
repositories {
|
||||
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
|
||||
maven { url 'https://maven.aliyun.com/repository/spring-plugin' }
|
||||
maven { url 'https://repo.spring.io/milestone' }
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = 'halo'
|
|
@ -1,24 +0,0 @@
|
|||
package run.halo.app;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* Halo main class.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2017-11-14
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
// Customize the spring config location
|
||||
System.setProperty("spring.config.additional-location",
|
||||
"optional:file:${user.home}/.halo/,optional:file:${user.home}/halo-dev/");
|
||||
|
||||
// Run application
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package run.halo.app.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import run.halo.app.model.enums.Mode;
|
||||
|
||||
/**
|
||||
* 该注解可以限制某些条件下禁止访问api
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2020-02-14 13:48
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface DisableOnCondition {
|
||||
@AliasFor("mode")
|
||||
Mode value() default Mode.DEMO;
|
||||
|
||||
@AliasFor("value")
|
||||
Mode mode() default Mode.DEMO;
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package run.halo.app.annotation;
|
||||
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author giveup
|
||||
* @description SensitiveConceal
|
||||
* @date 8:18 PM 26/5/2020
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface SensitiveConceal {
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
package run.halo.app.aspect;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.annotation.DisableOnCondition;
|
||||
import run.halo.app.config.properties.HaloProperties;
|
||||
import run.halo.app.exception.ForbiddenException;
|
||||
import run.halo.app.model.enums.Mode;
|
||||
|
||||
/**
|
||||
* 自定义注解DisableApi的切面
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2020-02-14 14:08
|
||||
*/
|
||||
@Aspect
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DisableOnConditionAspect {
|
||||
|
||||
private final HaloProperties haloProperties;
|
||||
|
||||
public DisableOnConditionAspect(HaloProperties haloProperties) {
|
||||
this.haloProperties = haloProperties;
|
||||
}
|
||||
|
||||
@Pointcut("within(run.halo.app.controller..*)")
|
||||
public void pointcut() {
|
||||
}
|
||||
|
||||
@Around("pointcut() && @annotation(disableApi)")
|
||||
public Object around(ProceedingJoinPoint joinPoint,
|
||||
DisableOnCondition disableApi) throws Throwable {
|
||||
Mode mode = disableApi.mode();
|
||||
if (haloProperties.getMode().equals(mode)) {
|
||||
throw new ForbiddenException("禁止访问");
|
||||
}
|
||||
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package run.halo.app.aspect;
|
||||
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.model.entity.BaseComment;
|
||||
import run.halo.app.security.context.SecurityContextHolder;
|
||||
|
||||
|
||||
/**
|
||||
* @author giveup
|
||||
* @description SensitiveMaskAspect
|
||||
* @date 10:22 PM 25/5/2020
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
public class SensitiveConcealAspect {
|
||||
|
||||
|
||||
@Pointcut("within(run.halo.app.repository..*) "
|
||||
+ "&& @annotation(run.halo.app.annotation.SensitiveConceal)")
|
||||
public void pointCut() {
|
||||
}
|
||||
|
||||
private Object sensitiveMask(Object comment) {
|
||||
if (comment instanceof BaseComment) {
|
||||
((BaseComment) comment).setEmail("");
|
||||
((BaseComment) comment).setIpAddress("");
|
||||
}
|
||||
return comment;
|
||||
}
|
||||
|
||||
@Around("pointCut()")
|
||||
public Object mask(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
Object result = joinPoint.proceed();
|
||||
if (SecurityContextHolder.getContext().isAuthenticated()) {
|
||||
return result;
|
||||
}
|
||||
if (result instanceof Iterable) {
|
||||
((Iterable<?>) result).forEach(this::sensitiveMask);
|
||||
}
|
||||
return sensitiveMask(result);
|
||||
}
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
package run.halo.app.cache;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import run.halo.app.config.properties.HaloProperties;
|
||||
import run.halo.app.utils.DateUtils;
|
||||
|
||||
/**
|
||||
* Abstract cache store.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 3/28/19
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractCacheStore<K, V> implements CacheStore<K, V> {
|
||||
|
||||
protected HaloProperties haloProperties;
|
||||
|
||||
/**
|
||||
* Get cache wrapper by key.
|
||||
*
|
||||
* @param key key must not be null
|
||||
* @return an optional cache wrapper
|
||||
*/
|
||||
@NonNull
|
||||
abstract Optional<CacheWrapper<V>> getInternal(@NonNull K key);
|
||||
|
||||
/**
|
||||
* Puts the cache wrapper.
|
||||
*
|
||||
* @param key key must not be null
|
||||
* @param cacheWrapper cache wrapper must not be null
|
||||
*/
|
||||
abstract void putInternal(@NonNull K key, @NonNull CacheWrapper<V> cacheWrapper);
|
||||
|
||||
/**
|
||||
* Puts the cache wrapper if the key is absent.
|
||||
*
|
||||
* @param key key must not be null
|
||||
* @param cacheWrapper cache wrapper must not be null
|
||||
* @return true if the key is absent and the value is set, false if the key is present
|
||||
* before, or null if any other reason
|
||||
*/
|
||||
abstract Boolean putInternalIfAbsent(@NonNull K key, @NonNull CacheWrapper<V> cacheWrapper);
|
||||
|
||||
@Override
|
||||
public Optional<V> get(K key) {
|
||||
Assert.notNull(key, "Cache key must not be blank");
|
||||
|
||||
return getInternal(key).map(cacheWrapper -> {
|
||||
// Check expiration
|
||||
if (cacheWrapper.getExpireAt() != null
|
||||
&& cacheWrapper.getExpireAt().before(run.halo.app.utils.DateUtils.now())) {
|
||||
// Expired then delete it
|
||||
log.warn("Cache key: [{}] has been expired", key);
|
||||
|
||||
// Delete the key
|
||||
delete(key);
|
||||
|
||||
// Return null
|
||||
return null;
|
||||
}
|
||||
|
||||
return cacheWrapper.getData();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(K key, V value, long timeout, TimeUnit timeUnit) {
|
||||
putInternal(key, buildCacheWrapper(value, timeout, timeUnit));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(K key, V value) {
|
||||
putInternal(key, buildCacheWrapper(value, 0, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean putIfAbsent(K key, V value, long timeout, TimeUnit timeUnit) {
|
||||
return putInternalIfAbsent(key, buildCacheWrapper(value, timeout, timeUnit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds cache wrapper.
|
||||
*
|
||||
* @param value cache value must not be null
|
||||
* @param timeout the key expiry time, if the expiry time is less than 1, the cache won't be
|
||||
* expired
|
||||
* @param timeUnit timeout unit must
|
||||
* @return cache wrapper
|
||||
*/
|
||||
@NonNull
|
||||
private CacheWrapper<V> buildCacheWrapper(@NonNull V value, long timeout,
|
||||
@Nullable TimeUnit timeUnit) {
|
||||
Assert.notNull(value, "Cache value must not be null");
|
||||
Assert.isTrue(timeout >= 0, "Cache expiration timeout must not be less than 1");
|
||||
|
||||
Date now = run.halo.app.utils.DateUtils.now();
|
||||
|
||||
Date expireAt = null;
|
||||
|
||||
if (timeout > 0 && timeUnit != null) {
|
||||
expireAt = DateUtils.add(now, timeout, timeUnit);
|
||||
}
|
||||
|
||||
// Build cache wrapper
|
||||
CacheWrapper<V> cacheWrapper = new CacheWrapper<>();
|
||||
cacheWrapper.setCreateAt(now);
|
||||
cacheWrapper.setExpireAt(expireAt);
|
||||
cacheWrapper.setData(value);
|
||||
|
||||
return cacheWrapper;
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
package run.halo.app.cache;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
import run.halo.app.exception.ServiceException;
|
||||
import run.halo.app.utils.JsonUtils;
|
||||
|
||||
/**
|
||||
* String cache store.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractStringCacheStore extends AbstractCacheStore<String, String> {
|
||||
|
||||
protected Optional<CacheWrapper<String>> jsonToCacheWrapper(String json) {
|
||||
Assert.hasText(json, "json value must not be null");
|
||||
CacheWrapper<String> cacheWrapper = null;
|
||||
try {
|
||||
cacheWrapper = JsonUtils.jsonToObject(json, new TypeReference<>() {});
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to convert json to wrapper value bytes: [{}]", json, e);
|
||||
}
|
||||
return Optional.ofNullable(cacheWrapper);
|
||||
}
|
||||
|
||||
public <T> void putAny(String key, T value) {
|
||||
try {
|
||||
put(key, JsonUtils.objectToJson(value));
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new ServiceException("Failed to convert " + value + " to json", e);
|
||||
}
|
||||
}
|
||||
|
||||
public <T> void putAny(@NonNull String key, @NonNull T value, long timeout,
|
||||
@NonNull TimeUnit timeUnit) {
|
||||
try {
|
||||
put(key, JsonUtils.objectToJson(value), timeout, timeUnit);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new ServiceException("Failed to convert " + value + " to json", e);
|
||||
}
|
||||
}
|
||||
|
||||
public <T> Optional<T> getAny(String key, Class<T> type) {
|
||||
Assert.notNull(type, "Type must not be null");
|
||||
|
||||
return get(key).map(value -> {
|
||||
try {
|
||||
return JsonUtils.jsonToObject(value, type);
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to convert json to type: " + type.getName(), e);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
package run.halo.app.cache;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* Cache store interface.
|
||||
*
|
||||
* @param <K> cache key type
|
||||
* @param <V> cache value type
|
||||
* @author johnniang
|
||||
* *
|
||||
*/
|
||||
public interface CacheStore<K, V> {
|
||||
|
||||
/**
|
||||
* Gets by cache key.
|
||||
*
|
||||
* @param key must not be null
|
||||
* @return cache value
|
||||
*/
|
||||
@NonNull
|
||||
Optional<V> get(@NonNull K key);
|
||||
|
||||
/**
|
||||
* Puts a cache which will be expired.
|
||||
*
|
||||
* @param key cache key must not be null
|
||||
* @param value cache value must not be null
|
||||
* @param timeout the key expiration must not be less than 1
|
||||
* @param timeUnit timeout unit
|
||||
*/
|
||||
void put(@NonNull K key, @NonNull V value, long timeout, @NonNull TimeUnit timeUnit);
|
||||
|
||||
|
||||
/**
|
||||
* Puts a non-expired cache.
|
||||
*
|
||||
* @param key cache key must not be null
|
||||
* @param value cache value must not be null
|
||||
*/
|
||||
void put(@NonNull K key, @NonNull V value);
|
||||
|
||||
/**
|
||||
* Puts a cache which will be expired if the key is absent.
|
||||
*
|
||||
* @param key cache key must not be null
|
||||
* @param value cache value must not be null
|
||||
* @param timeout the key expiration must not be less than 1
|
||||
* @param timeUnit timeout unit must not be null
|
||||
* @return true if the key is absent and the value is set, false if the key is present
|
||||
* before, or null if any other reason
|
||||
*/
|
||||
Boolean putIfAbsent(@NonNull K key, @NonNull V value, long timeout, @NonNull TimeUnit timeUnit);
|
||||
|
||||
/**
|
||||
* Delete a key.
|
||||
*
|
||||
* @param key cache key must not be null
|
||||
*/
|
||||
void delete(@NonNull K key);
|
||||
|
||||
/**
|
||||
* Returns a view of the entries stored in this cache as a none thread-safe map.
|
||||
* Modifications made to the map do not directly affect the cache.
|
||||
*/
|
||||
LinkedHashMap<K, V> toMap();
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package run.halo.app.cache;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* Cache wrapper.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
@Data
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
class CacheWrapper<V> implements Serializable {
|
||||
|
||||
/**
|
||||
* Cache data
|
||||
*/
|
||||
private V data;
|
||||
|
||||
/**
|
||||
* Expired time.
|
||||
*/
|
||||
private Date expireAt;
|
||||
|
||||
/**
|
||||
* Create time.
|
||||
*/
|
||||
private Date createAt;
|
||||
}
|
|
@ -1,136 +0,0 @@
|
|||
package run.halo.app.cache;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Optional;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import javax.annotation.PreDestroy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* In-memory cache store.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
@Slf4j
|
||||
public class InMemoryCacheStore extends AbstractStringCacheStore {
|
||||
|
||||
/**
|
||||
* Cleaner schedule period. (ms)
|
||||
*/
|
||||
private static final long PERIOD = 60 * 1000;
|
||||
|
||||
/**
|
||||
* Cache container.
|
||||
*/
|
||||
private static final ConcurrentHashMap<String, CacheWrapper<String>> CACHE_CONTAINER =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
private final Timer timer;
|
||||
|
||||
/**
|
||||
* Lock.
|
||||
*/
|
||||
private final Lock lock = new ReentrantLock();
|
||||
|
||||
public InMemoryCacheStore() {
|
||||
// Run a cache store cleaner
|
||||
timer = new Timer();
|
||||
timer.scheduleAtFixedRate(new CacheExpiryCleaner(), 0, PERIOD);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
Optional<CacheWrapper<String>> getInternal(@NonNull String key) {
|
||||
Assert.hasText(key, "Cache key must not be blank");
|
||||
|
||||
return Optional.ofNullable(CACHE_CONTAINER.get(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
void putInternal(@NonNull String key, @NonNull CacheWrapper<String> cacheWrapper) {
|
||||
Assert.hasText(key, "Cache key must not be blank");
|
||||
Assert.notNull(cacheWrapper, "Cache wrapper must not be null");
|
||||
|
||||
// Put the cache wrapper
|
||||
CacheWrapper<String> putCacheWrapper = CACHE_CONTAINER.put(key, cacheWrapper);
|
||||
log.debug("Put [{}] cache result: [{}], original cache wrapper: [{}]", key, putCacheWrapper,
|
||||
cacheWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
Boolean putInternalIfAbsent(@NonNull String key, @NonNull CacheWrapper<String> cacheWrapper) {
|
||||
Assert.hasText(key, "Cache key must not be blank");
|
||||
Assert.notNull(cacheWrapper, "Cache wrapper must not be null");
|
||||
|
||||
log.debug("Preparing to put key: [{}], value: [{}]", key, cacheWrapper);
|
||||
|
||||
lock.lock();
|
||||
try {
|
||||
// Get the value before
|
||||
Optional<String> valueOptional = get(key);
|
||||
|
||||
if (valueOptional.isPresent()) {
|
||||
log.warn("Failed to put the cache, because the key: [{}] has been present already",
|
||||
key);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Put the cache wrapper
|
||||
putInternal(key, cacheWrapper);
|
||||
log.debug("Put successfully");
|
||||
return true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(@NonNull String key) {
|
||||
Assert.hasText(key, "Cache key must not be blank");
|
||||
|
||||
CACHE_CONTAINER.remove(key);
|
||||
log.debug("Removed key: [{}]", key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LinkedHashMap<String, String> toMap() {
|
||||
LinkedHashMap<String, String> map = new LinkedHashMap<>();
|
||||
CACHE_CONTAINER.forEach((key, value) -> map.put(key, value.getData()));
|
||||
return map;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void preDestroy() {
|
||||
log.debug("Cancelling all timer tasks");
|
||||
timer.cancel();
|
||||
clear();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
CACHE_CONTAINER.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache cleaner.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 03/28/19
|
||||
*/
|
||||
private class CacheExpiryCleaner extends TimerTask {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
CACHE_CONTAINER.keySet().forEach(key -> {
|
||||
if (!InMemoryCacheStore.this.get(key).isPresent()) {
|
||||
log.debug("Deleted the cache: [{}] for expiration", key);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,182 +0,0 @@
|
|||
package run.halo.app.cache;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.iq80.leveldb.DB;
|
||||
import org.iq80.leveldb.DBFactory;
|
||||
import org.iq80.leveldb.DBIterator;
|
||||
import org.iq80.leveldb.Options;
|
||||
import org.iq80.leveldb.WriteBatch;
|
||||
import org.iq80.leveldb.impl.Iq80DBFactory;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import run.halo.app.config.properties.HaloProperties;
|
||||
import run.halo.app.utils.JsonUtils;
|
||||
|
||||
/**
|
||||
* level-db cache store
|
||||
* Create by Pencilso on 2020/1/9 7:20 下午
|
||||
*/
|
||||
@Slf4j
|
||||
public class LevelCacheStore extends AbstractStringCacheStore {
|
||||
/**
|
||||
* Cleaner schedule period. (ms)
|
||||
*/
|
||||
private static final long PERIOD = 60 * 1000;
|
||||
|
||||
private static DB LEVEL_DB;
|
||||
|
||||
private Timer timer;
|
||||
|
||||
public LevelCacheStore(HaloProperties haloProperties) {
|
||||
super.haloProperties = haloProperties;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
if (LEVEL_DB != null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
//work path
|
||||
File folder = new File(haloProperties.getWorkDir() + ".leveldb");
|
||||
DBFactory factory = new Iq80DBFactory();
|
||||
Options options = new Options();
|
||||
options.createIfMissing(true);
|
||||
//open leveldb store folder
|
||||
LEVEL_DB = factory.open(folder, options);
|
||||
timer = new Timer();
|
||||
timer.scheduleAtFixedRate(new CacheExpiryCleaner(), 0, PERIOD);
|
||||
} catch (Exception ex) {
|
||||
log.error("init leveldb error ", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁
|
||||
*/
|
||||
@PreDestroy
|
||||
public void preDestroy() {
|
||||
try {
|
||||
LEVEL_DB.close();
|
||||
timer.cancel();
|
||||
} catch (IOException e) {
|
||||
log.error("close leveldb error ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
Optional<CacheWrapper<String>> getInternal(@NonNull String key) {
|
||||
Assert.hasText(key, "Cache key must not be blank");
|
||||
byte[] bytes = LEVEL_DB.get(stringToBytes(key));
|
||||
if (bytes != null) {
|
||||
String valueJson = bytesToString(bytes);
|
||||
return StringUtils.isEmpty(valueJson) ? Optional.empty() :
|
||||
jsonToCacheWrapper(valueJson);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
void putInternal(@NonNull String key, @NonNull CacheWrapper<String> cacheWrapper) {
|
||||
putInternalIfAbsent(key, cacheWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
Boolean putInternalIfAbsent(@NonNull String key, @NonNull CacheWrapper<String> cacheWrapper) {
|
||||
Assert.hasText(key, "Cache key must not be blank");
|
||||
Assert.notNull(cacheWrapper, "Cache wrapper must not be null");
|
||||
try {
|
||||
LEVEL_DB.put(
|
||||
stringToBytes(key),
|
||||
stringToBytes(JsonUtils.objectToJson(cacheWrapper))
|
||||
);
|
||||
return true;
|
||||
} catch (JsonProcessingException e) {
|
||||
log.warn("Put cache fail json2object key: [{}] value:[{}]", key, cacheWrapper);
|
||||
}
|
||||
log.debug("Cache key: [{}], original cache wrapper: [{}]", key, cacheWrapper);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(@NonNull String key) {
|
||||
LEVEL_DB.delete(stringToBytes(key));
|
||||
log.debug("cache remove key: [{}]", key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LinkedHashMap<String, String> toMap() {
|
||||
LinkedHashMap<String, String> map = new LinkedHashMap<>();
|
||||
LEVEL_DB.forEach(entry -> {
|
||||
String key = bytesToString(entry.getKey());
|
||||
String valueJson = bytesToString(entry.getValue());
|
||||
Optional<CacheWrapper<String>> cacheWrapperOptional = jsonToCacheWrapper(valueJson);
|
||||
if (cacheWrapperOptional.isPresent()) {
|
||||
map.put(key, cacheWrapperOptional.get().getData());
|
||||
} else {
|
||||
map.put(key, null);
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
private byte[] stringToBytes(String str) {
|
||||
return str.getBytes(Charset.defaultCharset());
|
||||
}
|
||||
|
||||
private String bytesToString(byte[] bytes) {
|
||||
return new String(bytes, Charset.defaultCharset());
|
||||
}
|
||||
|
||||
private class CacheExpiryCleaner extends TimerTask {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
//batch
|
||||
WriteBatch writeBatch = LEVEL_DB.createWriteBatch();
|
||||
|
||||
DBIterator iterator = LEVEL_DB.iterator();
|
||||
long currentTimeMillis = System.currentTimeMillis();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<byte[], byte[]> next = iterator.next();
|
||||
if (next.getKey() == null || next.getValue() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String valueJson = bytesToString(next.getValue());
|
||||
Optional<CacheWrapper<String>> stringCacheWrapper =
|
||||
StringUtils.isEmpty(valueJson) ? Optional.empty() :
|
||||
jsonToCacheWrapper(valueJson);
|
||||
if (stringCacheWrapper.isPresent()) {
|
||||
//get expireat time
|
||||
long expireAtTime = stringCacheWrapper.map(CacheWrapper::getExpireAt)
|
||||
.map(Date::getTime)
|
||||
.orElse(0L);
|
||||
//if expire
|
||||
if (expireAtTime != 0 && currentTimeMillis > expireAtTime) {
|
||||
writeBatch.delete(next.getKey());
|
||||
log.debug("deleted the cache: [{}] for expiration",
|
||||
bytesToString(next.getKey()));
|
||||
}
|
||||
}
|
||||
}
|
||||
LEVEL_DB.write(writeBatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
package run.halo.app.cache.lock;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
/**
|
||||
* Cache lock annotation.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 3/28/19
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface CacheLock {
|
||||
|
||||
/**
|
||||
* Cache prefix, default is ""
|
||||
*
|
||||
* @return cache prefix
|
||||
*/
|
||||
@AliasFor("value")
|
||||
String prefix() default "";
|
||||
|
||||
/**
|
||||
* Alias of prefix, default is ""
|
||||
*
|
||||
* @return alias of prefix
|
||||
*/
|
||||
@AliasFor("prefix")
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* Expired time, default is 5.
|
||||
*
|
||||
* @return expired time
|
||||
*/
|
||||
long expired() default 5;
|
||||
|
||||
/**
|
||||
* Time unit, default is TimeUnit.SECONDS.
|
||||
*
|
||||
* @return time unit
|
||||
*/
|
||||
TimeUnit timeUnit() default TimeUnit.SECONDS;
|
||||
|
||||
/**
|
||||
* Delimiter, default is ':'
|
||||
*
|
||||
* @return delimiter
|
||||
*/
|
||||
String delimiter() default ":";
|
||||
|
||||
/**
|
||||
* Whether delete cache after method invocation.
|
||||
*
|
||||
* @return true if delete cache after method invocation; false otherwise
|
||||
*/
|
||||
boolean autoDelete() default true;
|
||||
|
||||
/**
|
||||
* Whether trace the request info.
|
||||
*
|
||||
* @return true if trace the request info; false otherwise
|
||||
*/
|
||||
boolean traceRequest() default false;
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
package run.halo.app.cache.lock;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
import run.halo.app.cache.AbstractStringCacheStore;
|
||||
import run.halo.app.exception.FrequentAccessException;
|
||||
import run.halo.app.exception.ServiceException;
|
||||
import run.halo.app.utils.ServletUtils;
|
||||
|
||||
/**
|
||||
* Interceptor for cache lock annotation.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 3/28/19
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Configuration
|
||||
public class CacheLockInterceptor {
|
||||
|
||||
private static final String CACHE_LOCK_PREFIX = "cache_lock_";
|
||||
|
||||
private static final String CACHE_LOCK_VALUE = "locked";
|
||||
|
||||
private final AbstractStringCacheStore cacheStore;
|
||||
|
||||
public CacheLockInterceptor(AbstractStringCacheStore cacheStore) {
|
||||
this.cacheStore = cacheStore;
|
||||
}
|
||||
|
||||
@Around("@annotation(run.halo.app.cache.lock.CacheLock)")
|
||||
public Object interceptCacheLock(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
// Get method signature
|
||||
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
|
||||
|
||||
log.debug("Starting locking: [{}]", methodSignature.toString());
|
||||
|
||||
// Get cache lock
|
||||
CacheLock cacheLock = methodSignature.getMethod().getAnnotation(CacheLock.class);
|
||||
|
||||
// Build cache lock key
|
||||
String cacheLockKey = buildCacheLockKey(cacheLock, joinPoint);
|
||||
|
||||
log.debug("Built lock key: [{}]", cacheLockKey);
|
||||
|
||||
|
||||
try {
|
||||
// Get from cache
|
||||
Boolean cacheResult = cacheStore
|
||||
.putIfAbsent(cacheLockKey, CACHE_LOCK_VALUE, cacheLock.expired(),
|
||||
cacheLock.timeUnit());
|
||||
|
||||
if (cacheResult == null) {
|
||||
throw new ServiceException("Unknown reason of cache " + cacheLockKey)
|
||||
.setErrorData(cacheLockKey);
|
||||
}
|
||||
|
||||
if (!cacheResult) {
|
||||
throw new FrequentAccessException("访问过于频繁,请稍后再试!").setErrorData(cacheLockKey);
|
||||
}
|
||||
|
||||
// Proceed the method
|
||||
return joinPoint.proceed();
|
||||
} finally {
|
||||
// Delete the cache
|
||||
if (cacheLock.autoDelete()) {
|
||||
cacheStore.delete(cacheLockKey);
|
||||
log.debug("Deleted the cache lock: [{}]", cacheLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String buildCacheLockKey(@NonNull CacheLock cacheLock,
|
||||
@NonNull ProceedingJoinPoint joinPoint) {
|
||||
Assert.notNull(cacheLock, "Cache lock must not be null");
|
||||
Assert.notNull(joinPoint, "Proceeding join point must not be null");
|
||||
|
||||
// Get the method
|
||||
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
|
||||
|
||||
// Build the cache lock key
|
||||
StringBuilder cacheKeyBuilder = new StringBuilder(CACHE_LOCK_PREFIX);
|
||||
|
||||
String delimiter = cacheLock.delimiter();
|
||||
|
||||
if (StringUtils.isNotBlank(cacheLock.prefix())) {
|
||||
cacheKeyBuilder.append(cacheLock.prefix());
|
||||
} else {
|
||||
cacheKeyBuilder.append(methodSignature.getMethod().toString());
|
||||
}
|
||||
|
||||
// Handle cache lock key building
|
||||
Annotation[][] parameterAnnotations = methodSignature.getMethod().getParameterAnnotations();
|
||||
|
||||
for (int i = 0; i < parameterAnnotations.length; i++) {
|
||||
log.debug("Parameter annotation[{}] = {}", i, parameterAnnotations[i]);
|
||||
|
||||
for (int j = 0; j < parameterAnnotations[i].length; j++) {
|
||||
Annotation annotation = parameterAnnotations[i][j];
|
||||
log.debug("Parameter annotation[{}][{}]: {}", i, j, annotation);
|
||||
if (annotation instanceof CacheParam) {
|
||||
// Get current argument
|
||||
Object arg = joinPoint.getArgs()[i];
|
||||
log.debug("Cache param args: [{}]", arg);
|
||||
|
||||
// Append to the cache key
|
||||
cacheKeyBuilder.append(delimiter).append(arg.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cacheLock.traceRequest()) {
|
||||
// Append http request info
|
||||
cacheKeyBuilder.append(delimiter).append(ServletUtils.getRequestIp());
|
||||
}
|
||||
|
||||
return cacheKeyBuilder.toString();
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package run.halo.app.cache.lock;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Cache parameter annotation.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 3/28/19
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface CacheParam {
|
||||
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
package run.halo.app.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import run.halo.app.cache.AbstractStringCacheStore;
|
||||
import run.halo.app.cache.InMemoryCacheStore;
|
||||
import run.halo.app.cache.LevelCacheStore;
|
||||
import run.halo.app.config.attributeconverter.AttributeConverterAutoGenerateConfiguration;
|
||||
import run.halo.app.config.properties.HaloProperties;
|
||||
import run.halo.app.repository.base.BaseRepositoryImpl;
|
||||
import run.halo.app.utils.HttpClientUtils;
|
||||
|
||||
/**
|
||||
* Halo configuration.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
@Slf4j
|
||||
@EnableAsync
|
||||
@EnableScheduling
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(HaloProperties.class)
|
||||
@EnableJpaRepositories(basePackages = "run.halo.app.repository", repositoryBaseClass =
|
||||
BaseRepositoryImpl.class)
|
||||
@Import(AttributeConverterAutoGenerateConfiguration.class)
|
||||
public class HaloConfiguration {
|
||||
|
||||
private final HaloProperties haloProperties;
|
||||
|
||||
public HaloConfiguration(HaloProperties haloProperties) {
|
||||
this.haloProperties = haloProperties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
|
||||
builder.failOnEmptyBeans(false);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
RestTemplate httpsRestTemplate(RestTemplateBuilder builder)
|
||||
throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
|
||||
RestTemplate httpsRestTemplate = builder.build();
|
||||
httpsRestTemplate.setRequestFactory(
|
||||
new HttpComponentsClientHttpRequestFactory(HttpClientUtils.createHttpsClient(
|
||||
(int) haloProperties.getDownloadTimeout().toMillis())));
|
||||
return httpsRestTemplate;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
AbstractStringCacheStore stringCacheStore() {
|
||||
AbstractStringCacheStore stringCacheStore;
|
||||
switch (haloProperties.getCache()) {
|
||||
case "level":
|
||||
stringCacheStore = new LevelCacheStore(this.haloProperties);
|
||||
break;
|
||||
case "memory":
|
||||
default:
|
||||
//memory or default
|
||||
stringCacheStore = new InMemoryCacheStore();
|
||||
break;
|
||||
}
|
||||
log.info("Halo cache store load impl : [{}]", stringCacheStore.getClass());
|
||||
return stringCacheStore;
|
||||
|
||||
}
|
||||
}
|
|
@ -1,210 +0,0 @@
|
|||
package run.halo.app.config;
|
||||
|
||||
import static run.halo.app.model.support.HaloConst.FILE_SEPARATOR;
|
||||
import static run.halo.app.utils.HaloUtils.URL_SEPARATOR;
|
||||
import static run.halo.app.utils.HaloUtils.ensureBoth;
|
||||
import static run.halo.app.utils.HaloUtils.ensureSuffix;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import freemarker.template.TemplateModel;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.servlet.MultipartConfigElement;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import kr.pe.kwonnam.freemarker.inheritance.BlockDirective;
|
||||
import kr.pe.kwonnam.freemarker.inheritance.PutDirective;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.fileupload.FileUploadBase;
|
||||
import org.apache.commons.fileupload.servlet.ServletRequestContext;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.MultipartProperties;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.jackson.JsonComponentModule;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.FileUrlResource;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.web.SortHandlerMethodArgumentResolver;
|
||||
import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.http.CacheControl;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.multipart.MultipartResolver;
|
||||
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import run.halo.app.config.properties.HaloProperties;
|
||||
import run.halo.app.core.PageJacksonSerializer;
|
||||
import run.halo.app.core.freemarker.inheritance.ThemeExtendsDirective;
|
||||
import run.halo.app.factory.StringToEnumConverterFactory;
|
||||
import run.halo.app.security.resolver.AuthenticationArgumentResolver;
|
||||
|
||||
/**
|
||||
* Halo mvc configuration.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2018-01-02
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(MultipartProperties.class)
|
||||
@ImportAutoConfiguration(exclude = MultipartAutoConfiguration.class)
|
||||
public class HaloMvcConfiguration implements WebMvcConfigurer {
|
||||
|
||||
private static final String FILE_PROTOCOL = "file:///";
|
||||
private final PageableHandlerMethodArgumentResolver pageableResolver;
|
||||
private final SortHandlerMethodArgumentResolver sortResolver;
|
||||
private final HaloProperties haloProperties;
|
||||
@Value("${springfox.documentation.swagger-ui.base-url:}")
|
||||
private String swaggerBaseUrl;
|
||||
|
||||
public HaloMvcConfiguration(PageableHandlerMethodArgumentResolver pageableResolver,
|
||||
SortHandlerMethodArgumentResolver sortResolver,
|
||||
HaloProperties haloProperties) {
|
||||
this.pageableResolver = pageableResolver;
|
||||
this.sortResolver = sortResolver;
|
||||
this.haloProperties = haloProperties;
|
||||
}
|
||||
|
||||
// @Bean
|
||||
public Map<String, TemplateModel> freemarkerLayoutDirectives() {
|
||||
Map<String, TemplateModel> freemarkerLayoutDirectives = new HashMap<>();
|
||||
freemarkerLayoutDirectives.put("extends", new ThemeExtendsDirective());
|
||||
freemarkerLayoutDirectives.put("block", new BlockDirective());
|
||||
freemarkerLayoutDirectives.put("put", new PutDirective());
|
||||
|
||||
return freemarkerLayoutDirectives;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuring multipartResolver for large file upload..
|
||||
*
|
||||
* @return new multipartResolver
|
||||
*/
|
||||
@Bean(name = "multipartResolver")
|
||||
@ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled",
|
||||
havingValue = "true", matchIfMissing = true)
|
||||
MultipartResolver multipartResolver(MultipartProperties multipartProperties)
|
||||
throws IOException {
|
||||
MultipartConfigElement multipartConfigElement = multipartProperties.createMultipartConfig();
|
||||
CommonsMultipartResolver resolver = new CommonsMultipartResolver() {
|
||||
@Override
|
||||
public boolean isMultipart(@NonNull HttpServletRequest request) {
|
||||
final var method = request.getMethod();
|
||||
if (!"POST".equalsIgnoreCase(method) && !"PUT".equalsIgnoreCase(method)) {
|
||||
return false;
|
||||
}
|
||||
return FileUploadBase.isMultipartContent(new ServletRequestContext(request));
|
||||
}
|
||||
};
|
||||
resolver.setDefaultEncoding("UTF-8");
|
||||
resolver.setMaxUploadSize(multipartConfigElement.getMaxRequestSize());
|
||||
resolver.setMaxUploadSizePerFile(multipartConfigElement.getMaxFileSize());
|
||||
var location = multipartProperties.getLocation();
|
||||
if (StringUtils.hasText(location)) {
|
||||
FileUrlResource resource = new FileUrlResource(location);
|
||||
resolver.setUploadTempDir(resource);
|
||||
}
|
||||
|
||||
//lazy multipart parsing, throwing parse exceptions once the application attempts to
|
||||
// obtain multipart files
|
||||
resolver.setResolveLazily(multipartProperties.isResolveLazily());
|
||||
|
||||
return resolver;
|
||||
}
|
||||
|
||||
@Bean
|
||||
WebMvcRegistrations webMvcRegistrations() {
|
||||
return new WebMvcRegistrations() {
|
||||
@Override
|
||||
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
|
||||
return new HaloRequestMappingHandlerMapping(haloProperties);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
converters.stream()
|
||||
.filter(c -> c instanceof MappingJackson2HttpMessageConverter)
|
||||
.findFirst()
|
||||
.ifPresent(converter -> {
|
||||
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter =
|
||||
(MappingJackson2HttpMessageConverter) converter;
|
||||
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
|
||||
JsonComponentModule module = new JsonComponentModule();
|
||||
module.addSerializer(PageImpl.class, new PageJacksonSerializer());
|
||||
ObjectMapper objectMapper = builder.modules(module).build();
|
||||
mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
|
||||
resolvers.add(new AuthenticationArgumentResolver());
|
||||
resolvers.add(pageableResolver);
|
||||
resolvers.add(sortResolver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addViewControllers(ViewControllerRegistry registry) {
|
||||
// for backward compatibility
|
||||
registry.addViewController("/swagger-ui.html")
|
||||
.setViewName("redirect:" + swaggerBaseUrl + "/swagger-ui/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuring static resource path
|
||||
*
|
||||
* @param registry registry
|
||||
*/
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
String workDir = FILE_PROTOCOL + ensureSuffix(haloProperties.getWorkDir(), FILE_SEPARATOR);
|
||||
|
||||
// register /** resource handler.
|
||||
registry.addResourceHandler("/**")
|
||||
.addResourceLocations("classpath:/admin/")
|
||||
.addResourceLocations(workDir + "static/");
|
||||
|
||||
// register /themes/** resource handler.
|
||||
registry.addResourceHandler("/themes/**")
|
||||
.addResourceLocations(workDir + "templates/themes/");
|
||||
|
||||
String uploadUrlPattern =
|
||||
ensureBoth(haloProperties.getUploadUrlPrefix(), URL_SEPARATOR) + "**";
|
||||
String adminPathPattern = ensureSuffix(haloProperties.getAdminPath(), URL_SEPARATOR) + "**";
|
||||
|
||||
registry.addResourceHandler(uploadUrlPattern)
|
||||
.setCacheControl(CacheControl.maxAge(7L, TimeUnit.DAYS))
|
||||
.addResourceLocations(workDir + "upload/");
|
||||
registry.addResourceHandler(adminPathPattern)
|
||||
.addResourceLocations("classpath:/admin/");
|
||||
|
||||
// If doc is enable
|
||||
registry.addResourceHandler("swagger-ui.html")
|
||||
.addResourceLocations("classpath:/META-INF/resources/");
|
||||
registry.addResourceHandler("/webjars/**")
|
||||
.addResourceLocations("classpath:/META-INF/resources/webjars/");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
registry.addConverterFactory(new StringToEnumConverterFactory());
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
package run.halo.app.config;
|
||||
|
||||
import static run.halo.app.utils.HaloUtils.URL_SEPARATOR;
|
||||
import static run.halo.app.utils.HaloUtils.ensureBoth;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import run.halo.app.config.properties.HaloProperties;
|
||||
import run.halo.app.event.StaticStorageChangedEvent;
|
||||
|
||||
/**
|
||||
* @author ryanwang
|
||||
* @date 2020-03-24
|
||||
*/
|
||||
@Slf4j
|
||||
public class HaloRequestMappingHandlerMapping extends RequestMappingHandlerMapping
|
||||
implements ApplicationListener<StaticStorageChangedEvent> {
|
||||
|
||||
private final Set<String> blackPatterns = new HashSet<>(16);
|
||||
|
||||
private final PathMatcher pathMatcher;
|
||||
|
||||
private final HaloProperties haloProperties;
|
||||
|
||||
public HaloRequestMappingHandlerMapping(HaloProperties haloProperties) {
|
||||
this.haloProperties = haloProperties;
|
||||
this.initBlackPatterns();
|
||||
pathMatcher = new AntPathMatcher();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request)
|
||||
throws Exception {
|
||||
log.debug("Looking path: [{}]", lookupPath);
|
||||
for (String blackPattern : blackPatterns) {
|
||||
if (this.pathMatcher.match(blackPattern, lookupPath)) {
|
||||
log.debug("Skipped path [{}] with pattern: [{}]", lookupPath, blackPattern);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return super.lookupHandlerMethod(lookupPath, request);
|
||||
}
|
||||
|
||||
private void initBlackPatterns() {
|
||||
String uploadUrlPattern =
|
||||
ensureBoth(haloProperties.getUploadUrlPrefix(), URL_SEPARATOR) + "**";
|
||||
String adminPathPattern =
|
||||
ensureBoth(haloProperties.getAdminPath(), URL_SEPARATOR) + "?*/**";
|
||||
|
||||
blackPatterns.add("/themes/**");
|
||||
blackPatterns.add("/js/**");
|
||||
blackPatterns.add("/images/**");
|
||||
blackPatterns.add("/fonts/**");
|
||||
blackPatterns.add("/css/**");
|
||||
blackPatterns.add("/assets/**");
|
||||
blackPatterns.add("/color.less");
|
||||
blackPatterns.add("/swagger-ui.html");
|
||||
blackPatterns.add("/swagger-ui/**");
|
||||
blackPatterns.add("/csrf");
|
||||
blackPatterns.add("/webjars/**");
|
||||
blackPatterns.add(uploadUrlPattern);
|
||||
blackPatterns.add(adminPathPattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(StaticStorageChangedEvent event) {
|
||||
Path staticPath = event.getStaticPath();
|
||||
try (Stream<Path> rootPathStream = Files.list(staticPath)) {
|
||||
synchronized (this) {
|
||||
blackPatterns.clear();
|
||||
initBlackPatterns();
|
||||
rootPathStream.forEach(rootPath -> {
|
||||
if (Files.isDirectory(rootPath)) {
|
||||
String directoryPattern = "/" + rootPath.getFileName().toString()
|
||||
+ "/**";
|
||||
blackPatterns.add(directoryPattern);
|
||||
log.debug("Exclude for folder path pattern: [{}]", directoryPattern);
|
||||
} else {
|
||||
String pathPattern = "/" + rootPath.getFileName().toString();
|
||||
blackPatterns.add(pathPattern);
|
||||
log.debug("Exclude for file path pattern: [{}]", pathPattern);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to refresh static directory mapping", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,265 +0,0 @@
|
|||
package run.halo.app.config;
|
||||
|
||||
import static run.halo.app.model.support.HaloConst.ADMIN_TOKEN_HEADER_NAME;
|
||||
import static run.halo.app.model.support.HaloConst.ADMIN_TOKEN_QUERY_NAME;
|
||||
import static run.halo.app.model.support.HaloConst.API_ACCESS_KEY_HEADER_NAME;
|
||||
import static run.halo.app.model.support.HaloConst.API_ACCESS_KEY_QUERY_NAME;
|
||||
import static run.halo.app.model.support.HaloConst.HALO_VERSION;
|
||||
import static run.halo.app.utils.SwaggerUtils.customMixin;
|
||||
import static run.halo.app.utils.SwaggerUtils.propertyBuilder;
|
||||
import static springfox.documentation.schema.AlternateTypeRules.newRule;
|
||||
|
||||
import com.fasterxml.classmate.TypeResolver;
|
||||
import io.swagger.models.auth.In;
|
||||
import java.lang.reflect.Type;
|
||||
import java.time.temporal.Temporal;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import run.halo.app.utils.SwaggerUtils;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.schema.AlternateTypeRule;
|
||||
import springfox.documentation.schema.AlternateTypeRuleConvention;
|
||||
import springfox.documentation.schema.WildcardType;
|
||||
import springfox.documentation.service.ApiInfo;
|
||||
import springfox.documentation.service.ApiKey;
|
||||
import springfox.documentation.service.AuthorizationScope;
|
||||
import springfox.documentation.service.Contact;
|
||||
import springfox.documentation.service.SecurityReference;
|
||||
import springfox.documentation.service.SecurityScheme;
|
||||
import springfox.documentation.spi.service.contexts.SecurityContext;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger.web.DocExpansion;
|
||||
import springfox.documentation.swagger.web.ModelRendering;
|
||||
import springfox.documentation.swagger.web.OperationsSorter;
|
||||
import springfox.documentation.swagger.web.SecurityConfiguration;
|
||||
import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
|
||||
import springfox.documentation.swagger.web.TagsSorter;
|
||||
import springfox.documentation.swagger.web.UiConfiguration;
|
||||
import springfox.documentation.swagger.web.UiConfigurationBuilder;
|
||||
|
||||
/**
|
||||
* Swagger configuration.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@ConditionalOnProperty(
|
||||
value = "springfox.documentation.enabled",
|
||||
havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public class SwaggerConfiguration {
|
||||
|
||||
@Bean
|
||||
public Docket haloDefaultApi() {
|
||||
return buildApiDocket("run.halo.app.content.api",
|
||||
"run.halo.app.controller.content.api",
|
||||
"/api/content/**")
|
||||
.securitySchemes(contentApiKeys())
|
||||
.securityContexts(contentSecurityContext());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Docket haloAdminApi() {
|
||||
return buildApiDocket("run.halo.app.admin.api",
|
||||
"run.halo.app.controller.admin",
|
||||
"/api/admin/**")
|
||||
.securitySchemes(adminApiKeys())
|
||||
.securityContexts(adminSecurityContext());
|
||||
}
|
||||
|
||||
@Bean
|
||||
SecurityConfiguration security() {
|
||||
return SecurityConfigurationBuilder.builder()
|
||||
.clientId("halo-app-client-id")
|
||||
.clientSecret("halo-app-client-secret")
|
||||
.realm("halo-app-realm")
|
||||
.appName("halo-app")
|
||||
.scopeSeparator(",")
|
||||
.additionalQueryStringParams(null)
|
||||
.useBasicAuthenticationWithAccessCodeGrant(false)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
UiConfiguration uiConfig() {
|
||||
return UiConfigurationBuilder.builder()
|
||||
.deepLinking(true)
|
||||
.displayOperationId(false)
|
||||
.defaultModelsExpandDepth(1)
|
||||
.defaultModelExpandDepth(1)
|
||||
.defaultModelRendering(ModelRendering.EXAMPLE)
|
||||
.displayRequestDuration(false)
|
||||
.docExpansion(DocExpansion.NONE)
|
||||
.filter(false)
|
||||
.maxDisplayedTags(null)
|
||||
.operationsSorter(OperationsSorter.ALPHA)
|
||||
.showExtensions(false)
|
||||
.showCommonExtensions(false)
|
||||
.tagsSorter(TagsSorter.ALPHA)
|
||||
.supportedSubmitMethods(UiConfiguration.Constants.DEFAULT_SUBMIT_METHODS)
|
||||
.validatorUrl(null)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Docket buildApiDocket(@NonNull String groupName, @NonNull String basePackage,
|
||||
@NonNull String antPattern) {
|
||||
Assert.hasText(groupName, "Group name must not be blank");
|
||||
Assert.hasText(basePackage, "Base package must not be blank");
|
||||
Assert.hasText(antPattern, "Ant pattern must not be blank");
|
||||
|
||||
return SwaggerUtils.defaultDocket()
|
||||
.groupName(groupName)
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage(basePackage))
|
||||
.paths(PathSelectors.ant(antPattern))
|
||||
.build()
|
||||
.apiInfo(apiInfo())
|
||||
.directModelSubstitute(Temporal.class, String.class);
|
||||
}
|
||||
|
||||
private List<SecurityScheme> adminApiKeys() {
|
||||
return Arrays.asList(
|
||||
new ApiKey(ADMIN_TOKEN_HEADER_NAME, ADMIN_TOKEN_HEADER_NAME, In.HEADER.name()),
|
||||
new ApiKey(ADMIN_TOKEN_QUERY_NAME, ADMIN_TOKEN_QUERY_NAME, In.QUERY.name())
|
||||
);
|
||||
}
|
||||
|
||||
private List<SecurityContext> adminSecurityContext() {
|
||||
final PathMatcher pathMatcher = new AntPathMatcher();
|
||||
return Collections.singletonList(
|
||||
SecurityContext.builder()
|
||||
.securityReferences(adminApiAuths())
|
||||
.operationSelector(operationContext -> {
|
||||
var requestMappingPattern = operationContext.requestMappingPattern();
|
||||
return pathMatcher.match("/api/admin/**/*", requestMappingPattern);
|
||||
})
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
private List<SecurityScheme> contentApiKeys() {
|
||||
return Arrays.asList(
|
||||
new ApiKey(API_ACCESS_KEY_HEADER_NAME, API_ACCESS_KEY_HEADER_NAME, In.HEADER.name()),
|
||||
new ApiKey(API_ACCESS_KEY_QUERY_NAME, API_ACCESS_KEY_QUERY_NAME, In.QUERY.name())
|
||||
);
|
||||
}
|
||||
|
||||
private List<SecurityContext> contentSecurityContext() {
|
||||
final PathMatcher pathMatcher = new AntPathMatcher();
|
||||
return Collections.singletonList(
|
||||
SecurityContext.builder()
|
||||
.securityReferences(contentApiAuths())
|
||||
.operationSelector(operationContext -> {
|
||||
var requestMappingPattern = operationContext.requestMappingPattern();
|
||||
return pathMatcher.match("/api/content/**/*", requestMappingPattern);
|
||||
})
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
private List<SecurityReference> adminApiAuths() {
|
||||
AuthorizationScope[] authorizationScopes =
|
||||
{new AuthorizationScope("Admin api", "Access admin api")};
|
||||
return Arrays.asList(new SecurityReference(ADMIN_TOKEN_HEADER_NAME, authorizationScopes),
|
||||
new SecurityReference(ADMIN_TOKEN_QUERY_NAME, authorizationScopes));
|
||||
}
|
||||
|
||||
private List<SecurityReference> contentApiAuths() {
|
||||
AuthorizationScope[] authorizationScopes =
|
||||
{new AuthorizationScope("content api", "Access content api")};
|
||||
return Arrays.asList(new SecurityReference(API_ACCESS_KEY_HEADER_NAME, authorizationScopes),
|
||||
new SecurityReference(API_ACCESS_KEY_QUERY_NAME, authorizationScopes));
|
||||
}
|
||||
|
||||
private ApiInfo apiInfo() {
|
||||
return new ApiInfoBuilder()
|
||||
.title("Halo API Documentation")
|
||||
.description("Documentation for Halo API")
|
||||
.version(HALO_VERSION)
|
||||
.termsOfServiceUrl("https://github.com/halo-dev")
|
||||
.contact(
|
||||
new Contact("halo-dev", "https://github.com/halo-dev/halo/issues", "hi@halo.run"))
|
||||
.license("GNU General Public License v3.0")
|
||||
.licenseUrl("https://github.com/halo-dev/halo/blob/master/LICENSE")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AlternateTypeRuleConvention customizeConvention(TypeResolver resolver) {
|
||||
return new AlternateTypeRuleConvention() {
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AlternateTypeRule> rules() {
|
||||
return Arrays.asList(
|
||||
newRule(resolver.resolve(Page.class, WildcardType.class),
|
||||
resolver.resolve(CustomizedPage.class, WildcardType.class)),
|
||||
newRule(resolver.resolve(Pageable.class), resolver.resolve(pageableMixin())),
|
||||
newRule(resolver.resolve(Sort.class), resolver.resolve(sortMixin())));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Type sortMixin() {
|
||||
return customMixin(Sort.class,
|
||||
Collections.singletonList(propertyBuilder(String[].class, "sort")));
|
||||
}
|
||||
|
||||
private Type pageableMixin() {
|
||||
return customMixin(Pageable.class, Arrays.asList(
|
||||
propertyBuilder(Integer.class, "page"),
|
||||
propertyBuilder(Integer.class, "size"),
|
||||
propertyBuilder(String[].class, "sort")
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative page type.
|
||||
*
|
||||
* @param <T> content type
|
||||
* @author johnniang
|
||||
*/
|
||||
interface CustomizedPage<T> {
|
||||
|
||||
List<T> getContent();
|
||||
|
||||
int getPage();
|
||||
|
||||
int getPages();
|
||||
|
||||
long getTotal();
|
||||
|
||||
int getRpp();
|
||||
|
||||
boolean getHasNext();
|
||||
|
||||
boolean getHasPrevious();
|
||||
|
||||
boolean getIsFirst();
|
||||
|
||||
boolean getIsEmpty();
|
||||
|
||||
boolean getHasContent();
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package run.halo.app.config.attributeconverter;
|
||||
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryBuilderCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* Jpa configuration.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
public class AttributeConverterAutoGenerateConfiguration {
|
||||
|
||||
@Bean
|
||||
EntityManagerFactoryBuilderCustomizer entityManagerFactoryBuilderCustomizer(
|
||||
ConfigurableListableBeanFactory factory) {
|
||||
return builder -> builder.setPersistenceUnitPostProcessors(
|
||||
new AutoGenerateConverterPersistenceUnitPostProcessor(factory));
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
package run.halo.app.config.attributeconverter;
|
||||
|
||||
import static net.bytebuddy.description.annotation.AnnotationDescription.Builder.ofType;
|
||||
import static net.bytebuddy.description.type.TypeDescription.Generic.Builder.parameterizedType;
|
||||
import static net.bytebuddy.implementation.FieldAccessor.ofField;
|
||||
import static net.bytebuddy.implementation.MethodDelegation.to;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isDefaultConstructor;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import javax.persistence.AttributeConverter;
|
||||
import javax.persistence.Converter;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.NamingStrategy;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
|
||||
import net.bytebuddy.implementation.MethodCall;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* Attribute converter auto generator.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
class AttributeConverterAutoGenerator {
|
||||
|
||||
/**
|
||||
* Auto generation suffix.
|
||||
*/
|
||||
public static final String AUTO_GENERATION_SUFFIX = "$AttributeConverterGeneratedByByteBuddy";
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
public AttributeConverterAutoGenerator(ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
public <T> Class<?> generate(Class<T> clazz) {
|
||||
try {
|
||||
return new ByteBuddy()
|
||||
.with(new NamingStrategy.AbstractBase() {
|
||||
@Override
|
||||
protected String name(TypeDescription superClass) {
|
||||
return clazz.getName() + AUTO_GENERATION_SUFFIX;
|
||||
}
|
||||
})
|
||||
.subclass(
|
||||
parameterizedType(AttributeConverter.class, clazz, Integer.class).build())
|
||||
.annotateType(ofType(Converter.class).define("autoApply", true).build())
|
||||
.constructor(isDefaultConstructor())
|
||||
.intercept(MethodCall.invoke(Object.class.getDeclaredConstructor())
|
||||
.andThen(ofField("enumType").setsValue(clazz)))
|
||||
.defineField("enumType", Class.class, Modifier.PRIVATE | Modifier.FINAL)
|
||||
.method(named("convertToDatabaseColumn"))
|
||||
.intercept(to(AttributeConverterInterceptor.class))
|
||||
.method(named("convertToEntityAttribute"))
|
||||
.intercept(to(AttributeConverterInterceptor.class))
|
||||
.make()
|
||||
.load(this.classLoader, ClassLoadingStrategy.Default.INJECTION.allowExistingTypes())
|
||||
.getLoaded();
|
||||
} catch (NoSuchMethodException e) {
|
||||
// should never happen
|
||||
throw new RuntimeException("Failed to get declared constructor.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isGeneratedByByteBuddy(String className) {
|
||||
return StringUtils.endsWith(className, AUTO_GENERATION_SUFFIX);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package run.halo.app.config.attributeconverter;
|
||||
|
||||
import net.bytebuddy.implementation.bind.annotation.FieldValue;
|
||||
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
|
||||
import run.halo.app.model.enums.ValueEnum;
|
||||
|
||||
/**
|
||||
* Attribute Converter Interceptor.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
public class AttributeConverterInterceptor {
|
||||
|
||||
private AttributeConverterInterceptor() {
|
||||
}
|
||||
|
||||
@RuntimeType
|
||||
public static <T extends Enum<T> & ValueEnum<V>, V> V convertToDatabaseColumn(T attribute) {
|
||||
return attribute == null ? null : attribute.getValue();
|
||||
}
|
||||
|
||||
@RuntimeType
|
||||
public static <T extends Enum<T> & ValueEnum<V>, V> T convertToEntityAttribute(V dbData,
|
||||
@FieldValue("enumType") Class<T> enumType) {
|
||||
return dbData == null ? null : ValueEnum.valueToEnum(enumType, dbData);
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
package run.halo.app.config.attributeconverter;
|
||||
|
||||
import static java.util.stream.Collectors.toUnmodifiableSet;
|
||||
|
||||
import java.util.Set;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.core.type.filter.AssignableTypeFilter;
|
||||
import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo;
|
||||
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitPostProcessor;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import run.halo.app.model.enums.ValueEnum;
|
||||
import run.halo.app.model.properties.PropertyEnum;
|
||||
|
||||
/**
|
||||
* Attribute converter persistence unit post processor.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
class AutoGenerateConverterPersistenceUnitPostProcessor implements PersistenceUnitPostProcessor {
|
||||
|
||||
private static final String PACKAGE_TO_SCAN = "run.halo.app";
|
||||
|
||||
private final ConfigurableListableBeanFactory factory;
|
||||
|
||||
public AutoGenerateConverterPersistenceUnitPostProcessor(
|
||||
ConfigurableListableBeanFactory factory) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo pui) {
|
||||
var generator = new AttributeConverterAutoGenerator(factory.getBeanClassLoader());
|
||||
|
||||
findValueEnumClasses()
|
||||
.stream()
|
||||
.map(generator::generate)
|
||||
.map(Class::getName)
|
||||
.forEach(pui::addManagedClassName);
|
||||
}
|
||||
|
||||
private Set<Class<?>> findValueEnumClasses() {
|
||||
var scanner = new ClassPathScanningCandidateComponentProvider(false);
|
||||
// include ValueEnum class
|
||||
scanner.addIncludeFilter(new AssignableTypeFilter(ValueEnum.class));
|
||||
// exclude PropertyEnum class
|
||||
scanner.addExcludeFilter(new AssignableTypeFilter(PropertyEnum.class));
|
||||
|
||||
return scanner.findCandidateComponents(PACKAGE_TO_SCAN)
|
||||
.stream()
|
||||
.filter(bd -> bd.getBeanClassName() != null)
|
||||
.map(bd -> ClassUtils.resolveClassName(bd.getBeanClassName(), null))
|
||||
.collect(toUnmodifiableSet());
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
package run.halo.app.config.properties;
|
||||
|
||||
import static run.halo.app.model.support.HaloConst.FILE_SEPARATOR;
|
||||
import static run.halo.app.model.support.HaloConst.TEMP_DIR;
|
||||
import static run.halo.app.model.support.HaloConst.USER_HOME;
|
||||
import static run.halo.app.utils.HaloUtils.ensureSuffix;
|
||||
|
||||
import java.time.Duration;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import run.halo.app.model.enums.Mode;
|
||||
|
||||
|
||||
/**
|
||||
* Halo configuration properties.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @date 2019-03-15
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties("halo")
|
||||
public class HaloProperties {
|
||||
|
||||
/**
|
||||
* Authentication enabled.
|
||||
*/
|
||||
private boolean authEnabled = true;
|
||||
|
||||
/**
|
||||
* Halo startup mode.
|
||||
*/
|
||||
private Mode mode = Mode.PRODUCTION;
|
||||
|
||||
/**
|
||||
* Admin path.
|
||||
*/
|
||||
private String adminPath = "admin";
|
||||
|
||||
/**
|
||||
* Work directory.
|
||||
*/
|
||||
private String workDir = ensureSuffix(USER_HOME, FILE_SEPARATOR) + ".halo" + FILE_SEPARATOR;
|
||||
|
||||
/**
|
||||
* Halo backup directory.(Not recommended to modify this config);
|
||||
*/
|
||||
private String backupDir =
|
||||
ensureSuffix(TEMP_DIR, FILE_SEPARATOR) + "halo-backup" + FILE_SEPARATOR;
|
||||
|
||||
/**
|
||||
* Halo backup markdown directory.(Not recommended to modify this config);
|
||||
*/
|
||||
private String backupMarkdownDir =
|
||||
ensureSuffix(TEMP_DIR, FILE_SEPARATOR) + "halo-backup-markdown" + FILE_SEPARATOR;
|
||||
|
||||
/**
|
||||
* Halo data export directory.
|
||||
*/
|
||||
private String dataExportDir =
|
||||
ensureSuffix(TEMP_DIR, FILE_SEPARATOR) + "halo-data-export" + FILE_SEPARATOR;
|
||||
|
||||
/**
|
||||
* Upload prefix.
|
||||
*/
|
||||
private String uploadUrlPrefix = "upload";
|
||||
|
||||
/**
|
||||
* Download Timeout.
|
||||
*/
|
||||
private Duration downloadTimeout = Duration.ofSeconds(30);
|
||||
|
||||
/**
|
||||
* cache store impl
|
||||
* memory
|
||||
* level
|
||||
*/
|
||||
private String cache = "memory";
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import javax.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.annotation.DisableOnCondition;
|
||||
import run.halo.app.cache.lock.CacheLock;
|
||||
import run.halo.app.model.dto.EnvironmentDTO;
|
||||
import run.halo.app.model.dto.LoginPreCheckDTO;
|
||||
import run.halo.app.model.entity.User;
|
||||
import run.halo.app.model.enums.MFAType;
|
||||
import run.halo.app.model.params.LoginParam;
|
||||
import run.halo.app.model.params.ResetPasswordParam;
|
||||
import run.halo.app.model.params.ResetPasswordSendCodeParam;
|
||||
import run.halo.app.model.properties.PrimaryProperties;
|
||||
import run.halo.app.model.support.BaseResponse;
|
||||
import run.halo.app.security.token.AuthToken;
|
||||
import run.halo.app.service.AdminService;
|
||||
import run.halo.app.service.OptionService;
|
||||
|
||||
/**
|
||||
* Admin controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @date 2019-03-19
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/admin")
|
||||
public class AdminController {
|
||||
|
||||
private final AdminService adminService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
public AdminController(AdminService adminService, OptionService optionService) {
|
||||
this.adminService = adminService;
|
||||
this.optionService = optionService;
|
||||
}
|
||||
|
||||
@GetMapping(value = "/is_installed")
|
||||
@ApiOperation("Checks Installation status")
|
||||
public boolean isInstall() {
|
||||
return optionService.getByPropertyOrDefault(PrimaryProperties.IS_INSTALLED, Boolean.class,
|
||||
false);
|
||||
}
|
||||
|
||||
@PostMapping("login/precheck")
|
||||
@ApiOperation("Login")
|
||||
@CacheLock(autoDelete = false, prefix = "login_precheck")
|
||||
public LoginPreCheckDTO authPreCheck(@RequestBody @Valid LoginParam loginParam) {
|
||||
final User user = adminService.authenticate(loginParam);
|
||||
return new LoginPreCheckDTO(MFAType.useMFA(user.getMfaType()));
|
||||
}
|
||||
|
||||
@PostMapping("login")
|
||||
@ApiOperation("Login")
|
||||
@CacheLock(autoDelete = false, prefix = "login_auth")
|
||||
public AuthToken auth(@RequestBody @Valid LoginParam loginParam) {
|
||||
return adminService.authCodeCheck(loginParam);
|
||||
}
|
||||
|
||||
@PostMapping("logout")
|
||||
@ApiOperation("Logs out (Clear session)")
|
||||
@CacheLock(autoDelete = false)
|
||||
public void logout() {
|
||||
adminService.clearToken();
|
||||
}
|
||||
|
||||
@PostMapping("password/code")
|
||||
@ApiOperation("Sends reset password verify code")
|
||||
@CacheLock(autoDelete = false)
|
||||
@DisableOnCondition
|
||||
public void sendResetCode(@RequestBody @Valid ResetPasswordSendCodeParam param) {
|
||||
adminService.sendResetPasswordCode(param);
|
||||
}
|
||||
|
||||
@PutMapping("password/reset")
|
||||
@ApiOperation("Resets password by verify code")
|
||||
@CacheLock(autoDelete = false)
|
||||
@DisableOnCondition
|
||||
public void resetPassword(@RequestBody @Valid ResetPasswordParam param) {
|
||||
adminService.resetPasswordByCode(param);
|
||||
}
|
||||
|
||||
@PostMapping("refresh/{refreshToken}")
|
||||
@ApiOperation("Refreshes token")
|
||||
@CacheLock(autoDelete = false)
|
||||
public AuthToken refresh(@PathVariable("refreshToken") String refreshToken) {
|
||||
return adminService.refreshToken(refreshToken);
|
||||
}
|
||||
|
||||
@GetMapping("environments")
|
||||
@ApiOperation("Gets environments info")
|
||||
public EnvironmentDTO getEnvironments() {
|
||||
return adminService.getEnvironments();
|
||||
}
|
||||
|
||||
@GetMapping(value = "halo/logfile")
|
||||
@ApiOperation("Gets halo log file content")
|
||||
@DisableOnCondition
|
||||
public BaseResponse<String> getLogFiles(@RequestParam("lines") Long lines) {
|
||||
return BaseResponse.ok(HttpStatus.OK.getReasonPhrase(), adminService.getLogFiles(lines));
|
||||
}
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import run.halo.app.model.dto.AttachmentDTO;
|
||||
import run.halo.app.model.entity.Attachment;
|
||||
import run.halo.app.model.enums.AttachmentType;
|
||||
import run.halo.app.model.params.AttachmentParam;
|
||||
import run.halo.app.model.params.AttachmentQuery;
|
||||
import run.halo.app.service.AttachmentService;
|
||||
|
||||
/**
|
||||
* Attachment controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 2019-03-21
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/attachments")
|
||||
public class AttachmentController {
|
||||
|
||||
private final AttachmentService attachmentService;
|
||||
|
||||
public AttachmentController(AttachmentService attachmentService) {
|
||||
this.attachmentService = attachmentService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public Page<AttachmentDTO> pageBy(
|
||||
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable,
|
||||
AttachmentQuery attachmentQuery) {
|
||||
return attachmentService.pageDtosBy(pageable, attachmentQuery);
|
||||
}
|
||||
|
||||
@GetMapping("{id:\\d+}")
|
||||
@ApiOperation("Gets attachment detail by id")
|
||||
public AttachmentDTO getBy(@PathVariable("id") Integer id) {
|
||||
Attachment attachment = attachmentService.getById(id);
|
||||
return attachmentService.convertToDto(attachment);
|
||||
}
|
||||
|
||||
@PutMapping("{attachmentId:\\d+}")
|
||||
@ApiOperation("Updates a attachment")
|
||||
public AttachmentDTO updateBy(@PathVariable("attachmentId") Integer attachmentId,
|
||||
@RequestBody @Valid AttachmentParam attachmentParam) {
|
||||
Attachment attachment = attachmentService.getById(attachmentId);
|
||||
attachmentParam.update(attachment);
|
||||
return new AttachmentDTO().convertFrom(attachmentService.update(attachment));
|
||||
}
|
||||
|
||||
@DeleteMapping("{id:\\d+}")
|
||||
@ApiOperation("Deletes attachment permanently by id")
|
||||
public AttachmentDTO deletePermanently(@PathVariable("id") Integer id) {
|
||||
return attachmentService.convertToDto(attachmentService.removePermanently(id));
|
||||
}
|
||||
|
||||
@DeleteMapping
|
||||
@ApiOperation("Deletes attachments permanently in batch by id array")
|
||||
public List<Attachment> deletePermanentlyInBatch(@RequestBody List<Integer> ids) {
|
||||
return attachmentService.removePermanently(ids);
|
||||
}
|
||||
|
||||
@PostMapping("upload")
|
||||
@ApiOperation("Uploads single file")
|
||||
public AttachmentDTO uploadAttachment(@RequestPart("file") MultipartFile file) {
|
||||
return attachmentService.convertToDto(attachmentService.upload(file));
|
||||
}
|
||||
|
||||
@PostMapping(value = "uploads", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@ApiOperation("Uploads multi files (Invalid in Swagger UI)")
|
||||
public List<AttachmentDTO> uploadAttachments(@RequestPart("files") MultipartFile[] files) {
|
||||
List<AttachmentDTO> result = new LinkedList<>();
|
||||
|
||||
for (MultipartFile file : files) {
|
||||
// Upload single file
|
||||
Attachment attachment = attachmentService.upload(file);
|
||||
// Convert and add
|
||||
result.add(attachmentService.convertToDto(attachment));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@GetMapping("media_types")
|
||||
@ApiOperation("Lists all of media types")
|
||||
public List<String> listMediaTypes() {
|
||||
return attachmentService.listAllMediaType();
|
||||
}
|
||||
|
||||
@GetMapping("types")
|
||||
@ApiOperation("Lists all of types.")
|
||||
public List<AttachmentType> listTypes() {
|
||||
return attachmentService.listAllType();
|
||||
}
|
||||
}
|
|
@ -1,248 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import static run.halo.app.service.BackupService.BackupType.JSON_DATA;
|
||||
import static run.halo.app.service.BackupService.BackupType.MARKDOWN;
|
||||
import static run.halo.app.service.BackupService.BackupType.WHOLE_SITE;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import run.halo.app.annotation.DisableOnCondition;
|
||||
import run.halo.app.config.properties.HaloProperties;
|
||||
import run.halo.app.exception.NotFoundException;
|
||||
import run.halo.app.model.dto.BackupDTO;
|
||||
import run.halo.app.model.dto.post.BasePostDetailDTO;
|
||||
import run.halo.app.model.params.PostMarkdownParam;
|
||||
import run.halo.app.service.BackupService;
|
||||
|
||||
/**
|
||||
* Backup controller
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @author Raremaa
|
||||
* @date 2019-04-26
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/backups")
|
||||
@Slf4j
|
||||
public class BackupController {
|
||||
|
||||
private final BackupService backupService;
|
||||
|
||||
private final HaloProperties haloProperties;
|
||||
|
||||
public BackupController(BackupService backupService, HaloProperties haloProperties) {
|
||||
this.backupService = backupService;
|
||||
this.haloProperties = haloProperties;
|
||||
}
|
||||
|
||||
@GetMapping("work-dir/fetch")
|
||||
public BackupDTO getWorkDirBackup(@RequestParam("filename") String filename) {
|
||||
return backupService
|
||||
.getBackup(Paths.get(haloProperties.getBackupDir(), filename), WHOLE_SITE)
|
||||
.orElseThrow(() ->
|
||||
new NotFoundException("备份文件 " + filename + " 不存在或已删除!").setErrorData(filename));
|
||||
}
|
||||
|
||||
@GetMapping("data/fetch")
|
||||
public BackupDTO getDataBackup(@RequestParam("filename") String filename) {
|
||||
return backupService
|
||||
.getBackup(Paths.get(haloProperties.getDataExportDir(), filename), JSON_DATA)
|
||||
.orElseThrow(() ->
|
||||
new NotFoundException("备份文件 " + filename + " 不存在或已删除!").setErrorData(filename));
|
||||
}
|
||||
|
||||
@GetMapping("markdown/fetch")
|
||||
public BackupDTO getMarkdownBackup(@RequestParam("filename") String filename) {
|
||||
return backupService
|
||||
.getBackup(Paths.get(haloProperties.getBackupMarkdownDir(), filename), MARKDOWN)
|
||||
.orElseThrow(() ->
|
||||
new NotFoundException("备份文件 " + filename + " 不存在或已删除!").setErrorData(filename));
|
||||
}
|
||||
|
||||
@PostMapping("work-dir")
|
||||
@ApiOperation("Backups work directory")
|
||||
@DisableOnCondition
|
||||
public BackupDTO backupHalo(@RequestBody List<String> options) {
|
||||
return backupService.backupWorkDirectory(options);
|
||||
}
|
||||
|
||||
@GetMapping("work-dir/options")
|
||||
@ApiOperation("Gets items that can be backed up")
|
||||
public List<String> listBackupItems() throws IOException {
|
||||
return Files.list(Paths.get(haloProperties.getWorkDir()))
|
||||
.map(Path::getFileName)
|
||||
.filter(Objects::nonNull)
|
||||
.map(Path::toString)
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@GetMapping("work-dir")
|
||||
@ApiOperation("Gets all work directory backups")
|
||||
public List<BackupDTO> listBackups() {
|
||||
return backupService.listWorkDirBackups();
|
||||
}
|
||||
|
||||
@GetMapping("work-dir/{filename:.+}")
|
||||
@ApiOperation("Downloads a work directory backup file")
|
||||
@DisableOnCondition
|
||||
public ResponseEntity<Resource> downloadBackup(@PathVariable("filename") String filename,
|
||||
HttpServletRequest request) {
|
||||
log.info("Trying to download backup file: [{}]", filename);
|
||||
|
||||
// Load file as resource
|
||||
Resource backupResource =
|
||||
backupService.loadFileAsResource(haloProperties.getBackupDir(), filename);
|
||||
|
||||
String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
|
||||
// Try to determine file's content type
|
||||
try {
|
||||
contentType =
|
||||
request.getServletContext().getMimeType(backupResource.getFile().getAbsolutePath());
|
||||
} catch (IOException e) {
|
||||
log.warn("Could not determine file type", e);
|
||||
// Ignore this error
|
||||
}
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.parseMediaType(contentType))
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"attachment; filename=\"" + backupResource.getFilename() + "\"")
|
||||
.body(backupResource);
|
||||
}
|
||||
|
||||
@DeleteMapping("work-dir")
|
||||
@ApiOperation("Deletes a work directory backup")
|
||||
@DisableOnCondition
|
||||
public void deleteBackup(@RequestParam("filename") String filename) {
|
||||
backupService.deleteWorkDirBackup(filename);
|
||||
}
|
||||
|
||||
@PostMapping("markdown/import")
|
||||
@ApiOperation("Imports markdown")
|
||||
public BasePostDetailDTO backupMarkdowns(@RequestPart("file") MultipartFile file)
|
||||
throws IOException {
|
||||
return backupService.importMarkdown(file);
|
||||
}
|
||||
|
||||
@PostMapping("data")
|
||||
@ApiOperation("Exports all data")
|
||||
@DisableOnCondition
|
||||
public BackupDTO exportData() {
|
||||
return backupService.exportData();
|
||||
}
|
||||
|
||||
@GetMapping("data")
|
||||
@ApiOperation("Lists all exported data")
|
||||
public List<BackupDTO> listExportedData() {
|
||||
return backupService.listExportedData();
|
||||
}
|
||||
|
||||
@DeleteMapping("data")
|
||||
@ApiOperation("Deletes a exported data")
|
||||
@DisableOnCondition
|
||||
public void deleteExportedData(@RequestParam("filename") String filename) {
|
||||
backupService.deleteExportedData(filename);
|
||||
}
|
||||
|
||||
@GetMapping("data/{fileName:.+}")
|
||||
@ApiOperation("Downloads a exported data")
|
||||
@DisableOnCondition
|
||||
public ResponseEntity<Resource> downloadExportedData(@PathVariable("fileName") String fileName,
|
||||
HttpServletRequest request) {
|
||||
log.info("Try to download exported data file: [{}]", fileName);
|
||||
|
||||
// Load exported data as resource
|
||||
Resource exportDataResource =
|
||||
backupService.loadFileAsResource(haloProperties.getDataExportDir(), fileName);
|
||||
|
||||
String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
|
||||
// Try to determine file's content type
|
||||
try {
|
||||
contentType = request.getServletContext()
|
||||
.getMimeType(exportDataResource.getFile().getAbsolutePath());
|
||||
} catch (IOException e) {
|
||||
log.warn("Could not determine file type", e);
|
||||
}
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.parseMediaType(contentType))
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"attachment; filename=\"" + exportDataResource.getFilename() + "\"")
|
||||
.body(exportDataResource);
|
||||
}
|
||||
|
||||
@PostMapping("markdown/export")
|
||||
@ApiOperation("Exports markdowns")
|
||||
@DisableOnCondition
|
||||
public BackupDTO exportMarkdowns(@RequestBody PostMarkdownParam postMarkdownParam)
|
||||
throws IOException {
|
||||
return backupService.exportMarkdowns(postMarkdownParam);
|
||||
}
|
||||
|
||||
@GetMapping("markdown/export")
|
||||
@ApiOperation("Gets all markdown backups")
|
||||
public List<BackupDTO> listMarkdowns() {
|
||||
return backupService.listMarkdowns();
|
||||
}
|
||||
|
||||
@DeleteMapping("markdown/export")
|
||||
@ApiOperation("Deletes a markdown backup")
|
||||
@DisableOnCondition
|
||||
public void deleteMarkdown(@RequestParam("filename") String filename) {
|
||||
backupService.deleteMarkdown(filename);
|
||||
}
|
||||
|
||||
@GetMapping("markdown/export/{fileName:.+}")
|
||||
@ApiOperation("Downloads a work markdown backup file")
|
||||
@DisableOnCondition
|
||||
public ResponseEntity<Resource> downloadMarkdown(@PathVariable("fileName") String fileName,
|
||||
HttpServletRequest request) {
|
||||
log.info("Try to download markdown backup file: [{}]", fileName);
|
||||
|
||||
// Load file as resource
|
||||
Resource backupResource =
|
||||
backupService.loadFileAsResource(haloProperties.getBackupMarkdownDir(), fileName);
|
||||
|
||||
String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
|
||||
// Try to determine file's content type
|
||||
try {
|
||||
contentType =
|
||||
request.getServletContext().getMimeType(backupResource.getFile().getAbsolutePath());
|
||||
} catch (IOException e) {
|
||||
log.warn("Could not determine file type", e);
|
||||
// Ignore this error
|
||||
}
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.parseMediaType(contentType))
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"attachment; filename=\"" + backupResource.getFilename() + "\"")
|
||||
.body(backupResource);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.ASC;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.model.dto.CategoryDTO;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.params.CategoryParam;
|
||||
import run.halo.app.model.vo.CategoryVO;
|
||||
import run.halo.app.service.CategoryService;
|
||||
import run.halo.app.service.PostCategoryService;
|
||||
|
||||
/**
|
||||
* Category controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 2019-03-21
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/categories")
|
||||
public class CategoryController {
|
||||
|
||||
private final CategoryService categoryService;
|
||||
|
||||
private final PostCategoryService postCategoryService;
|
||||
|
||||
public CategoryController(CategoryService categoryService,
|
||||
PostCategoryService postCategoryService) {
|
||||
this.categoryService = categoryService;
|
||||
this.postCategoryService = postCategoryService;
|
||||
}
|
||||
|
||||
@GetMapping("{categoryId:\\d+}")
|
||||
@ApiOperation("Gets category detail")
|
||||
public CategoryDTO getBy(@PathVariable("categoryId") Integer categoryId) {
|
||||
return categoryService.convertTo(categoryService.getById(categoryId));
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("Lists all categories")
|
||||
public List<? extends CategoryDTO> listAll(
|
||||
@SortDefault(sort = "priority", direction = ASC) Sort sort,
|
||||
@RequestParam(name = "more", required = false, defaultValue = "false") boolean more) {
|
||||
if (more) {
|
||||
return postCategoryService.listCategoryWithPostCountDto(sort);
|
||||
}
|
||||
|
||||
return categoryService.convertTo(categoryService.listAll(sort));
|
||||
}
|
||||
|
||||
@GetMapping("tree_view")
|
||||
@ApiOperation("List all categories as tree")
|
||||
public List<CategoryVO> listAsTree(
|
||||
@SortDefault(sort = "priority", direction = ASC) Sort sort) {
|
||||
return categoryService.listAsTree(sort);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ApiOperation("Creates category")
|
||||
public CategoryDTO createBy(@RequestBody @Valid CategoryParam categoryParam) {
|
||||
// Convert to category
|
||||
Category category = categoryParam.convertTo();
|
||||
|
||||
// Save it
|
||||
return categoryService.convertTo(categoryService.create(category));
|
||||
}
|
||||
|
||||
@PutMapping("{categoryId:\\d+}")
|
||||
@ApiOperation("Updates category")
|
||||
public CategoryDTO updateBy(@PathVariable("categoryId") Integer categoryId,
|
||||
@RequestBody @Valid CategoryParam categoryParam
|
||||
) {
|
||||
Category categoryToUpdate = categoryService.getById(categoryId);
|
||||
categoryParam.update(categoryToUpdate);
|
||||
return categoryService.convertTo(categoryService.update(categoryToUpdate));
|
||||
}
|
||||
|
||||
@PutMapping("/batch")
|
||||
@ApiOperation("Updates category in batch")
|
||||
public List<CategoryDTO> updateBatchBy(@RequestBody List<@Valid CategoryParam> categoryParams) {
|
||||
List<Category> categoriesToUpdate = categoryParams.stream()
|
||||
.filter(categoryParam -> Objects.nonNull(categoryParam.getId()))
|
||||
.map(categoryParam -> {
|
||||
Category categoryToUpdate = categoryService.getById(categoryParam.getId());
|
||||
categoryParam.update(categoryToUpdate);
|
||||
return categoryToUpdate;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
return categoryService.convertTo(categoryService.updateInBatch(categoriesToUpdate));
|
||||
}
|
||||
|
||||
@DeleteMapping("{categoryId:\\d+}")
|
||||
@ApiOperation("Deletes category")
|
||||
public void deletePermanently(@PathVariable("categoryId") Integer categoryId) {
|
||||
categoryService.removeCategoryAndPostCategoryBy(categoryId);
|
||||
}
|
||||
}
|
|
@ -1,304 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import run.halo.app.cache.lock.CacheLock;
|
||||
import run.halo.app.event.logger.LogEvent;
|
||||
import run.halo.app.exception.BadRequestException;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.PostComment;
|
||||
import run.halo.app.model.entity.User;
|
||||
import run.halo.app.model.enums.LogType;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.params.CategoryParam;
|
||||
import run.halo.app.model.params.InstallParam;
|
||||
import run.halo.app.model.params.MenuParam;
|
||||
import run.halo.app.model.params.PostParam;
|
||||
import run.halo.app.model.params.SheetParam;
|
||||
import run.halo.app.model.properties.BlogProperties;
|
||||
import run.halo.app.model.properties.OtherProperties;
|
||||
import run.halo.app.model.properties.PrimaryProperties;
|
||||
import run.halo.app.model.properties.PropertyEnum;
|
||||
import run.halo.app.model.support.BaseResponse;
|
||||
import run.halo.app.model.support.CreateCheck;
|
||||
import run.halo.app.model.vo.PostDetailVO;
|
||||
import run.halo.app.service.CategoryService;
|
||||
import run.halo.app.service.MenuService;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PostCommentService;
|
||||
import run.halo.app.service.PostService;
|
||||
import run.halo.app.service.SheetService;
|
||||
import run.halo.app.service.UserService;
|
||||
import run.halo.app.utils.ValidationUtils;
|
||||
|
||||
/**
|
||||
* Installation controller.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-03-17
|
||||
*/
|
||||
@Slf4j
|
||||
@Controller
|
||||
@RequestMapping("/api/admin/installations")
|
||||
public class InstallController {
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
private final CategoryService categoryService;
|
||||
|
||||
private final PostService postService;
|
||||
|
||||
private final SheetService sheetService;
|
||||
|
||||
private final PostCommentService postCommentService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final MenuService menuService;
|
||||
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
|
||||
public InstallController(UserService userService,
|
||||
CategoryService categoryService,
|
||||
PostService postService,
|
||||
SheetService sheetService,
|
||||
PostCommentService postCommentService,
|
||||
OptionService optionService,
|
||||
MenuService menuService,
|
||||
ApplicationEventPublisher eventPublisher) {
|
||||
this.userService = userService;
|
||||
this.categoryService = categoryService;
|
||||
this.postService = postService;
|
||||
this.sheetService = sheetService;
|
||||
this.postCommentService = postCommentService;
|
||||
this.optionService = optionService;
|
||||
this.menuService = menuService;
|
||||
this.eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ResponseBody
|
||||
@CacheLock
|
||||
@ApiOperation("Initializes the blog")
|
||||
public BaseResponse<String> installBlog(@RequestBody InstallParam installParam) {
|
||||
// Validate manually
|
||||
ValidationUtils.validate(installParam, CreateCheck.class);
|
||||
|
||||
// Check is installed
|
||||
boolean isInstalled = optionService
|
||||
.getByPropertyOrDefault(PrimaryProperties.IS_INSTALLED, Boolean.class, false);
|
||||
|
||||
if (isInstalled) {
|
||||
throw new BadRequestException("该博客已初始化,不能再次安装!");
|
||||
}
|
||||
|
||||
// Initialize settings
|
||||
initSettings(installParam);
|
||||
|
||||
// Create default user
|
||||
User user = createUser(installParam);
|
||||
|
||||
// Create default category
|
||||
Category category = createDefaultCategoryIfAbsent();
|
||||
|
||||
// Create default post
|
||||
PostDetailVO post = createDefaultPostIfAbsent(category);
|
||||
|
||||
// Create default sheet
|
||||
createDefaultSheet();
|
||||
|
||||
// Create default postComment
|
||||
createDefaultComment(post);
|
||||
|
||||
// Create default menu
|
||||
createDefaultMenu();
|
||||
|
||||
eventPublisher.publishEvent(
|
||||
new LogEvent(this, user.getId().toString(), LogType.BLOG_INITIALIZED, "博客已成功初始化")
|
||||
);
|
||||
|
||||
return BaseResponse.ok("安装完成!");
|
||||
}
|
||||
|
||||
private void createDefaultMenu() {
|
||||
long menuCount = menuService.count();
|
||||
|
||||
if (menuCount > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
MenuParam menuIndex = new MenuParam();
|
||||
|
||||
menuIndex.setName("首页");
|
||||
menuIndex.setUrl("/");
|
||||
menuIndex.setPriority(1);
|
||||
|
||||
menuService.create(menuIndex.convertTo());
|
||||
|
||||
MenuParam menuArchive = new MenuParam();
|
||||
|
||||
menuArchive.setName("文章归档");
|
||||
menuArchive.setUrl("/archives");
|
||||
menuArchive.setPriority(2);
|
||||
menuService.create(menuArchive.convertTo());
|
||||
|
||||
MenuParam menuCategory = new MenuParam();
|
||||
menuCategory.setName("默认分类");
|
||||
menuCategory.setUrl("/categories/default");
|
||||
menuCategory.setPriority(3);
|
||||
menuService.create(menuCategory.convertTo());
|
||||
|
||||
MenuParam menuSheet = new MenuParam();
|
||||
menuSheet.setName("关于页面");
|
||||
menuSheet.setUrl("/s/about");
|
||||
menuSheet.setPriority(4);
|
||||
menuService.create(menuSheet.convertTo());
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
private void createDefaultComment(@Nullable PostDetailVO post) {
|
||||
if (post == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
long commentCount = postCommentService.count();
|
||||
|
||||
if (commentCount > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
PostComment comment = new PostComment();
|
||||
comment.setAuthor("Halo");
|
||||
comment.setAuthorUrl("https://halo.run");
|
||||
comment.setContent(
|
||||
"欢迎使用 Halo,这是你的第一条评论,头像来自 [Gravatar](https://cn.gravatar.com),"
|
||||
+ "你也可以通过注册 [Gravatar]"
|
||||
+ "(https://cn.gravatar.com) 来显示自己的头像。");
|
||||
comment.setEmail("hi@halo.run");
|
||||
comment.setPostId(post.getId());
|
||||
postCommentService.create(comment);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PostDetailVO createDefaultPostIfAbsent(@Nullable Category category) {
|
||||
|
||||
long publishedCount = postService.countByStatus(PostStatus.PUBLISHED);
|
||||
|
||||
if (publishedCount > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PostParam postParam = new PostParam();
|
||||
postParam.setSlug("hello-halo");
|
||||
postParam.setTitle("Hello Halo");
|
||||
postParam.setStatus(PostStatus.PUBLISHED);
|
||||
postParam.setOriginalContent("## Hello Halo\n"
|
||||
+ "\n"
|
||||
+ "如果你看到了这一篇文章,那么证明你已经安装成功了,感谢使用 [Halo](https://halo.run) 进行创作,希望能够使用愉快。\n"
|
||||
+ "\n"
|
||||
+ "## 相关链接\n"
|
||||
+ "\n"
|
||||
+ "- 官网:[https://halo.run](https://halo.run)\n"
|
||||
+ "- 文档:[https://docs.halo.run](https://docs.halo.run)\n"
|
||||
+ "- 社区:[https://bbs.halo.run](https://bbs.halo.run)\n"
|
||||
+ "- 主题仓库:[https://halo.run/themes.html](https://halo.run/themes.html)\n"
|
||||
+ "- 开源地址:[https://github.com/halo-dev/halo](https://github.com/halo-dev/halo)\n"
|
||||
+ "\n"
|
||||
+ "在使用过程中,有任何问题都可以通过以上链接找寻答案,或者联系我们。\n"
|
||||
+ "\n"
|
||||
+ "> 这是一篇自动生成的文章,请删除这篇文章之后开始你的创作吧!\n"
|
||||
+ "\n");
|
||||
|
||||
Set<Integer> categoryIds = new HashSet<>();
|
||||
if (category != null) {
|
||||
categoryIds.add(category.getId());
|
||||
postParam.setCategoryIds(categoryIds);
|
||||
}
|
||||
return postService
|
||||
.createBy(postParam.convertTo(), Collections.emptySet(), categoryIds, false);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private void createDefaultSheet() {
|
||||
long publishedCount = sheetService.countByStatus(PostStatus.PUBLISHED);
|
||||
if (publishedCount > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
SheetParam sheetParam = new SheetParam();
|
||||
sheetParam.setSlug("about");
|
||||
sheetParam.setTitle("关于页面");
|
||||
sheetParam.setStatus(PostStatus.PUBLISHED);
|
||||
sheetParam.setOriginalContent("## 关于页面\n"
|
||||
+ "\n"
|
||||
+ "这是一个自定义页面,你可以在后台的 `页面` -> `所有页面` -> `自定义页面` 找到它,"
|
||||
+ "你可以用于新建关于页面、留言板页面等等。发挥你自己的想象力!\n"
|
||||
+ "\n"
|
||||
+ "> 这是一篇自动生成的页面,你可以在后台删除它。");
|
||||
sheetService.createBy(sheetParam.convertTo(), false);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Category createDefaultCategoryIfAbsent() {
|
||||
long categoryCount = categoryService.count();
|
||||
if (categoryCount > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CategoryParam category = new CategoryParam();
|
||||
category.setName("默认分类");
|
||||
category.setSlug("default");
|
||||
category.setDescription("这是你的默认分类,如不需要,删除即可。");
|
||||
ValidationUtils.validate(category);
|
||||
return categoryService.create(category.convertTo());
|
||||
}
|
||||
|
||||
private User createUser(InstallParam installParam) {
|
||||
// Get user
|
||||
return userService.getCurrentUser().map(user -> {
|
||||
// Update this user
|
||||
installParam.update(user);
|
||||
// Set password manually
|
||||
userService.setPassword(user, installParam.getPassword());
|
||||
// Update user
|
||||
return userService.update(user);
|
||||
}).orElseGet(() -> {
|
||||
String gravatar =
|
||||
"//cn.gravatar.com/avatar/" + DigestUtils.md5Hex(installParam.getEmail())
|
||||
+ "?s=256&d=mm";
|
||||
installParam.setAvatar(gravatar);
|
||||
return userService.createBy(installParam);
|
||||
});
|
||||
}
|
||||
|
||||
private void initSettings(InstallParam installParam) {
|
||||
// Init default properties
|
||||
Map<PropertyEnum, String> properties = new HashMap<>(11);
|
||||
properties.put(PrimaryProperties.IS_INSTALLED, Boolean.TRUE.toString());
|
||||
properties.put(BlogProperties.BLOG_LOCALE, installParam.getLocale());
|
||||
properties.put(BlogProperties.BLOG_TITLE, installParam.getTitle());
|
||||
properties.put(BlogProperties.BLOG_URL,
|
||||
StringUtils.isBlank(installParam.getUrl()) ? optionService.getBlogBaseUrl() :
|
||||
installParam.getUrl());
|
||||
properties.put(OtherProperties.GLOBAL_ABSOLUTE_PATH_ENABLED, Boolean.FALSE.toString());
|
||||
|
||||
// Create properties
|
||||
optionService.saveProperties(properties);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.util.List;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.model.dto.BaseCommentDTO;
|
||||
import run.halo.app.model.entity.JournalComment;
|
||||
import run.halo.app.model.enums.CommentStatus;
|
||||
import run.halo.app.model.params.CommentQuery;
|
||||
import run.halo.app.model.params.JournalCommentParam;
|
||||
import run.halo.app.model.vo.BaseCommentVO;
|
||||
import run.halo.app.model.vo.BaseCommentWithParentVO;
|
||||
import run.halo.app.model.vo.JournalCommentWithJournalVO;
|
||||
import run.halo.app.service.JournalCommentService;
|
||||
import run.halo.app.service.OptionService;
|
||||
|
||||
/**
|
||||
* Journal comment controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author guqing
|
||||
* @date 2019-04-25
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/journals/comments")
|
||||
public class JournalCommentController {
|
||||
|
||||
private final JournalCommentService journalCommentService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
public JournalCommentController(JournalCommentService journalCommentService,
|
||||
OptionService optionService) {
|
||||
this.journalCommentService = journalCommentService;
|
||||
this.optionService = optionService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("Lists journal comments")
|
||||
public Page<JournalCommentWithJournalVO> pageBy(
|
||||
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable,
|
||||
CommentQuery commentQuery) {
|
||||
Page<JournalComment> journalCommentPage =
|
||||
journalCommentService.pageBy(commentQuery, pageable);
|
||||
|
||||
return journalCommentService.convertToWithJournalVo(journalCommentPage);
|
||||
}
|
||||
|
||||
@GetMapping("latest")
|
||||
@ApiOperation("Lists latest journal comments")
|
||||
public List<JournalCommentWithJournalVO> listLatest(
|
||||
@RequestParam(name = "top", defaultValue = "10") int top,
|
||||
@RequestParam(name = "status", required = false) CommentStatus status) {
|
||||
List<JournalComment> latestComments =
|
||||
journalCommentService.pageLatest(top, status).getContent();
|
||||
return journalCommentService.convertToWithJournalVo(latestComments);
|
||||
}
|
||||
|
||||
@GetMapping("{journalId:\\d+}/tree_view")
|
||||
@ApiOperation("Lists comments with tree view")
|
||||
public Page<BaseCommentVO> listCommentTree(@PathVariable("journalId") Integer journalId,
|
||||
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
return journalCommentService.pageVosAllBy(journalId,
|
||||
PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||
}
|
||||
|
||||
@GetMapping("{journalId:\\d+}/list_view")
|
||||
@ApiOperation("Lists comment with list view")
|
||||
public Page<BaseCommentWithParentVO> listComments(@PathVariable("journalId") Integer journalId,
|
||||
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
return journalCommentService.pageWithParentVoBy(journalId,
|
||||
PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ApiOperation("Creates a journal comment")
|
||||
public BaseCommentDTO createCommentBy(@RequestBody JournalCommentParam journalCommentParam) {
|
||||
JournalComment journalComment = journalCommentService.createBy(journalCommentParam);
|
||||
return journalCommentService.convertTo(journalComment);
|
||||
}
|
||||
|
||||
@PutMapping("{commentId:\\d+}/status/{status}")
|
||||
@ApiOperation("Updates comment status")
|
||||
public BaseCommentDTO updateStatusBy(@PathVariable("commentId") Long commentId,
|
||||
@PathVariable("status") CommentStatus status) {
|
||||
// Update comment status
|
||||
JournalComment updatedJournalComment =
|
||||
journalCommentService.updateStatus(commentId, status);
|
||||
return journalCommentService.convertTo(updatedJournalComment);
|
||||
}
|
||||
|
||||
@PutMapping("/{commentId:\\d+}")
|
||||
@ApiOperation("Updates a journal comment by comment id")
|
||||
public BaseCommentDTO updateCommentBy(@PathVariable Long commentId,
|
||||
@RequestBody JournalCommentParam journalCommentParam) {
|
||||
JournalComment commentToUpdate = journalCommentService.getById(commentId);
|
||||
journalCommentParam.update(commentToUpdate);
|
||||
|
||||
return journalCommentService.convertTo(journalCommentService.update(commentToUpdate));
|
||||
}
|
||||
|
||||
@DeleteMapping("{commentId:\\d+}")
|
||||
@ApiOperation("Deletes comment permanently and recursively")
|
||||
public BaseCommentDTO deleteBy(@PathVariable("commentId") Long commentId) {
|
||||
JournalComment deletedJournalComment = journalCommentService.removeById(commentId);
|
||||
return journalCommentService.convertTo(deletedJournalComment);
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.util.List;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.model.dto.JournalDTO;
|
||||
import run.halo.app.model.dto.JournalWithCmtCountDTO;
|
||||
import run.halo.app.model.entity.Journal;
|
||||
import run.halo.app.model.params.JournalParam;
|
||||
import run.halo.app.model.params.JournalQuery;
|
||||
import run.halo.app.service.JournalService;
|
||||
|
||||
/**
|
||||
* Journal controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @date 2019-04-25
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/journals")
|
||||
public class JournalController {
|
||||
|
||||
private final JournalService journalService;
|
||||
|
||||
public JournalController(JournalService journalService) {
|
||||
this.journalService = journalService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("Lists journals")
|
||||
public Page<JournalWithCmtCountDTO> pageBy(
|
||||
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable,
|
||||
JournalQuery journalQuery) {
|
||||
Page<Journal> journalPage = journalService.pageBy(journalQuery, pageable);
|
||||
return journalService.convertToCmtCountDto(journalPage);
|
||||
}
|
||||
|
||||
@GetMapping("latest")
|
||||
@ApiOperation("Gets latest journals")
|
||||
public List<JournalWithCmtCountDTO> pageLatest(
|
||||
@RequestParam(name = "top", defaultValue = "10") int top) {
|
||||
List<Journal> journals = journalService.pageLatest(top).getContent();
|
||||
return journalService.convertToCmtCountDto(journals);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ApiOperation("Creates a journal")
|
||||
public JournalDTO createBy(@RequestBody @Valid JournalParam journalParam) {
|
||||
Journal createdJournal = journalService.createBy(journalParam);
|
||||
return journalService.convertTo(createdJournal);
|
||||
}
|
||||
|
||||
@PutMapping("{id:\\d+}")
|
||||
@ApiOperation("Updates a Journal")
|
||||
public JournalDTO updateBy(@PathVariable("id") Integer id,
|
||||
@RequestBody @Valid JournalParam journalParam) {
|
||||
Journal journal = journalService.getById(id);
|
||||
journalParam.update(journal);
|
||||
Journal updatedJournal = journalService.updateBy(journal);
|
||||
return journalService.convertTo(updatedJournal);
|
||||
}
|
||||
|
||||
@DeleteMapping("{journalId:\\d+}")
|
||||
@ApiOperation("Delete journal")
|
||||
public JournalDTO deleteBy(@PathVariable("journalId") Integer journalId) {
|
||||
Journal deletedJournal = journalService.removeById(journalId);
|
||||
return journalService.convertTo(deletedJournal);
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.ASC;
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.util.List;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.model.dto.LinkDTO;
|
||||
import run.halo.app.model.entity.Link;
|
||||
import run.halo.app.model.params.LinkParam;
|
||||
import run.halo.app.service.LinkService;
|
||||
|
||||
/**
|
||||
* Link Controller
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-03-21
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/links")
|
||||
public class LinkController {
|
||||
|
||||
private final LinkService linkService;
|
||||
|
||||
public LinkController(LinkService linkService) {
|
||||
this.linkService = linkService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("Lists links")
|
||||
public List<LinkDTO> listLinks(@SortDefault(sort = "team", direction = DESC) Sort sort) {
|
||||
return linkService.listDtos(sort.and(Sort.by(ASC, "priority")));
|
||||
}
|
||||
|
||||
@GetMapping("{id:\\d+}")
|
||||
@ApiOperation("Gets link detail by id")
|
||||
public LinkDTO getBy(@PathVariable("id") Integer id) {
|
||||
return new LinkDTO().convertFrom(linkService.getById(id));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ApiOperation("Creates a link")
|
||||
public LinkDTO createBy(@RequestBody @Valid LinkParam linkParam) {
|
||||
Link link = linkService.createBy(linkParam);
|
||||
return new LinkDTO().convertFrom(link);
|
||||
}
|
||||
|
||||
@PutMapping("{id:\\d+}")
|
||||
@ApiOperation("Updates a link")
|
||||
public LinkDTO updateBy(@PathVariable("id") Integer id,
|
||||
@RequestBody @Valid LinkParam linkParam) {
|
||||
Link link = linkService.updateBy(id, linkParam);
|
||||
return new LinkDTO().convertFrom(link);
|
||||
}
|
||||
|
||||
@DeleteMapping("{id:\\d+}")
|
||||
@ApiOperation("Delete link by id")
|
||||
public void deletePermanently(@PathVariable("id") Integer id) {
|
||||
linkService.removeById(id);
|
||||
}
|
||||
|
||||
@GetMapping("teams")
|
||||
@ApiOperation("Lists all link teams")
|
||||
public List<String> teams() {
|
||||
return linkService.listAllTeams();
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.util.List;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.model.dto.LogDTO;
|
||||
import run.halo.app.model.entity.Log;
|
||||
import run.halo.app.service.LogService;
|
||||
|
||||
/**
|
||||
* Log controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 2019-03-19
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/logs")
|
||||
public class LogController {
|
||||
|
||||
private final LogService logService;
|
||||
|
||||
public LogController(LogService logService) {
|
||||
this.logService = logService;
|
||||
}
|
||||
|
||||
@GetMapping("latest")
|
||||
@ApiOperation("Pages latest logs")
|
||||
public List<LogDTO> pageLatest(@RequestParam(name = "top", defaultValue = "10") int top) {
|
||||
return logService.pageLatest(top).getContent();
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("Lists logs")
|
||||
public Page<LogDTO> pageBy(
|
||||
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable) {
|
||||
Page<Log> logPage = logService.listAll(pageable);
|
||||
return logPage.map(log -> new LogDTO().convertFrom(log));
|
||||
}
|
||||
|
||||
@GetMapping("clear")
|
||||
@ApiOperation("Clears all logs")
|
||||
public void clear() {
|
||||
logService.removeAll();
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.annotation.DisableOnCondition;
|
||||
import run.halo.app.mail.MailService;
|
||||
import run.halo.app.model.params.MailParam;
|
||||
import run.halo.app.model.support.BaseResponse;
|
||||
|
||||
/**
|
||||
* Mail controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 2019-05-07
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/mails")
|
||||
public class MailController {
|
||||
|
||||
private final MailService mailService;
|
||||
|
||||
public MailController(MailService mailService) {
|
||||
this.mailService = mailService;
|
||||
}
|
||||
|
||||
@PostMapping("test")
|
||||
@ApiOperation("Tests the SMTP service")
|
||||
@DisableOnCondition
|
||||
public BaseResponse<String> testMail(@Valid @RequestBody MailParam mailParam) {
|
||||
mailService.sendTextMail(mailParam.getTo(), mailParam.getSubject(), mailParam.getContent());
|
||||
return BaseResponse.ok("已发送,请查收。若确认没有收到邮件,请检查服务器日志");
|
||||
}
|
||||
|
||||
@PostMapping("test/connection")
|
||||
@ApiOperation("Test connection with email server")
|
||||
@DisableOnCondition
|
||||
public BaseResponse<String> testConnection() {
|
||||
mailService.testConnection();
|
||||
return BaseResponse.ok("您和邮箱服务器的连接通畅");
|
||||
}
|
||||
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.ASC;
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.model.dto.MenuDTO;
|
||||
import run.halo.app.model.dto.base.InputConverter;
|
||||
import run.halo.app.model.entity.Menu;
|
||||
import run.halo.app.model.params.MenuParam;
|
||||
import run.halo.app.model.vo.MenuVO;
|
||||
import run.halo.app.service.MenuService;
|
||||
|
||||
/**
|
||||
* Menu controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @date 2019-04-03
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/menus")
|
||||
public class MenuController {
|
||||
|
||||
private final MenuService menuService;
|
||||
|
||||
public MenuController(MenuService menuService) {
|
||||
this.menuService = menuService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("Lists all menus")
|
||||
public List<MenuDTO> listAll(@SortDefault(sort = "team", direction = DESC) Sort sort) {
|
||||
return menuService.listDtos(sort.and(Sort.by(ASC, "priority")));
|
||||
}
|
||||
|
||||
@GetMapping("tree_view")
|
||||
@ApiOperation("Lists menus as tree")
|
||||
public List<MenuVO> listAsTree(@SortDefault(sort = "team", direction = DESC) Sort sort) {
|
||||
return menuService.listAsTree(sort.and(Sort.by(ASC, "priority")));
|
||||
}
|
||||
|
||||
@GetMapping("team/tree_view")
|
||||
@ApiOperation("Lists menus as tree by team")
|
||||
public List<MenuVO> listDefaultsAsTreeByTeam(
|
||||
@SortDefault(sort = "priority", direction = ASC) Sort sort,
|
||||
@RequestParam(name = "team") String team) {
|
||||
return menuService.listByTeamAsTree(team, sort);
|
||||
}
|
||||
|
||||
@GetMapping("{menuId:\\d+}")
|
||||
@ApiOperation("Gets menu detail by id")
|
||||
public MenuDTO getBy(@PathVariable("menuId") Integer menuId) {
|
||||
return new MenuDTO().convertFrom(menuService.getById(menuId));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ApiOperation("Creates a menu")
|
||||
public MenuDTO createBy(@RequestBody @Valid MenuParam menuParam) {
|
||||
return new MenuDTO().convertFrom(menuService.createBy(menuParam));
|
||||
}
|
||||
|
||||
@PostMapping("/batch")
|
||||
public List<MenuDTO> createBatchBy(@RequestBody @Valid List<MenuParam> menuParams) {
|
||||
List<Menu> menus = menuParams
|
||||
.stream()
|
||||
.map(InputConverter::convertTo)
|
||||
.collect(Collectors.toList());
|
||||
return menuService.createInBatch(menus).stream()
|
||||
.map(menu -> (MenuDTO) new MenuDTO().convertFrom(menu))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@PutMapping("{menuId:\\d+}")
|
||||
@ApiOperation("Updates a menu")
|
||||
public MenuDTO updateBy(@PathVariable("menuId") Integer menuId,
|
||||
@RequestBody @Valid MenuParam menuParam) {
|
||||
// Get the menu
|
||||
Menu menu = menuService.getById(menuId);
|
||||
|
||||
// Update changed properties of the menu
|
||||
menuParam.update(menu);
|
||||
|
||||
// Update menu in database
|
||||
return new MenuDTO().convertFrom(menuService.update(menu));
|
||||
}
|
||||
|
||||
@PutMapping("/batch")
|
||||
public List<MenuDTO> updateBatchBy(@RequestBody @Valid List<MenuParam> menuParams) {
|
||||
List<Menu> menus = menuParams
|
||||
.stream()
|
||||
.filter(menuParam -> Objects.nonNull(menuParam.getId()))
|
||||
.map(InputConverter::convertTo)
|
||||
.collect(Collectors.toList());
|
||||
return menuService.updateInBatch(menus).stream()
|
||||
.map(menu -> (MenuDTO) new MenuDTO().convertFrom(menu))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@DeleteMapping("{menuId:\\d+}")
|
||||
@ApiOperation("Deletes a menu")
|
||||
public MenuDTO deleteBy(@PathVariable("menuId") Integer menuId) {
|
||||
List<Menu> menus = menuService.listByParentId(menuId);
|
||||
if (null != menus && menus.size() > 0) {
|
||||
menus.forEach(menu -> {
|
||||
menu.setParentId(0);
|
||||
menuService.update(menu);
|
||||
});
|
||||
}
|
||||
return new MenuDTO().convertFrom(menuService.removeById(menuId));
|
||||
}
|
||||
|
||||
@DeleteMapping("/batch")
|
||||
public List<MenuDTO> deleteBatchBy(@RequestBody List<Integer> menuIds) {
|
||||
List<Menu> menus = menuService.listAllByIds(menuIds);
|
||||
menuService.removeInBatch(menuIds);
|
||||
return menus.stream()
|
||||
.map(menu -> (MenuDTO) new MenuDTO().convertFrom(menu))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@GetMapping("teams")
|
||||
@ApiOperation("Lists all menu teams")
|
||||
public List<String> teams() {
|
||||
return menuService.listAllTeams();
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import run.halo.app.exception.BadRequestException;
|
||||
import run.halo.app.model.enums.MigrateType;
|
||||
import run.halo.app.model.properties.PrimaryProperties;
|
||||
import run.halo.app.service.MigrateService;
|
||||
import run.halo.app.service.OptionService;
|
||||
|
||||
/**
|
||||
* Migrate controller
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-10-29
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/migrations")
|
||||
public class MigrateController {
|
||||
|
||||
private final MigrateService migrateService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
public MigrateController(MigrateService migrateService,
|
||||
OptionService optionService) {
|
||||
this.migrateService = migrateService;
|
||||
this.optionService = optionService;
|
||||
}
|
||||
|
||||
@PostMapping("halo")
|
||||
@ApiOperation("Migrate from Halo")
|
||||
public void migrateHalo(@RequestPart("file") MultipartFile file) {
|
||||
if (optionService.getByPropertyOrDefault(
|
||||
PrimaryProperties.IS_INSTALLED, Boolean.class, false)) {
|
||||
throw new BadRequestException("无法在博客初始化完成之后迁移数据");
|
||||
}
|
||||
migrateService.migrate(file, MigrateType.HALO);
|
||||
}
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.annotation.DisableOnCondition;
|
||||
import run.halo.app.model.dto.OptionDTO;
|
||||
import run.halo.app.model.dto.OptionSimpleDTO;
|
||||
import run.halo.app.model.entity.Option;
|
||||
import run.halo.app.model.params.OptionParam;
|
||||
import run.halo.app.model.params.OptionQuery;
|
||||
import run.halo.app.service.OptionService;
|
||||
|
||||
/**
|
||||
* Option Controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @date 2019-03-20
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/options")
|
||||
public class OptionController {
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
public OptionController(OptionService optionService) {
|
||||
this.optionService = optionService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("Lists options")
|
||||
public List<OptionDTO> listAll() {
|
||||
return optionService.listDtos();
|
||||
}
|
||||
|
||||
@PostMapping("saving")
|
||||
@ApiOperation("Saves options")
|
||||
@DisableOnCondition
|
||||
public void saveOptions(@Valid @RequestBody List<OptionParam> optionParams) {
|
||||
optionService.save(optionParams);
|
||||
}
|
||||
|
||||
@GetMapping("map_view")
|
||||
@ApiOperation("Lists all options with map view")
|
||||
public Map<String, Object> listAllWithMapView() {
|
||||
return optionService.listOptions();
|
||||
}
|
||||
|
||||
@PostMapping("map_view/keys")
|
||||
@ApiOperation("Lists options with map view by keys")
|
||||
public Map<String, Object> listAllWithMapView(@RequestBody List<String> keys) {
|
||||
return optionService.listOptions(keys);
|
||||
}
|
||||
|
||||
@GetMapping("list_view")
|
||||
@ApiOperation("Lists all options with list view")
|
||||
public Page<OptionSimpleDTO> listAllWithListView(
|
||||
@PageableDefault(sort = "updateTime", direction = DESC) Pageable pageable,
|
||||
OptionQuery optionQuery) {
|
||||
return optionService.pageDtosBy(pageable, optionQuery);
|
||||
}
|
||||
|
||||
@GetMapping("{id:\\d+}")
|
||||
@ApiOperation("Gets option detail by id")
|
||||
public OptionSimpleDTO getBy(@PathVariable("id") Integer id) {
|
||||
Option option = optionService.getById(id);
|
||||
return optionService.convertToDto(option);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ApiOperation("Creates option")
|
||||
@DisableOnCondition
|
||||
public void createBy(@RequestBody @Valid OptionParam optionParam) {
|
||||
optionService.save(optionParam);
|
||||
}
|
||||
|
||||
@PutMapping("{optionId:\\d+}")
|
||||
@ApiOperation("Updates option")
|
||||
@DisableOnCondition
|
||||
public void updateBy(@PathVariable("optionId") Integer optionId,
|
||||
@RequestBody @Valid OptionParam optionParam) {
|
||||
optionService.update(optionId, optionParam);
|
||||
}
|
||||
|
||||
@DeleteMapping("{optionId:\\d+}")
|
||||
@ApiOperation("Deletes option")
|
||||
@DisableOnCondition
|
||||
public void deletePermanently(@PathVariable("optionId") Integer optionId) {
|
||||
optionService.removePermanently(optionId);
|
||||
}
|
||||
|
||||
@PostMapping("map_view/saving")
|
||||
@ApiOperation("Saves options by option map")
|
||||
@DisableOnCondition
|
||||
public void saveOptionsWithMapView(@RequestBody Map<String, Object> optionMap) {
|
||||
optionService.save(optionMap);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,144 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.cache.lock.CacheLock;
|
||||
import run.halo.app.cache.lock.CacheParam;
|
||||
import run.halo.app.model.dto.PhotoDTO;
|
||||
import run.halo.app.model.entity.Photo;
|
||||
import run.halo.app.model.params.PhotoParam;
|
||||
import run.halo.app.model.params.PhotoQuery;
|
||||
import run.halo.app.service.PhotoService;
|
||||
|
||||
/**
|
||||
* Photo controller
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-03-21
|
||||
*/
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/photos")
|
||||
public class PhotoController {
|
||||
|
||||
private final PhotoService photoService;
|
||||
|
||||
public PhotoController(PhotoService photoService) {
|
||||
this.photoService = photoService;
|
||||
}
|
||||
|
||||
@GetMapping(value = "latest")
|
||||
@ApiOperation("Lists latest photos")
|
||||
public List<PhotoDTO> listPhotos(
|
||||
@SortDefault(sort = "createTime", direction = Sort.Direction.DESC) Sort sort) {
|
||||
return photoService.listDtos(sort);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("Lists photos")
|
||||
public Page<PhotoDTO> pageBy(
|
||||
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable,
|
||||
PhotoQuery photoQuery) {
|
||||
return photoService.pageDtosBy(pageable, photoQuery);
|
||||
}
|
||||
|
||||
@GetMapping("{photoId:\\d+}")
|
||||
@ApiOperation("Gets photo detail by id")
|
||||
public PhotoDTO getBy(@PathVariable("photoId") Integer photoId) {
|
||||
return new PhotoDTO().convertFrom(photoService.getById(photoId));
|
||||
}
|
||||
|
||||
@DeleteMapping("{photoId:\\d+}")
|
||||
@ApiOperation("Deletes photo by id")
|
||||
public void deletePermanently(@PathVariable("photoId") Integer photoId) {
|
||||
photoService.removeById(photoId);
|
||||
}
|
||||
|
||||
@DeleteMapping("/batch")
|
||||
@ApiOperation("Deletes photos permanently in batch by id array")
|
||||
public List<PhotoDTO> deletePermanentlyInBatch(@RequestBody List<Integer> ids) {
|
||||
return ids.stream().map(photoService::removeById)
|
||||
.map(photo -> (PhotoDTO) new PhotoDTO().convertFrom(photo))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ApiOperation("Creates a photo")
|
||||
public PhotoDTO createBy(@Valid @RequestBody PhotoParam photoParam) {
|
||||
return new PhotoDTO().convertFrom(photoService.createBy(photoParam));
|
||||
}
|
||||
|
||||
@PostMapping("/batch")
|
||||
@ApiOperation("Batch creation photos")
|
||||
public List<PhotoDTO> createBatchBy(@RequestBody List<@Valid PhotoParam> photoParams) {
|
||||
return photoParams.stream()
|
||||
.map(photoParam -> {
|
||||
PhotoDTO photoDto = new PhotoDTO();
|
||||
photoDto.convertFrom(photoService.createBy(photoParam));
|
||||
return photoDto;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@PutMapping("{photoId:\\d+}")
|
||||
@ApiOperation("Updates a photo")
|
||||
public PhotoDTO updateBy(@PathVariable("photoId") Integer photoId,
|
||||
@RequestBody @Valid PhotoParam photoParam) {
|
||||
// Get the photo
|
||||
Photo photo = photoService.getById(photoId);
|
||||
|
||||
// Update changed properties of the photo
|
||||
photoParam.update(photo);
|
||||
|
||||
// Update menu in database
|
||||
return new PhotoDTO().convertFrom(photoService.update(photo));
|
||||
}
|
||||
|
||||
@PutMapping("/batch")
|
||||
@ApiOperation("Updates photo in batch")
|
||||
public List<PhotoDTO> updateBatchBy(@RequestBody List<@Valid PhotoParam> photoParams) {
|
||||
List<Photo> photosToUpdate = photoParams.stream()
|
||||
.filter(photoParam -> Objects.nonNull(photoParam.getId()))
|
||||
.map(photoParam -> {
|
||||
Photo photoToUpdate = photoService.getById(photoParam.getId());
|
||||
photoParam.update(photoToUpdate);
|
||||
return photoToUpdate;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
return photoService.updateInBatch(photosToUpdate).stream()
|
||||
.map(photo -> (PhotoDTO) new PhotoDTO().convertFrom(photo))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@PutMapping("{photoId:\\d+}/likes")
|
||||
@ApiOperation("Likes a photo")
|
||||
@CacheLock(autoDelete = false, traceRequest = true)
|
||||
public void likes(@PathVariable @CacheParam Integer photoId) {
|
||||
photoService.increaseLike(photoId);
|
||||
}
|
||||
|
||||
@GetMapping("teams")
|
||||
@ApiOperation("Lists all of photo teams")
|
||||
public List<String> listTeams() {
|
||||
return photoService.listAllTeams();
|
||||
}
|
||||
}
|
|
@ -1,149 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.util.List;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.model.dto.BaseCommentDTO;
|
||||
import run.halo.app.model.entity.PostComment;
|
||||
import run.halo.app.model.enums.CommentStatus;
|
||||
import run.halo.app.model.params.CommentQuery;
|
||||
import run.halo.app.model.params.PostCommentParam;
|
||||
import run.halo.app.model.vo.BaseCommentVO;
|
||||
import run.halo.app.model.vo.BaseCommentWithParentVO;
|
||||
import run.halo.app.model.vo.PostCommentWithPostVO;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PostCommentService;
|
||||
|
||||
/**
|
||||
* Post comment controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @date 2019-03-29
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/posts/comments")
|
||||
public class PostCommentController {
|
||||
|
||||
private final PostCommentService postCommentService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
public PostCommentController(PostCommentService postCommentService,
|
||||
OptionService optionService) {
|
||||
this.postCommentService = postCommentService;
|
||||
this.optionService = optionService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("Lists post comments")
|
||||
public Page<PostCommentWithPostVO> pageBy(
|
||||
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable,
|
||||
CommentQuery commentQuery) {
|
||||
Page<PostComment> commentPage = postCommentService.pageBy(commentQuery, pageable);
|
||||
return postCommentService.convertToWithPostVo(commentPage);
|
||||
}
|
||||
|
||||
@GetMapping("latest")
|
||||
@ApiOperation("Pages post latest comments")
|
||||
public List<PostCommentWithPostVO> listLatest(
|
||||
@RequestParam(name = "top", defaultValue = "10") int top,
|
||||
@RequestParam(name = "status", required = false) CommentStatus status) {
|
||||
// Get latest comment
|
||||
List<PostComment> content = postCommentService.pageLatest(top, status).getContent();
|
||||
|
||||
// Convert and return
|
||||
return postCommentService.convertToWithPostVo(content);
|
||||
}
|
||||
|
||||
@GetMapping("{postId:\\d+}/tree_view")
|
||||
@ApiOperation("Lists post comments with tree view")
|
||||
public Page<BaseCommentVO> listCommentTree(@PathVariable("postId") Integer postId,
|
||||
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
return postCommentService
|
||||
.pageVosAllBy(postId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||
}
|
||||
|
||||
@GetMapping("{postId:\\d+}/list_view")
|
||||
@ApiOperation("Lists post comment with list view")
|
||||
public Page<BaseCommentWithParentVO> listComments(@PathVariable("postId") Integer postId,
|
||||
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
return postCommentService.pageWithParentVoBy(postId,
|
||||
PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ApiOperation("Creates a post comment (new or reply)")
|
||||
public BaseCommentDTO createBy(@RequestBody PostCommentParam postCommentParam) {
|
||||
PostComment createdPostComment = postCommentService.createBy(postCommentParam);
|
||||
return postCommentService.convertTo(createdPostComment);
|
||||
}
|
||||
|
||||
@PutMapping("{commentId:\\d+}/status/{status}")
|
||||
@ApiOperation("Updates post comment status")
|
||||
public BaseCommentDTO updateStatusBy(@PathVariable("commentId") Long commentId,
|
||||
@PathVariable("status") CommentStatus status) {
|
||||
// Update comment status
|
||||
PostComment updatedPostComment = postCommentService.updateStatus(commentId, status);
|
||||
return postCommentService.convertTo(updatedPostComment);
|
||||
}
|
||||
|
||||
@PutMapping("status/{status}")
|
||||
@ApiOperation("Updates post comment status in batch")
|
||||
public List<BaseCommentDTO> updateStatusInBatch(
|
||||
@PathVariable(name = "status") CommentStatus status,
|
||||
@RequestBody List<Long> ids) {
|
||||
List<PostComment> comments = postCommentService.updateStatusByIds(ids, status);
|
||||
return postCommentService.convertTo(comments);
|
||||
}
|
||||
|
||||
@DeleteMapping("{commentId:\\d+}")
|
||||
@ApiOperation("Deletes post comment permanently and recursively")
|
||||
public BaseCommentDTO deletePermanently(@PathVariable("commentId") Long commentId) {
|
||||
PostComment deletedPostComment = postCommentService.removeById(commentId);
|
||||
return postCommentService.convertTo(deletedPostComment);
|
||||
}
|
||||
|
||||
@DeleteMapping
|
||||
@ApiOperation("Delete post comments permanently in batch by id array")
|
||||
public List<PostComment> deletePermanentlyInBatch(@RequestBody List<Long> ids) {
|
||||
return postCommentService.removeByIds(ids);
|
||||
}
|
||||
|
||||
@GetMapping("{commentId:\\d+}")
|
||||
@ApiOperation("Gets a post comment by comment id")
|
||||
public PostCommentWithPostVO getBy(@PathVariable("commentId") Long commentId) {
|
||||
PostComment comment = postCommentService.getById(commentId);
|
||||
return postCommentService.convertToWithPostVo(comment);
|
||||
}
|
||||
|
||||
@PutMapping("{commentId:\\d+}")
|
||||
@ApiOperation("Updates a post comment")
|
||||
public BaseCommentDTO updateBy(@Valid @RequestBody PostCommentParam commentParam,
|
||||
@PathVariable("commentId") Long commentId) {
|
||||
PostComment commentToUpdate = postCommentService.getById(commentId);
|
||||
|
||||
commentParam.update(commentToUpdate);
|
||||
|
||||
return postCommentService.convertTo(postCommentService.update(commentToUpdate));
|
||||
}
|
||||
}
|
|
@ -1,215 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.validation.Valid;
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.cache.AbstractStringCacheStore;
|
||||
import run.halo.app.model.dto.post.BasePostDetailDTO;
|
||||
import run.halo.app.model.dto.post.BasePostMinimalDTO;
|
||||
import run.halo.app.model.dto.post.BasePostSimpleDTO;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.params.PostContentParam;
|
||||
import run.halo.app.model.params.PostParam;
|
||||
import run.halo.app.model.params.PostQuery;
|
||||
import run.halo.app.model.vo.PostDetailVO;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PostService;
|
||||
import run.halo.app.service.assembler.PostAssembler;
|
||||
import run.halo.app.utils.HaloUtils;
|
||||
|
||||
/**
|
||||
* Post controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @author guqing
|
||||
* @date 2019-03-19
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/posts")
|
||||
public class PostController {
|
||||
|
||||
private final PostService postService;
|
||||
|
||||
private final AbstractStringCacheStore cacheStore;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final PostAssembler postAssembler;
|
||||
|
||||
public PostController(PostService postService,
|
||||
AbstractStringCacheStore cacheStore,
|
||||
OptionService optionService,
|
||||
PostAssembler postAssembler) {
|
||||
this.postService = postService;
|
||||
this.cacheStore = cacheStore;
|
||||
this.optionService = optionService;
|
||||
this.postAssembler = postAssembler;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("Lists posts")
|
||||
public Page<? extends BasePostSimpleDTO> pageBy(
|
||||
@PageableDefault(sort = {"topPriority", "createTime"}, direction = DESC) Pageable pageable,
|
||||
PostQuery postQuery,
|
||||
@RequestParam(value = "more", defaultValue = "true") Boolean more) {
|
||||
Page<Post> postPage = postService.pageBy(postQuery, pageable);
|
||||
if (more) {
|
||||
return postAssembler.convertToListVo(postPage);
|
||||
}
|
||||
|
||||
return postAssembler.convertToSimple(postPage);
|
||||
}
|
||||
|
||||
@GetMapping("latest")
|
||||
@ApiOperation("Pages latest post")
|
||||
public List<BasePostMinimalDTO> pageLatest(
|
||||
@RequestParam(name = "top", defaultValue = "10") int top) {
|
||||
return postAssembler.convertToMinimal(postService.pageLatest(top).getContent());
|
||||
}
|
||||
|
||||
@GetMapping("status/{status}")
|
||||
@ApiOperation("Gets a page of post by post status")
|
||||
public Page<? extends BasePostSimpleDTO> pageByStatus(
|
||||
@PathVariable(name = "status") PostStatus status,
|
||||
@RequestParam(value = "more", required = false, defaultValue = "false") Boolean more,
|
||||
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable) {
|
||||
Page<Post> posts = postService.pageBy(status, pageable);
|
||||
|
||||
if (more) {
|
||||
return postAssembler.convertToListVo(posts);
|
||||
}
|
||||
|
||||
return postAssembler.convertToSimple(posts);
|
||||
}
|
||||
|
||||
@GetMapping("{postId:\\d+}")
|
||||
@ApiOperation("Gets a post")
|
||||
public PostDetailVO getBy(@PathVariable("postId") Integer postId) {
|
||||
Post post = postService.getWithLatestContentById(postId);
|
||||
return postAssembler.convertToDetailVo(post);
|
||||
}
|
||||
|
||||
@PutMapping("{postId:\\d+}/likes")
|
||||
@ApiOperation("Likes a post")
|
||||
public void likes(@PathVariable("postId") Integer postId) {
|
||||
postService.increaseLike(postId);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ApiOperation("Creates a post")
|
||||
public PostDetailVO createBy(@Valid @RequestBody PostParam postParam,
|
||||
@RequestParam(value = "autoSave", required = false, defaultValue = "false") Boolean autoSave
|
||||
) {
|
||||
// Convert to
|
||||
Post post = postParam.convertTo();
|
||||
return postService.createBy(post, postParam.getTagIds(), postParam.getCategoryIds(),
|
||||
postParam.getPostMetas(), autoSave);
|
||||
}
|
||||
|
||||
@PutMapping("{postId:\\d+}")
|
||||
@ApiOperation("Updates a post")
|
||||
public PostDetailVO updateBy(@Valid @RequestBody PostParam postParam,
|
||||
@PathVariable("postId") Integer postId,
|
||||
@RequestParam(value = "autoSave", required = false, defaultValue = "false") Boolean autoSave
|
||||
) {
|
||||
// Get the post info
|
||||
Post postToUpdate = postService.getWithLatestContentById(postId);
|
||||
|
||||
postParam.update(postToUpdate);
|
||||
return postService.updateBy(postToUpdate, postParam.getTagIds(), postParam.getCategoryIds(),
|
||||
postParam.getPostMetas(), autoSave);
|
||||
}
|
||||
|
||||
@PutMapping("{postId:\\d+}/status/{status}")
|
||||
@ApiOperation("Updates post status")
|
||||
public BasePostMinimalDTO updateStatusBy(
|
||||
@PathVariable("postId") Integer postId,
|
||||
@PathVariable("status") PostStatus status) {
|
||||
Post post = postService.updateStatus(status, postId);
|
||||
|
||||
return new BasePostMinimalDTO().convertFrom(post);
|
||||
}
|
||||
|
||||
@PutMapping("status/{status}")
|
||||
@ApiOperation("Updates post status in batch")
|
||||
public List<Post> updateStatusInBatch(@PathVariable(name = "status") PostStatus status,
|
||||
@RequestBody List<Integer> ids) {
|
||||
return postService.updateStatusByIds(ids, status);
|
||||
}
|
||||
|
||||
@PutMapping("{postId:\\d+}/status/draft/content")
|
||||
@ApiOperation("Updates draft")
|
||||
public BasePostDetailDTO updateDraftBy(
|
||||
@PathVariable("postId") Integer postId,
|
||||
@RequestBody PostContentParam contentParam) {
|
||||
Post postToUse = postService.getById(postId);
|
||||
String formattedContent = contentParam.decideContentBy(postToUse.getEditorType());
|
||||
// Update draft content
|
||||
Post post = postService.updateDraftContent(formattedContent,
|
||||
contentParam.getOriginalContent(), postId);
|
||||
return postAssembler.convertToDetail(post);
|
||||
}
|
||||
|
||||
@DeleteMapping("{postId:\\d+}")
|
||||
@ApiOperation("Deletes a photo permanently")
|
||||
public void deletePermanently(@PathVariable("postId") Integer postId) {
|
||||
postService.removeById(postId);
|
||||
}
|
||||
|
||||
@DeleteMapping
|
||||
@ApiOperation("Deletes posts permanently in batch by id array")
|
||||
public List<Post> deletePermanentlyInBatch(@RequestBody List<Integer> ids) {
|
||||
return postService.removeByIds(ids);
|
||||
}
|
||||
|
||||
@GetMapping(value = {"preview/{postId:\\d+}", "{postId:\\d+}/preview"})
|
||||
@ApiOperation("Gets a post preview link")
|
||||
public String preview(@PathVariable("postId") Integer postId)
|
||||
throws UnsupportedEncodingException, URISyntaxException {
|
||||
Post post = postService.getById(postId);
|
||||
|
||||
post.setSlug(URLEncoder.encode(post.getSlug(), StandardCharsets.UTF_8.name()));
|
||||
|
||||
BasePostMinimalDTO postMinimalDTO = postAssembler.convertToMinimal(post);
|
||||
|
||||
String token = HaloUtils.simpleUUID();
|
||||
|
||||
// cache preview token
|
||||
cacheStore.putAny(token, token, 10, TimeUnit.MINUTES);
|
||||
|
||||
StringBuilder previewUrl = new StringBuilder();
|
||||
|
||||
if (!optionService.isEnabledAbsolutePath()) {
|
||||
previewUrl.append(optionService.getBlogBaseUrl());
|
||||
}
|
||||
|
||||
previewUrl.append(postMinimalDTO.getFullPath());
|
||||
|
||||
// build preview post url and return
|
||||
return new URIBuilder(previewUrl.toString())
|
||||
.addParameter("token", token)
|
||||
.build().toString();
|
||||
}
|
||||
}
|
|
@ -1,147 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.util.List;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.model.dto.BaseCommentDTO;
|
||||
import run.halo.app.model.entity.SheetComment;
|
||||
import run.halo.app.model.enums.CommentStatus;
|
||||
import run.halo.app.model.params.CommentQuery;
|
||||
import run.halo.app.model.params.SheetCommentParam;
|
||||
import run.halo.app.model.vo.BaseCommentVO;
|
||||
import run.halo.app.model.vo.BaseCommentWithParentVO;
|
||||
import run.halo.app.model.vo.SheetCommentWithSheetVO;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.SheetCommentService;
|
||||
|
||||
/**
|
||||
* Sheet comment controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @date 2019-04-25
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/sheets/comments")
|
||||
public class SheetCommentController {
|
||||
|
||||
private final SheetCommentService sheetCommentService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
public SheetCommentController(SheetCommentService sheetCommentService,
|
||||
OptionService optionService) {
|
||||
this.sheetCommentService = sheetCommentService;
|
||||
this.optionService = optionService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("Lists sheet comments")
|
||||
public Page<SheetCommentWithSheetVO> pageBy(
|
||||
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable,
|
||||
CommentQuery commentQuery) {
|
||||
Page<SheetComment> sheetCommentPage = sheetCommentService.pageBy(commentQuery, pageable);
|
||||
return sheetCommentService.convertToWithSheetVo(sheetCommentPage);
|
||||
}
|
||||
|
||||
@GetMapping("latest")
|
||||
@ApiOperation("Lists latest sheet comments")
|
||||
public List<SheetCommentWithSheetVO> listLatest(
|
||||
@RequestParam(name = "top", defaultValue = "10") int top,
|
||||
@RequestParam(name = "status", required = false) CommentStatus status) {
|
||||
Page<SheetComment> sheetCommentPage = sheetCommentService.pageLatest(top, status);
|
||||
return sheetCommentService.convertToWithSheetVo(sheetCommentPage.getContent());
|
||||
}
|
||||
|
||||
@GetMapping("{sheetId:\\d+}/tree_view")
|
||||
@ApiOperation("Lists sheet comments with tree view")
|
||||
public Page<BaseCommentVO> listCommentTree(@PathVariable("sheetId") Integer sheetId,
|
||||
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
return sheetCommentService
|
||||
.pageVosAllBy(sheetId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||
}
|
||||
|
||||
@GetMapping("{sheetId:\\d+}/list_view")
|
||||
@ApiOperation("Lists sheet comment with list view")
|
||||
public Page<BaseCommentWithParentVO> listComments(@PathVariable("sheetId") Integer sheetId,
|
||||
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
return sheetCommentService.pageWithParentVoBy(sheetId,
|
||||
PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ApiOperation("Creates a sheet comment (new or reply)")
|
||||
public BaseCommentDTO createBy(@RequestBody SheetCommentParam commentParam) {
|
||||
SheetComment createdComment = sheetCommentService.createBy(commentParam);
|
||||
return sheetCommentService.convertTo(createdComment);
|
||||
}
|
||||
|
||||
@PutMapping("{commentId:\\d+}/status/{status}")
|
||||
@ApiOperation("Updates sheet comment status")
|
||||
public BaseCommentDTO updateStatusBy(@PathVariable("commentId") Long commentId,
|
||||
@PathVariable("status") CommentStatus status) {
|
||||
// Update comment status
|
||||
SheetComment updatedSheetComment = sheetCommentService.updateStatus(commentId, status);
|
||||
return sheetCommentService.convertTo(updatedSheetComment);
|
||||
}
|
||||
|
||||
@PutMapping("status/{status}")
|
||||
@ApiOperation("Updates sheet comment status in batch")
|
||||
public List<BaseCommentDTO> updateStatusInBatch(
|
||||
@PathVariable(name = "status") CommentStatus status,
|
||||
@RequestBody List<Long> ids) {
|
||||
List<SheetComment> comments = sheetCommentService.updateStatusByIds(ids, status);
|
||||
return sheetCommentService.convertTo(comments);
|
||||
}
|
||||
|
||||
|
||||
@DeleteMapping("{commentId:\\d+}")
|
||||
@ApiOperation("Deletes sheet comment permanently and recursively")
|
||||
public BaseCommentDTO deletePermanently(@PathVariable("commentId") Long commentId) {
|
||||
SheetComment deletedSheetComment = sheetCommentService.removeById(commentId);
|
||||
return sheetCommentService.convertTo(deletedSheetComment);
|
||||
}
|
||||
|
||||
@DeleteMapping
|
||||
@ApiOperation("Deletes sheet comments permanently in batch by id array")
|
||||
public List<SheetComment> deletePermanentlyInBatch(@RequestBody List<Long> ids) {
|
||||
return sheetCommentService.removeByIds(ids);
|
||||
}
|
||||
|
||||
@GetMapping("{commentId:\\d+}")
|
||||
@ApiOperation("Gets a sheet comment by comment id")
|
||||
public SheetCommentWithSheetVO getBy(@PathVariable("commentId") Long commentId) {
|
||||
SheetComment comment = sheetCommentService.getById(commentId);
|
||||
return sheetCommentService.convertToWithSheetVo(comment);
|
||||
}
|
||||
|
||||
@PutMapping("{commentId:\\d+}")
|
||||
@ApiOperation("Updates a sheet comment")
|
||||
public BaseCommentDTO updateBy(@Valid @RequestBody SheetCommentParam commentParam,
|
||||
@PathVariable("commentId") Long commentId) {
|
||||
SheetComment commentToUpdate = sheetCommentService.getById(commentId);
|
||||
|
||||
commentParam.update(commentToUpdate);
|
||||
|
||||
return sheetCommentService.convertTo(sheetCommentService.update(commentToUpdate));
|
||||
}
|
||||
}
|
|
@ -1,178 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.cache.AbstractStringCacheStore;
|
||||
import run.halo.app.model.dto.IndependentSheetDTO;
|
||||
import run.halo.app.model.dto.post.BasePostDetailDTO;
|
||||
import run.halo.app.model.dto.post.BasePostMinimalDTO;
|
||||
import run.halo.app.model.entity.Sheet;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.params.PostContentParam;
|
||||
import run.halo.app.model.params.SheetParam;
|
||||
import run.halo.app.model.vo.SheetDetailVO;
|
||||
import run.halo.app.model.vo.SheetListVO;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.SheetService;
|
||||
import run.halo.app.service.assembler.SheetAssembler;
|
||||
import run.halo.app.utils.HaloUtils;
|
||||
|
||||
/**
|
||||
* Sheet controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @date 19-4-24
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/sheets")
|
||||
public class SheetController {
|
||||
|
||||
private final SheetService sheetService;
|
||||
|
||||
private final AbstractStringCacheStore cacheStore;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final SheetAssembler sheetAssembler;
|
||||
|
||||
public SheetController(SheetService sheetService,
|
||||
AbstractStringCacheStore cacheStore,
|
||||
OptionService optionService,
|
||||
SheetAssembler sheetAssembler) {
|
||||
this.sheetService = sheetService;
|
||||
this.cacheStore = cacheStore;
|
||||
this.optionService = optionService;
|
||||
this.sheetAssembler = sheetAssembler;
|
||||
}
|
||||
|
||||
@GetMapping("{sheetId:\\d+}")
|
||||
@ApiOperation("Gets a sheet")
|
||||
public SheetDetailVO getBy(@PathVariable("sheetId") Integer sheetId) {
|
||||
Sheet sheet = sheetService.getWithLatestContentById(sheetId);
|
||||
return sheetAssembler.convertToDetailVo(sheet);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("Gets a page of sheet")
|
||||
public Page<SheetListVO> pageBy(
|
||||
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable) {
|
||||
Page<Sheet> sheetPage = sheetService.pageBy(pageable);
|
||||
return sheetAssembler.convertToListVo(sheetPage);
|
||||
}
|
||||
|
||||
@GetMapping("independent")
|
||||
@ApiOperation("Lists independent sheets")
|
||||
public List<IndependentSheetDTO> independentSheets() {
|
||||
return sheetService.listIndependentSheets();
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ApiOperation("Creates a sheet")
|
||||
public SheetDetailVO createBy(@RequestBody @Valid SheetParam sheetParam,
|
||||
@RequestParam(value = "autoSave", required = false, defaultValue = "false")
|
||||
Boolean autoSave) {
|
||||
Sheet sheet =
|
||||
sheetService.createBy(sheetParam.convertTo(), sheetParam.getSheetMetas(), autoSave);
|
||||
return sheetAssembler.convertToDetailVo(sheet);
|
||||
}
|
||||
|
||||
@PutMapping("{sheetId:\\d+}")
|
||||
@ApiOperation("Updates a sheet")
|
||||
public SheetDetailVO updateBy(
|
||||
@PathVariable("sheetId") Integer sheetId,
|
||||
@RequestBody @Valid SheetParam sheetParam,
|
||||
@RequestParam(value = "autoSave", required = false, defaultValue = "false")
|
||||
Boolean autoSave) {
|
||||
Sheet sheetToUpdate = sheetService.getWithLatestContentById(sheetId);
|
||||
|
||||
sheetParam.update(sheetToUpdate);
|
||||
|
||||
Sheet sheet = sheetService.updateBy(sheetToUpdate, sheetParam.getSheetMetas(), autoSave);
|
||||
|
||||
return sheetAssembler.convertToDetailVo(sheet);
|
||||
}
|
||||
|
||||
@PutMapping("{sheetId:\\d+}/{status}")
|
||||
@ApiOperation("Updates a sheet")
|
||||
public void updateStatusBy(
|
||||
@PathVariable("sheetId") Integer sheetId,
|
||||
@PathVariable("status") PostStatus status) {
|
||||
Sheet sheet = sheetService.getById(sheetId);
|
||||
|
||||
// Set status
|
||||
sheet.setStatus(status);
|
||||
|
||||
// Update
|
||||
sheetService.update(sheet);
|
||||
}
|
||||
|
||||
@PutMapping("{sheetId:\\d+}/status/draft/content")
|
||||
@ApiOperation("Updates draft")
|
||||
public BasePostDetailDTO updateDraftBy(
|
||||
@PathVariable("sheetId") Integer sheetId,
|
||||
@RequestBody PostContentParam contentParam) {
|
||||
Sheet sheetToUse = sheetService.getById(sheetId);
|
||||
String formattedContent = contentParam.decideContentBy(sheetToUse.getEditorType());
|
||||
|
||||
// Update draft content
|
||||
Sheet sheet = sheetService.updateDraftContent(formattedContent,
|
||||
contentParam.getOriginalContent(), sheetId);
|
||||
return sheetAssembler.convertToDetail(sheet);
|
||||
}
|
||||
|
||||
@DeleteMapping("{sheetId:\\d+}")
|
||||
@ApiOperation("Deletes a sheet")
|
||||
public SheetDetailVO deleteBy(@PathVariable("sheetId") Integer sheetId) {
|
||||
Sheet sheet = sheetService.removeById(sheetId);
|
||||
return sheetAssembler.convertToDetailVo(sheet);
|
||||
}
|
||||
|
||||
@GetMapping("preview/{sheetId:\\d+}")
|
||||
@ApiOperation("Gets a sheet preview link")
|
||||
public String preview(@PathVariable("sheetId") Integer sheetId)
|
||||
throws UnsupportedEncodingException {
|
||||
Sheet sheet = sheetService.getById(sheetId);
|
||||
|
||||
sheet.setSlug(URLEncoder.encode(sheet.getSlug(), StandardCharsets.UTF_8.name()));
|
||||
|
||||
BasePostMinimalDTO sheetMinimalDTO = sheetAssembler.convertToMinimal(sheet);
|
||||
|
||||
String token = HaloUtils.simpleUUID();
|
||||
|
||||
// cache preview token
|
||||
cacheStore.putAny(token, token, 10, TimeUnit.MINUTES);
|
||||
|
||||
StringBuilder previewUrl = new StringBuilder();
|
||||
|
||||
if (!optionService.isEnabledAbsolutePath()) {
|
||||
previewUrl.append(optionService.getBlogBaseUrl());
|
||||
}
|
||||
|
||||
previewUrl.append(sheetMinimalDTO.getFullPath())
|
||||
.append("?token=")
|
||||
.append(token);
|
||||
|
||||
// build preview post url and return
|
||||
return previewUrl.toString();
|
||||
}
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.util.List;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import run.halo.app.model.params.StaticContentParam;
|
||||
import run.halo.app.model.support.StaticFile;
|
||||
import run.halo.app.service.StaticStorageService;
|
||||
|
||||
/**
|
||||
* Static storage controller.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-12-06
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/statics")
|
||||
public class StaticStorageController {
|
||||
|
||||
private final StaticStorageService staticStorageService;
|
||||
|
||||
public StaticStorageController(StaticStorageService staticStorageService) {
|
||||
this.staticStorageService = staticStorageService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("Lists static files")
|
||||
public List<StaticFile> list() {
|
||||
return staticStorageService.listStaticFolder();
|
||||
}
|
||||
|
||||
@DeleteMapping
|
||||
@ApiOperation("Deletes file by relative path")
|
||||
public void deletePermanently(@RequestParam("path") String path) {
|
||||
staticStorageService.delete(path);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ApiOperation("Creates a folder")
|
||||
public void createFolder(String basePath,
|
||||
@RequestParam("folderName") String folderName) {
|
||||
staticStorageService.createFolder(basePath, folderName);
|
||||
}
|
||||
|
||||
@PostMapping("upload")
|
||||
@ApiOperation("Uploads static file")
|
||||
public void upload(String basePath,
|
||||
@RequestPart("file") MultipartFile file) {
|
||||
staticStorageService.upload(basePath, file);
|
||||
}
|
||||
|
||||
@PostMapping("rename")
|
||||
@ApiOperation("Renames static file")
|
||||
public void rename(String basePath,
|
||||
String newName) {
|
||||
staticStorageService.rename(basePath, newName);
|
||||
}
|
||||
|
||||
@PutMapping("files")
|
||||
@ApiOperation("Save static file")
|
||||
public void save(@RequestBody StaticContentParam param) {
|
||||
staticStorageService.save(param.getPath(), param.getContent());
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.model.dto.StatisticDTO;
|
||||
import run.halo.app.model.dto.StatisticWithUserDTO;
|
||||
import run.halo.app.service.StatisticService;
|
||||
|
||||
/**
|
||||
* Statistic controller.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-12-16
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/statistics")
|
||||
public class StatisticController {
|
||||
|
||||
private final StatisticService statisticService;
|
||||
|
||||
public StatisticController(StatisticService statisticService) {
|
||||
this.statisticService = statisticService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("Gets blog statistics.")
|
||||
public StatisticDTO statistics() {
|
||||
return statisticService.getStatistic();
|
||||
}
|
||||
|
||||
@GetMapping("user")
|
||||
@ApiOperation("Gets blog statistics with user")
|
||||
public StatisticWithUserDTO statisticsWithUser() {
|
||||
return statisticService.getStatisticWithUser();
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import java.util.List;
|
||||
import javax.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.model.dto.TagDTO;
|
||||
import run.halo.app.model.entity.Tag;
|
||||
import run.halo.app.model.params.TagParam;
|
||||
import run.halo.app.service.PostTagService;
|
||||
import run.halo.app.service.TagService;
|
||||
|
||||
/**
|
||||
* Tag controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 3/20/19
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/tags")
|
||||
public class TagController {
|
||||
|
||||
private final TagService tagService;
|
||||
|
||||
private final PostTagService postTagService;
|
||||
|
||||
public TagController(TagService tagService,
|
||||
PostTagService postTagService) {
|
||||
this.tagService = tagService;
|
||||
this.postTagService = postTagService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("Lists tags")
|
||||
public List<? extends TagDTO> listTags(
|
||||
@SortDefault(sort = "createTime", direction = Sort.Direction.DESC) Sort sort,
|
||||
@ApiParam("Return more information(post count) if it is set")
|
||||
@RequestParam(name = "more", required = false, defaultValue = "false") Boolean more) {
|
||||
if (more) {
|
||||
return postTagService.listTagWithCountDtos(sort);
|
||||
}
|
||||
return tagService.convertTo(tagService.listAll(sort));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ApiOperation("Creates a tag")
|
||||
public TagDTO createTag(@Valid @RequestBody TagParam tagParam) {
|
||||
// Convert to tag
|
||||
Tag tag = tagParam.convertTo();
|
||||
|
||||
log.debug("Tag to be created: [{}]", tag);
|
||||
|
||||
// Create and convert
|
||||
return tagService.convertTo(tagService.create(tag));
|
||||
}
|
||||
|
||||
@GetMapping("{tagId:\\d+}")
|
||||
@ApiOperation("Gets tag detail by id")
|
||||
public TagDTO getBy(@PathVariable("tagId") Integer tagId) {
|
||||
return tagService.convertTo(tagService.getById(tagId));
|
||||
}
|
||||
|
||||
@PutMapping("{tagId:\\d+}")
|
||||
@ApiOperation("Updates a tag")
|
||||
public TagDTO updateBy(@PathVariable("tagId") Integer tagId,
|
||||
@Valid @RequestBody TagParam tagParam) {
|
||||
// Get old tag
|
||||
Tag tag = tagService.getById(tagId);
|
||||
|
||||
// Update tag
|
||||
tagParam.update(tag);
|
||||
|
||||
// Update tag
|
||||
return tagService.convertTo(tagService.update(tag));
|
||||
}
|
||||
|
||||
@DeleteMapping("{tagId:\\d+}")
|
||||
@ApiOperation("Deletes a tag")
|
||||
public TagDTO deletePermanently(@PathVariable("tagId") Integer tagId) {
|
||||
// Remove the tag
|
||||
Tag deletedTag = tagService.removeById(tagId);
|
||||
// Remove the post tag relationship
|
||||
postTagService.removeByTagId(tagId);
|
||||
|
||||
return tagService.convertTo(deletedTag);
|
||||
}
|
||||
}
|
|
@ -1,234 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import run.halo.app.annotation.DisableOnCondition;
|
||||
import run.halo.app.cache.lock.CacheLock;
|
||||
import run.halo.app.handler.theme.config.support.Group;
|
||||
import run.halo.app.handler.theme.config.support.Item;
|
||||
import run.halo.app.handler.theme.config.support.ThemeProperty;
|
||||
import run.halo.app.model.params.ThemeContentParam;
|
||||
import run.halo.app.model.support.BaseResponse;
|
||||
import run.halo.app.model.support.ThemeFile;
|
||||
import run.halo.app.service.ThemeService;
|
||||
import run.halo.app.service.ThemeSettingService;
|
||||
import run.halo.app.utils.ServiceUtils;
|
||||
|
||||
/**
|
||||
* Theme controller.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @author guqing
|
||||
* @date 2019-03-20
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/themes")
|
||||
public class ThemeController {
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
private final ThemeSettingService themeSettingService;
|
||||
|
||||
public ThemeController(ThemeService themeService,
|
||||
ThemeSettingService themeSettingService) {
|
||||
this.themeService = themeService;
|
||||
this.themeSettingService = themeSettingService;
|
||||
}
|
||||
|
||||
@GetMapping("{themeId:.+}")
|
||||
@ApiOperation("Gets theme property by theme id")
|
||||
public ThemeProperty getBy(@PathVariable("themeId") String themeId) {
|
||||
return themeService.getThemeOfNonNullBy(themeId);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("Lists all themes")
|
||||
public List<ThemeProperty> listAll() {
|
||||
return themeService.getThemes();
|
||||
}
|
||||
|
||||
@GetMapping("activation/files")
|
||||
@ApiOperation("Lists all activate theme files")
|
||||
public List<ThemeFile> listFiles() {
|
||||
return themeService.listThemeFolderBy(themeService.getActivatedThemeId());
|
||||
}
|
||||
|
||||
@GetMapping("{themeId:.+}/files")
|
||||
@ApiOperation("Lists theme files by theme id")
|
||||
public List<ThemeFile> listFiles(@PathVariable("themeId") String themeId) {
|
||||
return themeService.listThemeFolderBy(themeId);
|
||||
}
|
||||
|
||||
@GetMapping("files/content")
|
||||
@ApiOperation("Gets template content")
|
||||
public BaseResponse<String> getContentBy(@RequestParam(name = "path") String path) {
|
||||
return BaseResponse
|
||||
.ok(HttpStatus.OK.getReasonPhrase(), themeService.getTemplateContent(path));
|
||||
}
|
||||
|
||||
@GetMapping("{themeId:.+}/files/content")
|
||||
@ApiOperation("Gets template content by theme id")
|
||||
public BaseResponse<String> getContentBy(@PathVariable("themeId") String themeId,
|
||||
@RequestParam(name = "path") String path) {
|
||||
return BaseResponse
|
||||
.ok(HttpStatus.OK.getReasonPhrase(), themeService.getTemplateContent(themeId, path));
|
||||
}
|
||||
|
||||
@PutMapping("files/content")
|
||||
@ApiOperation("Updates template content")
|
||||
@DisableOnCondition
|
||||
public void updateContentBy(@RequestBody ThemeContentParam param) {
|
||||
themeService.saveTemplateContent(param.getPath(), param.getContent());
|
||||
}
|
||||
|
||||
@PutMapping("{themeId:.+}/files/content")
|
||||
@ApiOperation("Updates template content by theme id")
|
||||
@DisableOnCondition
|
||||
public void updateContentBy(@PathVariable("themeId") String themeId,
|
||||
@RequestBody ThemeContentParam param) {
|
||||
themeService.saveTemplateContent(themeId, param.getPath(), param.getContent());
|
||||
}
|
||||
|
||||
@GetMapping("activation/template/custom/sheet")
|
||||
@ApiOperation("Gets custom sheet templates")
|
||||
public List<String> customSheetTemplate() {
|
||||
return themeService.listCustomTemplates(themeService.getActivatedThemeId(),
|
||||
ThemeService.CUSTOM_SHEET_PREFIX);
|
||||
}
|
||||
|
||||
@GetMapping("activation/template/custom/post")
|
||||
@ApiOperation("Gets custom post templates")
|
||||
public List<String> customPostTemplate() {
|
||||
return themeService.listCustomTemplates(themeService.getActivatedThemeId(),
|
||||
ThemeService.CUSTOM_POST_PREFIX);
|
||||
}
|
||||
|
||||
@PostMapping("{themeId:.+}/activation")
|
||||
@ApiOperation("Activates a theme")
|
||||
public ThemeProperty active(@PathVariable("themeId") String themeId) {
|
||||
return themeService.activateTheme(themeId);
|
||||
}
|
||||
|
||||
@GetMapping("activation")
|
||||
@ApiOperation("Gets activate theme")
|
||||
public ThemeProperty getActivateTheme() {
|
||||
return themeService.getThemeOfNonNullBy(themeService.getActivatedThemeId());
|
||||
}
|
||||
|
||||
@GetMapping("activation/configurations")
|
||||
@ApiOperation("Fetches activated theme configuration")
|
||||
public BaseResponse<Object> fetchConfig() {
|
||||
return BaseResponse.ok(themeService.fetchConfig(themeService.getActivatedThemeId()));
|
||||
}
|
||||
|
||||
@GetMapping("{themeId:.+}/configurations")
|
||||
@ApiOperation("Fetches theme configuration by theme id")
|
||||
public List<Group> fetchConfig(@PathVariable("themeId") String themeId) {
|
||||
return themeService.fetchConfig(themeId);
|
||||
}
|
||||
|
||||
@GetMapping("{themeId:.+}/configurations/groups/{group}")
|
||||
@ApiOperation("Fetches theme configuration by theme id and group name")
|
||||
public Set<Item> fetchConfigByGroup(@PathVariable("themeId") String themeId,
|
||||
@PathVariable String group) {
|
||||
return themeService.fetchConfigItemsBy(themeId, group);
|
||||
}
|
||||
|
||||
@GetMapping("{themeId:.+}/configurations/groups")
|
||||
@ApiOperation("Fetches theme configuration group names by theme id")
|
||||
public Set<String> fetchConfigGroups(@PathVariable("themeId") String themeId) {
|
||||
return ServiceUtils.fetchProperty(themeService.fetchConfig(themeId), Group::getName);
|
||||
}
|
||||
|
||||
@GetMapping("activation/settings")
|
||||
@ApiOperation("Lists activated theme settings")
|
||||
public Map<String, Object> listSettingsBy() {
|
||||
return themeSettingService.listAsMapBy(themeService.getActivatedThemeId());
|
||||
}
|
||||
|
||||
@GetMapping("{themeId:.+}/settings")
|
||||
@ApiOperation("Lists theme settings by theme id")
|
||||
public Map<String, Object> listSettingsBy(@PathVariable("themeId") String themeId) {
|
||||
return themeSettingService.listAsMapBy(themeId);
|
||||
}
|
||||
|
||||
@GetMapping("{themeId:.+}/groups/{group}/settings")
|
||||
@ApiOperation("Lists theme settings by theme id and group name")
|
||||
public Map<String, Object> listSettingsBy(@PathVariable("themeId") String themeId,
|
||||
@PathVariable String group) {
|
||||
return themeSettingService.listAsMapBy(themeId, group);
|
||||
}
|
||||
|
||||
@PostMapping("activation/settings")
|
||||
@ApiOperation("Saves theme settings")
|
||||
public void saveSettingsBy(@RequestBody Map<String, Object> settings) {
|
||||
themeSettingService.save(settings, themeService.getActivatedThemeId());
|
||||
}
|
||||
|
||||
@PostMapping("{themeId:.+}/settings")
|
||||
@ApiOperation("Saves theme settings")
|
||||
@CacheLock(prefix = "save_theme_setting_by_themeId")
|
||||
public void saveSettingsBy(@PathVariable("themeId") String themeId,
|
||||
@RequestBody Map<String, Object> settings) {
|
||||
themeSettingService.save(settings, themeId);
|
||||
}
|
||||
|
||||
@DeleteMapping("{themeId:.+}")
|
||||
@ApiOperation("Deletes a theme")
|
||||
@DisableOnCondition
|
||||
public void deleteBy(@PathVariable("themeId") String themeId,
|
||||
@RequestParam(value = "deleteSettings", defaultValue = "false") Boolean deleteSettings) {
|
||||
themeService.deleteTheme(themeId, deleteSettings);
|
||||
}
|
||||
|
||||
@PostMapping("upload")
|
||||
@ApiOperation("Uploads a theme")
|
||||
public ThemeProperty uploadTheme(@RequestPart("file") MultipartFile file) {
|
||||
return themeService.upload(file);
|
||||
}
|
||||
|
||||
@PutMapping("upload/{themeId:.+}")
|
||||
@ApiOperation("Upgrades theme by file")
|
||||
public ThemeProperty updateThemeByUpload(@PathVariable("themeId") String themeId,
|
||||
@RequestPart("file") MultipartFile file) {
|
||||
return themeService.update(themeId, file);
|
||||
}
|
||||
|
||||
@PostMapping("fetching")
|
||||
@ApiOperation("Fetches a new theme")
|
||||
public ThemeProperty fetchTheme(@RequestParam("uri") String uri) {
|
||||
return themeService.fetch(uri);
|
||||
}
|
||||
|
||||
@PutMapping("fetching/{themeId:.+}")
|
||||
@ApiOperation("Upgrades theme from remote")
|
||||
public ThemeProperty updateThemeByFetching(@PathVariable("themeId") String themeId) {
|
||||
return themeService.update(themeId);
|
||||
}
|
||||
|
||||
@PostMapping("reload")
|
||||
@ApiOperation("Reloads themes")
|
||||
public void reload() {
|
||||
themeService.reload();
|
||||
}
|
||||
|
||||
@GetMapping(value = "activation/template/exists")
|
||||
@ApiOperation("Determines if template exists")
|
||||
public BaseResponse<Boolean> exists(@RequestParam(value = "template") String template) {
|
||||
return BaseResponse.ok(themeService.templateExists(template));
|
||||
}
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
package run.halo.app.controller.admin.api;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import javax.validation.Valid;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.util.Base64Utils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.annotation.DisableOnCondition;
|
||||
import run.halo.app.cache.lock.CacheLock;
|
||||
import run.halo.app.exception.BadRequestException;
|
||||
import run.halo.app.model.dto.UserDTO;
|
||||
import run.halo.app.model.entity.User;
|
||||
import run.halo.app.model.enums.MFAType;
|
||||
import run.halo.app.model.params.MultiFactorAuthParam;
|
||||
import run.halo.app.model.params.PasswordParam;
|
||||
import run.halo.app.model.params.UserParam;
|
||||
import run.halo.app.model.support.BaseResponse;
|
||||
import run.halo.app.model.support.UpdateCheck;
|
||||
import run.halo.app.model.vo.MultiFactorAuthVO;
|
||||
import run.halo.app.service.UserService;
|
||||
import run.halo.app.utils.HaloUtils;
|
||||
import run.halo.app.utils.TwoFactorAuthUtils;
|
||||
import run.halo.app.utils.ValidationUtils;
|
||||
|
||||
/**
|
||||
* User controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 2019-03-19
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/users")
|
||||
public class UserController {
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
public UserController(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@GetMapping("profiles")
|
||||
@ApiOperation("Gets user profile")
|
||||
public UserDTO getProfile(User user) {
|
||||
return new UserDTO().convertFrom(user);
|
||||
}
|
||||
|
||||
@PutMapping("profiles")
|
||||
@ApiOperation("Updates user profile")
|
||||
@DisableOnCondition
|
||||
public UserDTO updateProfile(@RequestBody UserParam userParam, User user) {
|
||||
// Validate the user param
|
||||
ValidationUtils.validate(userParam, UpdateCheck.class);
|
||||
|
||||
// Update properties
|
||||
userParam.update(user);
|
||||
|
||||
// Update user and convert to dto
|
||||
return new UserDTO().convertFrom(userService.update(user));
|
||||
}
|
||||
|
||||
@PutMapping("profiles/password")
|
||||
@ApiOperation("Updates user's password")
|
||||
@DisableOnCondition
|
||||
public BaseResponse<String> updatePassword(@RequestBody @Valid PasswordParam passwordParam,
|
||||
User user) {
|
||||
userService.updatePassword(passwordParam.getOldPassword(), passwordParam.getNewPassword(),
|
||||
user.getId());
|
||||
return BaseResponse.ok("密码修改成功");
|
||||
}
|
||||
|
||||
@PutMapping("mfa/generate")
|
||||
@ApiOperation("Generate Multi-Factor Auth qr image")
|
||||
@DisableOnCondition
|
||||
public MultiFactorAuthVO generateMFAQrImage(
|
||||
@RequestBody MultiFactorAuthParam multiFactorAuthParam, User user) {
|
||||
if (MFAType.NONE == user.getMfaType()) {
|
||||
if (MFAType.TFA_TOTP == multiFactorAuthParam.getMfaType()) {
|
||||
String mfaKey = TwoFactorAuthUtils.generateTFAKey();
|
||||
String optAuthUrl =
|
||||
TwoFactorAuthUtils.generateOtpAuthUrl(user.getNickname(), mfaKey);
|
||||
String qrImageBase64 = "data:image/png;base64,"
|
||||
+ Base64Utils.encodeToString(
|
||||
HaloUtils.generateQrCodeToPng(optAuthUrl, 128, 128));
|
||||
return new MultiFactorAuthVO(qrImageBase64, optAuthUrl, mfaKey, MFAType.TFA_TOTP);
|
||||
} else {
|
||||
throw new BadRequestException("暂不支持的 MFA 认证的方式");
|
||||
}
|
||||
} else {
|
||||
throw new BadRequestException("MFA 认证已启用,无需重复操作");
|
||||
}
|
||||
}
|
||||
|
||||
@PutMapping("mfa/update")
|
||||
@ApiOperation("Updates user's Multi Factor Auth")
|
||||
@CacheLock(autoDelete = false, prefix = "mfa")
|
||||
@DisableOnCondition
|
||||
public MultiFactorAuthVO updateMFAuth(
|
||||
@RequestBody @Valid MultiFactorAuthParam multiFactorAuthParam, User user) {
|
||||
if (StringUtils.isNotBlank(user.getMfaKey())
|
||||
&& MFAType.useMFA(multiFactorAuthParam.getMfaType())) {
|
||||
return new MultiFactorAuthVO(MFAType.TFA_TOTP);
|
||||
} else if (StringUtils.isBlank(user.getMfaKey())
|
||||
&& !MFAType.useMFA(multiFactorAuthParam.getMfaType())) {
|
||||
return new MultiFactorAuthVO(MFAType.NONE);
|
||||
} else {
|
||||
final String mfaKey = StringUtils.isNotBlank(user.getMfaKey()) ? user.getMfaKey() :
|
||||
multiFactorAuthParam.getMfaKey();
|
||||
TwoFactorAuthUtils.validateTFACode(mfaKey, multiFactorAuthParam.getAuthcode());
|
||||
}
|
||||
// update MFA key
|
||||
User updateUser = userService
|
||||
.updateMFA(multiFactorAuthParam.getMfaType(), multiFactorAuthParam.getMfaKey(),
|
||||
user.getId());
|
||||
|
||||
return new MultiFactorAuthVO(updateUser.getMfaType());
|
||||
}
|
||||
}
|
|
@ -1,334 +0,0 @@
|
|||
package run.halo.app.controller.content;
|
||||
|
||||
import static run.halo.app.model.support.HaloConst.POST_PASSWORD_TEMPLATE;
|
||||
import static run.halo.app.model.support.HaloConst.SUFFIX_FTL;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import run.halo.app.cache.lock.CacheLock;
|
||||
import run.halo.app.controller.content.auth.ContentAuthenticationManager;
|
||||
import run.halo.app.controller.content.auth.ContentAuthenticationRequest;
|
||||
import run.halo.app.controller.content.model.CategoryModel;
|
||||
import run.halo.app.controller.content.model.JournalModel;
|
||||
import run.halo.app.controller.content.model.LinkModel;
|
||||
import run.halo.app.controller.content.model.PhotoModel;
|
||||
import run.halo.app.controller.content.model.PostModel;
|
||||
import run.halo.app.controller.content.model.SheetModel;
|
||||
import run.halo.app.controller.content.model.TagModel;
|
||||
import run.halo.app.exception.AuthenticationException;
|
||||
import run.halo.app.exception.NotFoundException;
|
||||
import run.halo.app.exception.UnsupportedException;
|
||||
import run.halo.app.model.dto.CategoryDTO;
|
||||
import run.halo.app.model.dto.post.BasePostMinimalDTO;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.entity.Sheet;
|
||||
import run.halo.app.model.enums.EncryptTypeEnum;
|
||||
import run.halo.app.model.enums.PostPermalinkType;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.enums.SheetPermalinkType;
|
||||
import run.halo.app.service.CategoryService;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PostService;
|
||||
import run.halo.app.service.SheetService;
|
||||
import run.halo.app.service.ThemeService;
|
||||
import run.halo.app.service.assembler.PostRenderAssembler;
|
||||
|
||||
/**
|
||||
* @author ryanwang
|
||||
* @author guqing
|
||||
* @date 2020-01-07
|
||||
*/
|
||||
@Slf4j
|
||||
@Controller
|
||||
@RequestMapping
|
||||
public class ContentContentController {
|
||||
|
||||
private final PostModel postModel;
|
||||
|
||||
private final SheetModel sheetModel;
|
||||
|
||||
private final CategoryModel categoryModel;
|
||||
|
||||
private final TagModel tagModel;
|
||||
|
||||
private final JournalModel journalModel;
|
||||
|
||||
private final PhotoModel photoModel;
|
||||
|
||||
private final LinkModel linkModel;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final PostService postService;
|
||||
|
||||
private final SheetService sheetService;
|
||||
|
||||
private final CategoryService categoryService;
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
private final PostRenderAssembler postRenderAssembler;
|
||||
|
||||
private final ContentAuthenticationManager providerManager;
|
||||
|
||||
public ContentContentController(PostModel postModel,
|
||||
SheetModel sheetModel,
|
||||
CategoryModel categoryModel,
|
||||
TagModel tagModel,
|
||||
JournalModel journalModel,
|
||||
PhotoModel photoModel,
|
||||
LinkModel linkModel,
|
||||
OptionService optionService,
|
||||
PostService postService,
|
||||
SheetService sheetService,
|
||||
CategoryService categoryService,
|
||||
ThemeService themeService,
|
||||
PostRenderAssembler postRenderAssembler,
|
||||
ContentAuthenticationManager providerManager) {
|
||||
this.postModel = postModel;
|
||||
this.sheetModel = sheetModel;
|
||||
this.categoryModel = categoryModel;
|
||||
this.tagModel = tagModel;
|
||||
this.journalModel = journalModel;
|
||||
this.photoModel = photoModel;
|
||||
this.linkModel = linkModel;
|
||||
this.optionService = optionService;
|
||||
this.postService = postService;
|
||||
this.sheetService = sheetService;
|
||||
this.categoryService = categoryService;
|
||||
this.themeService = themeService;
|
||||
this.postRenderAssembler = postRenderAssembler;
|
||||
this.providerManager = providerManager;
|
||||
}
|
||||
|
||||
@GetMapping("{prefix}")
|
||||
public String content(@PathVariable("prefix") String prefix,
|
||||
@RequestParam(value = "token", required = false) String token,
|
||||
Model model) {
|
||||
if (optionService.getArchivesPrefix().equals(prefix)) {
|
||||
return postModel.archives(1, model);
|
||||
}
|
||||
if (optionService.getCategoriesPrefix().equals(prefix)) {
|
||||
return categoryModel.list(model);
|
||||
}
|
||||
if (optionService.getTagsPrefix().equals(prefix)) {
|
||||
return tagModel.list(model);
|
||||
}
|
||||
if (optionService.getJournalsPrefix().equals(prefix)) {
|
||||
return journalModel.list(1, model);
|
||||
}
|
||||
if (optionService.getPhotosPrefix().equals(prefix)) {
|
||||
return photoModel.list(1, model);
|
||||
}
|
||||
if (optionService.getLinksPrefix().equals(prefix)) {
|
||||
return linkModel.list(model);
|
||||
}
|
||||
if (optionService.getSheetPermalinkType().equals(SheetPermalinkType.ROOT)) {
|
||||
Sheet sheet = sheetService.getBySlug(prefix);
|
||||
return sheetModel.content(sheet, token, model);
|
||||
}
|
||||
|
||||
throw buildPathNotFoundException();
|
||||
}
|
||||
|
||||
@GetMapping("{prefix}/page/{page:\\d+}")
|
||||
public String content(@PathVariable("prefix") String prefix,
|
||||
@PathVariable(value = "page") Integer page,
|
||||
HttpServletRequest request,
|
||||
Model model) {
|
||||
if (optionService.getArchivesPrefix().equals(prefix)) {
|
||||
return postModel.archives(page, model);
|
||||
}
|
||||
|
||||
if (optionService.getJournalsPrefix().equals(prefix)) {
|
||||
return journalModel.list(page, model);
|
||||
}
|
||||
|
||||
if (optionService.getPhotosPrefix().equals(prefix)) {
|
||||
return photoModel.list(page, model);
|
||||
}
|
||||
|
||||
throw buildPathNotFoundException();
|
||||
}
|
||||
|
||||
@GetMapping("{prefix}/{slug}")
|
||||
public String content(@PathVariable("prefix") String prefix,
|
||||
@PathVariable("slug") String slug,
|
||||
@RequestParam(value = "token", required = false) String token,
|
||||
Model model) {
|
||||
PostPermalinkType postPermalinkType = optionService.getPostPermalinkType();
|
||||
if (optionService.getArchivesPrefix().equals(prefix)) {
|
||||
if (postPermalinkType.equals(PostPermalinkType.DEFAULT)) {
|
||||
Post post = postService.getBySlug(slug);
|
||||
return postModel.content(post, token, model);
|
||||
}
|
||||
if (postPermalinkType.equals(PostPermalinkType.ID_SLUG)
|
||||
&& StringUtils.isNumeric(slug)) {
|
||||
Post post = postService.getById(Integer.parseInt(slug));
|
||||
return postModel.content(post, token, model);
|
||||
}
|
||||
}
|
||||
|
||||
if (optionService.getCategoriesPrefix().equals(prefix)) {
|
||||
return categoryModel.listPost(model, slug, 1);
|
||||
}
|
||||
|
||||
if (optionService.getTagsPrefix().equals(prefix)) {
|
||||
return tagModel.listPost(model, slug, 1);
|
||||
}
|
||||
|
||||
if (postPermalinkType.equals(PostPermalinkType.YEAR) && prefix.length() == 4
|
||||
&& StringUtils.isNumeric(prefix)) {
|
||||
Post post = postService.getBy(Integer.parseInt(prefix), slug);
|
||||
return postModel.content(post, token, model);
|
||||
}
|
||||
|
||||
if (optionService.getSheetPermalinkType().equals(SheetPermalinkType.SECONDARY)
|
||||
&& optionService.getSheetPrefix().equals(prefix)) {
|
||||
Sheet sheet = sheetService.getBySlug(slug);
|
||||
return sheetModel.content(sheet, token, model);
|
||||
}
|
||||
|
||||
throw buildPathNotFoundException();
|
||||
}
|
||||
|
||||
@GetMapping("{prefix}/{slug}/page/{page:\\d+}")
|
||||
public String content(@PathVariable("prefix") String prefix,
|
||||
@PathVariable("slug") String slug,
|
||||
@PathVariable("page") Integer page,
|
||||
Model model) {
|
||||
if (optionService.getCategoriesPrefix().equals(prefix)) {
|
||||
return categoryModel.listPost(model, slug, page);
|
||||
}
|
||||
|
||||
if (optionService.getTagsPrefix().equals(prefix)) {
|
||||
return tagModel.listPost(model, slug, page);
|
||||
}
|
||||
|
||||
throw buildPathNotFoundException();
|
||||
}
|
||||
|
||||
@GetMapping("{year:\\d+}/{month:\\d+}/{slug}")
|
||||
public String content(@PathVariable("year") Integer year,
|
||||
@PathVariable("month") Integer month,
|
||||
@PathVariable("slug") String slug,
|
||||
@RequestParam(value = "token", required = false) String token,
|
||||
Model model) {
|
||||
PostPermalinkType postPermalinkType = optionService.getPostPermalinkType();
|
||||
if (postPermalinkType.equals(PostPermalinkType.DATE)) {
|
||||
Post post = postService.getBy(year, month, slug);
|
||||
return postModel.content(post, token, model);
|
||||
}
|
||||
|
||||
throw buildPathNotFoundException();
|
||||
}
|
||||
|
||||
@GetMapping("{year:\\d+}/{month:\\d+}/{day:\\d+}/{slug}")
|
||||
public String content(@PathVariable("year") Integer year,
|
||||
@PathVariable("month") Integer month,
|
||||
@PathVariable("day") Integer day,
|
||||
@PathVariable("slug") String slug,
|
||||
@RequestParam(value = "token", required = false) String token,
|
||||
Model model) {
|
||||
PostPermalinkType postPermalinkType = optionService.getPostPermalinkType();
|
||||
if (postPermalinkType.equals(PostPermalinkType.DAY)) {
|
||||
Post post = postService.getBy(year, month, day, slug);
|
||||
return postModel.content(post, token, model);
|
||||
}
|
||||
|
||||
throw buildPathNotFoundException();
|
||||
}
|
||||
|
||||
@PostMapping(value = "content/{type}/{slug:.*}/authentication")
|
||||
@CacheLock(traceRequest = true, expired = 2)
|
||||
public String password(@PathVariable("type") String type,
|
||||
@PathVariable("slug") String slug,
|
||||
@RequestParam(value = "password") String password,
|
||||
HttpServletRequest request) throws UnsupportedEncodingException {
|
||||
if (EncryptTypeEnum.POST.getName().equals(type)) {
|
||||
return authenticatePost(slug, type, password, request);
|
||||
} else if (EncryptTypeEnum.CATEGORY.getName().equals(type)) {
|
||||
return authenticateCategory(slug, type, password, request);
|
||||
} else {
|
||||
throw new UnsupportedException("未知的加密类型");
|
||||
}
|
||||
}
|
||||
|
||||
private String authenticatePost(String slug, String type, String password,
|
||||
HttpServletRequest request) {
|
||||
ContentAuthenticationRequest authRequest = new ContentAuthenticationRequest();
|
||||
authRequest.setPassword(password);
|
||||
Post post = postService.getBy(PostStatus.INTIMATE, slug);
|
||||
authRequest.setId(post.getId());
|
||||
authRequest.setPrincipal(EncryptTypeEnum.POST.getName());
|
||||
try {
|
||||
providerManager.authenticate(authRequest);
|
||||
BasePostMinimalDTO basePostMinimal = postRenderAssembler.convertToMinimal(post);
|
||||
return "redirect:" + buildRedirectUrl(basePostMinimal.getFullPath());
|
||||
} catch (AuthenticationException e) {
|
||||
request.setAttribute("errorMsg", e.getMessage());
|
||||
request.setAttribute("type", type);
|
||||
request.setAttribute("slug", slug);
|
||||
return getPasswordPageUriToForward();
|
||||
}
|
||||
}
|
||||
|
||||
private String authenticateCategory(String slug, String type, String password,
|
||||
HttpServletRequest request) {
|
||||
ContentAuthenticationRequest authRequest = new ContentAuthenticationRequest();
|
||||
authRequest.setPassword(password);
|
||||
Category category = categoryService.getBySlugOfNonNull(slug);
|
||||
authRequest.setId(category.getId());
|
||||
authRequest.setPrincipal(EncryptTypeEnum.CATEGORY.getName());
|
||||
try {
|
||||
providerManager.authenticate(authRequest);
|
||||
CategoryDTO categoryDto = categoryService.convertTo(category);
|
||||
return "redirect:" + buildRedirectUrl(categoryDto.getFullPath());
|
||||
} catch (AuthenticationException e) {
|
||||
request.setAttribute("errorMsg", e.getMessage());
|
||||
request.setAttribute("type", type);
|
||||
request.setAttribute("slug", slug);
|
||||
return getPasswordPageUriToForward();
|
||||
}
|
||||
}
|
||||
|
||||
private String getPasswordPageUriToForward() {
|
||||
if (themeService.templateExists(POST_PASSWORD_TEMPLATE + SUFFIX_FTL)) {
|
||||
return themeService.render(POST_PASSWORD_TEMPLATE);
|
||||
}
|
||||
return "common/template/" + POST_PASSWORD_TEMPLATE;
|
||||
}
|
||||
|
||||
private NotFoundException buildPathNotFoundException() {
|
||||
var requestAttributes = RequestContextHolder.currentRequestAttributes();
|
||||
|
||||
var requestUri = "";
|
||||
if (requestAttributes instanceof ServletRequestAttributes) {
|
||||
requestUri =
|
||||
((ServletRequestAttributes) requestAttributes).getRequest().getRequestURI();
|
||||
}
|
||||
return new NotFoundException("无法定位到该路径:" + requestUri);
|
||||
}
|
||||
|
||||
private String buildRedirectUrl(String fullPath) {
|
||||
StringBuilder redirectUrl = new StringBuilder();
|
||||
|
||||
if (!optionService.isEnabledAbsolutePath()) {
|
||||
redirectUrl.append(optionService.getBlogBaseUrl());
|
||||
}
|
||||
redirectUrl.append(fullPath);
|
||||
return redirectUrl.toString();
|
||||
}
|
||||
}
|
|
@ -1,306 +0,0 @@
|
|||
package run.halo.app.controller.content;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import freemarker.template.Template;
|
||||
import freemarker.template.TemplateException;
|
||||
import java.io.IOException;
|
||||
import java.sql.Timestamp;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.OptionalLong;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.RegExUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
|
||||
import run.halo.app.model.dto.CategoryDTO;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.vo.PostDetailVO;
|
||||
import run.halo.app.service.CategoryService;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PostCategoryService;
|
||||
import run.halo.app.service.PostService;
|
||||
import run.halo.app.service.assembler.PostRenderAssembler;
|
||||
|
||||
/**
|
||||
* @author ryanwang
|
||||
* @author guqing
|
||||
* @date 2019-03-21
|
||||
*/
|
||||
@Slf4j
|
||||
@Controller
|
||||
public class ContentFeedController {
|
||||
|
||||
private static final String UTF_8_SUFFIX = ";charset=UTF-8";
|
||||
|
||||
private static final String XML_INVALID_CHAR = "[\\x00-\\x1F\\x7F]";
|
||||
|
||||
private static final String XML_MEDIA_TYPE = MediaType.APPLICATION_XML_VALUE + UTF_8_SUFFIX;
|
||||
|
||||
private static final String LAST_MODIFIED_HEADER = "Last-Modified";
|
||||
|
||||
private final PostService postService;
|
||||
|
||||
private final PostRenderAssembler postRenderAssembler;
|
||||
|
||||
private final CategoryService categoryService;
|
||||
|
||||
private final PostCategoryService postCategoryService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final FreeMarkerConfigurer freeMarker;
|
||||
|
||||
public ContentFeedController(PostService postService,
|
||||
PostRenderAssembler postRenderAssembler, CategoryService categoryService,
|
||||
PostCategoryService postCategoryService,
|
||||
OptionService optionService,
|
||||
FreeMarkerConfigurer freeMarker) {
|
||||
this.postService = postService;
|
||||
this.postRenderAssembler = postRenderAssembler;
|
||||
this.categoryService = categoryService;
|
||||
this.postCategoryService = postCategoryService;
|
||||
this.optionService = optionService;
|
||||
this.freeMarker = freeMarker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get post rss.
|
||||
*
|
||||
* @param model model
|
||||
* @return rss xml content
|
||||
* @throws IOException throw IOException
|
||||
* @throws TemplateException throw TemplateException
|
||||
*/
|
||||
@GetMapping(value = {"feed", "feed.xml", "rss", "rss.xml"}, produces = XML_MEDIA_TYPE)
|
||||
@ResponseBody
|
||||
public String feed(Model model, HttpServletResponse response)
|
||||
throws IOException, TemplateException {
|
||||
List<PostDetailVO> posts = buildPosts(buildPostPageable(optionService.getRssPageSize()));
|
||||
model.addAttribute("posts", posts);
|
||||
Timestamp lastModified = this.getLastModifiedTime(posts);
|
||||
this.lastModified2ResponseHeader(response, lastModified);
|
||||
model.addAttribute("lastModified", lastModified);
|
||||
Template template = freeMarker.getConfiguration().getTemplate("common/web/rss.ftl");
|
||||
return FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get category post rss.
|
||||
*
|
||||
* @param model model
|
||||
* @param slug slug
|
||||
* @return rss xml content
|
||||
* @throws IOException throw IOException
|
||||
* @throws TemplateException throw TemplateException
|
||||
*/
|
||||
@GetMapping(value = {"feed/categories/{slug}",
|
||||
"feed/categories/{slug}.xml"}, produces = XML_MEDIA_TYPE)
|
||||
@ResponseBody
|
||||
public String feed(Model model, @PathVariable(name = "slug") String slug,
|
||||
HttpServletResponse response)
|
||||
throws IOException, TemplateException {
|
||||
Category category = categoryService.getBySlugOfNonNull(slug);
|
||||
CategoryDTO categoryDTO = categoryService.convertTo(category);
|
||||
List<PostDetailVO> posts =
|
||||
buildCategoryPosts(buildPostPageable(optionService.getRssPageSize()), categoryDTO);
|
||||
model.addAttribute("category", categoryDTO);
|
||||
model.addAttribute("posts", posts);
|
||||
Timestamp lastModified = this.getLastModifiedTime(posts);
|
||||
this.lastModified2ResponseHeader(response, lastModified);
|
||||
model.addAttribute("lastModified", lastModified);
|
||||
Template template = freeMarker.getConfiguration().getTemplate("common/web/rss.ftl");
|
||||
return FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get atom.xml
|
||||
*
|
||||
* @param model model
|
||||
* @return atom xml content
|
||||
* @throws IOException IOException
|
||||
* @throws TemplateException TemplateException
|
||||
*/
|
||||
@GetMapping(value = {"atom", "atom.xml"}, produces = XML_MEDIA_TYPE)
|
||||
@ResponseBody
|
||||
public String atom(Model model, HttpServletResponse response)
|
||||
throws IOException, TemplateException {
|
||||
List<PostDetailVO> posts = buildPosts(buildPostPageable(optionService.getRssPageSize()));
|
||||
model.addAttribute("posts", posts);
|
||||
Timestamp lastModified = this.getLastModifiedTime(posts);
|
||||
this.lastModified2ResponseHeader(response, lastModified);
|
||||
model.addAttribute("lastModified", lastModified);
|
||||
Template template = freeMarker.getConfiguration().getTemplate("common/web/atom.ftl");
|
||||
return FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get category posts atom.xml
|
||||
*
|
||||
* @param model model
|
||||
* @param slug slug
|
||||
* @return atom xml content
|
||||
* @throws IOException throw IOException
|
||||
* @throws TemplateException throw TemplateException
|
||||
*/
|
||||
@GetMapping(value = {"atom/categories/{slug}",
|
||||
"atom/categories/{slug}.xml"}, produces = XML_MEDIA_TYPE)
|
||||
@ResponseBody
|
||||
public String atom(Model model, @PathVariable(name = "slug") String slug,
|
||||
HttpServletResponse response)
|
||||
throws IOException, TemplateException {
|
||||
Category category = categoryService.getBySlugOfNonNull(slug);
|
||||
CategoryDTO categoryDTO = categoryService.convertTo(category);
|
||||
List<PostDetailVO> posts =
|
||||
buildCategoryPosts(buildPostPageable(optionService.getRssPageSize()), categoryDTO);
|
||||
model.addAttribute("category", categoryDTO);
|
||||
model.addAttribute("posts", posts);
|
||||
Timestamp lastModified = this.getLastModifiedTime(posts);
|
||||
this.lastModified2ResponseHeader(response, lastModified);
|
||||
model.addAttribute("lastModified", lastModified);
|
||||
Template template = freeMarker.getConfiguration().getTemplate("common/web/atom.ftl");
|
||||
return FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sitemap.xml.
|
||||
*
|
||||
* @param model model
|
||||
* @return sitemap xml content.
|
||||
* @throws IOException IOException
|
||||
* @throws TemplateException TemplateException
|
||||
*/
|
||||
@GetMapping(value = {"sitemap", "sitemap.xml"}, produces = XML_MEDIA_TYPE)
|
||||
@ResponseBody
|
||||
public String sitemapXml(Model model,
|
||||
@PageableDefault(size = Integer.MAX_VALUE, sort = "createTime", direction = DESC)
|
||||
Pageable pageable) throws IOException, TemplateException {
|
||||
model.addAttribute("posts", buildPosts(pageable));
|
||||
Template template = freeMarker.getConfiguration().getTemplate("common/web/sitemap_xml.ftl");
|
||||
return FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sitemap.html.
|
||||
*
|
||||
* @param model model
|
||||
* @return template path: common/web/sitemap_html
|
||||
*/
|
||||
@GetMapping(value = "sitemap.html")
|
||||
public String sitemapHtml(Model model,
|
||||
@PageableDefault(size = Integer.MAX_VALUE, sort = "createTime", direction = DESC)
|
||||
Pageable pageable) {
|
||||
model.addAttribute("posts", buildPosts(pageable));
|
||||
return "common/web/sitemap_html";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get robots.txt
|
||||
*
|
||||
* @param model model
|
||||
* @return robots.txt content
|
||||
* @throws IOException IOException
|
||||
* @throws TemplateException TemplateException
|
||||
*/
|
||||
@GetMapping(value = "robots.txt", produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@ResponseBody
|
||||
public String robots(Model model) throws IOException, TemplateException {
|
||||
Template template = freeMarker.getConfiguration().getTemplate("common/web/robots.ftl");
|
||||
return FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds page info for post.
|
||||
*
|
||||
* @param size page size
|
||||
* @return page info
|
||||
*/
|
||||
@NonNull
|
||||
private Pageable buildPostPageable(int size) {
|
||||
return PageRequest.of(0, size, Sort.by(DESC, "createTime"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build posts.
|
||||
*
|
||||
* @param pageable pageable
|
||||
* @return list of post detail vo
|
||||
*/
|
||||
private List<PostDetailVO> buildPosts(@NonNull Pageable pageable) {
|
||||
Assert.notNull(pageable, "Pageable must not be null");
|
||||
|
||||
Page<Post> postPage = postService.pageBy(PostStatus.PUBLISHED, pageable);
|
||||
Page<PostDetailVO> posts = convertToDetailPageVo(postPage);
|
||||
return posts.getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts to a page of detail vo.
|
||||
* Notes: this method will escape the XML tag characters in the post content and summary.
|
||||
*
|
||||
* @param postPage post page must not be null
|
||||
* @return a page of post detail vo that content and summary escaped.
|
||||
*/
|
||||
@NonNull
|
||||
private Page<PostDetailVO> convertToDetailPageVo(Page<Post> postPage) {
|
||||
Assert.notNull(postPage, "The postPage must not be null.");
|
||||
Page<PostDetailVO> posts = postRenderAssembler.convertToDetailVo(postPage);
|
||||
posts.getContent().forEach(postDetailVO -> {
|
||||
postDetailVO.setContent(
|
||||
RegExUtils.replaceAll(postDetailVO.getContent(), XML_INVALID_CHAR, ""));
|
||||
postDetailVO
|
||||
.setSummary(RegExUtils.replaceAll(postDetailVO.getSummary(), XML_INVALID_CHAR, ""));
|
||||
});
|
||||
return posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build category posts.
|
||||
*
|
||||
* @param pageable pageable must not be null.
|
||||
* @param category category
|
||||
* @return list of post detail vo.
|
||||
*/
|
||||
private List<PostDetailVO> buildCategoryPosts(@NonNull Pageable pageable,
|
||||
@NonNull CategoryDTO category) {
|
||||
Assert.notNull(pageable, "Pageable must not be null");
|
||||
Assert.notNull(category, "Category slug must not be null");
|
||||
|
||||
Page<Post> postPage =
|
||||
postCategoryService.pagePostBy(category.getId(), PostStatus.PUBLISHED, pageable);
|
||||
Page<PostDetailVO> posts = convertToDetailPageVo(postPage);
|
||||
return posts.getContent();
|
||||
}
|
||||
|
||||
private Timestamp getLastModifiedTime(List<PostDetailVO> posts) {
|
||||
OptionalLong lastModifiedTimestamp =
|
||||
posts.stream().mapToLong(post -> post.getEditTime().getTime()).max();
|
||||
if (lastModifiedTimestamp.isEmpty()) {
|
||||
return new Timestamp(System.currentTimeMillis());
|
||||
}
|
||||
return new Timestamp(lastModifiedTimestamp.getAsLong());
|
||||
}
|
||||
|
||||
private void lastModified2ResponseHeader(HttpServletResponse response, Timestamp time) {
|
||||
SimpleDateFormat dateFormat =
|
||||
new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.ENGLISH);
|
||||
response.setHeader(LAST_MODIFIED_HEADER, dateFormat.format(time));
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
package run.halo.app.controller.content;
|
||||
|
||||
import java.util.Objects;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import run.halo.app.controller.content.model.PostModel;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.enums.PostPermalinkType;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PostService;
|
||||
|
||||
/**
|
||||
* Blog index page controller
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-03-17
|
||||
*/
|
||||
@Slf4j
|
||||
@Controller
|
||||
@RequestMapping
|
||||
public class ContentIndexController {
|
||||
|
||||
private final PostService postService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final PostModel postModel;
|
||||
|
||||
public ContentIndexController(PostService postService,
|
||||
OptionService optionService,
|
||||
PostModel postModel) {
|
||||
this.postService = postService;
|
||||
this.optionService = optionService;
|
||||
this.postModel = postModel;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Render blog index
|
||||
*
|
||||
* @param p post id
|
||||
* @param model model
|
||||
* @return template path: themes/{theme}/index.ftl
|
||||
*/
|
||||
@GetMapping
|
||||
public String index(Integer p, String token, Model model) {
|
||||
|
||||
PostPermalinkType permalinkType = optionService.getPostPermalinkType();
|
||||
|
||||
if (PostPermalinkType.ID.equals(permalinkType) && !Objects.isNull(p)) {
|
||||
Post post = postService.getById(p);
|
||||
return postModel.content(post, token, model);
|
||||
}
|
||||
|
||||
return this.index(model, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render blog index
|
||||
*
|
||||
* @param model model
|
||||
* @param page current page number
|
||||
* @return template path: themes/{theme}/index.ftl
|
||||
*/
|
||||
@GetMapping(value = "page/{page}")
|
||||
public String index(Model model,
|
||||
@PathVariable(value = "page") Integer page) {
|
||||
return postModel.list(page, model);
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
package run.halo.app.controller.content;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.vo.PostListVO;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PostService;
|
||||
import run.halo.app.service.ThemeService;
|
||||
import run.halo.app.service.assembler.PostRenderAssembler;
|
||||
|
||||
/**
|
||||
* Search controller.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-04-21
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping(value = "/search")
|
||||
public class ContentSearchController {
|
||||
|
||||
private final PostService postService;
|
||||
|
||||
private final PostRenderAssembler postRenderAssembler;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
public ContentSearchController(PostService postService,
|
||||
PostRenderAssembler postRenderAssembler, OptionService optionService,
|
||||
ThemeService themeService) {
|
||||
this.postService = postService;
|
||||
this.postRenderAssembler = postRenderAssembler;
|
||||
this.optionService = optionService;
|
||||
this.themeService = themeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render post search page.
|
||||
*
|
||||
* @param model model
|
||||
* @param keyword keyword
|
||||
* @return template path : themes/{theme}/search.ftl
|
||||
*/
|
||||
@GetMapping
|
||||
public String search(Model model,
|
||||
@RequestParam(value = "keyword") String keyword) {
|
||||
return this.search(model, HtmlUtils.htmlEscape(keyword), 1, Sort.by(DESC, "createTime"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render post search page.
|
||||
*
|
||||
* @param model model
|
||||
* @param keyword keyword
|
||||
* @return template path :themes/{theme}/search.ftl
|
||||
*/
|
||||
@GetMapping(value = "page/{page}")
|
||||
public String search(Model model,
|
||||
@RequestParam(value = "keyword") String keyword,
|
||||
@PathVariable(value = "page") Integer page,
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
final Pageable pageable = PageRequest.of(page - 1, optionService.getPostPageSize(), sort);
|
||||
final Page<Post> postPage = postService.pageBy(keyword, pageable);
|
||||
|
||||
final Page<PostListVO> posts = postRenderAssembler.convertToListVo(postPage);
|
||||
|
||||
model.addAttribute("is_search", true);
|
||||
model.addAttribute("keyword", HtmlUtils.htmlEscape(keyword));
|
||||
model.addAttribute("posts", posts);
|
||||
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
|
||||
model.addAttribute("meta_description", optionService.getSeoDescription());
|
||||
return themeService.render("search");
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
package run.halo.app.controller.content;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import run.halo.app.config.properties.HaloProperties;
|
||||
import run.halo.app.exception.ServiceException;
|
||||
import run.halo.app.model.entity.User;
|
||||
import run.halo.app.model.properties.BlogProperties;
|
||||
import run.halo.app.model.support.HaloConst;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.UserService;
|
||||
import run.halo.app.utils.HaloUtils;
|
||||
|
||||
/**
|
||||
* Main controller.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-04-23
|
||||
*/
|
||||
@Controller
|
||||
public class MainController {
|
||||
|
||||
/**
|
||||
* Index redirect uri.
|
||||
*/
|
||||
private static final String INDEX_REDIRECT_URI = "index.html";
|
||||
|
||||
/**
|
||||
* Install redirect uri.
|
||||
*/
|
||||
private static final String INSTALL_REDIRECT_URI = INDEX_REDIRECT_URI + "#install";
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final HaloProperties haloProperties;
|
||||
|
||||
public MainController(UserService userService, OptionService optionService,
|
||||
HaloProperties haloProperties) {
|
||||
this.userService = userService;
|
||||
this.optionService = optionService;
|
||||
this.haloProperties = haloProperties;
|
||||
}
|
||||
|
||||
@GetMapping("${halo.admin-path:admin}")
|
||||
public void admin(HttpServletResponse response) throws IOException {
|
||||
String adminIndexRedirectUri =
|
||||
HaloUtils.ensureBoth(haloProperties.getAdminPath(), HaloUtils.URL_SEPARATOR)
|
||||
+ INDEX_REDIRECT_URI;
|
||||
response.sendRedirect(adminIndexRedirectUri);
|
||||
}
|
||||
|
||||
@GetMapping("version")
|
||||
@ResponseBody
|
||||
public String version() {
|
||||
return HaloConst.HALO_VERSION;
|
||||
}
|
||||
|
||||
@GetMapping("install")
|
||||
public void installation(HttpServletResponse response) throws IOException {
|
||||
String installRedirectUri =
|
||||
StringUtils.appendIfMissing(this.haloProperties.getAdminPath(), "/")
|
||||
+ INSTALL_REDIRECT_URI;
|
||||
response.sendRedirect(installRedirectUri);
|
||||
}
|
||||
|
||||
@GetMapping("avatar")
|
||||
public void avatar(HttpServletResponse response) throws IOException {
|
||||
User user =
|
||||
userService.getCurrentUser().orElseThrow(() -> new ServiceException("未查询到博主信息"));
|
||||
if (StringUtils.isNotEmpty(user.getAvatar())) {
|
||||
response.sendRedirect(HaloUtils.normalizeUrl(user.getAvatar()));
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("logo")
|
||||
public void logo(HttpServletResponse response) throws IOException {
|
||||
String blogLogo =
|
||||
optionService.getByProperty(BlogProperties.BLOG_LOGO).orElse("").toString();
|
||||
if (StringUtils.isNotEmpty(blogLogo)) {
|
||||
response.sendRedirect(HaloUtils.normalizeUrl(blogLogo));
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("favicon.ico")
|
||||
public void favicon(HttpServletResponse response) throws IOException {
|
||||
String favicon =
|
||||
optionService.getByProperty(BlogProperties.BLOG_FAVICON).orElse("").toString();
|
||||
if (StringUtils.isNotEmpty(favicon)) {
|
||||
response.sendRedirect(HaloUtils.normalizeUrl(favicon));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package run.halo.app.controller.content.api;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.model.vo.ArchiveMonthVO;
|
||||
import run.halo.app.model.vo.ArchiveYearVO;
|
||||
import run.halo.app.service.PostService;
|
||||
|
||||
/**
|
||||
* Content archive controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 2019-04-02
|
||||
*/
|
||||
@RestController("ApiContentArchiveController")
|
||||
@RequestMapping("/api/content/archives")
|
||||
public class ArchiveController {
|
||||
|
||||
private final PostService postService;
|
||||
|
||||
public ArchiveController(PostService postService) {
|
||||
this.postService = postService;
|
||||
}
|
||||
|
||||
@GetMapping("years")
|
||||
public List<ArchiveYearVO> listYearArchives() {
|
||||
return postService.listYearArchives();
|
||||
}
|
||||
|
||||
@GetMapping("months")
|
||||
public List<ArchiveMonthVO> listMonthArchives() {
|
||||
return postService.listMonthArchives();
|
||||
}
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
package run.halo.app.controller.content.api;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.controller.content.auth.CategoryAuthentication;
|
||||
import run.halo.app.controller.content.auth.ContentAuthenticationManager;
|
||||
import run.halo.app.controller.content.auth.ContentAuthenticationRequest;
|
||||
import run.halo.app.exception.ForbiddenException;
|
||||
import run.halo.app.model.dto.CategoryDTO;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.enums.EncryptTypeEnum;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.vo.PostListVO;
|
||||
import run.halo.app.service.CategoryService;
|
||||
import run.halo.app.service.PostCategoryService;
|
||||
import run.halo.app.service.assembler.PostRenderAssembler;
|
||||
|
||||
/**
|
||||
* Content category controller.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-06-09
|
||||
*/
|
||||
@RestController("ApiContentCategoryController")
|
||||
@RequestMapping("/api/content/categories")
|
||||
public class CategoryController {
|
||||
|
||||
private final CategoryService categoryService;
|
||||
|
||||
private final PostCategoryService postCategoryService;
|
||||
|
||||
private final PostRenderAssembler postRenderAssembler;
|
||||
|
||||
private final CategoryAuthentication categoryAuthentication;
|
||||
|
||||
private final ContentAuthenticationManager contentAuthenticationManager;
|
||||
|
||||
public CategoryController(CategoryService categoryService,
|
||||
PostCategoryService postCategoryService,
|
||||
PostRenderAssembler postRenderAssembler,
|
||||
CategoryAuthentication categoryAuthentication,
|
||||
ContentAuthenticationManager contentAuthenticationManager) {
|
||||
this.categoryService = categoryService;
|
||||
this.postCategoryService = postCategoryService;
|
||||
this.postRenderAssembler = postRenderAssembler;
|
||||
this.categoryAuthentication = categoryAuthentication;
|
||||
this.contentAuthenticationManager = contentAuthenticationManager;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("Lists categories")
|
||||
public List<? extends CategoryDTO> listCategories(
|
||||
@SortDefault(sort = "updateTime", direction = DESC) Sort sort,
|
||||
@RequestParam(name = "more", required = false, defaultValue = "false") Boolean more) {
|
||||
if (more) {
|
||||
return postCategoryService.listCategoryWithPostCountDto(sort);
|
||||
}
|
||||
return categoryService.convertTo(categoryService.listAll(sort));
|
||||
}
|
||||
|
||||
@GetMapping("{slug}/posts")
|
||||
@ApiOperation("Lists posts by category slug")
|
||||
public Page<PostListVO> listPostsBy(@PathVariable("slug") String slug,
|
||||
@RequestParam(value = "password", required = false) String password,
|
||||
@PageableDefault(sort = {"topPriority", "updateTime"}, direction = DESC)
|
||||
Pageable pageable) {
|
||||
// Get category by slug
|
||||
Category category = categoryService.getBySlugOfNonNull(slug);
|
||||
|
||||
Set<PostStatus> statusesToQuery = Sets.immutableEnumSet(PostStatus.PUBLISHED);
|
||||
if (allowIntimatePosts(category.getId(), password)) {
|
||||
statusesToQuery = Sets.immutableEnumSet(PostStatus.PUBLISHED, PostStatus.INTIMATE);
|
||||
}
|
||||
|
||||
Page<Post> postPage =
|
||||
postCategoryService.pagePostBy(category.getId(), statusesToQuery, pageable);
|
||||
return postRenderAssembler.convertToListVo(postPage);
|
||||
}
|
||||
|
||||
private boolean allowIntimatePosts(Integer categoryId, String password) {
|
||||
Assert.notNull(categoryId, "The categoryId must not be null.");
|
||||
|
||||
if (!categoryService.isPrivate(categoryId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (categoryAuthentication.isAuthenticated(categoryId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (password != null) {
|
||||
ContentAuthenticationRequest authRequest =
|
||||
ContentAuthenticationRequest.of(categoryId, password,
|
||||
EncryptTypeEnum.CATEGORY.getName());
|
||||
// authenticate this request,throw an error if authenticate failed
|
||||
contentAuthenticationManager.authenticate(authRequest);
|
||||
return true;
|
||||
}
|
||||
throw new ForbiddenException("您没有该分类的访问权限");
|
||||
}
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
package run.halo.app.controller.content.api;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
import run.halo.app.cache.lock.CacheLock;
|
||||
import run.halo.app.cache.lock.CacheParam;
|
||||
import run.halo.app.model.dto.BaseCommentDTO;
|
||||
import run.halo.app.model.dto.JournalDTO;
|
||||
import run.halo.app.model.dto.JournalWithCmtCountDTO;
|
||||
import run.halo.app.model.entity.Journal;
|
||||
import run.halo.app.model.entity.JournalComment;
|
||||
import run.halo.app.model.enums.CommentStatus;
|
||||
import run.halo.app.model.enums.JournalType;
|
||||
import run.halo.app.model.params.JournalCommentParam;
|
||||
import run.halo.app.model.vo.BaseCommentVO;
|
||||
import run.halo.app.model.vo.BaseCommentWithParentVO;
|
||||
import run.halo.app.model.vo.CommentWithHasChildrenVO;
|
||||
import run.halo.app.service.JournalCommentService;
|
||||
import run.halo.app.service.JournalService;
|
||||
import run.halo.app.service.OptionService;
|
||||
|
||||
/**
|
||||
* Content journal controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @date 2019-04-26
|
||||
*/
|
||||
@RestController("ApiContentJournalController")
|
||||
@RequestMapping("/api/content/journals")
|
||||
public class JournalController {
|
||||
|
||||
private final JournalService journalService;
|
||||
|
||||
private final JournalCommentService journalCommentService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
public JournalController(JournalService journalService,
|
||||
JournalCommentService journalCommentService,
|
||||
OptionService optionService) {
|
||||
this.journalService = journalService;
|
||||
this.journalCommentService = journalCommentService;
|
||||
this.optionService = optionService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("Lists journals")
|
||||
public Page<JournalWithCmtCountDTO> pageBy(
|
||||
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable) {
|
||||
Page<Journal> journals = journalService.pageBy(JournalType.PUBLIC, pageable);
|
||||
return journalService.convertToCmtCountDto(journals);
|
||||
}
|
||||
|
||||
@GetMapping("{journalId:\\d+}")
|
||||
@ApiOperation("Gets a journal detail")
|
||||
public JournalWithCmtCountDTO getBy(@PathVariable("journalId") Integer journalId) {
|
||||
Journal journal = journalService.getById(journalId);
|
||||
return journalService.convertTo(journal);
|
||||
}
|
||||
|
||||
@GetMapping("{journalId:\\d+}/comments/top_view")
|
||||
public Page<CommentWithHasChildrenVO> listTopComments(
|
||||
@PathVariable("journalId") Integer journalId,
|
||||
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
return journalCommentService.pageTopCommentsBy(journalId, CommentStatus.PUBLISHED,
|
||||
PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||
}
|
||||
|
||||
@GetMapping("{journalId:\\d+}/comments/{commentParentId:\\d+}/children")
|
||||
public List<BaseCommentDTO> listChildrenBy(@PathVariable("journalId") Integer journalId,
|
||||
@PathVariable("commentParentId") Long commentParentId,
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
// Find all children comments
|
||||
List<JournalComment> postComments = journalCommentService
|
||||
.listChildrenBy(journalId, commentParentId, CommentStatus.PUBLISHED, sort);
|
||||
// Convert to base comment dto
|
||||
return journalCommentService.convertTo(postComments);
|
||||
}
|
||||
|
||||
@GetMapping("{journalId:\\d+}/comments/tree_view")
|
||||
@ApiOperation("Lists comments with tree view")
|
||||
public Page<BaseCommentVO> listCommentsTree(@PathVariable("journalId") Integer journalId,
|
||||
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
return journalCommentService
|
||||
.pageVosBy(journalId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||
}
|
||||
|
||||
@GetMapping("{journalId:\\d+}/comments/list_view")
|
||||
@ApiOperation("Lists comment with list view")
|
||||
public Page<BaseCommentWithParentVO> listComments(@PathVariable("journalId") Integer journalId,
|
||||
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
return journalCommentService.pageWithParentVoBy(journalId,
|
||||
PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||
}
|
||||
|
||||
@PostMapping("comments")
|
||||
@ApiOperation("Comments a post")
|
||||
@CacheLock(autoDelete = false, traceRequest = true)
|
||||
public BaseCommentDTO comment(@RequestBody JournalCommentParam journalCommentParam) {
|
||||
|
||||
// Escape content
|
||||
journalCommentParam.setContent(HtmlUtils
|
||||
.htmlEscape(journalCommentParam.getContent(), StandardCharsets.UTF_8.displayName()));
|
||||
return journalCommentService.convertTo(journalCommentService.createBy(journalCommentParam));
|
||||
}
|
||||
|
||||
@PostMapping("{id:\\d+}/likes")
|
||||
@ApiOperation("Likes a journal")
|
||||
@CacheLock(autoDelete = false, traceRequest = true)
|
||||
public void like(@PathVariable("id") @CacheParam Integer id) {
|
||||
journalService.increaseLike(id);
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package run.halo.app.controller.content.api;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.util.List;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.model.dto.LinkDTO;
|
||||
import run.halo.app.model.vo.LinkTeamVO;
|
||||
import run.halo.app.service.LinkService;
|
||||
|
||||
/**
|
||||
* Content link controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @date 2019-04-03
|
||||
*/
|
||||
@RestController("ApiContentLinkController")
|
||||
@RequestMapping("/api/content/links")
|
||||
public class LinkController {
|
||||
|
||||
private final LinkService linkService;
|
||||
|
||||
public LinkController(LinkService linkService) {
|
||||
this.linkService = linkService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("List all links")
|
||||
public List<LinkDTO> listLinks(@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
return linkService.listDtos(sort);
|
||||
}
|
||||
|
||||
@GetMapping("team_view")
|
||||
@ApiOperation("List all links with team view")
|
||||
public List<LinkTeamVO> listTeamVos(Sort sort) {
|
||||
return linkService.listTeamVos(sort);
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
package run.halo.app.controller.content.api;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.util.List;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.model.dto.MenuDTO;
|
||||
import run.halo.app.model.vo.MenuVO;
|
||||
import run.halo.app.service.MenuService;
|
||||
|
||||
/**
|
||||
* Content menu controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @date 2019-04-03
|
||||
*/
|
||||
@RestController("ApiContentMenuController")
|
||||
@RequestMapping("/api/content/menus")
|
||||
public class MenuController {
|
||||
|
||||
private final MenuService menuService;
|
||||
|
||||
public MenuController(MenuService menuService) {
|
||||
this.menuService = menuService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("Lists all menus")
|
||||
public List<MenuDTO> listAll(@SortDefault(sort = "priority", direction = DESC) Sort sort) {
|
||||
return menuService.listDtos(sort);
|
||||
}
|
||||
|
||||
@GetMapping(value = "tree_view")
|
||||
@ApiOperation("Lists menus with tree view")
|
||||
public List<MenuVO> listMenusTree(
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
return menuService.listAsTree(sort);
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
package run.halo.app.controller.content.api;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.model.dto.OptionDTO;
|
||||
import run.halo.app.model.properties.CommentProperties;
|
||||
import run.halo.app.model.support.BaseResponse;
|
||||
import run.halo.app.service.ClientOptionService;
|
||||
|
||||
/**
|
||||
* Content option controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 2019-04-03
|
||||
*/
|
||||
@RestController("ApiContentOptionController")
|
||||
@RequestMapping("/api/content/options")
|
||||
public class OptionController {
|
||||
|
||||
private final ClientOptionService optionService;
|
||||
|
||||
public OptionController(ClientOptionService clientOptionService) {
|
||||
this.optionService = clientOptionService;
|
||||
}
|
||||
|
||||
@GetMapping("list_view")
|
||||
@ApiOperation("Lists all options with list view")
|
||||
public List<OptionDTO> listAll() {
|
||||
return optionService.listDtos();
|
||||
}
|
||||
|
||||
@GetMapping("map_view")
|
||||
@ApiOperation("Lists options with map view")
|
||||
public Map<String, Object> listAllWithMapView(
|
||||
@Deprecated(since = "1.4.8", forRemoval = true)
|
||||
@RequestParam(value = "key", required = false) List<String> keyList,
|
||||
@RequestParam(value = "keys", required = false) String keys) {
|
||||
// handle for key list
|
||||
if (!CollectionUtils.isEmpty(keyList)) {
|
||||
return optionService.listOptions(keyList);
|
||||
}
|
||||
// handle for keys
|
||||
if (StringUtils.hasText(keys)) {
|
||||
var nameSet = Arrays.stream(keys.split(","))
|
||||
.map(String::trim)
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
return optionService.listOptions(nameSet);
|
||||
}
|
||||
// list all
|
||||
return optionService.listOptions();
|
||||
}
|
||||
|
||||
@GetMapping("keys/{key}")
|
||||
@ApiOperation("Gets option value by option key")
|
||||
public BaseResponse<Object> getBy(@PathVariable("key") String key) {
|
||||
Object optionValue = optionService.getByKey(key).orElse(null);
|
||||
return BaseResponse.ok(optionValue);
|
||||
}
|
||||
|
||||
@GetMapping("comment")
|
||||
@ApiOperation("Options for comment(@deprecated, use /bulk api instead of this.)")
|
||||
@Deprecated
|
||||
public Map<String, Object> comment() {
|
||||
List<String> keys = new ArrayList<>();
|
||||
keys.add(CommentProperties.GRAVATAR_DEFAULT.getValue());
|
||||
keys.add(CommentProperties.CONTENT_PLACEHOLDER.getValue());
|
||||
keys.add(CommentProperties.GRAVATAR_SOURCE.getValue());
|
||||
return optionService.listOptions(keys);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
package run.halo.app.controller.content.api;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.util.List;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.model.dto.PhotoDTO;
|
||||
import run.halo.app.model.params.PhotoQuery;
|
||||
import run.halo.app.service.PhotoService;
|
||||
|
||||
/**
|
||||
* Content photo controller.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-09-21
|
||||
*/
|
||||
@RestController("ApiContentPhotoController")
|
||||
@RequestMapping("/api/content/photos")
|
||||
public class PhotoController {
|
||||
|
||||
private final PhotoService photoService;
|
||||
|
||||
public PhotoController(PhotoService photoService) {
|
||||
this.photoService = photoService;
|
||||
}
|
||||
|
||||
/**
|
||||
* List all photos
|
||||
*
|
||||
* @param sort sort
|
||||
* @return all of photos
|
||||
*/
|
||||
@GetMapping(value = "latest")
|
||||
public List<PhotoDTO> listPhotos(
|
||||
@SortDefault(sort = "updateTime", direction = Sort.Direction.DESC) Sort sort) {
|
||||
return photoService.listDtos(sort);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public Page<PhotoDTO> pageBy(
|
||||
@PageableDefault(sort = "updateTime", direction = DESC) Pageable pageable,
|
||||
PhotoQuery photoQuery) {
|
||||
return photoService.pageDtosBy(pageable, photoQuery);
|
||||
}
|
||||
|
||||
@GetMapping("teams")
|
||||
@ApiOperation("Lists all of photo teams")
|
||||
public List<String> listTeams() {
|
||||
return photoService.listAllTeams();
|
||||
}
|
||||
}
|
|
@ -1,255 +0,0 @@
|
|||
package run.halo.app.controller.content.api;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
import run.halo.app.cache.lock.CacheLock;
|
||||
import run.halo.app.cache.lock.CacheParam;
|
||||
import run.halo.app.controller.content.auth.PostAuthentication;
|
||||
import run.halo.app.exception.ForbiddenException;
|
||||
import run.halo.app.exception.NotFoundException;
|
||||
import run.halo.app.model.dto.BaseCommentDTO;
|
||||
import run.halo.app.model.dto.post.BasePostSimpleDTO;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.entity.PostComment;
|
||||
import run.halo.app.model.enums.CommentStatus;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.params.PostCommentParam;
|
||||
import run.halo.app.model.params.PostQuery;
|
||||
import run.halo.app.model.vo.BaseCommentVO;
|
||||
import run.halo.app.model.vo.BaseCommentWithParentVO;
|
||||
import run.halo.app.model.vo.CommentWithHasChildrenVO;
|
||||
import run.halo.app.model.vo.PostDetailVO;
|
||||
import run.halo.app.model.vo.PostListVO;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PostCommentService;
|
||||
import run.halo.app.service.PostService;
|
||||
import run.halo.app.service.assembler.PostRenderAssembler;
|
||||
|
||||
/**
|
||||
* Content post controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @author guqing
|
||||
* @date 2019-04-02
|
||||
*/
|
||||
@RestController("ApiContentPostController")
|
||||
@RequestMapping("/api/content/posts")
|
||||
public class PostController {
|
||||
|
||||
private final PostService postService;
|
||||
|
||||
private final PostCommentService postCommentService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final PostRenderAssembler postRenderAssembler;
|
||||
|
||||
private final PostAuthentication postAuthentication;
|
||||
|
||||
public PostController(PostService postService,
|
||||
PostCommentService postCommentService,
|
||||
OptionService optionService, PostRenderAssembler postRenderAssembler,
|
||||
PostAuthentication postAuthentication) {
|
||||
this.postService = postService;
|
||||
this.postCommentService = postCommentService;
|
||||
this.optionService = optionService;
|
||||
this.postRenderAssembler = postRenderAssembler;
|
||||
this.postAuthentication = postAuthentication;
|
||||
}
|
||||
|
||||
//CS304 issue for https://github.com/halo-dev/halo/issues/1351
|
||||
|
||||
/**
|
||||
* Enable users search published articles with keywords.
|
||||
*
|
||||
* @param pageable store the priority of the sort algorithm
|
||||
* @param keyword search articles with keyword
|
||||
* @param categoryId search articles with categoryId
|
||||
* @return published articles that contains keywords and specific categoryId
|
||||
*/
|
||||
@GetMapping
|
||||
@ApiOperation("Lists posts")
|
||||
public Page<PostListVO> pageBy(
|
||||
@PageableDefault(sort = {"topPriority", "createTime"}, direction = DESC) Pageable pageable,
|
||||
@RequestParam(value = "keyword", required = false) String keyword,
|
||||
@RequestParam(value = "categoryId", required = false) Integer categoryId) {
|
||||
PostQuery postQuery = new PostQuery();
|
||||
postQuery.setKeyword(keyword);
|
||||
postQuery.setCategoryId(categoryId);
|
||||
postQuery.setStatuses(Set.of(PostStatus.PUBLISHED));
|
||||
Page<Post> postPage = postService.pageBy(postQuery, pageable);
|
||||
return postRenderAssembler.convertToListVo(postPage);
|
||||
}
|
||||
|
||||
@PostMapping(value = "search")
|
||||
@ApiOperation("Lists posts by keyword")
|
||||
public Page<BasePostSimpleDTO> pageBy(@RequestParam(value = "keyword") String keyword,
|
||||
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable) {
|
||||
Page<Post> postPage = postService.pageBy(keyword, pageable);
|
||||
return postRenderAssembler.convertToSimple(postPage);
|
||||
}
|
||||
|
||||
@GetMapping("{postId:\\d+}")
|
||||
@ApiOperation("Gets a post")
|
||||
public PostDetailVO getBy(@PathVariable("postId") Integer postId,
|
||||
@RequestParam(value = "formatDisabled", required = false, defaultValue = "true")
|
||||
Boolean formatDisabled,
|
||||
@RequestParam(value = "sourceDisabled", required = false, defaultValue = "false")
|
||||
Boolean sourceDisabled) {
|
||||
Post post = postService.getById(postId);
|
||||
|
||||
checkAuthenticate(postId);
|
||||
|
||||
PostDetailVO postDetailVO = postRenderAssembler.convertToDetailVo(post);
|
||||
|
||||
if (formatDisabled) {
|
||||
// Clear the format content
|
||||
postDetailVO.setContent(null);
|
||||
}
|
||||
|
||||
if (sourceDisabled) {
|
||||
// Clear the original content
|
||||
postDetailVO.setOriginalContent(null);
|
||||
}
|
||||
|
||||
postService.publishVisitEvent(postDetailVO.getId());
|
||||
|
||||
return postDetailVO;
|
||||
}
|
||||
|
||||
@GetMapping("/slug")
|
||||
@ApiOperation("Gets a post")
|
||||
public PostDetailVO getBy(@RequestParam("slug") String slug,
|
||||
@RequestParam(value = "formatDisabled", required = false, defaultValue = "true")
|
||||
Boolean formatDisabled,
|
||||
@RequestParam(value = "sourceDisabled", required = false, defaultValue = "false")
|
||||
Boolean sourceDisabled) {
|
||||
Post post = postService.getBySlug(slug);
|
||||
|
||||
checkAuthenticate(post.getId());
|
||||
|
||||
PostDetailVO postDetailVO = postRenderAssembler.convertToDetailVo(post);
|
||||
|
||||
if (formatDisabled) {
|
||||
// Clear the format content
|
||||
postDetailVO.setContent(null);
|
||||
}
|
||||
|
||||
if (sourceDisabled) {
|
||||
// Clear the original content
|
||||
postDetailVO.setOriginalContent(null);
|
||||
}
|
||||
|
||||
postService.publishVisitEvent(postDetailVO.getId());
|
||||
|
||||
return postDetailVO;
|
||||
}
|
||||
|
||||
@GetMapping("{postId:\\d+}/prev")
|
||||
@ApiOperation("Gets previous post by current post id.")
|
||||
public PostDetailVO getPrevPostBy(@PathVariable("postId") Integer postId) {
|
||||
Post post = postService.getById(postId);
|
||||
Post prevPost =
|
||||
postService.getPrevPost(post).orElseThrow(() -> new NotFoundException("查询不到该文章的信息"));
|
||||
checkAuthenticate(prevPost.getId());
|
||||
return postRenderAssembler.convertToDetailVo(prevPost);
|
||||
}
|
||||
|
||||
@GetMapping("{postId:\\d+}/next")
|
||||
@ApiOperation("Gets next post by current post id.")
|
||||
public PostDetailVO getNextPostBy(@PathVariable("postId") Integer postId) {
|
||||
Post post = postService.getById(postId);
|
||||
Post nextPost =
|
||||
postService.getNextPost(post).orElseThrow(() -> new NotFoundException("查询不到该文章的信息"));
|
||||
checkAuthenticate(nextPost.getId());
|
||||
return postRenderAssembler.convertToDetailVo(nextPost);
|
||||
}
|
||||
|
||||
@GetMapping("{postId:\\d+}/comments/top_view")
|
||||
public Page<CommentWithHasChildrenVO> listTopComments(@PathVariable("postId") Integer postId,
|
||||
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
checkAuthenticate(postId);
|
||||
return postCommentService.pageTopCommentsBy(postId, CommentStatus.PUBLISHED,
|
||||
PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||
}
|
||||
|
||||
@GetMapping("{postId:\\d+}/comments/{commentParentId:\\d+}/children")
|
||||
public List<BaseCommentDTO> listChildrenBy(@PathVariable("postId") Integer postId,
|
||||
@PathVariable("commentParentId") Long commentParentId,
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
checkAuthenticate(postId);
|
||||
// Find all children comments
|
||||
List<PostComment> postComments = postCommentService
|
||||
.listChildrenBy(postId, commentParentId, CommentStatus.PUBLISHED, sort);
|
||||
// Convert to base comment dto
|
||||
|
||||
return postCommentService.convertTo(postComments);
|
||||
}
|
||||
|
||||
@GetMapping("{postId:\\d+}/comments/tree_view")
|
||||
@ApiOperation("Lists comments with tree view")
|
||||
public Page<BaseCommentVO> listCommentsTree(@PathVariable("postId") Integer postId,
|
||||
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
checkAuthenticate(postId);
|
||||
return postCommentService
|
||||
.pageVosBy(postId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||
}
|
||||
|
||||
@GetMapping("{postId:\\d+}/comments/list_view")
|
||||
@ApiOperation("Lists comment with list view")
|
||||
public Page<BaseCommentWithParentVO> listComments(@PathVariable("postId") Integer postId,
|
||||
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
checkAuthenticate(postId);
|
||||
return postCommentService.pageWithParentVoBy(postId,
|
||||
PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||
}
|
||||
|
||||
@PostMapping("comments")
|
||||
@ApiOperation("Comments a post")
|
||||
@CacheLock(autoDelete = false, traceRequest = true)
|
||||
public BaseCommentDTO comment(@RequestBody PostCommentParam postCommentParam) {
|
||||
checkAuthenticate(postCommentParam.getPostId());
|
||||
postCommentService.validateCommentBlackListStatus();
|
||||
|
||||
// Escape content
|
||||
postCommentParam.setContent(HtmlUtils
|
||||
.htmlEscape(postCommentParam.getContent(), StandardCharsets.UTF_8.displayName()));
|
||||
return postCommentService.convertTo(postCommentService.createBy(postCommentParam));
|
||||
}
|
||||
|
||||
@PostMapping("{postId:\\d+}/likes")
|
||||
@ApiOperation("Likes a post")
|
||||
@CacheLock(autoDelete = false, traceRequest = true)
|
||||
public void like(@PathVariable("postId") @CacheParam Integer postId) {
|
||||
checkAuthenticate(postId);
|
||||
postService.increaseLike(postId);
|
||||
}
|
||||
|
||||
private void checkAuthenticate(Integer postId) {
|
||||
if (!postAuthentication.isAuthenticated(postId)) {
|
||||
throw new ForbiddenException("您没有该分类的访问权限");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,175 +0,0 @@
|
|||
package run.halo.app.controller.content.api;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
import run.halo.app.cache.lock.CacheLock;
|
||||
import run.halo.app.model.dto.BaseCommentDTO;
|
||||
import run.halo.app.model.entity.Sheet;
|
||||
import run.halo.app.model.entity.SheetComment;
|
||||
import run.halo.app.model.enums.CommentStatus;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.params.SheetCommentParam;
|
||||
import run.halo.app.model.vo.BaseCommentVO;
|
||||
import run.halo.app.model.vo.BaseCommentWithParentVO;
|
||||
import run.halo.app.model.vo.CommentWithHasChildrenVO;
|
||||
import run.halo.app.model.vo.SheetDetailVO;
|
||||
import run.halo.app.model.vo.SheetListVO;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.SheetCommentService;
|
||||
import run.halo.app.service.SheetService;
|
||||
import run.halo.app.service.assembler.SheetRenderAssembler;
|
||||
|
||||
/**
|
||||
* Content sheet controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @date 2019-04-26
|
||||
*/
|
||||
@RestController("ApiContentSheetController")
|
||||
@RequestMapping("/api/content/sheets")
|
||||
public class SheetController {
|
||||
|
||||
private final SheetService sheetService;
|
||||
|
||||
private final SheetRenderAssembler sheetRenderAssembler;
|
||||
|
||||
private final SheetCommentService sheetCommentService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
public SheetController(SheetService sheetService,
|
||||
SheetRenderAssembler sheetRenderAssembler,
|
||||
SheetCommentService sheetCommentService,
|
||||
OptionService optionService) {
|
||||
this.sheetService = sheetService;
|
||||
this.sheetRenderAssembler = sheetRenderAssembler;
|
||||
this.sheetCommentService = sheetCommentService;
|
||||
this.optionService = optionService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("Lists sheets")
|
||||
public Page<SheetListVO> pageBy(
|
||||
@PageableDefault(sort = "createTime", direction = DESC) Pageable pageable) {
|
||||
Page<Sheet> sheetPage = sheetService.pageBy(PostStatus.PUBLISHED, pageable);
|
||||
return sheetRenderAssembler.convertToListVo(sheetPage);
|
||||
}
|
||||
|
||||
@GetMapping("{sheetId:\\d+}")
|
||||
@ApiOperation("Gets a sheet")
|
||||
public SheetDetailVO getBy(@PathVariable("sheetId") Integer sheetId,
|
||||
@RequestParam(value = "formatDisabled", required = false, defaultValue = "true")
|
||||
Boolean formatDisabled,
|
||||
@RequestParam(value = "sourceDisabled", required = false, defaultValue = "false")
|
||||
Boolean sourceDisabled) {
|
||||
Sheet sheet = sheetService.getById(sheetId);
|
||||
|
||||
SheetDetailVO sheetDetailVO = sheetRenderAssembler.convertToDetailVo(sheet);
|
||||
|
||||
if (formatDisabled) {
|
||||
// Clear the format content
|
||||
sheetDetailVO.setContent(null);
|
||||
}
|
||||
|
||||
if (sourceDisabled) {
|
||||
// Clear the original content
|
||||
sheetDetailVO.setOriginalContent(null);
|
||||
}
|
||||
|
||||
sheetService.publishVisitEvent(sheetDetailVO.getId());
|
||||
|
||||
return sheetDetailVO;
|
||||
}
|
||||
|
||||
@GetMapping("/slug")
|
||||
@ApiOperation("Gets a sheet by slug")
|
||||
public SheetDetailVO getBy(@RequestParam("slug") String slug,
|
||||
@RequestParam(value = "formatDisabled", required = false, defaultValue = "true")
|
||||
Boolean formatDisabled,
|
||||
@RequestParam(value = "sourceDisabled", required = false, defaultValue = "false")
|
||||
Boolean sourceDisabled) {
|
||||
Sheet sheet = sheetService.getBySlug(slug);
|
||||
SheetDetailVO sheetDetailVO = sheetRenderAssembler.convertToDetailVo(sheet);
|
||||
|
||||
if (formatDisabled) {
|
||||
// Clear the format content
|
||||
sheetDetailVO.setContent(null);
|
||||
}
|
||||
|
||||
if (sourceDisabled) {
|
||||
// Clear the original content
|
||||
sheetDetailVO.setOriginalContent(null);
|
||||
}
|
||||
|
||||
sheetService.publishVisitEvent(sheetDetailVO.getId());
|
||||
|
||||
return sheetDetailVO;
|
||||
}
|
||||
|
||||
@GetMapping("{sheetId:\\d+}/comments/top_view")
|
||||
public Page<CommentWithHasChildrenVO> listTopComments(@PathVariable("sheetId") Integer sheetId,
|
||||
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
return sheetCommentService.pageTopCommentsBy(sheetId, CommentStatus.PUBLISHED,
|
||||
PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||
}
|
||||
|
||||
@GetMapping("{sheetId:\\d+}/comments/{commentParentId:\\d+}/children")
|
||||
public List<BaseCommentDTO> listChildrenBy(@PathVariable("sheetId") Integer sheetId,
|
||||
@PathVariable("commentParentId") Long commentParentId,
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
// Find all children comments
|
||||
List<SheetComment> sheetComments = sheetCommentService
|
||||
.listChildrenBy(sheetId, commentParentId, CommentStatus.PUBLISHED, sort);
|
||||
// Convert to base comment dto
|
||||
return sheetCommentService.convertTo(sheetComments);
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("{sheetId:\\d+}/comments/tree_view")
|
||||
@ApiOperation("Lists comments with tree view")
|
||||
public Page<BaseCommentVO> listCommentsTree(@PathVariable("sheetId") Integer sheetId,
|
||||
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
return sheetCommentService
|
||||
.pageVosBy(sheetId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||
}
|
||||
|
||||
@GetMapping("{sheetId:\\d+}/comments/list_view")
|
||||
@ApiOperation("Lists comment with list view")
|
||||
public Page<BaseCommentWithParentVO> listComments(@PathVariable("sheetId") Integer sheetId,
|
||||
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
|
||||
@SortDefault(sort = "createTime", direction = DESC) Sort sort) {
|
||||
return sheetCommentService.pageWithParentVoBy(sheetId,
|
||||
PageRequest.of(page, optionService.getCommentPageSize(), sort));
|
||||
}
|
||||
|
||||
@PostMapping("comments")
|
||||
@ApiOperation("Comments a post")
|
||||
@CacheLock(autoDelete = false, traceRequest = true)
|
||||
public BaseCommentDTO comment(@RequestBody SheetCommentParam sheetCommentParam) {
|
||||
|
||||
// Escape content
|
||||
sheetCommentParam.setContent(HtmlUtils
|
||||
.htmlEscape(sheetCommentParam.getContent(), StandardCharsets.UTF_8.displayName()));
|
||||
return sheetCommentService.convertTo(sheetCommentService.createBy(sheetCommentParam));
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package run.halo.app.controller.content.api;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.model.dto.StatisticDTO;
|
||||
import run.halo.app.model.dto.StatisticWithUserDTO;
|
||||
import run.halo.app.service.StatisticService;
|
||||
|
||||
/**
|
||||
* Content statistic controller.
|
||||
*
|
||||
* @author ryan0up
|
||||
* @date 2019-12-16
|
||||
*/
|
||||
@RestController("ApiContentStatisticController")
|
||||
@RequestMapping("/api/content/statistics")
|
||||
public class StatisticController {
|
||||
|
||||
private final StatisticService statisticService;
|
||||
|
||||
public StatisticController(StatisticService statisticService) {
|
||||
this.statisticService = statisticService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("Gets blog statistics.")
|
||||
public StatisticDTO statistics() {
|
||||
return statisticService.getStatistic();
|
||||
}
|
||||
|
||||
@GetMapping("user")
|
||||
@ApiOperation("Gets blog statistics with user")
|
||||
public StatisticWithUserDTO statisticsWithUser() {
|
||||
return statisticService.getStatisticWithUser();
|
||||
}
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
package run.halo.app.controller.content.api;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import java.util.List;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.model.dto.TagDTO;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.entity.Tag;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.vo.PostListVO;
|
||||
import run.halo.app.service.PostTagService;
|
||||
import run.halo.app.service.TagService;
|
||||
import run.halo.app.service.assembler.PostRenderAssembler;
|
||||
|
||||
/**
|
||||
* Content tag controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @date 2019-04-02
|
||||
*/
|
||||
@RestController("ApiContentTagController")
|
||||
@RequestMapping("/api/content/tags")
|
||||
public class TagController {
|
||||
|
||||
private final TagService tagService;
|
||||
|
||||
private final PostTagService postTagService;
|
||||
|
||||
private final PostRenderAssembler postRenderAssembler;
|
||||
|
||||
public TagController(TagService tagService,
|
||||
PostTagService postTagService,
|
||||
PostRenderAssembler postRenderAssembler) {
|
||||
this.tagService = tagService;
|
||||
this.postTagService = postTagService;
|
||||
this.postRenderAssembler = postRenderAssembler;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@ApiOperation("Lists tags")
|
||||
public List<? extends TagDTO> listTags(
|
||||
@SortDefault(sort = "updateTime", direction = DESC) Sort sort,
|
||||
@ApiParam("If the param is true, post count of tag will be returned")
|
||||
@RequestParam(name = "more", required = false, defaultValue = "false") Boolean more) {
|
||||
if (more) {
|
||||
return postTagService.listTagWithCountDtos(sort);
|
||||
}
|
||||
return tagService.convertTo(tagService.listAll(sort));
|
||||
}
|
||||
|
||||
@GetMapping("{slug}/posts")
|
||||
@ApiOperation("Lists posts by tag slug")
|
||||
public Page<PostListVO> listPostsBy(@PathVariable("slug") String slug,
|
||||
@PageableDefault(sort = {"topPriority", "updateTime"}, direction = DESC)
|
||||
Pageable pageable) {
|
||||
// Get tag by slug
|
||||
Tag tag = tagService.getBySlugOfNonNull(slug);
|
||||
|
||||
// Get posts, convert and return
|
||||
Page<Post> postPage =
|
||||
postTagService.pagePostsBy(tag.getId(), PostStatus.PUBLISHED, pageable);
|
||||
return postRenderAssembler.convertToListVo(postPage);
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
package run.halo.app.controller.content.api;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import java.util.Map;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.handler.theme.config.support.ThemeProperty;
|
||||
import run.halo.app.service.ThemeService;
|
||||
import run.halo.app.service.ThemeSettingService;
|
||||
|
||||
/**
|
||||
* Content theme controller.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2020-01-17
|
||||
*/
|
||||
@RestController("ApiContentThemeController")
|
||||
@RequestMapping("/api/content/themes")
|
||||
public class ThemeController {
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
private final ThemeSettingService themeSettingService;
|
||||
|
||||
public ThemeController(ThemeService themeService, ThemeSettingService themeSettingService) {
|
||||
this.themeService = themeService;
|
||||
this.themeSettingService = themeSettingService;
|
||||
}
|
||||
|
||||
@GetMapping("activation")
|
||||
@ApiOperation("Gets activated theme property")
|
||||
public ThemeProperty getBy() {
|
||||
return themeService.getThemeOfNonNullBy(themeService.getActivatedThemeId());
|
||||
}
|
||||
|
||||
@GetMapping("{themeId:.+}")
|
||||
@ApiOperation("Gets theme property by theme id")
|
||||
public ThemeProperty getBy(@PathVariable("themeId") String themeId) {
|
||||
return themeService.getThemeOfNonNullBy(themeId);
|
||||
}
|
||||
|
||||
@GetMapping("activation/settings")
|
||||
@ApiOperation("Lists activated theme settings")
|
||||
public Map<String, Object> listSettingsBy() {
|
||||
return themeSettingService.listAsMapBy(themeService.getActivatedThemeId());
|
||||
}
|
||||
|
||||
@GetMapping("{themeId:.+}/settings")
|
||||
@ApiOperation("Lists theme settings by theme id")
|
||||
public Map<String, Object> listSettingsBy(@PathVariable("themeId") String themeId) {
|
||||
return themeSettingService.listAsMapBy(themeId);
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
package run.halo.app.controller.content.api;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.model.dto.UserDTO;
|
||||
import run.halo.app.service.UserService;
|
||||
|
||||
/**
|
||||
* Content user controller.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 2019-04-03
|
||||
*/
|
||||
@RestController("ApiContentUserController")
|
||||
@RequestMapping("/api/content/users")
|
||||
public class UserController {
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
public UserController(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@GetMapping("profile")
|
||||
@ApiOperation("Gets blogger profile")
|
||||
public UserDTO getProfile() {
|
||||
return userService.getCurrentUser().map(user -> (UserDTO) new UserDTO().convertFrom(user))
|
||||
.get();
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
package run.halo.app.controller.content.auth;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.cache.AbstractStringCacheStore;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.enums.EncryptTypeEnum;
|
||||
import run.halo.app.service.CategoryService;
|
||||
|
||||
/**
|
||||
* Authentication for category.
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2022-02-23
|
||||
*/
|
||||
@Component
|
||||
public class CategoryAuthentication implements ContentAuthentication {
|
||||
|
||||
private final CategoryService categoryService;
|
||||
|
||||
private final AbstractStringCacheStore cacheStore;
|
||||
|
||||
public CategoryAuthentication(CategoryService categoryService,
|
||||
AbstractStringCacheStore cacheStore) {
|
||||
this.categoryService = categoryService;
|
||||
this.cacheStore = cacheStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Object getPrincipal() {
|
||||
return EncryptTypeEnum.CATEGORY.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAuthenticated(Integer categoryId) {
|
||||
Category category = categoryService.getById(categoryId);
|
||||
if (StringUtils.isBlank(category.getPassword())) {
|
||||
// All parent category is not encrypted
|
||||
if (categoryService.lookupFirstEncryptedBy(category.getId()).isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
String sessionId = getSessionId();
|
||||
// No session is represent a client request
|
||||
if (StringUtils.isEmpty(sessionId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String cacheKey =
|
||||
buildCacheKey(sessionId, getPrincipal().toString(), String.valueOf(categoryId));
|
||||
return cacheStore.get(cacheKey).isPresent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticated(Integer resourceId, boolean isAuthenticated) {
|
||||
String sessionId = getSessionId();
|
||||
// No session is represent a client request
|
||||
if (StringUtils.isEmpty(sessionId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String cacheKey =
|
||||
buildCacheKey(sessionId, getPrincipal().toString(), String.valueOf(resourceId));
|
||||
if (isAuthenticated) {
|
||||
cacheStore.putAny(cacheKey, StringUtils.EMPTY, 1, TimeUnit.DAYS);
|
||||
return;
|
||||
}
|
||||
cacheStore.delete(cacheKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearByResourceId(Integer resourceId) {
|
||||
String resourceCachePrefix =
|
||||
StringUtils.joinWith(":", CACHE_PREFIX, getPrincipal(), resourceId);
|
||||
cacheStore.toMap().forEach((key, value) -> {
|
||||
if (StringUtils.startsWith(key, resourceCachePrefix)) {
|
||||
cacheStore.delete(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
package run.halo.app.controller.content.auth;
|
||||
|
||||
import java.util.Optional;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import run.halo.app.utils.ServletUtils;
|
||||
|
||||
/**
|
||||
* Content authentication.
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2022-02-23
|
||||
*/
|
||||
public interface ContentAuthentication {
|
||||
|
||||
/**
|
||||
* The identity of the principal being authenticated.
|
||||
*
|
||||
* @return authentication principal.
|
||||
*/
|
||||
Object getPrincipal();
|
||||
|
||||
/**
|
||||
* whether the resource been authenticated by a sessionId.
|
||||
*
|
||||
* @param resourceId resourceId to authentication
|
||||
* @see HttpServletRequest#getRequestedSessionId()
|
||||
* @return true if the resourceId has been authenticated by a sessionId
|
||||
*/
|
||||
boolean isAuthenticated(Integer resourceId);
|
||||
|
||||
/**
|
||||
* Set authentication state.
|
||||
*
|
||||
* @param resourceId resource identity
|
||||
* @param isAuthenticated authentication state
|
||||
* @see HttpServletRequest#getRequestedSessionId()
|
||||
*/
|
||||
void setAuthenticated(Integer resourceId, boolean isAuthenticated);
|
||||
|
||||
/**
|
||||
* Clear authentication state.
|
||||
*
|
||||
* @param resourceId resource id.
|
||||
*/
|
||||
void clearByResourceId(Integer resourceId);
|
||||
|
||||
String CACHE_PREFIX = "CONTENT_AUTHENTICATED";
|
||||
|
||||
/**
|
||||
* build authentication cache key.
|
||||
*
|
||||
* @param sessionId session id
|
||||
* @param principal authentication principal
|
||||
* @param value principal identity
|
||||
* @return cache key
|
||||
*/
|
||||
default String buildCacheKey(String sessionId, String principal,
|
||||
String value) {
|
||||
Assert.notNull(sessionId, "The sessionId must not be null.");
|
||||
Assert.notNull(principal, "The principal must not be null.");
|
||||
Assert.notNull(value, "The value must not be null.");
|
||||
return StringUtils.joinWith(":", CACHE_PREFIX, principal, value, sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets request session id.
|
||||
*
|
||||
* @return request session id.
|
||||
*/
|
||||
default String getSessionId() {
|
||||
Optional<HttpServletRequest> currentRequest = ServletUtils.getCurrentRequest();
|
||||
if (currentRequest.isEmpty()) {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
return currentRequest.get().getRequestedSessionId();
|
||||
}
|
||||
}
|
|
@ -1,140 +0,0 @@
|
|||
package run.halo.app.controller.content.auth;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import run.halo.app.event.category.CategoryUpdatedEvent;
|
||||
import run.halo.app.event.post.PostUpdatedEvent;
|
||||
import run.halo.app.exception.AuthenticationException;
|
||||
import run.halo.app.exception.NotFoundException;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.enums.EncryptTypeEnum;
|
||||
import run.halo.app.service.CategoryService;
|
||||
import run.halo.app.service.PostCategoryService;
|
||||
import run.halo.app.service.PostService;
|
||||
|
||||
/**
|
||||
* Content authentication manager.
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2022-02-24
|
||||
*/
|
||||
@Component
|
||||
public class ContentAuthenticationManager {
|
||||
private final CategoryService categoryService;
|
||||
private final CategoryAuthentication categoryAuthentication;
|
||||
private final PostService postService;
|
||||
private final PostAuthentication postAuthentication;
|
||||
private final PostCategoryService postCategoryService;
|
||||
|
||||
public ContentAuthenticationManager(CategoryService categoryService,
|
||||
CategoryAuthentication categoryAuthentication, PostService postService,
|
||||
PostAuthentication postAuthentication,
|
||||
PostCategoryService postCategoryService) {
|
||||
this.categoryService = categoryService;
|
||||
this.categoryAuthentication = categoryAuthentication;
|
||||
this.postService = postService;
|
||||
this.postAuthentication = postAuthentication;
|
||||
this.postCategoryService = postCategoryService;
|
||||
}
|
||||
|
||||
public ContentAuthentication authenticate(ContentAuthenticationRequest authRequest) throws
|
||||
AuthenticationException {
|
||||
if (EncryptTypeEnum.POST.getName().equals(authRequest.getPrincipal())) {
|
||||
return authenticatePost(authRequest);
|
||||
}
|
||||
if (EncryptTypeEnum.CATEGORY.getName().equals(authRequest.getPrincipal())) {
|
||||
return authenticateCategory(authRequest);
|
||||
}
|
||||
throw new NotFoundException(
|
||||
"Could not be found suitable authentication processor for ["
|
||||
+ authRequest.getPrincipal() + "]");
|
||||
}
|
||||
|
||||
@EventListener(CategoryUpdatedEvent.class)
|
||||
public void categoryUpdatedListener(CategoryUpdatedEvent event) {
|
||||
Category category = event.getCategory();
|
||||
categoryAuthentication.clearByResourceId(category.getId());
|
||||
}
|
||||
|
||||
@EventListener(PostUpdatedEvent.class)
|
||||
public void postUpdatedListener(PostUpdatedEvent event) {
|
||||
Post post = event.getPost();
|
||||
postAuthentication.clearByResourceId(post.getId());
|
||||
}
|
||||
|
||||
private PostAuthentication authenticatePost(ContentAuthenticationRequest authRequest) {
|
||||
Post post = postService.getById(authRequest.getId());
|
||||
if (StringUtils.isNotBlank(post.getPassword())) {
|
||||
if (StringUtils.equals(post.getPassword(), authRequest.getPassword())) {
|
||||
postAuthentication.setAuthenticated(post.getId(), true);
|
||||
return postAuthentication;
|
||||
} else {
|
||||
throw new AuthenticationException("密码不正确");
|
||||
}
|
||||
} else {
|
||||
List<Category> encryptedCategories = postCategoryService.listCategoriesBy(post.getId())
|
||||
.stream()
|
||||
.filter(category -> categoryService.isPrivate(category.getId()))
|
||||
.collect(Collectors.toList());
|
||||
// The post has no password and does not belong to any encryption categories.
|
||||
// Return it directly
|
||||
if (CollectionUtils.isEmpty(encryptedCategories)) {
|
||||
return postAuthentication;
|
||||
}
|
||||
|
||||
// Try all categories until the password is correct
|
||||
for (Category category : encryptedCategories) {
|
||||
if (StringUtils.equals(category.getPassword(), authRequest.getPassword())) {
|
||||
postAuthentication.setAuthenticated(post.getId(), true);
|
||||
return postAuthentication;
|
||||
}
|
||||
}
|
||||
|
||||
for (Category category : encryptedCategories) {
|
||||
boolean authenticated = categoryService.lookupFirstEncryptedBy(category.getId())
|
||||
.filter(parentCategory -> StringUtils.equals(parentCategory.getPassword(),
|
||||
authRequest.getPassword()))
|
||||
.isPresent();
|
||||
|
||||
if (authenticated) {
|
||||
postAuthentication.setAuthenticated(post.getId(), true);
|
||||
return postAuthentication;
|
||||
}
|
||||
}
|
||||
throw new AuthenticationException("密码不正确");
|
||||
}
|
||||
}
|
||||
|
||||
private CategoryAuthentication authenticateCategory(ContentAuthenticationRequest authRequest) {
|
||||
Category category = categoryService.getById(authRequest.getId());
|
||||
if (category.getPassword() == null) {
|
||||
String parentPassword = categoryService.lookupFirstEncryptedBy(category.getId())
|
||||
.map(Category::getPassword)
|
||||
.orElse(null);
|
||||
if (parentPassword == null) {
|
||||
return categoryAuthentication;
|
||||
}
|
||||
category.setPassword(parentPassword);
|
||||
}
|
||||
|
||||
if (StringUtils.equals(category.getPassword(), authRequest.getPassword())) {
|
||||
categoryAuthentication.setAuthenticated(category.getId(), true);
|
||||
return categoryAuthentication;
|
||||
}
|
||||
// Finds the first encrypted parent category to authenticate
|
||||
Category parentCategory =
|
||||
categoryService.lookupFirstEncryptedBy(authRequest.getId())
|
||||
.orElseThrow(() -> new AuthenticationException("密码不正确"));
|
||||
if (!Objects.equals(parentCategory.getPassword(), authRequest.getPassword())) {
|
||||
throw new AuthenticationException("密码不正确");
|
||||
}
|
||||
categoryAuthentication.setAuthenticated(category.getId(), true);
|
||||
return categoryAuthentication;
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
package run.halo.app.controller.content.auth;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Authentication request for {@link ContentAuthenticationManager}.
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2022-02-24
|
||||
*/
|
||||
@Data
|
||||
public class ContentAuthenticationRequest implements ContentAuthentication {
|
||||
private Integer id;
|
||||
|
||||
private String password;
|
||||
|
||||
private String principal;
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return this.principal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAuthenticated(Integer resourceId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticated(Integer resourceId, boolean isAuthenticated) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearByResourceId(Integer resourceId) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ContentAuthenticationRequest}.
|
||||
*
|
||||
* @param id resource id
|
||||
* @param password resource password
|
||||
* @param principal authentication principal
|
||||
* @return a {@link ContentAuthenticationRequest} instance.
|
||||
*/
|
||||
public static ContentAuthenticationRequest of(Integer id, String password, String principal) {
|
||||
ContentAuthenticationRequest request = new ContentAuthenticationRequest();
|
||||
request.setId(id);
|
||||
request.setPassword(password);
|
||||
request.setPrincipal(principal);
|
||||
return request;
|
||||
}
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
package run.halo.app.controller.content.auth;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.cache.AbstractStringCacheStore;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.entity.PostCategory;
|
||||
import run.halo.app.model.enums.EncryptTypeEnum;
|
||||
import run.halo.app.service.CategoryService;
|
||||
import run.halo.app.service.PostCategoryService;
|
||||
import run.halo.app.service.PostService;
|
||||
|
||||
/**
|
||||
* Authentication for post.
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2022-02-24
|
||||
*/
|
||||
@Component
|
||||
public class PostAuthentication implements ContentAuthentication {
|
||||
|
||||
private final PostService postService;
|
||||
private final CategoryService categoryService;
|
||||
private final PostCategoryService postCategoryService;
|
||||
private final AbstractStringCacheStore cacheStore;
|
||||
private final CategoryAuthentication categoryAuthentication;
|
||||
|
||||
public PostAuthentication(PostService postService,
|
||||
CategoryService categoryService,
|
||||
PostCategoryService postCategoryService,
|
||||
AbstractStringCacheStore cacheStore,
|
||||
CategoryAuthentication categoryAuthentication) {
|
||||
this.postService = postService;
|
||||
this.categoryService = categoryService;
|
||||
this.postCategoryService = postCategoryService;
|
||||
this.cacheStore = cacheStore;
|
||||
this.categoryAuthentication = categoryAuthentication;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return EncryptTypeEnum.POST.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAuthenticated(Integer postId) {
|
||||
Post post = postService.getById(postId);
|
||||
if (StringUtils.isBlank(post.getPassword())) {
|
||||
List<PostCategory> postCategories = postCategoryService.listByPostId(postId);
|
||||
boolean categoryEncrypted = postCategories.stream()
|
||||
.anyMatch(postCategory -> categoryService.isPrivate(postCategory.getCategoryId()));
|
||||
if (!categoryEncrypted) {
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean anyCategoryAuthenticated = postCategories.stream()
|
||||
.anyMatch(postCategory ->
|
||||
categoryAuthentication.isAuthenticated(postCategory.getCategoryId()));
|
||||
if (anyCategoryAuthenticated) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
String sessionId = getSessionId();
|
||||
// No session is represent a client request
|
||||
if (StringUtils.isEmpty(sessionId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String cacheKey =
|
||||
buildCacheKey(sessionId, getPrincipal().toString(), String.valueOf(postId));
|
||||
return cacheStore.get(cacheKey).isPresent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticated(Integer resourceId, boolean isAuthenticated) {
|
||||
String sessionId = getSessionId();
|
||||
// No session is represent a client request
|
||||
if (StringUtils.isEmpty(sessionId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String cacheKey =
|
||||
buildCacheKey(sessionId, getPrincipal().toString(), String.valueOf(resourceId));
|
||||
if (isAuthenticated) {
|
||||
cacheStore.putAny(cacheKey, StringUtils.EMPTY, 1, TimeUnit.DAYS);
|
||||
return;
|
||||
}
|
||||
cacheStore.delete(cacheKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearByResourceId(Integer resourceId) {
|
||||
String resourceCachePrefix =
|
||||
StringUtils.joinWith(":", CACHE_PREFIX, getPrincipal(), resourceId);
|
||||
cacheStore.toMap().forEach((key, value) -> {
|
||||
if (StringUtils.startsWith(key, resourceCachePrefix)) {
|
||||
cacheStore.delete(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
package run.halo.app.controller.content.model;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
import static run.halo.app.model.support.HaloConst.POST_PASSWORD_TEMPLATE;
|
||||
import static run.halo.app.model.support.HaloConst.SUFFIX_FTL;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.ui.Model;
|
||||
import run.halo.app.controller.content.auth.CategoryAuthentication;
|
||||
import run.halo.app.model.dto.CategoryDTO;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.enums.EncryptTypeEnum;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.vo.PostListVO;
|
||||
import run.halo.app.service.CategoryService;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PostCategoryService;
|
||||
import run.halo.app.service.ThemeService;
|
||||
import run.halo.app.service.assembler.PostRenderAssembler;
|
||||
|
||||
/**
|
||||
* Category Model.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @author guqing
|
||||
* @date 2020-01-11
|
||||
*/
|
||||
@Component
|
||||
public class CategoryModel {
|
||||
|
||||
private final CategoryService categoryService;
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
private final PostCategoryService postCategoryService;
|
||||
|
||||
private final PostRenderAssembler postRenderAssembler;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final CategoryAuthentication categoryAuthentication;
|
||||
|
||||
public CategoryModel(CategoryService categoryService,
|
||||
ThemeService themeService,
|
||||
PostCategoryService postCategoryService,
|
||||
PostRenderAssembler postRenderAssembler, OptionService optionService,
|
||||
CategoryAuthentication categoryAuthentication) {
|
||||
this.categoryService = categoryService;
|
||||
this.themeService = themeService;
|
||||
this.postCategoryService = postCategoryService;
|
||||
this.postRenderAssembler = postRenderAssembler;
|
||||
this.optionService = optionService;
|
||||
this.categoryAuthentication = categoryAuthentication;
|
||||
}
|
||||
|
||||
/**
|
||||
* List categories.
|
||||
*
|
||||
* @param model model
|
||||
* @return template name
|
||||
*/
|
||||
public String list(Model model) {
|
||||
model.addAttribute("is_categories", true);
|
||||
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
|
||||
model.addAttribute("meta_description", optionService.getSeoDescription());
|
||||
return themeService.render("categories");
|
||||
}
|
||||
|
||||
/**
|
||||
* List category posts.
|
||||
*
|
||||
* @param model model
|
||||
* @param slug slug
|
||||
* @param page current page
|
||||
* @return template name
|
||||
*/
|
||||
public String listPost(Model model, String slug, Integer page) {
|
||||
|
||||
// Get category by slug
|
||||
final Category category = categoryService.getBySlugOfNonNull(slug);
|
||||
|
||||
if (!categoryAuthentication.isAuthenticated(category.getId())) {
|
||||
model.addAttribute("slug", category.getSlug());
|
||||
model.addAttribute("type", EncryptTypeEnum.CATEGORY.getName());
|
||||
if (themeService.templateExists(POST_PASSWORD_TEMPLATE + SUFFIX_FTL)) {
|
||||
return themeService.render(POST_PASSWORD_TEMPLATE);
|
||||
}
|
||||
return "common/template/" + POST_PASSWORD_TEMPLATE;
|
||||
}
|
||||
|
||||
Set<PostStatus> statuses = Sets.immutableEnumSet(PostStatus.PUBLISHED);
|
||||
if (categoryService.isPrivate(category.getId())) {
|
||||
statuses = Sets.immutableEnumSet(PostStatus.INTIMATE);
|
||||
}
|
||||
|
||||
CategoryDTO categoryDTO = categoryService.convertTo(category);
|
||||
|
||||
final Pageable pageable = PageRequest.of(page - 1,
|
||||
optionService.getArchivesPageSize(),
|
||||
Sort.by(DESC, "topPriority", "createTime"));
|
||||
Page<Post> postPage =
|
||||
postCategoryService.pagePostBy(category.getId(), statuses, pageable);
|
||||
Page<PostListVO> posts = postRenderAssembler.convertToListVo(postPage);
|
||||
|
||||
// Generate meta description.
|
||||
if (StringUtils.isNotEmpty(category.getDescription())) {
|
||||
model.addAttribute("meta_description", category.getDescription());
|
||||
} else {
|
||||
model.addAttribute("meta_description", optionService.getSeoDescription());
|
||||
}
|
||||
|
||||
model.addAttribute("is_category", true);
|
||||
model.addAttribute("posts", posts);
|
||||
model.addAttribute("category", categoryDTO);
|
||||
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
|
||||
return themeService.render("category");
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package run.halo.app.controller.content.model;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.ui.Model;
|
||||
import run.halo.app.model.entity.Journal;
|
||||
import run.halo.app.model.enums.JournalType;
|
||||
import run.halo.app.model.properties.SheetProperties;
|
||||
import run.halo.app.service.JournalService;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.ThemeService;
|
||||
|
||||
/**
|
||||
* @author ryanwang
|
||||
* @date 2020-02-11
|
||||
*/
|
||||
@Component
|
||||
public class JournalModel {
|
||||
|
||||
private final JournalService journalService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
public JournalModel(JournalService journalService,
|
||||
OptionService optionService,
|
||||
ThemeService themeService) {
|
||||
this.journalService = journalService;
|
||||
this.optionService = optionService;
|
||||
this.themeService = themeService;
|
||||
}
|
||||
|
||||
public String list(Integer page, Model model) {
|
||||
|
||||
int pageSize = optionService
|
||||
.getByPropertyOrDefault(SheetProperties.JOURNALS_PAGE_SIZE, Integer.class,
|
||||
Integer.parseInt(SheetProperties.JOURNALS_PAGE_SIZE.defaultValue()));
|
||||
|
||||
Pageable pageable =
|
||||
PageRequest.of(page >= 1 ? page - 1 : page, pageSize, Sort.by(DESC, "createTime"));
|
||||
|
||||
Page<Journal> journals = journalService.pageBy(JournalType.PUBLIC, pageable);
|
||||
|
||||
model.addAttribute("is_journals", true);
|
||||
model.addAttribute("journals", journalService.convertToCmtCountDto(journals));
|
||||
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
|
||||
model.addAttribute("meta_description", optionService.getSeoDescription());
|
||||
return themeService.render("journals");
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package run.halo.app.controller.content.model;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.ui.Model;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.ThemeService;
|
||||
|
||||
/**
|
||||
* @author ryanwang
|
||||
* @date 2020-03-04
|
||||
*/
|
||||
@Component
|
||||
public class LinkModel {
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
public LinkModel(ThemeService themeService,
|
||||
OptionService optionService) {
|
||||
this.themeService = themeService;
|
||||
this.optionService = optionService;
|
||||
}
|
||||
|
||||
public String list(Model model) {
|
||||
model.addAttribute("is_links", true);
|
||||
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
|
||||
model.addAttribute("meta_description", optionService.getSeoDescription());
|
||||
return themeService.render("links");
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
package run.halo.app.controller.content.model;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.ui.Model;
|
||||
import run.halo.app.model.dto.PhotoDTO;
|
||||
import run.halo.app.model.properties.SheetProperties;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PhotoService;
|
||||
import run.halo.app.service.ThemeService;
|
||||
|
||||
/**
|
||||
* @author ryanwang
|
||||
* @date 2020-02-11
|
||||
*/
|
||||
@Component
|
||||
public class PhotoModel {
|
||||
|
||||
private final PhotoService photoService;
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
public PhotoModel(PhotoService photoService,
|
||||
ThemeService themeService,
|
||||
OptionService optionService) {
|
||||
this.photoService = photoService;
|
||||
this.themeService = themeService;
|
||||
this.optionService = optionService;
|
||||
}
|
||||
|
||||
public String list(Integer page, Model model) {
|
||||
|
||||
int pageSize = optionService.getByPropertyOrDefault(SheetProperties.PHOTOS_PAGE_SIZE,
|
||||
Integer.class,
|
||||
Integer.parseInt(SheetProperties.PHOTOS_PAGE_SIZE.defaultValue()));
|
||||
|
||||
Pageable pageable =
|
||||
PageRequest.of(page >= 1 ? page - 1 : page, pageSize, Sort.by(DESC, "createTime"));
|
||||
|
||||
Page<PhotoDTO> photos = photoService.pageBy(pageable);
|
||||
|
||||
model.addAttribute("is_photos", true);
|
||||
model.addAttribute("photos", photos);
|
||||
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
|
||||
model.addAttribute("meta_description", optionService.getSeoDescription());
|
||||
return themeService.render("photos");
|
||||
}
|
||||
}
|
|
@ -1,208 +0,0 @@
|
|||
package run.halo.app.controller.content.model;
|
||||
|
||||
import static run.halo.app.model.support.HaloConst.POST_PASSWORD_TEMPLATE;
|
||||
import static run.halo.app.model.support.HaloConst.SUFFIX_FTL;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.ui.Model;
|
||||
import run.halo.app.cache.AbstractStringCacheStore;
|
||||
import run.halo.app.controller.content.auth.PostAuthentication;
|
||||
import run.halo.app.exception.ForbiddenException;
|
||||
import run.halo.app.exception.NotFoundException;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.entity.PostMeta;
|
||||
import run.halo.app.model.entity.Tag;
|
||||
import run.halo.app.model.enums.EncryptTypeEnum;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.vo.ArchiveYearVO;
|
||||
import run.halo.app.model.vo.PostListVO;
|
||||
import run.halo.app.service.CategoryService;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PostCategoryService;
|
||||
import run.halo.app.service.PostMetaService;
|
||||
import run.halo.app.service.PostService;
|
||||
import run.halo.app.service.PostTagService;
|
||||
import run.halo.app.service.TagService;
|
||||
import run.halo.app.service.ThemeService;
|
||||
import run.halo.app.service.assembler.PostRenderAssembler;
|
||||
|
||||
/**
|
||||
* Post Model
|
||||
*
|
||||
* @author ryanwang
|
||||
* @author guqing
|
||||
* @date 2020-01-07
|
||||
*/
|
||||
@Component
|
||||
public class PostModel {
|
||||
|
||||
private final PostRenderAssembler postRenderAssembler;
|
||||
|
||||
private final PostService postService;
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
private final PostCategoryService postCategoryService;
|
||||
|
||||
private final CategoryService categoryService;
|
||||
|
||||
private final PostTagService postTagService;
|
||||
|
||||
private final TagService tagService;
|
||||
|
||||
private final PostMetaService postMetaService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final AbstractStringCacheStore cacheStore;
|
||||
|
||||
private final PostAuthentication postAuthentication;
|
||||
|
||||
public PostModel(PostRenderAssembler postRenderAssembler,
|
||||
PostService postService,
|
||||
ThemeService themeService,
|
||||
PostCategoryService postCategoryService,
|
||||
CategoryService categoryService,
|
||||
PostMetaService postMetaService,
|
||||
PostTagService postTagService,
|
||||
TagService tagService,
|
||||
OptionService optionService,
|
||||
AbstractStringCacheStore cacheStore,
|
||||
PostAuthentication postAuthentication) {
|
||||
this.postRenderAssembler = postRenderAssembler;
|
||||
this.postService = postService;
|
||||
this.themeService = themeService;
|
||||
this.postCategoryService = postCategoryService;
|
||||
this.categoryService = categoryService;
|
||||
this.postMetaService = postMetaService;
|
||||
this.postTagService = postTagService;
|
||||
this.tagService = tagService;
|
||||
this.optionService = optionService;
|
||||
this.cacheStore = cacheStore;
|
||||
this.postAuthentication = postAuthentication;
|
||||
}
|
||||
|
||||
public String content(Post post, String token, Model model) {
|
||||
if (PostStatus.RECYCLE.equals(post.getStatus())) {
|
||||
// Articles in the recycle bin are not allowed to be accessed.
|
||||
throw new NotFoundException("查询不到该文章的信息");
|
||||
} else if (StringUtils.isNotBlank(token)) {
|
||||
// If the token is not empty, it means it is an admin request,
|
||||
// then verify the token.
|
||||
|
||||
// verify token
|
||||
String cachedToken = cacheStore.getAny(token, String.class)
|
||||
.orElseThrow(() -> new ForbiddenException("您没有该文章的访问权限"));
|
||||
if (!cachedToken.equals(token)) {
|
||||
throw new ForbiddenException("您没有该文章的访问权限");
|
||||
}
|
||||
} else if (PostStatus.DRAFT.equals(post.getStatus())) {
|
||||
// Drafts are not allowed bo be accessed by outsiders.
|
||||
throw new NotFoundException("查询不到该文章的信息");
|
||||
} else if (PostStatus.INTIMATE.equals(post.getStatus())
|
||||
&& !postAuthentication.isAuthenticated(post.getId())
|
||||
) {
|
||||
// Encrypted articles must has the correct password before they can be accessed.
|
||||
|
||||
model.addAttribute("slug", post.getSlug());
|
||||
model.addAttribute("type", EncryptTypeEnum.POST.getName());
|
||||
if (themeService.templateExists(POST_PASSWORD_TEMPLATE + SUFFIX_FTL)) {
|
||||
return themeService.render(POST_PASSWORD_TEMPLATE);
|
||||
}
|
||||
return "common/template/" + POST_PASSWORD_TEMPLATE;
|
||||
}
|
||||
|
||||
post = postService.getById(post.getId());
|
||||
|
||||
postService.publishVisitEvent(post.getId());
|
||||
|
||||
postService.getPrevPost(post)
|
||||
.ifPresent(prevPost -> model.addAttribute("prevPost",
|
||||
postRenderAssembler.convertToDetailVo(prevPost)));
|
||||
postService.getNextPost(post)
|
||||
.ifPresent(nextPost -> model.addAttribute("nextPost",
|
||||
postRenderAssembler.convertToDetailVo(nextPost)));
|
||||
|
||||
List<Category> categories = postCategoryService.listCategoriesBy(post.getId());
|
||||
List<Tag> tags = postTagService.listTagsBy(post.getId());
|
||||
List<PostMeta> metas = postMetaService.listBy(post.getId());
|
||||
|
||||
// Generate meta keywords.
|
||||
if (StringUtils.isNotEmpty(post.getMetaKeywords())) {
|
||||
model.addAttribute("meta_keywords", post.getMetaKeywords());
|
||||
} else {
|
||||
model.addAttribute("meta_keywords",
|
||||
tags.stream().map(Tag::getName).collect(Collectors.joining(",")));
|
||||
}
|
||||
|
||||
// Generate meta description.
|
||||
if (StringUtils.isNotEmpty(post.getMetaDescription())) {
|
||||
model.addAttribute("meta_description", post.getMetaDescription());
|
||||
} else {
|
||||
model.addAttribute("meta_description",
|
||||
postService.generateDescription(post.getContent().getContent()));
|
||||
}
|
||||
|
||||
model.addAttribute("is_post", true);
|
||||
|
||||
if (StringUtils.isNotBlank(token)) {
|
||||
model.addAttribute("post", postRenderAssembler.convertToPreviewDetailVo(post));
|
||||
} else {
|
||||
model.addAttribute("post", postRenderAssembler.convertToDetailVo(post));
|
||||
}
|
||||
|
||||
model.addAttribute("categories", categoryService.convertTo(categories));
|
||||
model.addAttribute("tags", tagService.convertTo(tags));
|
||||
model.addAttribute("metas", postMetaService.convertToMap(metas));
|
||||
|
||||
if (themeService.templateExists(
|
||||
ThemeService.CUSTOM_POST_PREFIX + post.getTemplate() + SUFFIX_FTL)) {
|
||||
return themeService.render(ThemeService.CUSTOM_POST_PREFIX + post.getTemplate());
|
||||
}
|
||||
|
||||
return themeService.render("post");
|
||||
}
|
||||
|
||||
public String list(Integer page, Model model) {
|
||||
int pageSize = optionService.getPostPageSize();
|
||||
Pageable pageable = PageRequest
|
||||
.of(page >= 1 ? page - 1 : page, pageSize, postService.getPostDefaultSort());
|
||||
|
||||
Page<Post> postPage = postService.pageBy(PostStatus.PUBLISHED, pageable);
|
||||
Page<PostListVO> posts = postRenderAssembler.convertToListVo(postPage);
|
||||
|
||||
model.addAttribute("is_index", true);
|
||||
model.addAttribute("posts", posts);
|
||||
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
|
||||
model.addAttribute("meta_description", optionService.getSeoDescription());
|
||||
return themeService.render("index");
|
||||
}
|
||||
|
||||
public String archives(Integer page, Model model) {
|
||||
int pageSize = optionService.getArchivesPageSize();
|
||||
Pageable pageable = PageRequest
|
||||
.of(page >= 1 ? page - 1 : page, pageSize, Sort.by(Sort.Direction.DESC, "createTime"));
|
||||
|
||||
Page<Post> postPage = postService.pageBy(PostStatus.PUBLISHED, pageable);
|
||||
|
||||
Page<PostListVO> posts = postRenderAssembler.convertToListVo(postPage);
|
||||
|
||||
List<ArchiveYearVO> archives =
|
||||
postRenderAssembler.convertToYearArchives(postPage.getContent());
|
||||
|
||||
model.addAttribute("is_archives", true);
|
||||
model.addAttribute("posts", posts);
|
||||
model.addAttribute("archives", archives);
|
||||
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
|
||||
model.addAttribute("meta_description", optionService.getSeoDescription());
|
||||
return themeService.render("archives");
|
||||
}
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
package run.halo.app.controller.content.model;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.ui.Model;
|
||||
import run.halo.app.cache.AbstractStringCacheStore;
|
||||
import run.halo.app.exception.ForbiddenException;
|
||||
import run.halo.app.model.entity.Sheet;
|
||||
import run.halo.app.model.entity.SheetMeta;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.support.HaloConst;
|
||||
import run.halo.app.model.vo.SheetDetailVO;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.SheetMetaService;
|
||||
import run.halo.app.service.SheetService;
|
||||
import run.halo.app.service.ThemeService;
|
||||
import run.halo.app.service.assembler.SheetRenderAssembler;
|
||||
|
||||
/**
|
||||
* Sheet model.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2020-01-07
|
||||
*/
|
||||
@Component
|
||||
public class SheetModel {
|
||||
|
||||
private final SheetService sheetService;
|
||||
|
||||
private final SheetRenderAssembler sheetRenderAssembler;
|
||||
|
||||
private final SheetMetaService sheetMetaService;
|
||||
|
||||
private final AbstractStringCacheStore cacheStore;
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
public SheetModel(SheetService sheetService,
|
||||
SheetRenderAssembler sheetRenderAssembler,
|
||||
SheetMetaService sheetMetaService,
|
||||
AbstractStringCacheStore cacheStore,
|
||||
ThemeService themeService,
|
||||
OptionService optionService) {
|
||||
this.sheetService = sheetService;
|
||||
this.sheetRenderAssembler = sheetRenderAssembler;
|
||||
this.sheetMetaService = sheetMetaService;
|
||||
this.cacheStore = cacheStore;
|
||||
this.themeService = themeService;
|
||||
this.optionService = optionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sheet content.
|
||||
*
|
||||
* @param sheet sheet
|
||||
* @param token token
|
||||
* @param model model
|
||||
* @return template name
|
||||
*/
|
||||
public String content(Sheet sheet, String token, Model model) {
|
||||
SheetDetailVO sheetDetailVo;
|
||||
if (StringUtils.isEmpty(token)) {
|
||||
sheet = sheetService.getBy(PostStatus.PUBLISHED, sheet.getSlug());
|
||||
sheetDetailVo = sheetRenderAssembler.convertToDetailVo(sheet);
|
||||
} else {
|
||||
// verify token
|
||||
String cachedToken = cacheStore.getAny(token, String.class)
|
||||
.orElseThrow(() -> new ForbiddenException("您没有该页面的访问权限"));
|
||||
if (!cachedToken.equals(token)) {
|
||||
throw new ForbiddenException("您没有该页面的访问权限");
|
||||
}
|
||||
sheetDetailVo = sheetRenderAssembler.convertToPreviewDetailVo(sheet);
|
||||
}
|
||||
|
||||
sheetService.publishVisitEvent(sheet.getId());
|
||||
|
||||
List<SheetMeta> metas = sheetMetaService.listBy(sheet.getId());
|
||||
|
||||
// Generate meta keywords.
|
||||
if (StringUtils.isNotEmpty(sheet.getMetaKeywords())) {
|
||||
model.addAttribute("meta_keywords", sheet.getMetaKeywords());
|
||||
} else {
|
||||
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
|
||||
}
|
||||
|
||||
// Generate meta description.
|
||||
if (StringUtils.isNotEmpty(sheet.getMetaDescription())) {
|
||||
model.addAttribute("meta_description", sheet.getMetaDescription());
|
||||
} else {
|
||||
model.addAttribute("meta_description",
|
||||
sheetService.generateDescription(sheet.getContent().getContent()));
|
||||
}
|
||||
|
||||
// sheet and post all can use
|
||||
model.addAttribute("sheet", sheetDetailVo);
|
||||
model.addAttribute("post", sheetDetailVo);
|
||||
model.addAttribute("is_sheet", true);
|
||||
model.addAttribute("metas", sheetMetaService.convertToMap(metas));
|
||||
|
||||
if (themeService.templateExists(
|
||||
ThemeService.CUSTOM_SHEET_PREFIX + sheet.getTemplate() + HaloConst.SUFFIX_FTL)) {
|
||||
return themeService.render(ThemeService.CUSTOM_SHEET_PREFIX + sheet.getTemplate());
|
||||
}
|
||||
return themeService.render("sheet");
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
package run.halo.app.controller.content.model;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.ui.Model;
|
||||
import run.halo.app.model.dto.TagDTO;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.entity.Tag;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.vo.PostListVO;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PostTagService;
|
||||
import run.halo.app.service.TagService;
|
||||
import run.halo.app.service.ThemeService;
|
||||
import run.halo.app.service.assembler.PostRenderAssembler;
|
||||
|
||||
/**
|
||||
* Tag Model.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2020-01-11
|
||||
*/
|
||||
@Component
|
||||
public class TagModel {
|
||||
|
||||
private final TagService tagService;
|
||||
|
||||
private final PostRenderAssembler postRenderAssembler;
|
||||
|
||||
private final PostTagService postTagService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
public TagModel(TagService tagService,
|
||||
PostRenderAssembler postRenderAssembler,
|
||||
PostTagService postTagService,
|
||||
OptionService optionService,
|
||||
ThemeService themeService) {
|
||||
this.tagService = tagService;
|
||||
this.postRenderAssembler = postRenderAssembler;
|
||||
this.postTagService = postTagService;
|
||||
this.optionService = optionService;
|
||||
this.themeService = themeService;
|
||||
}
|
||||
|
||||
public String list(Model model) {
|
||||
model.addAttribute("is_tags", true);
|
||||
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
|
||||
model.addAttribute("meta_description", optionService.getSeoDescription());
|
||||
return themeService.render("tags");
|
||||
}
|
||||
|
||||
public String listPost(Model model, String slug, Integer page) {
|
||||
// Get tag by slug
|
||||
final Tag tag = tagService.getBySlugOfNonNull(slug);
|
||||
TagDTO tagDTO = tagService.convertTo(tag);
|
||||
|
||||
final Pageable pageable = PageRequest
|
||||
.of(page - 1, optionService.getArchivesPageSize(), Sort.by(DESC, "createTime"));
|
||||
Page<Post> postPage =
|
||||
postTagService.pagePostsBy(tag.getId(), PostStatus.PUBLISHED, pageable);
|
||||
Page<PostListVO> posts = postRenderAssembler.convertToListVo(postPage);
|
||||
|
||||
model.addAttribute("is_tag", true);
|
||||
model.addAttribute("posts", posts);
|
||||
model.addAttribute("tag", tagDTO);
|
||||
model.addAttribute("meta_keywords", optionService.getSeoKeywords());
|
||||
model.addAttribute("meta_description", optionService.getSeoDescription());
|
||||
return themeService.render("tag");
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
package run.halo.app.controller.error;
|
||||
|
||||
import java.util.List;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
|
||||
import org.springframework.boot.web.servlet.error.ErrorAttributes;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.util.NestedServletException;
|
||||
import run.halo.app.exception.AbstractHaloException;
|
||||
|
||||
/**
|
||||
* Default error controller.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class DefaultErrorController extends BasicErrorController {
|
||||
|
||||
public DefaultErrorController(
|
||||
ErrorAttributes errorAttributes,
|
||||
ServerProperties serverProperties,
|
||||
List<ErrorViewResolver> errorViewResolvers) {
|
||||
super(errorAttributes, serverProperties.getError(), errorViewResolvers);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpStatus getStatus(HttpServletRequest request) {
|
||||
var status = super.getStatus(request);
|
||||
// deduce status
|
||||
var exception = request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
|
||||
if (exception instanceof NestedServletException) {
|
||||
var nse = (NestedServletException) exception;
|
||||
if (nse.getCause() instanceof AbstractHaloException) {
|
||||
status = resolveHaloException((AbstractHaloException) nse.getCause(), request);
|
||||
}
|
||||
} else if (exception instanceof AbstractHaloException) {
|
||||
status = resolveHaloException((AbstractHaloException) exception, request);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
private HttpStatus resolveHaloException(AbstractHaloException haloException,
|
||||
HttpServletRequest request) {
|
||||
HttpStatus status = haloException.getStatus();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.error("Halo exception occurred.", haloException);
|
||||
}
|
||||
// reset status
|
||||
request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, status.value());
|
||||
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, haloException);
|
||||
request.setAttribute(RequestDispatcher.ERROR_MESSAGE, haloException.getMessage());
|
||||
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE, haloException.getClass());
|
||||
return status;
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
package run.halo.app.controller.error;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatus.Series;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import run.halo.app.service.ThemeService;
|
||||
|
||||
@Component
|
||||
public class DefaultErrorViewResolver implements ErrorViewResolver {
|
||||
|
||||
private static final Map<Series, String> SERIES_VIEWS;
|
||||
|
||||
static {
|
||||
EnumMap<Series, String> views = new EnumMap<>(Series.class);
|
||||
views.put(Series.CLIENT_ERROR, "4xx");
|
||||
views.put(Series.SERVER_ERROR, "5xx");
|
||||
SERIES_VIEWS = Collections.unmodifiableMap(views);
|
||||
}
|
||||
|
||||
private final ThemeService themeService;
|
||||
|
||||
private final TemplateAvailabilityProviders templateAvailabilityProviders;
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
public DefaultErrorViewResolver(ThemeService themeService,
|
||||
ApplicationContext applicationContext) {
|
||||
this.themeService = themeService;
|
||||
this.applicationContext = applicationContext;
|
||||
this.templateAvailabilityProviders = new TemplateAvailabilityProviders(applicationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
|
||||
Map<String, Object> model) {
|
||||
// for compatibility
|
||||
var errorModel = new HashMap<>(model);
|
||||
|
||||
// resolve with status code. eg: 400.ftl
|
||||
var modelAndView = resolve(String.valueOf(status.value()), errorModel);
|
||||
|
||||
// resolve with status series. eg: 4xx.ftl
|
||||
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
|
||||
modelAndView = resolve(SERIES_VIEWS.get(status.series()), errorModel);
|
||||
}
|
||||
|
||||
// resolve error template. eg: error.ftl
|
||||
if (modelAndView == null) {
|
||||
modelAndView = resolve("error", errorModel);
|
||||
}
|
||||
|
||||
if (modelAndView == null) {
|
||||
// resolve common error template
|
||||
modelAndView = new ModelAndView("common/error/error", errorModel);
|
||||
}
|
||||
|
||||
return modelAndView;
|
||||
}
|
||||
|
||||
private ModelAndView resolve(String viewName, Map<String, Object> model) {
|
||||
var errorViewName = this.themeService.render(viewName);
|
||||
var provider =
|
||||
this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
|
||||
if (provider != null) {
|
||||
return new ModelAndView(errorViewName, model);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
package run.halo.app.core;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJacksonValue;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
||||
import run.halo.app.model.support.BaseResponse;
|
||||
|
||||
/**
|
||||
* Controller advice for comment result.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
@ControllerAdvice("run.halo.app.controller")
|
||||
public class CommonResultControllerAdvice implements ResponseBodyAdvice<Object> {
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter returnType,
|
||||
@NonNull Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
return AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public final Object beforeBodyWrite(@Nullable Object body,
|
||||
@NonNull MethodParameter returnType,
|
||||
@NonNull MediaType contentType,
|
||||
@NonNull Class<? extends HttpMessageConverter<?>> converterType,
|
||||
@NonNull ServerHttpRequest request,
|
||||
@NonNull ServerHttpResponse response) {
|
||||
MappingJacksonValue container = getOrCreateContainer(body);
|
||||
// The contain body will never be null
|
||||
beforeBodyWriteInternal(container, contentType, returnType, request, response);
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the body in a {@link MappingJacksonValue} value container (for providing
|
||||
* additional serialization instructions) or simply cast it if already wrapped.
|
||||
*/
|
||||
private MappingJacksonValue getOrCreateContainer(Object body) {
|
||||
return body instanceof MappingJacksonValue ? (MappingJacksonValue) body :
|
||||
new MappingJacksonValue(body);
|
||||
}
|
||||
|
||||
private void beforeBodyWriteInternal(MappingJacksonValue bodyContainer,
|
||||
MediaType contentType,
|
||||
MethodParameter returnType,
|
||||
ServerHttpRequest request,
|
||||
ServerHttpResponse response) {
|
||||
// Get return body
|
||||
Object returnBody = bodyContainer.getValue();
|
||||
|
||||
if (returnBody instanceof BaseResponse) {
|
||||
// If the return body is instance of BaseResponse, then just do nothing
|
||||
BaseResponse<?> baseResponse = (BaseResponse<?>) returnBody;
|
||||
HttpStatus status = HttpStatus.resolve(baseResponse.getStatus());
|
||||
if (status == null) {
|
||||
status = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
response.setStatusCode(status);
|
||||
return;
|
||||
}
|
||||
|
||||
// get status
|
||||
var status = HttpStatus.OK;
|
||||
if (response instanceof ServletServerHttpResponse) {
|
||||
var servletResponse =
|
||||
((ServletServerHttpResponse) response).getServletResponse();
|
||||
status = HttpStatus.resolve(servletResponse.getStatus());
|
||||
if (status == null) {
|
||||
status = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
}
|
||||
var baseResponse = new BaseResponse<>(status.value(), status.getReasonPhrase(), returnBody);
|
||||
bodyContainer.setValue(baseResponse);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,161 +0,0 @@
|
|||
package run.halo.app.core;
|
||||
|
||||
import java.util.Map;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.multipart.MaxUploadSizeExceededException;
|
||||
import org.springframework.web.servlet.NoHandlerFoundException;
|
||||
import run.halo.app.exception.AbstractHaloException;
|
||||
import run.halo.app.model.support.BaseResponse;
|
||||
import run.halo.app.utils.ExceptionUtils;
|
||||
import run.halo.app.utils.ValidationUtils;
|
||||
|
||||
/**
|
||||
* Exception handler of controller.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
@RestControllerAdvice(value = {"run.halo.app.controller.admin.api",
|
||||
"run.halo.app.controller.content.api"})
|
||||
@Slf4j
|
||||
public class ControllerExceptionHandler {
|
||||
|
||||
@ExceptionHandler(DataIntegrityViolationException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public BaseResponse<?> handleDataIntegrityViolationException(
|
||||
DataIntegrityViolationException e) {
|
||||
BaseResponse<?> baseResponse = handleBaseException(e);
|
||||
if (e.getCause() instanceof org.hibernate.exception.ConstraintViolationException) {
|
||||
baseResponse = handleBaseException(e.getCause());
|
||||
}
|
||||
baseResponse.setMessage("字段验证错误,请完善后重试!");
|
||||
return baseResponse;
|
||||
}
|
||||
|
||||
@ExceptionHandler(MissingServletRequestParameterException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public BaseResponse<?> handleMissingServletRequestParameterException(
|
||||
MissingServletRequestParameterException e) {
|
||||
BaseResponse<?> baseResponse = handleBaseException(e);
|
||||
baseResponse.setMessage(
|
||||
String.format("请求字段缺失, 类型为 %s,名称为 %s", e.getParameterType(), e.getParameterName()));
|
||||
return baseResponse;
|
||||
}
|
||||
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public BaseResponse<?> handleConstraintViolationException(ConstraintViolationException e) {
|
||||
BaseResponse<Map<String, String>> baseResponse = handleBaseException(e);
|
||||
baseResponse.setStatus(HttpStatus.BAD_REQUEST.value());
|
||||
baseResponse.setMessage("字段验证错误,请完善后重试!");
|
||||
baseResponse.setData(ValidationUtils.mapWithValidError(e.getConstraintViolations()));
|
||||
return baseResponse;
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public BaseResponse<?> handleMethodArgumentNotValidException(
|
||||
MethodArgumentNotValidException e) {
|
||||
BaseResponse<Map<String, String>> baseResponse = handleBaseException(e);
|
||||
baseResponse.setStatus(HttpStatus.BAD_REQUEST.value());
|
||||
baseResponse.setMessage("字段验证错误,请完善后重试!");
|
||||
Map<String, String> errMap =
|
||||
ValidationUtils.mapWithFieldError(e.getBindingResult().getFieldErrors());
|
||||
baseResponse.setData(errMap);
|
||||
return baseResponse;
|
||||
}
|
||||
|
||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public BaseResponse<?> handleHttpRequestMethodNotSupportedException(
|
||||
HttpRequestMethodNotSupportedException e) {
|
||||
BaseResponse<?> baseResponse = handleBaseException(e);
|
||||
baseResponse.setStatus(HttpStatus.BAD_REQUEST.value());
|
||||
return baseResponse;
|
||||
}
|
||||
|
||||
@ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
|
||||
@ResponseStatus(HttpStatus.NOT_ACCEPTABLE)
|
||||
public BaseResponse<?> handleHttpMediaTypeNotAcceptableException(
|
||||
HttpMediaTypeNotAcceptableException e) {
|
||||
BaseResponse<?> baseResponse = handleBaseException(e);
|
||||
baseResponse.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
|
||||
return baseResponse;
|
||||
}
|
||||
|
||||
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public BaseResponse<?> handleHttpMessageNotReadableException(
|
||||
HttpMessageNotReadableException e) {
|
||||
BaseResponse<?> baseResponse = handleBaseException(e);
|
||||
baseResponse.setStatus(HttpStatus.BAD_REQUEST.value());
|
||||
baseResponse.setMessage("缺失请求主体");
|
||||
return baseResponse;
|
||||
}
|
||||
|
||||
@ExceptionHandler(NoHandlerFoundException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_GATEWAY)
|
||||
public BaseResponse<?> handleNoHandlerFoundException(NoHandlerFoundException e) {
|
||||
BaseResponse<?> baseResponse = handleBaseException(e);
|
||||
HttpStatus status = HttpStatus.BAD_GATEWAY;
|
||||
baseResponse.setStatus(status.value());
|
||||
baseResponse.setMessage(status.getReasonPhrase());
|
||||
return baseResponse;
|
||||
}
|
||||
|
||||
@ExceptionHandler(MaxUploadSizeExceededException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public BaseResponse<?> handleUploadSizeExceededException(MaxUploadSizeExceededException e) {
|
||||
BaseResponse<Object> response = handleBaseException(e);
|
||||
response.setStatus(HttpStatus.BAD_REQUEST.value());
|
||||
response.setMessage("当前请求超出最大限制:" + e.getMaxUploadSize() + " bytes");
|
||||
return response;
|
||||
}
|
||||
|
||||
@ExceptionHandler(AbstractHaloException.class)
|
||||
public ResponseEntity<BaseResponse<?>> handleHaloException(AbstractHaloException e) {
|
||||
BaseResponse<Object> baseResponse = handleBaseException(e);
|
||||
baseResponse.setStatus(e.getStatus().value());
|
||||
baseResponse.setData(e.getErrorData());
|
||||
return new ResponseEntity<>(baseResponse, e.getStatus());
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public BaseResponse<?> handleGlobalException(Exception e) {
|
||||
BaseResponse<?> baseResponse = handleBaseException(e);
|
||||
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
baseResponse.setStatus(status.value());
|
||||
baseResponse.setMessage(status.getReasonPhrase());
|
||||
return baseResponse;
|
||||
}
|
||||
|
||||
private <T> BaseResponse<T> handleBaseException(Throwable t) {
|
||||
Assert.notNull(t, "Throwable must not be null");
|
||||
|
||||
BaseResponse<T> baseResponse = new BaseResponse<>();
|
||||
baseResponse.setMessage(t.getMessage());
|
||||
|
||||
log.error("Captured an exception:", t);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
baseResponse.setDevMessage(ExceptionUtils.getStackTrace(t));
|
||||
}
|
||||
|
||||
return baseResponse;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
package run.halo.app.core;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Objects;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StopWatch;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import run.halo.app.utils.JsonUtils;
|
||||
import run.halo.app.utils.ServletUtils;
|
||||
|
||||
/**
|
||||
* @author johnniang
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
@Slf4j
|
||||
public class ControllerLogAop {
|
||||
|
||||
@Pointcut("@within(org.springframework.web.bind.annotation.RestController)")
|
||||
public void restController() {
|
||||
}
|
||||
|
||||
@Pointcut("@within(org.springframework.stereotype.Controller)")
|
||||
public void controller() {
|
||||
}
|
||||
|
||||
@Around("controller() || restController()")
|
||||
public Object controller(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
final Method method = signature.getMethod();
|
||||
if (method == null || !log.isDebugEnabled()) {
|
||||
// should never happen
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
|
||||
String className = joinPoint.getTarget().getClass().getSimpleName();
|
||||
String methodName = joinPoint.getSignature().getName();
|
||||
Object[] args = joinPoint.getArgs();
|
||||
|
||||
// Get request attribute
|
||||
ServletRequestAttributes requestAttributes =
|
||||
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
HttpServletRequest request = Objects.requireNonNull(requestAttributes).getRequest();
|
||||
|
||||
final StopWatch watch = new StopWatch(request.getRequestURI());
|
||||
|
||||
watch.start("PrintRequest");
|
||||
printRequestLog(request, className, methodName, args);
|
||||
watch.stop();
|
||||
|
||||
watch.start(className + "#" + methodName);
|
||||
final Object returnObj = joinPoint.proceed();
|
||||
watch.stop();
|
||||
|
||||
watch.start("PrintResponse");
|
||||
printResponseLog(request, className, methodName, returnObj);
|
||||
watch.stop();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Usage:\n{}", watch.prettyPrint());
|
||||
}
|
||||
return returnObj;
|
||||
}
|
||||
|
||||
private void printRequestLog(HttpServletRequest request, String clazzName, String methodName,
|
||||
Object[] args) throws JsonProcessingException {
|
||||
log.debug("Request URL: [{}], URI: [{}], Request Method: [{}], IP: [{}]",
|
||||
request.getRequestURL(),
|
||||
request.getRequestURI(),
|
||||
request.getMethod(),
|
||||
ServletUtils.getClientIP(request));
|
||||
|
||||
if (args == null || !log.isDebugEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean shouldNotLog = false;
|
||||
for (Object arg : args) {
|
||||
if (arg == null
|
||||
|| arg instanceof HttpServletRequest
|
||||
|| arg instanceof HttpServletResponse
|
||||
|| arg instanceof MultipartFile
|
||||
|| arg.getClass().isAssignableFrom(MultipartFile[].class)) {
|
||||
shouldNotLog = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldNotLog) {
|
||||
String requestBody = JsonUtils.objectToJson(args);
|
||||
log.debug("{}.{} Parameters: [{}]", clazzName, methodName, requestBody);
|
||||
}
|
||||
}
|
||||
|
||||
private void printResponseLog(HttpServletRequest request, String className, String methodName,
|
||||
Object returnObj)
|
||||
throws JsonProcessingException {
|
||||
if (log.isDebugEnabled()) {
|
||||
String returnData = "";
|
||||
if (returnObj != null) {
|
||||
if (returnObj instanceof ResponseEntity) {
|
||||
ResponseEntity<?> responseEntity = (ResponseEntity<?>) returnObj;
|
||||
if (responseEntity.getBody() instanceof Resource) {
|
||||
returnData = "[ BINARY DATA ]";
|
||||
} else if (responseEntity.getBody() != null) {
|
||||
returnData = toString(responseEntity.getBody());
|
||||
}
|
||||
} else {
|
||||
returnData = toString(returnObj);
|
||||
}
|
||||
|
||||
}
|
||||
log.debug("{}.{} Response: [{}]", className, methodName, returnData);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String toString(@NonNull Object obj) throws JsonProcessingException {
|
||||
Assert.notNull(obj, "Return object must not be null");
|
||||
|
||||
String toString;
|
||||
if (obj.getClass().isAssignableFrom(byte[].class) && obj instanceof Resource) {
|
||||
toString = "[ BINARY DATA ]";
|
||||
} else {
|
||||
toString = JsonUtils.objectToJson(obj);
|
||||
}
|
||||
return toString;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
package run.halo.app.core;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import java.io.IOException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import run.halo.app.model.support.CommentPage;
|
||||
|
||||
/**
|
||||
* Custom serializer for Page object.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 3/19/19
|
||||
*/
|
||||
public class PageJacksonSerializer extends JsonSerializer<Page> {
|
||||
|
||||
@Override
|
||||
public void serialize(Page page, JsonGenerator generator, SerializerProvider serializers)
|
||||
throws IOException {
|
||||
generator.writeStartObject();
|
||||
|
||||
generator.writeObjectField("content", page.getContent());
|
||||
generator.writeNumberField("pages", page.getTotalPages());
|
||||
generator.writeNumberField("total", page.getTotalElements());
|
||||
generator.writeNumberField("page", page.getNumber());
|
||||
generator.writeNumberField("rpp", page.getSize());
|
||||
generator.writeBooleanField("hasNext", page.hasNext());
|
||||
generator.writeBooleanField("hasPrevious", page.hasPrevious());
|
||||
generator.writeBooleanField("isFirst", page.isFirst());
|
||||
generator.writeBooleanField("isLast", page.isLast());
|
||||
generator.writeBooleanField("isEmpty", page.isEmpty());
|
||||
generator.writeBooleanField("hasContent", page.hasContent());
|
||||
|
||||
// Handle comment page
|
||||
if (page instanceof CommentPage) {
|
||||
CommentPage commentPage = (CommentPage) page;
|
||||
generator.writeNumberField("commentCount", commentPage.getCommentCount());
|
||||
}
|
||||
|
||||
generator.writeEndObject();
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package run.halo.app.core.freemarker.inheritance;
|
||||
|
||||
import freemarker.core.Environment;
|
||||
import freemarker.template.SimpleScalar;
|
||||
import freemarker.template.TemplateDirectiveBody;
|
||||
import freemarker.template.TemplateException;
|
||||
import freemarker.template.TemplateModel;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import kr.pe.kwonnam.freemarker.inheritance.ExtendsDirective;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author LIlGG
|
||||
* @date 2021/3/4
|
||||
*/
|
||||
@Component
|
||||
public class ThemeExtendsDirective extends ExtendsDirective {
|
||||
private static final Pattern THEME_TEMPLATE_PATH_PATTERN = Pattern.compile("^themes/.*?/");
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void execute(Environment env, Map params, TemplateModel[] loopVars,
|
||||
TemplateDirectiveBody body) throws TemplateException, IOException {
|
||||
String currTemplateName = getTemplateRelativePath(env);
|
||||
String name = ((SimpleScalar) params.get("name")).getAsString();
|
||||
|
||||
String includeTemplateName = env.rootBasedToAbsoluteTemplateName(
|
||||
env.toFullTemplateName(currTemplateName, name)
|
||||
);
|
||||
params.put("name", new SimpleScalar(includeTemplateName));
|
||||
|
||||
super.execute(env, params, loopVars, body);
|
||||
}
|
||||
|
||||
private String getTemplateRelativePath(Environment env) {
|
||||
String templateName = env.getCurrentTemplate().getName();
|
||||
|
||||
Matcher matcher = THEME_TEMPLATE_PATH_PATTERN.matcher(templateName);
|
||||
return matcher.find()
|
||||
? matcher.group()
|
||||
: "";
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
package run.halo.app.core.freemarker.method;
|
||||
|
||||
import freemarker.template.Configuration;
|
||||
import freemarker.template.SimpleNumber;
|
||||
import freemarker.template.TemplateMethodModelEx;
|
||||
import freemarker.template.TemplateModelException;
|
||||
import java.util.List;
|
||||
import org.apache.commons.lang3.RandomUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.utils.HaloUtils;
|
||||
|
||||
/**
|
||||
* Freemarker template random method.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2018-12-21
|
||||
*/
|
||||
@Component
|
||||
public class RandomMethod implements TemplateMethodModelEx {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param configuration injected by spring.
|
||||
*/
|
||||
public RandomMethod(Configuration configuration) {
|
||||
configuration.setSharedVariable("randomMethod", this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机数
|
||||
*
|
||||
* @param arguments 参数
|
||||
* @return Object
|
||||
* @throws TemplateModelException TemplateModelException
|
||||
*/
|
||||
@Override
|
||||
public Object exec(List arguments) throws TemplateModelException {
|
||||
if (arguments.size() != 2) {
|
||||
throw new TemplateModelException("Wrong arguments! 2 arguments are needed");
|
||||
}
|
||||
SimpleNumber argOne = (SimpleNumber) arguments.get(0);
|
||||
SimpleNumber argTwo = (SimpleNumber) arguments.get(1);
|
||||
int start = argOne.getAsNumber().intValue();
|
||||
int end = argTwo.getAsNumber().intValue();
|
||||
return RandomUtils.nextInt(start, end);
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
package run.halo.app.core.freemarker.tag;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.ASC;
|
||||
|
||||
import freemarker.core.Environment;
|
||||
import freemarker.template.Configuration;
|
||||
import freemarker.template.DefaultObjectWrapperBuilder;
|
||||
import freemarker.template.TemplateDirectiveBody;
|
||||
import freemarker.template.TemplateDirectiveModel;
|
||||
import freemarker.template.TemplateException;
|
||||
import freemarker.template.TemplateModel;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.support.HaloConst;
|
||||
import run.halo.app.service.CategoryService;
|
||||
import run.halo.app.service.PostCategoryService;
|
||||
|
||||
/**
|
||||
* Freemarker custom tag of category.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-03-22
|
||||
*/
|
||||
@Component
|
||||
public class CategoryTagDirective implements TemplateDirectiveModel {
|
||||
|
||||
private final CategoryService categoryService;
|
||||
|
||||
private final PostCategoryService postCategoryService;
|
||||
|
||||
public CategoryTagDirective(Configuration configuration,
|
||||
CategoryService categoryService,
|
||||
PostCategoryService postCategoryService) {
|
||||
this.categoryService = categoryService;
|
||||
this.postCategoryService = postCategoryService;
|
||||
configuration.setSharedVariable("categoryTag", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Environment env, Map params, TemplateModel[] loopVars,
|
||||
TemplateDirectiveBody body) throws TemplateException, IOException {
|
||||
final DefaultObjectWrapperBuilder builder =
|
||||
new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_25);
|
||||
|
||||
if (params.containsKey(HaloConst.METHOD_KEY)) {
|
||||
String method = params.get(HaloConst.METHOD_KEY).toString();
|
||||
switch (method) {
|
||||
case "list":
|
||||
env.setVariable("categories", builder.build().wrap(postCategoryService
|
||||
.listCategoryWithPostCountDto(Sort.by(ASC, "priority"))));
|
||||
break;
|
||||
case "tree":
|
||||
env.setVariable("categories", builder.build()
|
||||
.wrap(categoryService.listAsTree(Sort.by(ASC, "priority"))));
|
||||
break;
|
||||
case "listByPostId":
|
||||
Integer postId = Integer.parseInt(params.get("postId").toString());
|
||||
List<Category> categories = postCategoryService.listCategoriesBy(postId);
|
||||
env.setVariable("categories",
|
||||
builder.build().wrap(categoryService.convertTo(categories)));
|
||||
break;
|
||||
case "count":
|
||||
env.setVariable("count", builder.build().wrap(categoryService.count()));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
body.render(env.getOut());
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
package run.halo.app.core.freemarker.tag;
|
||||
|
||||
import freemarker.core.Environment;
|
||||
import freemarker.template.Configuration;
|
||||
import freemarker.template.DefaultObjectWrapperBuilder;
|
||||
import freemarker.template.TemplateDirectiveBody;
|
||||
import freemarker.template.TemplateDirectiveModel;
|
||||
import freemarker.template.TemplateException;
|
||||
import freemarker.template.TemplateModel;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.model.entity.PostComment;
|
||||
import run.halo.app.model.enums.CommentStatus;
|
||||
import run.halo.app.model.support.HaloConst;
|
||||
import run.halo.app.service.PostCommentService;
|
||||
|
||||
/**
|
||||
* Freemarker custom tag of comment.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-03-22
|
||||
*/
|
||||
@Component
|
||||
public class CommentTagDirective implements TemplateDirectiveModel {
|
||||
|
||||
private final PostCommentService postCommentService;
|
||||
|
||||
public CommentTagDirective(Configuration configuration, PostCommentService postCommentService) {
|
||||
this.postCommentService = postCommentService;
|
||||
configuration.setSharedVariable("commentTag", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Environment env, Map params, TemplateModel[] loopVars,
|
||||
TemplateDirectiveBody body) throws TemplateException, IOException {
|
||||
final DefaultObjectWrapperBuilder builder =
|
||||
new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_25);
|
||||
|
||||
if (params.containsKey(HaloConst.METHOD_KEY)) {
|
||||
String method = params.get(HaloConst.METHOD_KEY).toString();
|
||||
switch (method) {
|
||||
case "latest":
|
||||
int top = Integer.parseInt(params.get("top").toString());
|
||||
Page<PostComment> postComments =
|
||||
postCommentService.pageLatest(top, CommentStatus.PUBLISHED);
|
||||
env.setVariable("comments",
|
||||
builder.build().wrap(postCommentService.convertToWithPostVo(postComments)));
|
||||
break;
|
||||
case "count":
|
||||
env.setVariable("count", builder.build().wrap(postCommentService.count()));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
body.render(env.getOut());
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
package run.halo.app.core.freemarker.tag;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import freemarker.core.Environment;
|
||||
import freemarker.template.Configuration;
|
||||
import freemarker.template.DefaultObjectWrapperBuilder;
|
||||
import freemarker.template.TemplateDirectiveBody;
|
||||
import freemarker.template.TemplateDirectiveModel;
|
||||
import freemarker.template.TemplateException;
|
||||
import freemarker.template.TemplateModel;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.model.support.HaloConst;
|
||||
import run.halo.app.service.LinkService;
|
||||
|
||||
/**
|
||||
* Freemarker custom tag of link.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-03-22
|
||||
*/
|
||||
@Component
|
||||
public class LinkTagDirective implements TemplateDirectiveModel {
|
||||
|
||||
private final LinkService linkService;
|
||||
|
||||
public LinkTagDirective(Configuration configuration, LinkService linkService) {
|
||||
this.linkService = linkService;
|
||||
configuration.setSharedVariable("linkTag", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Environment env, Map params, TemplateModel[] loopVars,
|
||||
TemplateDirectiveBody body) throws TemplateException, IOException {
|
||||
final DefaultObjectWrapperBuilder builder =
|
||||
new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_25);
|
||||
|
||||
if (params.containsKey(HaloConst.METHOD_KEY)) {
|
||||
String method = params.get(HaloConst.METHOD_KEY).toString();
|
||||
switch (method) {
|
||||
case "list":
|
||||
env.setVariable("links", builder.build().wrap(linkService.listAll()));
|
||||
break;
|
||||
case "listByRandom":
|
||||
env.setVariable("links", builder.build().wrap(linkService.listAllByRandom()));
|
||||
break;
|
||||
case "listTeams":
|
||||
env.setVariable("teams",
|
||||
builder.build().wrap(linkService.listTeamVos(Sort.by(DESC, "createTime"))));
|
||||
break;
|
||||
case "listTeamsByRandom":
|
||||
env.setVariable("teams", builder.build()
|
||||
.wrap(linkService.listTeamVosByRandom(Sort.by(DESC, "createTime"))));
|
||||
break;
|
||||
case "count":
|
||||
env.setVariable("count", builder.build().wrap(linkService.count()));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
body.render(env.getOut());
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
package run.halo.app.core.freemarker.tag;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import freemarker.core.Environment;
|
||||
import freemarker.template.Configuration;
|
||||
import freemarker.template.DefaultObjectWrapperBuilder;
|
||||
import freemarker.template.TemplateDirectiveBody;
|
||||
import freemarker.template.TemplateDirectiveModel;
|
||||
import freemarker.template.TemplateException;
|
||||
import freemarker.template.TemplateModel;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.model.properties.PrimaryProperties;
|
||||
import run.halo.app.model.support.HaloConst;
|
||||
import run.halo.app.service.MenuService;
|
||||
import run.halo.app.service.OptionService;
|
||||
|
||||
/**
|
||||
* Freemarker custom tag of menu.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-03-22
|
||||
*/
|
||||
@Component
|
||||
public class MenuTagDirective implements TemplateDirectiveModel {
|
||||
|
||||
private static final String METHOD_KEY = "method";
|
||||
|
||||
private final MenuService menuService;
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
public MenuTagDirective(Configuration configuration, MenuService menuService,
|
||||
OptionService optionService) {
|
||||
this.menuService = menuService;
|
||||
this.optionService = optionService;
|
||||
configuration.setSharedVariable("menuTag", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Environment env, Map params, TemplateModel[] loopVars,
|
||||
TemplateDirectiveBody body) throws TemplateException, IOException {
|
||||
final DefaultObjectWrapperBuilder builder =
|
||||
new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_25);
|
||||
|
||||
if (params.containsKey(HaloConst.METHOD_KEY)) {
|
||||
String method = params.get(HaloConst.METHOD_KEY).toString();
|
||||
switch (method) {
|
||||
case "list":
|
||||
String listTeam = optionService
|
||||
.getByPropertyOrDefault(PrimaryProperties.DEFAULT_MENU_TEAM, String.class,
|
||||
"");
|
||||
env.setVariable("menus", builder.build()
|
||||
.wrap(menuService.listByTeam(listTeam, Sort.by(DESC, "priority"))));
|
||||
break;
|
||||
case "tree":
|
||||
String treeTeam = optionService
|
||||
.getByPropertyOrDefault(PrimaryProperties.DEFAULT_MENU_TEAM, String.class,
|
||||
"");
|
||||
env.setVariable("menus", builder.build()
|
||||
.wrap(menuService.listByTeamAsTree(treeTeam, Sort.by(DESC, "priority"))));
|
||||
break;
|
||||
case "listTeams":
|
||||
env.setVariable("teams",
|
||||
builder.build().wrap(menuService.listTeamVos(Sort.by(DESC, "priority"))));
|
||||
break;
|
||||
case "listByTeam":
|
||||
String team = params.get("team").toString();
|
||||
env.setVariable("menus", builder.build()
|
||||
.wrap(menuService.listByTeam(team, Sort.by(DESC, "priority"))));
|
||||
break;
|
||||
case "treeByTeam":
|
||||
String treeTeamParam = params.get("team").toString();
|
||||
env.setVariable("menus", builder.build().wrap(
|
||||
menuService.listByTeamAsTree(treeTeamParam, Sort.by(DESC, "priority"))));
|
||||
break;
|
||||
case "count":
|
||||
env.setVariable("count", builder.build().wrap(menuService.count()));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
body.render(env.getOut());
|
||||
}
|
||||
}
|
|
@ -1,325 +0,0 @@
|
|||
package run.halo.app.core.freemarker.tag;
|
||||
|
||||
import static run.halo.app.model.support.HaloConst.URL_SEPARATOR;
|
||||
|
||||
import freemarker.core.Environment;
|
||||
import freemarker.template.Configuration;
|
||||
import freemarker.template.DefaultObjectWrapperBuilder;
|
||||
import freemarker.template.TemplateDirectiveBody;
|
||||
import freemarker.template.TemplateDirectiveModel;
|
||||
import freemarker.template.TemplateException;
|
||||
import freemarker.template.TemplateModel;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.model.support.HaloConst;
|
||||
import run.halo.app.model.support.Pagination;
|
||||
import run.halo.app.model.support.RainbowPage;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.utils.HaloUtils;
|
||||
|
||||
/**
|
||||
* @author ryanwang
|
||||
* @date 2020-03-07
|
||||
*/
|
||||
@Component
|
||||
public class PaginationTagDirective implements TemplateDirectiveModel {
|
||||
|
||||
private final OptionService optionService;
|
||||
|
||||
public PaginationTagDirective(Configuration configuration,
|
||||
OptionService optionService) {
|
||||
this.optionService = optionService;
|
||||
configuration.setSharedVariable("paginationTag", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Environment env, Map params, TemplateModel[] loopVars,
|
||||
TemplateDirectiveBody body) throws TemplateException, IOException {
|
||||
final DefaultObjectWrapperBuilder builder =
|
||||
new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_25);
|
||||
if (params.containsKey(HaloConst.METHOD_KEY)) {
|
||||
|
||||
// Get params
|
||||
String method = params.get(HaloConst.METHOD_KEY).toString();
|
||||
int page = Integer.parseInt(params.get("page").toString());
|
||||
int total = Integer.parseInt(params.get("total").toString());
|
||||
int display = Integer.parseInt(params.get("display").toString());
|
||||
|
||||
// Create pagination object.
|
||||
Pagination pagination = new Pagination();
|
||||
|
||||
// Generate next page full path and pre page full path.
|
||||
StringBuilder nextPageFullPath = new StringBuilder();
|
||||
StringBuilder prevPageFullPath = new StringBuilder();
|
||||
|
||||
if (optionService.isEnabledAbsolutePath()) {
|
||||
nextPageFullPath.append(optionService.getBlogBaseUrl());
|
||||
prevPageFullPath.append(optionService.getBlogBaseUrl());
|
||||
}
|
||||
|
||||
int[] rainbow = HaloUtils.rainbow(page + 1, total, display);
|
||||
|
||||
List<RainbowPage> rainbowPages = new ArrayList<>();
|
||||
StringBuilder fullPath = new StringBuilder();
|
||||
|
||||
if (optionService.isEnabledAbsolutePath()) {
|
||||
fullPath.append(optionService.getBlogBaseUrl());
|
||||
}
|
||||
|
||||
String pathSuffix = optionService.getPathSuffix();
|
||||
|
||||
switch (method) {
|
||||
case "index":
|
||||
|
||||
nextPageFullPath.append("/page/")
|
||||
.append(page + 2)
|
||||
.append(pathSuffix);
|
||||
|
||||
if (page == 1) {
|
||||
prevPageFullPath.append(URL_SEPARATOR);
|
||||
} else {
|
||||
prevPageFullPath.append("/page/")
|
||||
.append(page)
|
||||
.append(pathSuffix);
|
||||
}
|
||||
|
||||
fullPath.append("/page/");
|
||||
|
||||
for (int current : rainbow) {
|
||||
RainbowPage rainbowPage = new RainbowPage();
|
||||
rainbowPage.setPage(current);
|
||||
rainbowPage.setFullPath(fullPath.toString() + current + pathSuffix);
|
||||
rainbowPage.setIsCurrent(page + 1 == current);
|
||||
rainbowPages.add(rainbowPage);
|
||||
}
|
||||
break;
|
||||
case "archives":
|
||||
|
||||
nextPageFullPath.append(URL_SEPARATOR)
|
||||
.append(optionService.getArchivesPrefix());
|
||||
prevPageFullPath.append(URL_SEPARATOR)
|
||||
.append(optionService.getArchivesPrefix());
|
||||
|
||||
nextPageFullPath.append("/page/")
|
||||
.append(page + 2)
|
||||
.append(pathSuffix);
|
||||
|
||||
if (page == 1) {
|
||||
prevPageFullPath.append(pathSuffix);
|
||||
} else {
|
||||
prevPageFullPath.append("/page/")
|
||||
.append(page)
|
||||
.append(pathSuffix);
|
||||
}
|
||||
|
||||
fullPath.append(URL_SEPARATOR)
|
||||
.append(optionService.getArchivesPrefix());
|
||||
|
||||
fullPath.append("/page/");
|
||||
|
||||
for (int current : rainbow) {
|
||||
RainbowPage rainbowPage = new RainbowPage();
|
||||
rainbowPage.setPage(current);
|
||||
rainbowPage.setFullPath(fullPath.toString() + current + pathSuffix);
|
||||
rainbowPage.setIsCurrent(page + 1 == current);
|
||||
rainbowPages.add(rainbowPage);
|
||||
}
|
||||
break;
|
||||
case "search":
|
||||
String keyword = params.get("keyword").toString();
|
||||
|
||||
nextPageFullPath.append(URL_SEPARATOR)
|
||||
.append("search");
|
||||
prevPageFullPath.append(URL_SEPARATOR)
|
||||
.append("search");
|
||||
|
||||
nextPageFullPath.append("/page/")
|
||||
.append(page + 2)
|
||||
.append(pathSuffix)
|
||||
.append("?keyword=")
|
||||
.append(keyword);
|
||||
|
||||
if (page == 1) {
|
||||
prevPageFullPath.append(pathSuffix)
|
||||
.append("?keyword=")
|
||||
.append(keyword);
|
||||
} else {
|
||||
prevPageFullPath.append("/page/")
|
||||
.append(page)
|
||||
.append(pathSuffix)
|
||||
.append("?keyword=")
|
||||
.append(keyword);
|
||||
}
|
||||
|
||||
fullPath.append(URL_SEPARATOR)
|
||||
.append("search");
|
||||
|
||||
fullPath.append("/page/");
|
||||
|
||||
for (int current : rainbow) {
|
||||
RainbowPage rainbowPage = new RainbowPage();
|
||||
rainbowPage.setPage(current);
|
||||
rainbowPage.setFullPath(
|
||||
fullPath.toString() + current + pathSuffix + "?keyword=" + keyword);
|
||||
rainbowPage.setIsCurrent(page + 1 == current);
|
||||
rainbowPages.add(rainbowPage);
|
||||
}
|
||||
break;
|
||||
case "tagPosts":
|
||||
String tagSlug = params.get("slug").toString();
|
||||
|
||||
nextPageFullPath.append(URL_SEPARATOR)
|
||||
.append(optionService.getTagsPrefix())
|
||||
.append(URL_SEPARATOR)
|
||||
.append(tagSlug);
|
||||
prevPageFullPath.append(URL_SEPARATOR)
|
||||
.append(optionService.getTagsPrefix())
|
||||
.append(URL_SEPARATOR)
|
||||
.append(tagSlug);
|
||||
|
||||
nextPageFullPath.append("/page/")
|
||||
.append(page + 2)
|
||||
.append(pathSuffix);
|
||||
|
||||
if (page == 1) {
|
||||
prevPageFullPath.append(pathSuffix);
|
||||
} else {
|
||||
prevPageFullPath.append("/page/")
|
||||
.append(page)
|
||||
.append(pathSuffix);
|
||||
}
|
||||
|
||||
fullPath.append(URL_SEPARATOR)
|
||||
.append(optionService.getTagsPrefix())
|
||||
.append(URL_SEPARATOR)
|
||||
.append(tagSlug);
|
||||
|
||||
fullPath.append("/page/");
|
||||
|
||||
for (int current : rainbow) {
|
||||
RainbowPage rainbowPage = new RainbowPage();
|
||||
rainbowPage.setPage(current);
|
||||
rainbowPage.setFullPath(fullPath.toString() + current + pathSuffix);
|
||||
rainbowPage.setIsCurrent(page + 1 == current);
|
||||
rainbowPages.add(rainbowPage);
|
||||
}
|
||||
break;
|
||||
case "categoryPosts":
|
||||
String categorySlug = params.get("slug").toString();
|
||||
|
||||
nextPageFullPath.append(URL_SEPARATOR)
|
||||
.append(optionService.getCategoriesPrefix())
|
||||
.append(URL_SEPARATOR)
|
||||
.append(categorySlug);
|
||||
prevPageFullPath.append(URL_SEPARATOR)
|
||||
.append(optionService.getCategoriesPrefix())
|
||||
.append(URL_SEPARATOR)
|
||||
.append(categorySlug);
|
||||
|
||||
nextPageFullPath.append("/page/")
|
||||
.append(page + 2)
|
||||
.append(pathSuffix);
|
||||
|
||||
if (page == 1) {
|
||||
prevPageFullPath.append(pathSuffix);
|
||||
} else {
|
||||
prevPageFullPath.append("/page/")
|
||||
.append(page)
|
||||
.append(pathSuffix);
|
||||
}
|
||||
|
||||
fullPath.append(URL_SEPARATOR)
|
||||
.append(optionService.getCategoriesPrefix())
|
||||
.append(URL_SEPARATOR)
|
||||
.append(categorySlug);
|
||||
|
||||
fullPath.append("/page/");
|
||||
|
||||
for (int current : rainbow) {
|
||||
RainbowPage rainbowPage = new RainbowPage();
|
||||
rainbowPage.setPage(current);
|
||||
rainbowPage.setFullPath(fullPath.toString() + current + pathSuffix);
|
||||
rainbowPage.setIsCurrent(page + 1 == current);
|
||||
rainbowPages.add(rainbowPage);
|
||||
}
|
||||
break;
|
||||
case "photos":
|
||||
|
||||
nextPageFullPath.append(URL_SEPARATOR)
|
||||
.append(optionService.getPhotosPrefix());
|
||||
prevPageFullPath.append(URL_SEPARATOR)
|
||||
.append(optionService.getPhotosPrefix());
|
||||
|
||||
nextPageFullPath.append("/page/")
|
||||
.append(page + 2)
|
||||
.append(pathSuffix);
|
||||
|
||||
if (page == 1) {
|
||||
prevPageFullPath.append(pathSuffix);
|
||||
} else {
|
||||
prevPageFullPath.append("/page/")
|
||||
.append(page)
|
||||
.append(pathSuffix);
|
||||
}
|
||||
|
||||
fullPath.append(URL_SEPARATOR)
|
||||
.append(optionService.getPhotosPrefix());
|
||||
|
||||
fullPath.append("/page/");
|
||||
|
||||
for (int current : rainbow) {
|
||||
RainbowPage rainbowPage = new RainbowPage();
|
||||
rainbowPage.setPage(current);
|
||||
rainbowPage.setFullPath(fullPath.toString() + current + pathSuffix);
|
||||
rainbowPage.setIsCurrent(page + 1 == current);
|
||||
rainbowPages.add(rainbowPage);
|
||||
}
|
||||
break;
|
||||
case "journals":
|
||||
|
||||
nextPageFullPath.append(URL_SEPARATOR)
|
||||
.append(optionService.getJournalsPrefix());
|
||||
prevPageFullPath.append(URL_SEPARATOR)
|
||||
.append(optionService.getJournalsPrefix());
|
||||
|
||||
nextPageFullPath.append("/page/")
|
||||
.append(page + 2)
|
||||
.append(pathSuffix);
|
||||
|
||||
if (page == 1) {
|
||||
prevPageFullPath.append(pathSuffix);
|
||||
} else {
|
||||
prevPageFullPath.append("/page/")
|
||||
.append(page)
|
||||
.append(pathSuffix);
|
||||
}
|
||||
|
||||
fullPath.append(URL_SEPARATOR)
|
||||
.append(optionService.getJournalsPrefix());
|
||||
|
||||
fullPath.append("/page/");
|
||||
|
||||
for (int current : rainbow) {
|
||||
RainbowPage rainbowPage = new RainbowPage();
|
||||
rainbowPage.setPage(current);
|
||||
rainbowPage.setFullPath(fullPath.toString() + current + pathSuffix);
|
||||
rainbowPage.setIsCurrent(page + 1 == current);
|
||||
rainbowPages.add(rainbowPage);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
pagination.setNextPageFullPath(nextPageFullPath.toString());
|
||||
pagination.setPrevPageFullPath(prevPageFullPath.toString());
|
||||
pagination.setRainbowPages(rainbowPages);
|
||||
pagination.setHasNext(total != page + 1);
|
||||
pagination.setHasPrev(page != 0);
|
||||
env.setVariable("pagination", builder.build().wrap(pagination));
|
||||
}
|
||||
body.render(env.getOut());
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
package run.halo.app.core.freemarker.tag;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import freemarker.core.Environment;
|
||||
import freemarker.template.Configuration;
|
||||
import freemarker.template.DefaultObjectWrapperBuilder;
|
||||
import freemarker.template.TemplateDirectiveBody;
|
||||
import freemarker.template.TemplateDirectiveModel;
|
||||
import freemarker.template.TemplateException;
|
||||
import freemarker.template.TemplateModel;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.model.support.HaloConst;
|
||||
import run.halo.app.service.PhotoService;
|
||||
|
||||
/**
|
||||
* Freemarker custom tag of photo.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-04-21
|
||||
*/
|
||||
@Component
|
||||
public class PhotoTagDirective implements TemplateDirectiveModel {
|
||||
|
||||
private final PhotoService photoService;
|
||||
|
||||
public PhotoTagDirective(Configuration configuration, PhotoService photoService) {
|
||||
this.photoService = photoService;
|
||||
configuration.setSharedVariable("photoTag", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Environment env, Map params, TemplateModel[] loopVars,
|
||||
TemplateDirectiveBody body) throws TemplateException, IOException {
|
||||
final DefaultObjectWrapperBuilder builder =
|
||||
new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_25);
|
||||
|
||||
if (params.containsKey(HaloConst.METHOD_KEY)) {
|
||||
String method = params.get(HaloConst.METHOD_KEY).toString();
|
||||
switch (method) {
|
||||
case "list":
|
||||
env.setVariable("photos", builder.build().wrap(photoService.listAll()));
|
||||
break;
|
||||
case "listTeams":
|
||||
env.setVariable("teams", builder.build()
|
||||
.wrap(photoService.listTeamVos(Sort.by(DESC, "createTime"))));
|
||||
break;
|
||||
case "listByTeam":
|
||||
String team = params.get("team").toString();
|
||||
env.setVariable("photos", builder.build()
|
||||
.wrap(photoService.listByTeam(team, Sort.by(DESC, "createTime"))));
|
||||
break;
|
||||
case "count":
|
||||
env.setVariable("count", builder.build().wrap(photoService.count()));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
body.render(env.getOut());
|
||||
}
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
package run.halo.app.core.freemarker.tag;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import freemarker.core.Environment;
|
||||
import freemarker.template.Configuration;
|
||||
import freemarker.template.DefaultObjectWrapperBuilder;
|
||||
import freemarker.template.TemplateDirectiveBody;
|
||||
import freemarker.template.TemplateDirectiveModel;
|
||||
import freemarker.template.TemplateException;
|
||||
import freemarker.template.TemplateModel;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.support.HaloConst;
|
||||
import run.halo.app.service.PostCategoryService;
|
||||
import run.halo.app.service.PostService;
|
||||
import run.halo.app.service.PostTagService;
|
||||
import run.halo.app.service.assembler.PostRenderAssembler;
|
||||
|
||||
/**
|
||||
* Freemarker custom tag of post.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2018-04-26
|
||||
*/
|
||||
@Component
|
||||
public class PostTagDirective implements TemplateDirectiveModel {
|
||||
|
||||
private final PostService postService;
|
||||
|
||||
private final PostRenderAssembler postRenderAssembler;
|
||||
|
||||
private final PostTagService postTagService;
|
||||
|
||||
private final PostCategoryService postCategoryService;
|
||||
|
||||
public PostTagDirective(Configuration configuration,
|
||||
PostService postService,
|
||||
PostRenderAssembler postRenderAssembler,
|
||||
PostTagService postTagService,
|
||||
PostCategoryService postCategoryService) {
|
||||
this.postService = postService;
|
||||
this.postRenderAssembler = postRenderAssembler;
|
||||
this.postTagService = postTagService;
|
||||
this.postCategoryService = postCategoryService;
|
||||
configuration.setSharedVariable("postTag", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Environment env, Map params, TemplateModel[] loopVars,
|
||||
TemplateDirectiveBody body) throws TemplateException, IOException {
|
||||
final DefaultObjectWrapperBuilder builder =
|
||||
new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_25);
|
||||
if (params.containsKey(HaloConst.METHOD_KEY)) {
|
||||
String method = params.get(HaloConst.METHOD_KEY).toString();
|
||||
switch (method) {
|
||||
case "latest":
|
||||
int top = Integer.parseInt(params.get("top").toString());
|
||||
env.setVariable("posts", builder.build()
|
||||
.wrap(postRenderAssembler.convertToListVo(postService.listLatest(top))));
|
||||
break;
|
||||
case "count":
|
||||
env.setVariable("count",
|
||||
builder.build().wrap(postService.countByStatus(PostStatus.PUBLISHED)));
|
||||
break;
|
||||
case "archiveYear":
|
||||
env.setVariable("archives",
|
||||
builder.build().wrap(postService.listYearArchives()));
|
||||
break;
|
||||
case "archiveMonth":
|
||||
env.setVariable("archives",
|
||||
builder.build().wrap(postService.listMonthArchives()));
|
||||
break;
|
||||
case "archive":
|
||||
String type = params.get("type").toString();
|
||||
env.setVariable("archives", builder.build().wrap(
|
||||
"year".equals(type) ? postService.listYearArchives() :
|
||||
postService.listMonthArchives()));
|
||||
break;
|
||||
case "listByCategoryId":
|
||||
Integer categoryId = Integer.parseInt(params.get("categoryId").toString());
|
||||
env.setVariable("posts", builder.build()
|
||||
.wrap(postRenderAssembler.convertToListVo(
|
||||
postCategoryService.listPostBy(categoryId,
|
||||
Sets.immutableEnumSet(PostStatus.PUBLISHED,
|
||||
PostStatus.INTIMATE)))));
|
||||
break;
|
||||
case "listByCategorySlug":
|
||||
String categorySlug = params.get("categorySlug").toString();
|
||||
List<Post> posts =
|
||||
postCategoryService.listPostBy(categorySlug,
|
||||
Sets.immutableEnumSet(PostStatus.PUBLISHED, PostStatus.INTIMATE));
|
||||
env.setVariable("posts",
|
||||
builder.build().wrap(postRenderAssembler.convertToListVo(posts)));
|
||||
break;
|
||||
case "listByTagId":
|
||||
Integer tagId = Integer.parseInt(params.get("tagId").toString());
|
||||
env.setVariable("posts", builder.build().wrap(postRenderAssembler
|
||||
.convertToListVo(postTagService.listPostsBy(tagId, PostStatus.PUBLISHED))));
|
||||
break;
|
||||
case "listByTagSlug":
|
||||
String tagSlug = params.get("tagSlug").toString();
|
||||
env.setVariable("posts", builder.build()
|
||||
.wrap(
|
||||
postRenderAssembler.convertToListVo(
|
||||
postTagService.listPostsBy(tagSlug, PostStatus.PUBLISHED)
|
||||
)
|
||||
)
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
body.render(env.getOut());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
package run.halo.app.core.freemarker.tag;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import freemarker.core.Environment;
|
||||
import freemarker.template.Configuration;
|
||||
import freemarker.template.DefaultObjectWrapperBuilder;
|
||||
import freemarker.template.TemplateDirectiveBody;
|
||||
import freemarker.template.TemplateDirectiveModel;
|
||||
import freemarker.template.TemplateException;
|
||||
import freemarker.template.TemplateModel;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.model.entity.Tag;
|
||||
import run.halo.app.model.support.HaloConst;
|
||||
import run.halo.app.service.PostTagService;
|
||||
import run.halo.app.service.TagService;
|
||||
|
||||
/**
|
||||
* Freemarker custom tag of tag.
|
||||
*
|
||||
* @author ryanwang
|
||||
* @date 2019-03-22
|
||||
*/
|
||||
@Component
|
||||
public class TagTagDirective implements TemplateDirectiveModel {
|
||||
|
||||
private final TagService tagService;
|
||||
|
||||
private final PostTagService postTagService;
|
||||
|
||||
public TagTagDirective(Configuration configuration,
|
||||
TagService tagService,
|
||||
PostTagService postTagService) {
|
||||
this.tagService = tagService;
|
||||
this.postTagService = postTagService;
|
||||
configuration.setSharedVariable("tagTag", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Environment env, Map params, TemplateModel[] loopVars,
|
||||
TemplateDirectiveBody body) throws TemplateException, IOException {
|
||||
final DefaultObjectWrapperBuilder builder =
|
||||
new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_25);
|
||||
|
||||
if (params.containsKey(HaloConst.METHOD_KEY)) {
|
||||
String method = params.get(HaloConst.METHOD_KEY).toString();
|
||||
switch (method) {
|
||||
case "list":
|
||||
env.setVariable("tags", builder.build()
|
||||
.wrap(postTagService.listTagWithCountDtos(Sort.by(DESC, "createTime"))));
|
||||
break;
|
||||
case "listByPostId":
|
||||
Integer postId = Integer.parseInt(params.get("postId").toString());
|
||||
List<Tag> tags = postTagService.listTagsBy(postId);
|
||||
env.setVariable("tags", builder.build().wrap(tagService.convertTo(tags)));
|
||||
break;
|
||||
case "count":
|
||||
env.setVariable("count", builder.build().wrap(tagService.count()));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
body.render(env.getOut());
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue