diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..694188c --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ +/epaper.iml diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..a45eb6b --- /dev/null +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,118 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..2cc7d4a Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..642d572 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..3c8a553 --- /dev/null +++ b/mvnw @@ -0,0 +1,322 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ]; then + + if [ -f /etc/mavenrc ]; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ]; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false +darwin=false +mingw=false +case "$(uname)" in +CYGWIN*) cygwin=true ;; +MINGW*) mingw=true ;; +Darwin*) + darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="$(/usr/libexec/java_home)" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ]; then + if [ -r /etc/gentoo-release ]; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +if [ -z "$M2_HOME" ]; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ]; do + ls=$(ls -ld "$PRG") + link=$(expr "$ls" : '.*-> \(.*\)$') + if expr "$link" : '/.*' >/dev/null; then + PRG="$link" + else + PRG="$(dirname "$PRG")/$link" + fi + done + + saveddir=$(pwd) + + M2_HOME=$(dirname "$PRG")/.. + + # make it fully qualified + M2_HOME=$(cd "$M2_HOME" && pwd) + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=$(cygpath --unix "$M2_HOME") + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw; then + [ -n "$M2_HOME" ] && + M2_HOME="$( ( + cd "$M2_HOME" + pwd + ))" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="$( ( + cd "$JAVA_HOME" + pwd + ))" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr \"$javaExecutable\" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! $(expr "$readLink" : '\([^ ]*\)') = "no" ]; then + if $darwin; then + javaHome="$(dirname \"$javaExecutable\")" + javaExecutable="$(cd \"$javaHome\" && pwd -P)/javac" + else + javaExecutable="$(readlink -f \"$javaExecutable\")" + fi + javaHome="$(dirname \"$javaExecutable\")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ]; then + if [ -n "$JAVA_HOME" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(which java)" + fi +fi + +if [ ! -x "$JAVACMD" ]; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ]; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ]; then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ]; do + if [ -d "$wdir"/.mvn ]; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$( + cd "$wdir/.." + pwd + ) + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' <"$1")" + fi +} + +BASE_DIR=$(find_maven_basedir "$(pwd)") +if [ -z "$BASE_DIR" ]; then + exit 1 +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in wrapperUrl) + jarUrl="$value" + break + ;; + esac + done <"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget >/dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl >/dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=$(cygpath --path --windows "$M2_HOME") + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..c8d4337 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..5d8a4b9 --- /dev/null +++ b/pom.xml @@ -0,0 +1,226 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.6.RELEASE + + + org.codedream + epaper + 0.0.1-SNAPSHOT + epaper + ePaper For CodeDream + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-web + + + + com.h2database + h2 + runtime + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + org.springframework.security + spring-security-test + test + + + org.mariadb.jdbc + mariadb-java-client + 2.5.4 + + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.alibaba + fastjson + 1.2.61 + + + com.alibaba + fastjson + 1.1.71.android + + + com.github.java-json-tools + json-patch + 1.12 + + + commons-codec + commons-codec + 1.11 + + + + io.springfox + springfox-swagger2 + 2.9.2 + + + io.springfox + springfox-swagger-ui + 2.9.2 + + + com.baidu.aip + java-sdk + 4.12.0 + + + org.slf4j + slf4j-simple + + + + + org.apache.poi + poi-scratchpad + 4.1.2 + + + org.apache.poi + poi + 4.1.2 + + + org.apache.poi + poi-ooxml + 4.1.2 + + + junit + junit + test + + + + org.springframework.boot + spring-boot-starter-cache + + + org.python + jython-standalone + 2.7.0 + + + + org.apache.pdfbox + pdfbox + 2.0.19 + + + + args4j + args4j + 2.32 + + + org.docx4j + docx4j + 3.2.1 + + + org.slf4j + slf4j-log4j12 + + + + + fr.opensagres.xdocreport + org.apache.poi.xwpf.converter.pdf + 1.0.6 + + + fr.opensagres.xdocreport + org.odftoolkit.odfdom.converter.pdf + 1.0.6 + + + com.googlecode.jaxb-namespaceprefixmapper-interfaces + JAXBNamespacePrefixMapper + 2.2.4 + runtime + + + com.sun.xml.bind + jaxb-impl + 2.2.11 + + + com.sun.xml.bind + jaxb-core + 2.2.11 + + + + org.freemarker + freemarker + 2.3.30 + + + + log4j + log4j + 1.2.17 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + + AlibabaMaven + Maven Aliyun Mirror + http://maven.aliyun.com/nexus/content/repositories/central/ + + true + + + false + + + + + diff --git a/src/main/java/org/codedream/epaper/EpaperApplication.java b/src/main/java/org/codedream/epaper/EpaperApplication.java new file mode 100644 index 0000000..b077de6 --- /dev/null +++ b/src/main/java/org/codedream/epaper/EpaperApplication.java @@ -0,0 +1,21 @@ +package org.codedream.epaper; + +import org.codedream.epaper.configure.EPApplicationContextInitializer; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; + +@SpringBootApplication +@EnableScheduling +@EnableAsync +public class EpaperApplication { + + public static void main(String[] args) { + SpringApplication application = new SpringApplication(EpaperApplication.class); + // 添加启动检查 + application.addInitializers(new EPApplicationContextInitializer()); + application.run(args); + } + +} diff --git a/src/main/java/org/codedream/epaper/component/EPSpringUtil.java b/src/main/java/org/codedream/epaper/component/EPSpringUtil.java new file mode 100644 index 0000000..b4e2f20 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/EPSpringUtil.java @@ -0,0 +1,20 @@ +package org.codedream.epaper.component; + +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 手动获得Bean的工具类 + */ +@Component +public class EPSpringUtil { + @Resource + private ApplicationContext applicationContext; + + public T getBean(Class tClass){ + return applicationContext.getBean(tClass); + } + +} diff --git a/src/main/java/org/codedream/epaper/component/api/QuickJSONRespond.java b/src/main/java/org/codedream/epaper/component/api/QuickJSONRespond.java new file mode 100644 index 0000000..2e983f3 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/api/QuickJSONRespond.java @@ -0,0 +1,218 @@ +package org.codedream.epaper.component.api; + +import org.codedream.epaper.component.datamanager.JSONParameter; +import org.codedream.epaper.component.json.respond.JSONBaseRespondObject; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * JSON返回快速构造组件类(主要用于异常处理机制) + */ +@Component +public class QuickJSONRespond { + + @Resource + private JSONParameter jsonParameter; + + /** + * 根据对象构造获得标准的JSON响应字符串 + * @param status 状态码 + * @param msg 信息 + * @param info 附加信息 + * @param dataObject 附带对象 + * @return JSON字符串 + */ + public String getJSONStandardRespond(Integer status, String msg, String info, Object dataObject){ + JSONBaseRespondObject respondObject = new JSONBaseRespondObject(status, msg); + if(info != null) respondObject.setInfo(info); + else respondObject.setInfo(null); + + respondObject.setData(dataObject); + return jsonParameter.getJSONString(respondObject); + } + + /** + * 根据对象构造获得标准的JSON响应字符串 + * @param status 状态码 + * @param dataObject 附带对象 + * @return JSON字符串 + */ + public String getJSONStandardRespond(HttpStatus status, Object dataObject){ + JSONBaseRespondObject respondObject = new JSONBaseRespondObject(status.value(), status.getReasonPhrase()); + + respondObject.setData(dataObject); + return jsonParameter.getJSONString(respondObject); + } + + + /** + * 根据对象构造获得标准的JSON响应字符串 + * @param status 状态码 + * @param info 附加信息 + * @param dataObject 附带对象 + * @return JSON字符串 + */ + public String getJSONStandardRespond(HttpStatus status, String info, Object dataObject){ + JSONBaseRespondObject respondObject = new JSONBaseRespondObject(status.value(), status.getReasonPhrase()); + if(info != null) respondObject.setInfo(info); + else respondObject.setInfo(null); + + respondObject.setData(dataObject); + return jsonParameter.getJSONString(respondObject); + } + + + /** + * 根据对象构造获得标准的JSON响应字符串 + * @param status 状态码 + * @param info 附加信息 + * @return JSON字符串 + */ + public String getJSONStandardRespond(HttpStatus status, String info){ + JSONBaseRespondObject respondObject = new JSONBaseRespondObject(status.value(), status.getReasonPhrase()); + if(info != null) respondObject.setInfo(info); + else respondObject.setInfo(null); + + return jsonParameter.getJSONString(respondObject); + } + + /** + * 获得标准的JSON响应字符串返回特定状态码的和解释息 + * @param code 状态码 + * @param msg 信息 + * @param info 附加信息 + * @return JSON字符串 + */ + public String getJSONStandardRespond(Integer code, String msg, String info){ + JSONBaseRespondObject respondObject = new JSONBaseRespondObject(code, msg); + if(info != null) respondObject.setInfo(info); + else respondObject.setInfo(null); + respondObject.setData(null); + return jsonParameter.getJSONString(respondObject); + } + + /** + * 获得标准的JSON响应字符串返回(404状态) + * @param info 附加信息 + * @return JSON字符串 + */ + public String getRespond404(String info){ + return getJSONStandardRespond(HttpStatus.NOT_FOUND, info); + } + + /** + * 获得标准的JSON响应字符串返回(404状态) + * @param info 附加信息 + * @param object 附带对象 + * @return JSON字符串 + */ + public String getRespond404(String info, Object object){ + return getJSONStandardRespond(HttpStatus.NOT_FOUND, info, object); + } + + /** + * 获得标准的JSON响应字符串返回(500状态) + * @param info 附加信息 + * @return JSON字符串 + */ + public String getRespond500(String info){ + return getJSONStandardRespond(HttpStatus.INTERNAL_SERVER_ERROR, info); + } + + /** + * 获得标准的JSON响应字符串返回(200状态) + * @param info 附加信息 + * @return JSON字符串 + */ + public String getRespond200(String info){ + return getJSONStandardRespond(HttpStatus.OK, info); + } + + /** + * 获得标准的JSON响应字符串返回(200状态) + * @param info 附加信息 + * @param object 附带对象 + * @return JSON字符串 + */ + public String getRespond200(String info, Object object){ + return getJSONStandardRespond(HttpStatus.OK, info, object); + } + + /** + * 获得标准的JSON响应字符串返回(403状态) + * @param info 附加信息 + * @return JSON字符串 + */ + public String getRespond403(String info){ + return getJSONStandardRespond(HttpStatus.FORBIDDEN, info); + } + + /** + * 获得标准的JSON响应字符串返回(406状态) + * @param info 附加信息 + * @return JSON字符串 + */ + public String getRespond406(String info){ + return getJSONStandardRespond(HttpStatus.NOT_ACCEPTABLE, info); + } + + /** + * 获得标准的JSON响应字符串返回(406状态) + * @param info 附加信息 + * @param object 附带对象 + * @return JSON字符串 + */ + public String getRespond406(String info, Object object){ + return getJSONStandardRespond(HttpStatus.NOT_ACCEPTABLE, info, object); + } + + /** + * 获得标准的JSON响应字符串返回(501状态) + * @param info 附加信息 + * @return JSON字符串 + */ + public String getRespond501(String info){ + return getJSONStandardRespond(501, "Not Implemented", info) ; + } + + /** + * 获得标准的JSON响应字符串返回(401状态) + * @param info 附加信息 + * @return JSON字符串 + */ + public String getRespond401(String info){ + return getJSONStandardRespond(401, "Unauthorized", info); + } + + /** + * 获得标准的JSON响应字符串返回(400状态) + * @param info 附加信息 + * @return JSON字符串 + */ + public String getRespond400(String info){ + return getJSONStandardRespond(400, "Bad Request", info); + } + + /** + * 获得标准的JSON响应字符串返回(404状态) + * @param info 附加信息 + * @param object 附带对象 + * @return JSON字符串 + */ + public String getRespond400(String info, Object object){ + return getJSONStandardRespond(400, "Bad Request", info, object); + } + + /** + * 获得标准的JSON响应字符串返回(400状态) + * @param info 附加信息 + * @return JSON字符串 + */ + public String getRespond409(String info){ + return getJSONStandardRespond(409, "Conflict", info); + } + + +} diff --git a/src/main/java/org/codedream/epaper/component/article/GetSentenceFromArticle.java b/src/main/java/org/codedream/epaper/component/article/GetSentenceFromArticle.java new file mode 100644 index 0000000..66f10ff --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/article/GetSentenceFromArticle.java @@ -0,0 +1,31 @@ +package org.codedream.epaper.component.article; + +import org.codedream.epaper.component.json.model.JsonableSTN; +import org.codedream.epaper.model.article.Article; +import org.codedream.epaper.model.article.Paragraph; +import org.codedream.epaper.model.article.Sentence; +import org.codedream.epaper.model.task.Task; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +/** + * 获得章结构的句列表 + */ +@Component +public class GetSentenceFromArticle { + + /** + * 获得章结构的句列表 + * @param article 章结构 + * @return 句结构列表 + */ + public List get(Article article){ + List sentences = new ArrayList<>(); + for(Paragraph paragraph : article.getParagraphs()){ + sentences.addAll(paragraph.getSentences()); + } + return sentences; + } +} diff --git a/src/main/java/org/codedream/epaper/component/auth/AJAXRequestChecker.java b/src/main/java/org/codedream/epaper/component/auth/AJAXRequestChecker.java new file mode 100644 index 0000000..dfb57b0 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/auth/AJAXRequestChecker.java @@ -0,0 +1,22 @@ +package org.codedream.epaper.component.auth; + +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.util.Optional; + +/** + * 检查请求是否为Ajax的方式发起 + */ +@Component +public class AJAXRequestChecker { + + /** + * 检查请求是否为Ajax的方式发起 + * @param request HTTP请求 + * @return 布尔值 + */ + public boolean checkAjaxPOSTRequest(HttpServletRequest request){ + return Optional.ofNullable(request.getHeader("X-Requested-With")).isPresent(); + } +} diff --git a/src/main/java/org/codedream/epaper/component/auth/AuthTokenGenerator.java b/src/main/java/org/codedream/epaper/component/auth/AuthTokenGenerator.java new file mode 100644 index 0000000..d5e9643 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/auth/AuthTokenGenerator.java @@ -0,0 +1,27 @@ +package org.codedream.epaper.component.auth; + +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.UUID; + +/** + * Token生成器 + */ +@Component +public class AuthTokenGenerator { + @Resource + private SHA1Encoder encoder; + + /** + * 生成Token + * @param username 用户名 + * @return token字符串 + */ + public String generateAuthToken(String username){ + Date dateNow = new Date(); + UUID uuid = UUID.randomUUID(); + return encoder.encode(String.format("Token [%s][%d][%s]",username,dateNow.getTime(), uuid.toString())); + } +} diff --git a/src/main/java/org/codedream/epaper/component/auth/EPAccessDecisionManager.java b/src/main/java/org/codedream/epaper/component/auth/EPAccessDecisionManager.java new file mode 100644 index 0000000..a3333cb --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/auth/EPAccessDecisionManager.java @@ -0,0 +1,63 @@ +package org.codedream.epaper.component.auth; + +import org.springframework.security.access.AccessDecisionManager; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.ConfigAttribute; +import org.springframework.security.authentication.InsufficientAuthenticationException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.stereotype.Component; + +import java.util.Collection; + +/** + * 权限管理器 + */ +@Component +public class EPAccessDecisionManager implements AccessDecisionManager { + + /** + * 确定用户的权限 + * @param authentication 认证柄 + * @param object 传入对象 + * @param configAttributes 角色信息对象 + * @throws AccessDeniedException 访问禁止异常 + * @throws InsufficientAuthenticationException 会话认证异常 + */ + @Override + public void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { + if(null == configAttributes || configAttributes.size() <= 0) { + return; + } + + for (ConfigAttribute c : configAttributes) { + String needRole = c.getAttribute(); + for (GrantedAuthority ga : authentication.getAuthorities()) { + if (needRole.trim().equals(ga.getAuthority())) { + return; + } + } + } + throw new AccessDeniedException("Access Denied"); + } + + /** + * 是否支持该角色信息对象 + * @param attribute 角色信息对象 + * @return 布尔值 + */ + @Override + public boolean supports(ConfigAttribute attribute) { + return true; + } + + /** + * 是否支持该认证柄 + * @param clazz 认证柄对象信息 + * @return 布尔值 + */ + @Override + public boolean supports(Class clazz) { + return true; + } +} diff --git a/src/main/java/org/codedream/epaper/component/auth/EPAccessDeniedHandler.java b/src/main/java/org/codedream/epaper/component/auth/EPAccessDeniedHandler.java new file mode 100644 index 0000000..75ed363 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/auth/EPAccessDeniedHandler.java @@ -0,0 +1,43 @@ +package org.codedream.epaper.component.auth; + +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.component.api.QuickJSONRespond; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + + +/** + * 已认证用户访问无权限资源状态处理 + */ +@Slf4j +@Component +public class EPAccessDeniedHandler implements AccessDeniedHandler { + + @Resource + private QuickJSONRespond quickJSONRespond; + + /** + * 处理函数 + * @param request HTTP请求 + * @param response HTTP返回 + * @param accessDeniedException 无权限访问异常 + * @throws IOException I/O异常 + */ + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) + throws IOException { + + log.info("ASEAccessDeniedHandler Found!"); + + // 对无权限操作返回403 + response.getWriter().print(quickJSONRespond.getRespond403(null)); + response.setStatus(403); + + } +} diff --git a/src/main/java/org/codedream/epaper/component/auth/EPAuthenticationEntryPoint.java b/src/main/java/org/codedream/epaper/component/auth/EPAuthenticationEntryPoint.java new file mode 100644 index 0000000..9355fc7 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/auth/EPAuthenticationEntryPoint.java @@ -0,0 +1,38 @@ +package org.codedream.epaper.component.auth; + +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.component.api.QuickJSONRespond; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * 匿名用户访问无权限资源(未得到认证的用户视为匿名用户) + */ +@Slf4j +@Component +public class EPAuthenticationEntryPoint implements AuthenticationEntryPoint { + @Resource + private QuickJSONRespond quickJSONRespond; + + /** + * 处理函数 + * @param request HTTP请求 + * @param response HTTP返回 + * @param authException 认证异常 + * @throws IOException I/O异常 + */ + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) + throws IOException { + + // 对匿名用户返回401 + response.getWriter().print(quickJSONRespond.getRespond401(null)); + response.setStatus(401); + } +} diff --git a/src/main/java/org/codedream/epaper/component/auth/EPAuthenticationFailureHandler.java b/src/main/java/org/codedream/epaper/component/auth/EPAuthenticationFailureHandler.java new file mode 100644 index 0000000..942749e --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/auth/EPAuthenticationFailureHandler.java @@ -0,0 +1,53 @@ +package org.codedream.epaper.component.auth; + +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.component.api.QuickJSONRespond; +import org.codedream.epaper.component.json.respond.ErrorInfoJSONRespond; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Date; + +/** + * 认证失败处理 + */ +@Slf4j +@Component +public class EPAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { + + @Resource + private QuickJSONRespond quickJSONRespond; + + /** + * + * @param request HTTP请求 + * @param response HTTP返回 + * @param exception 异常类型 + * @throws IOException I/O异常 + */ + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) + throws IOException + { + log.info("ASEAuthenticationFailureHandler Login Fail!"); + + // 填写异常信息存储对象 + ErrorInfoJSONRespond errorInfoJSONRespond = new ErrorInfoJSONRespond(); + errorInfoJSONRespond.setDate(new Date()); + errorInfoJSONRespond.setExceptionMessage(exception.getMessage()); + errorInfoJSONRespond.setException(exception.getClass().getSimpleName()); + + // 认证失败返回406 + response.getWriter().write(quickJSONRespond.getJSONStandardRespond( + 406, + "Not Acceptable", + "Authentication Failure", + errorInfoJSONRespond)); + response.setStatus(406); + } +} diff --git a/src/main/java/org/codedream/epaper/component/auth/EPAuthenticationSuccessHandler.java b/src/main/java/org/codedream/epaper/component/auth/EPAuthenticationSuccessHandler.java new file mode 100644 index 0000000..01605da --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/auth/EPAuthenticationSuccessHandler.java @@ -0,0 +1,65 @@ +package org.codedream.epaper.component.auth; + +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.component.api.QuickJSONRespond; +import org.codedream.epaper.component.json.respond.UserLoginCheckerJSONRespond; +import org.codedream.epaper.model.user.User; +import org.codedream.epaper.service.IAuthService; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Optional; + +// 认证成功返回 +@Slf4j +@Component +public class EPAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { + + @Resource + private QuickJSONRespond quickJSONRespond; + + @Resource + private IAuthService authService; + + /** + * + * @param request HTTP请求 + * @param response HTTP返回 + * @param authentication 认证柄 + * @throws IOException I/O异常 + * @throws ServletException Servlet异常 + */ + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException, ServletException + { + + UserLoginCheckerJSONRespond respond = new UserLoginCheckerJSONRespond(); + respond.setUserExist(authentication.isAuthenticated()); + respond.setLoginStatus(authentication.isAuthenticated()); + respond.setPvc(authService.preValidationCodeGetter()); + + // 获得 JSONTokenAuthenticationToken + JSONTokenAuthenticationToken authenticationToken = (JSONTokenAuthenticationToken) authentication; + + User user = (User) authenticationToken.getPrincipal(); + + Optional tokenOptional = authService.userNewTokenGetter( + user.getUsername(), authenticationToken.getClientCode()); + + if(tokenOptional.isPresent()){ + respond.setToken(tokenOptional.get()); + } + else respond.setToken(""); + + // 认证成功返回200 + response.getWriter().write(quickJSONRespond.getRespond200("Authentication Success", respond)); + response.setStatus(200); + } +} diff --git a/src/main/java/org/codedream/epaper/component/auth/EPJSONTokenAuthenticationFilter.java b/src/main/java/org/codedream/epaper/component/auth/EPJSONTokenAuthenticationFilter.java new file mode 100644 index 0000000..21dd5d4 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/auth/EPJSONTokenAuthenticationFilter.java @@ -0,0 +1,126 @@ +package org.codedream.epaper.component.auth; + +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.model.auth.JSONToken; +import org.codedream.epaper.service.AuthService; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.annotation.Resource; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Collection; +import java.util.Date; +import java.util.Optional; + + +/** + * API请求验证服务 + */ +@Slf4j +public class EPJSONTokenAuthenticationFilter extends OncePerRequestFilter { + + @Resource + private JSONRandomCodeGenerator randomCodeGenerator; + + @Resource + private AuthService authService; + + @Resource + private JSONSignedGenerator signedGenerator; + + @Resource + private UserDetailsService userDetailsService; + + /** + * + * @param request HTTP请求 + * @param response HTTP返回 + * @param filterChain 过滤器链 + * @throws ServletException Servlet异常 + * @throws IOException I/O异常 + */ + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + // 用户名 + String openid = request.getHeader( "openid"); + // 客户端签名 + String signed = request.getHeader("signed"); + // 时间戳 + String timestamp = request.getHeader("timestamp"); + + // 服务端API测试豁免签名 + if(signed != null && signed.equals("6d4923fca4dcb51f67b85e54a23a8d763d9e02af")){ + //执行授权 + doAuthentication("test", request); + } + // 正常认证 + else if (signed != null && openid != null && timestamp != null) { + // 获得具体时间 + Date date = new Date(Long.parseLong(timestamp)); + + Date now = new Date(); + + // 限制时间戳有效区间为60s + long dtTime = 60*1000; + Date maxDate = new Date(now.getTime() + dtTime); + + // 检查时间戳是否合理 + if(maxDate.after(date)) { + // 从服务器中查找token + Optional optionalJSONToken = authService.findTokenByUserName(openid); + if (optionalJSONToken.isPresent()) { + JSONToken token = optionalJSONToken.get(); + + // 检查token是否过期 + if (!authService.checkTokenIfExpired(token)) { + // 生成特征随机代码 + String randomCode = randomCodeGenerator.generateRandomCode(openid, + date, token.getClientCode()); + + log.info(String.format("Determined Signed: %s", + signedGenerator.generateSigned(openid, randomCode, token.getToken()))); + log.info(String.format("Get Signed: %s", signed)); + + // 检查签名是否正确 + if (signed.equals(signedGenerator.generateSigned(openid, randomCode, token.getToken()))) { + // 执行授权操作 + doAuthentication(openid, request); + } + } + } + } + } + + filterChain.doFilter(request, response); + + } + + // 执行授权 + private void doAuthentication(String username, HttpServletRequest request){ + // 查询用户的相关信息 + UserDetails user = userDetailsService.loadUserByUsername(username); + + // 生成用户权限列表 + Collection authorities = user.getAuthorities(); + + // 生成授权柄 (储存上下文信息) + JSONTokenAuthenticationToken authentication = + new JSONTokenAuthenticationToken(user, null, authorities); + + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + // 执行授权 + SecurityContextHolder.getContext().setAuthentication(authentication); + } + +} diff --git a/src/main/java/org/codedream/epaper/component/auth/EPPasswordEncoder.java b/src/main/java/org/codedream/epaper/component/auth/EPPasswordEncoder.java new file mode 100644 index 0000000..bba5bf8 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/auth/EPPasswordEncoder.java @@ -0,0 +1,32 @@ +package org.codedream.epaper.component.auth; + +import org.apache.commons.codec.digest.DigestUtils; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +/** + * 密码编码器 + */ +@Component +public class EPPasswordEncoder implements PasswordEncoder { + /** + * 密码编码 + * @param charSequence + * @return 密文 + */ + @Override + public String encode(CharSequence charSequence) { + return DigestUtils.sha256Hex(charSequence.toString()); + } + + /** + * 密码验证 + * @param charSequence 字符队列 + * @param s 密文 + * @return 布尔值 + */ + @Override + public boolean matches(CharSequence charSequence, String s) { + return s.equals(DigestUtils.sha256Hex(charSequence.toString())); + } +} diff --git a/src/main/java/org/codedream/epaper/component/auth/EPSecurityAuthenticationProvider.java b/src/main/java/org/codedream/epaper/component/auth/EPSecurityAuthenticationProvider.java new file mode 100644 index 0000000..a3e8f6b --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/auth/EPSecurityAuthenticationProvider.java @@ -0,0 +1,70 @@ +package org.codedream.epaper.component.auth; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.*; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Collection; + +// 普通用户名密码验证, 用户获得Token +@Slf4j +@Component +public class EPSecurityAuthenticationProvider implements AuthenticationProvider { + @Resource + UserDetailsService userDetailsService; + + @Resource + EPPasswordEncoder passwordEncoder; + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + JSONTokenUsernamePasswordAuthenticationToken authenticationToken = + (JSONTokenUsernamePasswordAuthenticationToken) authentication; + + // 获得JSON中的学号 + String username = (String) authenticationToken.getPrincipal(); + // 获得JSON中的加密密码 + String encrypted_password = passwordEncoder.encode((String) authenticationToken.getCredentials()); + // 获得客户端代码 + String clientCode = authenticationToken.getClientCode(); + // 判断用户是否存在并获取用户信息 + UserDetails userInfo = userDetailsService.loadUserByUsername(username); + + if (userInfo == null) { + throw new UsernameNotFoundException("User Not Exist"); + } + + // 判断密码是否正确 + if(!userInfo.getPassword().equals(encrypted_password)){ + throw new BadCredentialsException("Password IS INCORRECT"); + } + + // 判断账号是否停用/删除 + if (!userInfo.isEnabled()) { + throw new DisabledException("User IS Disabled"); + } + else if(!userInfo.isAccountNonLocked()) { + throw new LockedException("User IS Locked"); + } + else if(!userInfo.isAccountNonExpired()) { + throw new AccountExpiredException("User IS Expired"); + } + + // 生成权限列表 + Collection authorities = userInfo.getAuthorities(); + + return new JSONTokenAuthenticationToken(userInfo, clientCode, authorities); + } + + @Override + public boolean supports(Class aClass) { + return aClass.equals(JSONTokenUsernamePasswordAuthenticationToken.class); + } +} diff --git a/src/main/java/org/codedream/epaper/component/auth/EPUsernameEncoder.java b/src/main/java/org/codedream/epaper/component/auth/EPUsernameEncoder.java new file mode 100644 index 0000000..a97de46 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/auth/EPUsernameEncoder.java @@ -0,0 +1,29 @@ +package org.codedream.epaper.component.auth; + +import org.apache.commons.codec.digest.DigestUtils; +import org.springframework.stereotype.Component; + +/** + * 用户名编码器 + */ +@Component +public class EPUsernameEncoder { + /** + * 编码 + * @param charSequence 字符队列 + * @return 密文 + */ + public String encode(CharSequence charSequence){ + return "openid_" + DigestUtils.sha256Hex(charSequence.toString()); + } + + /** + * 验证 + * @param charSequence 字符队列 + * @param s 密文 + * @return 布尔值 + */ + public boolean matches(CharSequence charSequence, String s){ + return s.equals(encode(charSequence.toString())); + } +} diff --git a/src/main/java/org/codedream/epaper/component/auth/EPUsernamePasswordAuthenticationFilter.java b/src/main/java/org/codedream/epaper/component/auth/EPUsernamePasswordAuthenticationFilter.java new file mode 100644 index 0000000..009c344 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/auth/EPUsernamePasswordAuthenticationFilter.java @@ -0,0 +1,87 @@ +package org.codedream.epaper.component.auth; + +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.component.datamanager.JSONParameter; +import org.codedream.epaper.component.json.request.UserLoginChecker; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Optional; + +/** + * 账号认证过滤器 + */ +@Slf4j +public class EPUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { + + @Resource + private JSONParameter jsonParameter; + + @Resource + private AJAXRequestChecker ajaxRequestChecker; + + @Resource + private TimestampExpiredChecker timestampExpiredChecker; + + /** + * 验证函数 + * @param request HTTP请求 + * @param response HTTP返回 + * @return 认证柄 + * @throws AuthenticationException 认证异常 + */ + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) + throws AuthenticationException { + + String timestamp = request.getHeader("timestamp"); + + // 检查时间戳是否合理(60秒内) + if(timestamp == null || !timestampExpiredChecker.checkTimestampBeforeMaxTime(timestamp, 60)){ + throw new AuthenticationServiceException("Timestamp Expired."); + } + + // 判断是否为AJAX请求格式的数据 + if(!ajaxRequestChecker.checkAjaxPOSTRequest(request)) { + throw new AuthenticationServiceException("Authentication method not supported: NOT Ajax Method."); + } + + Optional checkerOptional = jsonParameter.getJavaObjectByRequest(request, UserLoginChecker.class); + if(!checkerOptional.isPresent()) throw new BadCredentialsException("Invalid AJAX JSON Request"); + + UserLoginChecker checker = checkerOptional.get(); + + if(checker.getOpenid() == null + || checker.getPassword() == null + || checker.getClientCode() == null) + throw new AuthenticationServiceException("Request Data IS Incomplete"); + + // 获得相应的用户名密码 + String openid = checker.getOpenid(); + // 得到加密密码 + String password = checker.getPassword(); + String clientCode = checker.getClientCode(); + + if (openid == null) openid = ""; + if (password == null) password = ""; + + // 去除首尾两端的空白字符 + openid = openid.trim(); + password = password.trim(); + + + JSONTokenUsernamePasswordAuthenticationToken authRequest = + new JSONTokenUsernamePasswordAuthenticationToken(openid, password, clientCode); + + authRequest.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + return this.getAuthenticationManager().authenticate(authRequest); + } +} diff --git a/src/main/java/org/codedream/epaper/component/auth/JSONRandomCodeGenerator.java b/src/main/java/org/codedream/epaper/component/auth/JSONRandomCodeGenerator.java new file mode 100644 index 0000000..35d1168 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/auth/JSONRandomCodeGenerator.java @@ -0,0 +1,28 @@ +package org.codedream.epaper.component.auth; + +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Date; + +/** + * 随机特征值生成器 + */ +@Component +public class JSONRandomCodeGenerator { + + @Resource + private SHA1Encoder encoder; + + /** + * 生成随机特征值 + * @param username 用户名 + * @param date 时间 + * @param clientCode 客户端代码 + * @return 随机特征值字符串 + */ + public String generateRandomCode(String username, Date date, String clientCode){ + return encoder.encode(String.format("RandomCode [%s][%s][%s]", + username, Long.toString(date.getTime()), clientCode)); + } +} diff --git a/src/main/java/org/codedream/epaper/component/auth/JSONSignedGenerator.java b/src/main/java/org/codedream/epaper/component/auth/JSONSignedGenerator.java new file mode 100644 index 0000000..8077da3 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/auth/JSONSignedGenerator.java @@ -0,0 +1,25 @@ +package org.codedream.epaper.component.auth; + +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 客户端签名生成器 + */ +@Component +public class JSONSignedGenerator { + @Resource + SHA1Encoder encoder; + + /** + * 生成签名 + * @param username 用户名 + * @param randomCode 随机特征值 + * @param token Token + * @return 客户端签名 + */ + public String generateSigned(String username, String randomCode, String token){ + return encoder.encode(String.format("SIGN [%s][%s][%s]",username, randomCode, token)); + } +} diff --git a/src/main/java/org/codedream/epaper/component/auth/JSONTokenAuthenticationToken.java b/src/main/java/org/codedream/epaper/component/auth/JSONTokenAuthenticationToken.java new file mode 100644 index 0000000..f7f19c6 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/auth/JSONTokenAuthenticationToken.java @@ -0,0 +1,59 @@ +package org.codedream.epaper.component.auth; + + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; + +/** + * 关联Token与其他用户的相关数据的认证柄 + */ +public class JSONTokenAuthenticationToken extends AbstractAuthenticationToken { + + // 客户端签名 + private String signed = null; + // 用户名 + private Object principal = null; + // 客户端代码 + private String clientCode = null; + + + public JSONTokenAuthenticationToken(UserDetails principal, + String clientCode, + Collection authorities) + { + super(authorities); + this.principal = principal; + this.clientCode = clientCode; + this.signed = null; + setAuthenticated(true); + } + + public JSONTokenAuthenticationToken(String principal, String clientCode, String signed) { + super(null); + this.principal = principal; + this.clientCode = clientCode; + this.signed = signed; + setAuthenticated(false); + } + + @Override + public String getCredentials() { + return signed; + } + + @Override + public Object getPrincipal() { + return principal; + } + + public String getClientCode() { + return clientCode; + } + + public void setClientCode(String clientCode) { + this.clientCode = clientCode; + } +} diff --git a/src/main/java/org/codedream/epaper/component/auth/JSONTokenUsernamePasswordAuthenticationToken.java b/src/main/java/org/codedream/epaper/component/auth/JSONTokenUsernamePasswordAuthenticationToken.java new file mode 100644 index 0000000..a0728fe --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/auth/JSONTokenUsernamePasswordAuthenticationToken.java @@ -0,0 +1,43 @@ +package org.codedream.epaper.component.auth; + +import org.springframework.security.authentication.AbstractAuthenticationToken; + +/** + * 明文用户名密码验证认证柄 + */ +public class JSONTokenUsernamePasswordAuthenticationToken extends AbstractAuthenticationToken { + // 用户名 + private String username = null; + // 明文密码 + private String password = null; + // 授权柄 + private String clientCode = null; + + + public JSONTokenUsernamePasswordAuthenticationToken(String username, String password, String clientCode) { + super(null); + this.username = username; + this.password = password; + this.clientCode = clientCode; + setAuthenticated(false); + } + + @Override + public Object getCredentials() { + return password; + } + + + @Override + public Object getPrincipal() { + return username; + } + + /** + * 扩展接口 获得客户端代码 + * @return 客户端代码 + */ + public String getClientCode() { + return clientCode; + } +} diff --git a/src/main/java/org/codedream/epaper/component/auth/SHA1Encoder.java b/src/main/java/org/codedream/epaper/component/auth/SHA1Encoder.java new file mode 100644 index 0000000..f5b5a04 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/auth/SHA1Encoder.java @@ -0,0 +1,29 @@ +package org.codedream.epaper.component.auth; + +import org.apache.commons.codec.digest.DigestUtils; +import org.springframework.stereotype.Component; + +/** + * SHA1编码器 + */ +@Component +public class SHA1Encoder { + /** + * 编码 + * @param charSequence 字符队列 + * @return 密文 + */ + public String encode(CharSequence charSequence){ + return DigestUtils.sha1Hex(charSequence.toString()); + } + + /** + * 验证 + * @param charSequence 字符队列 + * @param s 密文 + * @return 布尔值 + */ + public boolean match (CharSequence charSequence, String s){ + return s.equals(encode(charSequence)); + } +} diff --git a/src/main/java/org/codedream/epaper/component/auth/TimestampExpiredChecker.java b/src/main/java/org/codedream/epaper/component/auth/TimestampExpiredChecker.java new file mode 100644 index 0000000..eb6d62f --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/auth/TimestampExpiredChecker.java @@ -0,0 +1,50 @@ +package org.codedream.epaper.component.auth; + +import org.springframework.stereotype.Component; + +import java.util.Date; + +/** + * 验证时间戳验证器 + */ +@Component +public class TimestampExpiredChecker { + + /** + * 检查时间戳是否在某个最大有效时间前 + * @param timestamp 时间戳字符串 + * @param seconds 最大有效时间 + * @return 布尔值 + */ + public boolean checkTimestampBeforeMaxTime(String timestamp, int seconds){ + Date timestampDate = new Date(Long.parseLong(timestamp)); + long currentTime = System.currentTimeMillis(); + Date maxDate = new Date(currentTime + seconds * 1000); + return timestampDate.before(maxDate); + } + + /** + * 检查时间戳是否在某个最大有效时间前 + * @param date 时间对象 + * @param seconds 最大做大有效时间 + * @return 布尔值 + */ + public boolean checkDateBeforeMaxTime(Date date, int seconds){ + long currentTime = System.currentTimeMillis(); + Date maxDate = new Date(currentTime + seconds * 1000); + return date.before(maxDate); + } + + /** + * 检查时间戳是否在某个设置的时间前 + * @param date 时间对象 + * @param seconds 设置时间 + * @return 布尔值 + */ + public boolean checkDateBeforeDeterminedTime(Date date, int seconds){ + long currentTime = System.currentTimeMillis(); + Date maxDate = new Date(currentTime - seconds * 1000); + return date.before(maxDate); + } + +} diff --git a/src/main/java/org/codedream/epaper/component/batchthread/ComparableFutureTask.java b/src/main/java/org/codedream/epaper/component/batchthread/ComparableFutureTask.java new file mode 100644 index 0000000..633b63a --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/batchthread/ComparableFutureTask.java @@ -0,0 +1,33 @@ +package org.codedream.epaper.component.batchthread; + +import org.codedream.epaper.model.task.BatchProcessingTask; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +import java.util.concurrent.Callable; +import java.util.concurrent.FutureTask; + +public class ComparableFutureTask extends FutureTask implements Comparable { + + private BatchProcessingTask taskToAppoint = new BatchProcessingTask(); + + public ComparableFutureTask(Callable callable) { + super(callable); + } + + @Override + public int compareTo(ComparableFutureTask o) { + + if (this.getTaskToAppoint().getPriority() > o.getTaskToAppoint().getPriority()) { + return 1; + } else if (this.getTaskToAppoint().getPriority() < o.getTaskToAppoint().getPriority()) { + return -1; + } + return 0; + } + + public BatchProcessingTask getTaskToAppoint() { + return taskToAppoint; + } + +} diff --git a/src/main/java/org/codedream/epaper/component/batchthread/TaskAppointer.java b/src/main/java/org/codedream/epaper/component/batchthread/TaskAppointer.java new file mode 100644 index 0000000..da652d5 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/batchthread/TaskAppointer.java @@ -0,0 +1,53 @@ +package org.codedream.epaper.component.batchthread; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.model.task.BatchProcessingTask; + +import javax.annotation.Resource; +import java.util.Comparator; +import java.util.concurrent.Callable; + +@Data +@Slf4j +public class TaskAppointer implements Callable, Comparable { + + Comparator comparator = new Comparator() { + @Override + public int compare(BatchProcessingTask o1, BatchProcessingTask o2) { + return o1.getPriority() - o2.getPriority() < 0 ? -1 : 1; + } + }; + @Resource + private BatchProcessingTask taskToAppoint = new BatchProcessingTask(); + + public TaskAppointer(BatchProcessingTask bpt) { + this.taskToAppoint = bpt; + } + + + @Override + public int compareTo(TaskAppointer o) { + + if (this.getTaskToAppoint().getPriority() > o.getTaskToAppoint().getPriority()) { + return 1; + } else if (this.getTaskToAppoint().getPriority() < o.getTaskToAppoint().getPriority()) { + return -1; + } + return 0; + } + + @Override + public BatchProcessingTask call() throws Exception { + log.info("Thead " + Thread.currentThread().getName() + "bpt" + this.getTaskToAppoint().getId()); + + try { + Thread.sleep(10); + } catch (Exception e) { + e.printStackTrace(); + } + + log.info("Thread " + Thread.currentThread().getName() + " executing"); + return getTaskToAppoint(); + } +} diff --git a/src/main/java/org/codedream/epaper/component/cache/ScheduleCacheTask.java b/src/main/java/org/codedream/epaper/component/cache/ScheduleCacheTask.java new file mode 100644 index 0000000..2c50743 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/cache/ScheduleCacheTask.java @@ -0,0 +1,31 @@ +package org.codedream.epaper.component.cache; + +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.repository.article.CacheRepository; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.time.LocalDateTime; + +@EnableScheduling +@Component +@Slf4j +public class ScheduleCacheTask { + + @Resource + CacheRepository cacheRepository; + + @Scheduled(cron = "0 0 0 1/1 * ? ") + public void deleteCache() { + + LocalDateTime deleteTime = LocalDateTime.now().minusDays(7); + try { + cacheRepository.deleteByRecordTime(deleteTime); + } catch (Exception e) { + log.error("Failed to delete cache."); + } + } + +} diff --git a/src/main/java/org/codedream/epaper/component/datamanager/DiffMatchPatch.java b/src/main/java/org/codedream/epaper/component/datamanager/DiffMatchPatch.java new file mode 100644 index 0000000..b0b8f47 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/datamanager/DiffMatchPatch.java @@ -0,0 +1,2522 @@ +/* + * Diff Match and Patch + * Copyright 2018 The diff-match-patch Authors. + * https://github.com/google/diff-match-patch + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.codedream.epaper.component.datamanager; + + +import org.springframework.stereotype.Component; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/* + * Functions for diff, match and patch. + * Computes the difference between two texts to create a patch. + * Applies the patch onto another text, allowing for errors. + * + * @author fraser@google.com (Neil Fraser) + */ + +/** + * Class containing the diff, match and patch methods. + * Also contains the behaviour settings. + */ +@Component +public class DiffMatchPatch { + + // Defaults. + // Set these on your diff_match_patch instance to override the defaults. + + /** + * Number of seconds to map a diff before giving up (0 for infinity). + */ + public float Diff_Timeout = 1.0f; + /** + * Cost of an empty edit operation in terms of edit characters. + */ + public short Diff_EditCost = 4; + /** + * At what point is no match declared (0.0 = perfection, 1.0 = very loose). + */ + public float Match_Threshold = 0.5f; + /** + * How far to search for a match (0 = exact location, 1000+ = broad match). + * A match this many characters away from the expected location will add + * 1.0 to the score (0.0 is a perfect match). + */ + public int Match_Distance = 1000; + /** + * When deleting a large block of text (over ~64 characters), how close do + * the contents have to be to match the expected contents. (0.0 = perfection, + * 1.0 = very loose). Note that Match_Threshold controls how closely the + * end points of a delete need to match. + */ + public float Patch_DeleteThreshold = 0.5f; + /** + * Chunk size for context length. + */ + public short Patch_Margin = 4; + + /** + * The number of bits in an int. + */ + private short Match_MaxBits = 32; + + /** + * Internal class for returning results from diff_linesToChars(). + * Other less paranoid languages just use a three-element array. + */ + protected static class LinesToCharsResult { + protected String chars1; + protected String chars2; + protected List lineArray; + + protected LinesToCharsResult(String chars1, String chars2, + List lineArray) { + this.chars1 = chars1; + this.chars2 = chars2; + this.lineArray = lineArray; + } + } + + + // DIFF FUNCTIONS + + + /** + * The data structure representing a diff is a Linked list of Diff objects: + * {Diff(Operation.DELETE, "Hello"), Diff(Operation.INSERT, "Goodbye"), + * Diff(Operation.EQUAL, " world.")} + * which means: delete "Hello", add "Goodbye" and keep " world." + */ + public enum Operation { + DELETE, INSERT, EQUAL + } + + /** + * Find the differences between two texts. + * Run a faster, slightly less optimal diff. + * This method allows the 'checklines' of diff_main() to be optional. + * Most of the time checklines is wanted, so default to true. + * + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @return Linked List of Diff objects. + */ + public LinkedList diff_main(String text1, String text2) { + return diff_main(text1, text2, true); + } + + /** + * Find the differences between two texts. + * + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param checklines Speedup flag. If false, then don't run a + * line-level diff first to identify the changed areas. + * If true, then run a faster slightly less optimal diff. + * @return Linked List of Diff objects. + */ + public LinkedList diff_main(String text1, String text2, + boolean checklines) { + // Set a deadline by which time the diff must be complete. + long deadline; + if (Diff_Timeout <= 0) { + deadline = Long.MAX_VALUE; + } else { + deadline = System.currentTimeMillis() + (long) (Diff_Timeout * 1000); + } + return diff_main(text1, text2, checklines, deadline); + } + + /** + * Find the differences between two texts. Simplifies the problem by + * stripping any common prefix or suffix off the texts before diffing. + * + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param checklines Speedup flag. If false, then don't run a + * line-level diff first to identify the changed areas. + * If true, then run a faster slightly less optimal diff. + * @param deadline Time when the diff should be complete by. Used + * internally for recursive calls. Users should set DiffTimeout instead. + * @return Linked List of Diff objects. + */ + private LinkedList diff_main(String text1, String text2, + boolean checklines, long deadline) { + // Check for null inputs. + if (text1 == null || text2 == null) { + throw new IllegalArgumentException("Null inputs. (diff_main)"); + } + + // Check for equality (speedup). + LinkedList diffs; + if (text1.equals(text2)) { + diffs = new LinkedList(); + if (text1.length() != 0) { + diffs.add(new Diff(Operation.EQUAL, text1)); + } + return diffs; + } + + // Trim off common prefix (speedup). + int commonlength = diff_commonPrefix(text1, text2); + String commonprefix = text1.substring(0, commonlength); + text1 = text1.substring(commonlength); + text2 = text2.substring(commonlength); + + // Trim off common suffix (speedup). + commonlength = diff_commonSuffix(text1, text2); + String commonsuffix = text1.substring(text1.length() - commonlength); + text1 = text1.substring(0, text1.length() - commonlength); + text2 = text2.substring(0, text2.length() - commonlength); + + // Compute the diff on the middle block. + diffs = diff_compute(text1, text2, checklines, deadline); + + // Restore the prefix and suffix. + if (commonprefix.length() != 0) { + diffs.addFirst(new Diff(Operation.EQUAL, commonprefix)); + } + if (commonsuffix.length() != 0) { + diffs.addLast(new Diff(Operation.EQUAL, commonsuffix)); + } + + diff_cleanupMerge(diffs); + return diffs; + } + + /** + * Find the differences between two texts. Assumes that the texts do not + * have any common prefix or suffix. + * + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param checklines Speedup flag. If false, then don't run a + * line-level diff first to identify the changed areas. + * If true, then run a faster slightly less optimal diff. + * @param deadline Time when the diff should be complete by. + * @return Linked List of Diff objects. + */ + private LinkedList diff_compute(String text1, String text2, + boolean checklines, long deadline) { + LinkedList diffs = new LinkedList(); + + if (text1.length() == 0) { + // Just add some text (speedup). + diffs.add(new Diff(Operation.INSERT, text2)); + return diffs; + } + + if (text2.length() == 0) { + // Just delete some text (speedup). + diffs.add(new Diff(Operation.DELETE, text1)); + return diffs; + } + + String longtext = text1.length() > text2.length() ? text1 : text2; + String shorttext = text1.length() > text2.length() ? text2 : text1; + int i = longtext.indexOf(shorttext); + if (i != -1) { + // Shorter text is inside the longer text (speedup). + Operation op = (text1.length() > text2.length()) ? + Operation.DELETE : Operation.INSERT; + diffs.add(new Diff(op, longtext.substring(0, i))); + diffs.add(new Diff(Operation.EQUAL, shorttext)); + diffs.add(new Diff(op, longtext.substring(i + shorttext.length()))); + return diffs; + } + + if (shorttext.length() == 1) { + // Single character string. + // After the previous speedup, the character can't be an equality. + diffs.add(new Diff(Operation.DELETE, text1)); + diffs.add(new Diff(Operation.INSERT, text2)); + return diffs; + } + + // Check to see if the problem can be split in two. + String[] hm = diff_halfMatch(text1, text2); + if (hm != null) { + // A half-match was found, sort out the return data. + String text1_a = hm[0]; + String text1_b = hm[1]; + String text2_a = hm[2]; + String text2_b = hm[3]; + String mid_common = hm[4]; + // Send both pairs off for separate processing. + LinkedList diffs_a = diff_main(text1_a, text2_a, + checklines, deadline); + LinkedList diffs_b = diff_main(text1_b, text2_b, + checklines, deadline); + // Merge the results. + diffs = diffs_a; + diffs.add(new Diff(Operation.EQUAL, mid_common)); + diffs.addAll(diffs_b); + return diffs; + } + + if (checklines && text1.length() > 100 && text2.length() > 100) { + return diff_lineMode(text1, text2, deadline); + } + + return diff_bisect(text1, text2, deadline); + } + + /** + * Do a quick line-level diff on both strings, then rediff the parts for + * greater accuracy. + * This speedup can produce non-minimal diffs. + * + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param deadline Time when the diff should be complete by. + * @return Linked List of Diff objects. + */ + private LinkedList diff_lineMode(String text1, String text2, + long deadline) { + // Scan the text on a line-by-line basis first. + LinesToCharsResult a = diff_linesToChars(text1, text2); + text1 = a.chars1; + text2 = a.chars2; + List linearray = a.lineArray; + + LinkedList diffs = diff_main(text1, text2, false, deadline); + + // Convert the diff back to original text. + diff_charsToLines(diffs, linearray); + // Eliminate freak matches (e.g. blank lines) + diff_cleanupSemantic(diffs); + + // Rediff any replacement blocks, this time character-by-character. + // Add a dummy entry at the end. + diffs.add(new Diff(Operation.EQUAL, "")); + int count_delete = 0; + int count_insert = 0; + String text_delete = ""; + String text_insert = ""; + ListIterator pointer = diffs.listIterator(); + Diff thisDiff = pointer.next(); + while (thisDiff != null) { + switch (thisDiff.operation) { + case INSERT: + count_insert++; + text_insert += thisDiff.text; + break; + case DELETE: + count_delete++; + text_delete += thisDiff.text; + break; + case EQUAL: + // Upon reaching an equality, check for prior redundancies. + if (count_delete >= 1 && count_insert >= 1) { + // Delete the offending records and add the merged ones. + pointer.previous(); + for (int j = 0; j < count_delete + count_insert; j++) { + pointer.previous(); + pointer.remove(); + } + for (Diff subDiff : diff_main(text_delete, text_insert, false, + deadline)) { + pointer.add(subDiff); + } + } + count_insert = 0; + count_delete = 0; + text_delete = ""; + text_insert = ""; + break; + } + thisDiff = pointer.hasNext() ? pointer.next() : null; + } + diffs.removeLast(); // Remove the dummy entry at the end. + + return diffs; + } + + /** + * Find the 'middle snake' of a diff, split the problem in two + * and return the recursively constructed diff. + * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. + * + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param deadline Time at which to bail if not yet complete. + * @return LinkedList of Diff objects. + */ + protected LinkedList diff_bisect(String text1, String text2, + long deadline) { + // Cache the text lengths to prevent multiple calls. + int text1_length = text1.length(); + int text2_length = text2.length(); + int max_d = (text1_length + text2_length + 1) / 2; + int v_offset = max_d; + int v_length = 2 * max_d; + int[] v1 = new int[v_length]; + int[] v2 = new int[v_length]; + for (int x = 0; x < v_length; x++) { + v1[x] = -1; + v2[x] = -1; + } + v1[v_offset + 1] = 0; + v2[v_offset + 1] = 0; + int delta = text1_length - text2_length; + // If the total number of characters is odd, then the front path will + // collide with the reverse path. + boolean front = (delta % 2 != 0); + // Offsets for start and end of k loop. + // Prevents mapping of space beyond the grid. + int k1start = 0; + int k1end = 0; + int k2start = 0; + int k2end = 0; + for (int d = 0; d < max_d; d++) { + // Bail out if deadline is reached. + if (System.currentTimeMillis() > deadline) { + break; + } + + // Walk the front path one step. + for (int k1 = -d + k1start; k1 <= d - k1end; k1 += 2) { + int k1_offset = v_offset + k1; + int x1; + if (k1 == -d || (k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1])) { + x1 = v1[k1_offset + 1]; + } else { + x1 = v1[k1_offset - 1] + 1; + } + int y1 = x1 - k1; + while (x1 < text1_length && y1 < text2_length + && text1.charAt(x1) == text2.charAt(y1)) { + x1++; + y1++; + } + v1[k1_offset] = x1; + if (x1 > text1_length) { + // Ran off the right of the graph. + k1end += 2; + } else if (y1 > text2_length) { + // Ran off the bottom of the graph. + k1start += 2; + } else if (front) { + int k2_offset = v_offset + delta - k1; + if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1) { + // Mirror x2 onto top-left coordinate system. + int x2 = text1_length - v2[k2_offset]; + if (x1 >= x2) { + // Overlap detected. + return diff_bisectSplit(text1, text2, x1, y1, deadline); + } + } + } + } + + // Walk the reverse path one step. + for (int k2 = -d + k2start; k2 <= d - k2end; k2 += 2) { + int k2_offset = v_offset + k2; + int x2; + if (k2 == -d || (k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1])) { + x2 = v2[k2_offset + 1]; + } else { + x2 = v2[k2_offset - 1] + 1; + } + int y2 = x2 - k2; + while (x2 < text1_length && y2 < text2_length + && text1.charAt(text1_length - x2 - 1) + == text2.charAt(text2_length - y2 - 1)) { + x2++; + y2++; + } + v2[k2_offset] = x2; + if (x2 > text1_length) { + // Ran off the left of the graph. + k2end += 2; + } else if (y2 > text2_length) { + // Ran off the top of the graph. + k2start += 2; + } else if (!front) { + int k1_offset = v_offset + delta - k2; + if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1) { + int x1 = v1[k1_offset]; + int y1 = v_offset + x1 - k1_offset; + // Mirror x2 onto top-left coordinate system. + x2 = text1_length - x2; + if (x1 >= x2) { + // Overlap detected. + return diff_bisectSplit(text1, text2, x1, y1, deadline); + } + } + } + } + } + // Diff took too long and hit the deadline or + // number of diffs equals number of characters, no commonality at all. + LinkedList diffs = new LinkedList(); + diffs.add(new Diff(Operation.DELETE, text1)); + diffs.add(new Diff(Operation.INSERT, text2)); + return diffs; + } + + /** + * Given the location of the 'middle snake', split the diff in two parts + * and recurse. + * + * @param text1 Old string to be diffed. + * @param text2 New string to be diffed. + * @param x Index of split point in text1. + * @param y Index of split point in text2. + * @param deadline Time at which to bail if not yet complete. + * @return LinkedList of Diff objects. + */ + private LinkedList diff_bisectSplit(String text1, String text2, + int x, int y, long deadline) { + String text1a = text1.substring(0, x); + String text2a = text2.substring(0, y); + String text1b = text1.substring(x); + String text2b = text2.substring(y); + + // Compute both diffs serially. + LinkedList diffs = diff_main(text1a, text2a, false, deadline); + LinkedList diffsb = diff_main(text1b, text2b, false, deadline); + + diffs.addAll(diffsb); + return diffs; + } + + /** + * Split two texts into a list of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * + * @param text1 First string. + * @param text2 Second string. + * @return An object containing the encoded text1, the encoded text2 and + * the List of unique strings. The zeroth element of the List of + * unique strings is intentionally blank. + */ + protected LinesToCharsResult diff_linesToChars(String text1, String text2) { + List lineArray = new ArrayList(); + Map lineHash = new HashMap(); + // e.g. linearray[4] == "Hello\n" + // e.g. linehash.get("Hello\n") == 4 + + // "\x00" is a valid character, but various debuggers don't like it. + // So we'll insert a junk entry to avoid generating a null character. + lineArray.add(""); + + // Allocate 2/3rds of the space for text1, the rest for text2. + String chars1 = diff_linesToCharsMunge(text1, lineArray, lineHash, 40000); + String chars2 = diff_linesToCharsMunge(text2, lineArray, lineHash, 65535); + return new LinesToCharsResult(chars1, chars2, lineArray); + } + + /** + * Split a text into a list of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * + * @param text String to encode. + * @param lineArray List of unique strings. + * @param lineHash Map of strings to indices. + * @param maxLines Maximum length of lineArray. + * @return Encoded string. + */ + private String diff_linesToCharsMunge(String text, List lineArray, + Map lineHash, int maxLines) { + int lineStart = 0; + int lineEnd = -1; + String line; + StringBuilder chars = new StringBuilder(); + // Walk the text, pulling out a substring for each line. + // text.split('\n') would would temporarily double our memory footprint. + // Modifying text would create many large strings to garbage collect. + while (lineEnd < text.length() - 1) { + lineEnd = text.indexOf('\n', lineStart); + if (lineEnd == -1) { + lineEnd = text.length() - 1; + } + line = text.substring(lineStart, lineEnd + 1); + + if (lineHash.containsKey(line)) { + chars.append(String.valueOf((char) (int) lineHash.get(line))); + } else { + if (lineArray.size() == maxLines) { + // Bail out at 65535 because + // String.valueOf((char) 65536).equals(String.valueOf(((char) 0))) + line = text.substring(lineStart); + lineEnd = text.length(); + } + lineArray.add(line); + lineHash.put(line, lineArray.size() - 1); + chars.append(String.valueOf((char) (lineArray.size() - 1))); + } + lineStart = lineEnd + 1; + } + return chars.toString(); + } + + /** + * Rehydrate the text in a diff from a string of line hashes to real lines of + * text. + * + * @param diffs List of Diff objects. + * @param lineArray List of unique strings. + */ + protected void diff_charsToLines(List diffs, + List lineArray) { + StringBuilder text; + for (Diff diff : diffs) { + text = new StringBuilder(); + for (int j = 0; j < diff.text.length(); j++) { + text.append(lineArray.get(diff.text.charAt(j))); + } + diff.text = text.toString(); + } + } + + /** + * Determine the common prefix of two strings + * + * @param text1 First string. + * @param text2 Second string. + * @return The number of characters common to the start of each string. + */ + public int diff_commonPrefix(String text1, String text2) { + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ + int n = Math.min(text1.length(), text2.length()); + for (int i = 0; i < n; i++) { + if (text1.charAt(i) != text2.charAt(i)) { + return i; + } + } + return n; + } + + /** + * Determine the common suffix of two strings + * + * @param text1 First string. + * @param text2 Second string. + * @return The number of characters common to the end of each string. + */ + public int diff_commonSuffix(String text1, String text2) { + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ + int text1_length = text1.length(); + int text2_length = text2.length(); + int n = Math.min(text1_length, text2_length); + for (int i = 1; i <= n; i++) { + if (text1.charAt(text1_length - i) != text2.charAt(text2_length - i)) { + return i - 1; + } + } + return n; + } + + /** + * Determine if the suffix of one string is the prefix of another. + * + * @param text1 First string. + * @param text2 Second string. + * @return The number of characters common to the end of the first + * string and the start of the second string. + */ + protected int diff_commonOverlap(String text1, String text2) { + // Cache the text lengths to prevent multiple calls. + int text1_length = text1.length(); + int text2_length = text2.length(); + // Eliminate the null case. + if (text1_length == 0 || text2_length == 0) { + return 0; + } + // Truncate the longer string. + if (text1_length > text2_length) { + text1 = text1.substring(text1_length - text2_length); + } else if (text1_length < text2_length) { + text2 = text2.substring(0, text1_length); + } + int text_length = Math.min(text1_length, text2_length); + // Quick check for the worst case. + if (text1.equals(text2)) { + return text_length; + } + + // Start by looking for a single character match + // and increase length until no match is found. + // Performance analysis: https://neil.fraser.name/news/2010/11/04/ + int best = 0; + int length = 1; + while (true) { + String pattern = text1.substring(text_length - length); + int found = text2.indexOf(pattern); + if (found == -1) { + return best; + } + length += found; + if (found == 0 || text1.substring(text_length - length).equals( + text2.substring(0, length))) { + best = length; + length++; + } + } + } + + /** + * Do the two texts share a substring which is at least half the length of + * the longer text? + * This speedup can produce non-minimal diffs. + * + * @param text1 First string. + * @param text2 Second string. + * @return Five element String array, containing the prefix of text1, the + * suffix of text1, the prefix of text2, the suffix of text2 and the + * common middle. Or null if there was no match. + */ + protected String[] diff_halfMatch(String text1, String text2) { + if (Diff_Timeout <= 0) { + // Don't risk returning a non-optimal diff if we have unlimited time. + return null; + } + String longtext = text1.length() > text2.length() ? text1 : text2; + String shorttext = text1.length() > text2.length() ? text2 : text1; + if (longtext.length() < 4 || shorttext.length() * 2 < longtext.length()) { + return null; // Pointless. + } + + // First check if the second quarter is the seed for a half-match. + String[] hm1 = diff_halfMatchI(longtext, shorttext, + (longtext.length() + 3) / 4); + // Check again based on the third quarter. + String[] hm2 = diff_halfMatchI(longtext, shorttext, + (longtext.length() + 1) / 2); + String[] hm; + if (hm1 == null && hm2 == null) { + return null; + } else if (hm2 == null) { + hm = hm1; + } else if (hm1 == null) { + hm = hm2; + } else { + // Both matched. Select the longest. + hm = hm1[4].length() > hm2[4].length() ? hm1 : hm2; + } + + // A half-match was found, sort out the return data. + if (text1.length() > text2.length()) { + return hm; + //return new String[]{hm[0], hm[1], hm[2], hm[3], hm[4]}; + } else { + return new String[]{hm[2], hm[3], hm[0], hm[1], hm[4]}; + } + } + + /** + * Does a substring of shorttext exist within longtext such that the + * substring is at least half the length of longtext? + * + * @param longtext Longer string. + * @param shorttext Shorter string. + * @param i Start index of quarter length substring within longtext. + * @return Five element String array, containing the prefix of longtext, the + * suffix of longtext, the prefix of shorttext, the suffix of shorttext + * and the common middle. Or null if there was no match. + */ + private String[] diff_halfMatchI(String longtext, String shorttext, int i) { + // Start with a 1/4 length substring at position i as a seed. + String seed = longtext.substring(i, i + longtext.length() / 4); + int j = -1; + String best_common = ""; + String best_longtext_a = "", best_longtext_b = ""; + String best_shorttext_a = "", best_shorttext_b = ""; + while ((j = shorttext.indexOf(seed, j + 1)) != -1) { + int prefixLength = diff_commonPrefix(longtext.substring(i), + shorttext.substring(j)); + int suffixLength = diff_commonSuffix(longtext.substring(0, i), + shorttext.substring(0, j)); + if (best_common.length() < suffixLength + prefixLength) { + best_common = shorttext.substring(j - suffixLength, j) + + shorttext.substring(j, j + prefixLength); + best_longtext_a = longtext.substring(0, i - suffixLength); + best_longtext_b = longtext.substring(i + prefixLength); + best_shorttext_a = shorttext.substring(0, j - suffixLength); + best_shorttext_b = shorttext.substring(j + prefixLength); + } + } + if (best_common.length() * 2 >= longtext.length()) { + return new String[]{best_longtext_a, best_longtext_b, + best_shorttext_a, best_shorttext_b, best_common}; + } else { + return null; + } + } + + /** + * Reduce the number of edits by eliminating semantically trivial equalities. + * + * @param diffs LinkedList of Diff objects. + */ + public void diff_cleanupSemantic(LinkedList diffs) { + if (diffs.isEmpty()) { + return; + } + boolean changes = false; + Deque equalities = new ArrayDeque(); // Double-ended queue of qualities. + String lastEquality = null; // Always equal to equalities.peek().text + ListIterator pointer = diffs.listIterator(); + // Number of characters that changed prior to the equality. + int length_insertions1 = 0; + int length_deletions1 = 0; + // Number of characters that changed after the equality. + int length_insertions2 = 0; + int length_deletions2 = 0; + Diff thisDiff = pointer.next(); + while (thisDiff != null) { + if (thisDiff.operation == Operation.EQUAL) { + // Equality found. + equalities.push(thisDiff); + length_insertions1 = length_insertions2; + length_deletions1 = length_deletions2; + length_insertions2 = 0; + length_deletions2 = 0; + lastEquality = thisDiff.text; + } else { + // An insertion or deletion. + if (thisDiff.operation == Operation.INSERT) { + length_insertions2 += thisDiff.text.length(); + } else { + length_deletions2 += thisDiff.text.length(); + } + // Eliminate an equality that is smaller or equal to the edits on both + // sides of it. + if (lastEquality != null && (lastEquality.length() + <= Math.max(length_insertions1, length_deletions1)) + && (lastEquality.length() + <= Math.max(length_insertions2, length_deletions2))) { + //System.out.println("Splitting: '" + lastEquality + "'"); + // Walk back to offending equality. + while (thisDiff != equalities.peek()) { + thisDiff = pointer.previous(); + } + pointer.next(); + + // Replace equality with a delete. + pointer.set(new Diff(Operation.DELETE, lastEquality)); + // Insert a corresponding an insert. + pointer.add(new Diff(Operation.INSERT, lastEquality)); + + equalities.pop(); // Throw away the equality we just deleted. + if (!equalities.isEmpty()) { + // Throw away the previous equality (it needs to be reevaluated). + equalities.pop(); + } + if (equalities.isEmpty()) { + // There are no previous equalities, walk back to the start. + while (pointer.hasPrevious()) { + pointer.previous(); + } + } else { + // There is a safe equality we can fall back to. + thisDiff = equalities.peek(); + while (thisDiff != pointer.previous()) { + // Intentionally empty loop. + } + } + + length_insertions1 = 0; // Reset the counters. + length_insertions2 = 0; + length_deletions1 = 0; + length_deletions2 = 0; + lastEquality = null; + changes = true; + } + } + thisDiff = pointer.hasNext() ? pointer.next() : null; + } + + // Normalize the diff. + if (changes) { + diff_cleanupMerge(diffs); + } + diff_cleanupSemanticLossless(diffs); + + // Find any overlaps between deletions and insertions. + // e.g: abcxxxxxxdef + // -> abcxxxdef + // e.g: xxxabcdefxxx + // -> defxxxabc + // Only extract an overlap if it is as big as the edit ahead or behind it. + pointer = diffs.listIterator(); + Diff prevDiff = null; + thisDiff = null; + if (pointer.hasNext()) { + prevDiff = pointer.next(); + if (pointer.hasNext()) { + thisDiff = pointer.next(); + } + } + while (thisDiff != null) { + if (prevDiff.operation == Operation.DELETE && + thisDiff.operation == Operation.INSERT) { + String deletion = prevDiff.text; + String insertion = thisDiff.text; + int overlap_length1 = this.diff_commonOverlap(deletion, insertion); + int overlap_length2 = this.diff_commonOverlap(insertion, deletion); + if (overlap_length1 >= overlap_length2) { + if (overlap_length1 >= deletion.length() / 2.0 || + overlap_length1 >= insertion.length() / 2.0) { + // Overlap found. Insert an equality and trim the surrounding edits. + pointer.previous(); + pointer.add(new Diff(Operation.EQUAL, + insertion.substring(0, overlap_length1))); + prevDiff.text = + deletion.substring(0, deletion.length() - overlap_length1); + thisDiff.text = insertion.substring(overlap_length1); + // pointer.add inserts the element before the cursor, so there is + // no need to step past the new element. + } + } else { + if (overlap_length2 >= deletion.length() / 2.0 || + overlap_length2 >= insertion.length() / 2.0) { + // Reverse overlap found. + // Insert an equality and swap and trim the surrounding edits. + pointer.previous(); + pointer.add(new Diff(Operation.EQUAL, + deletion.substring(0, overlap_length2))); + prevDiff.operation = Operation.INSERT; + prevDiff.text = + insertion.substring(0, insertion.length() - overlap_length2); + thisDiff.operation = Operation.DELETE; + thisDiff.text = deletion.substring(overlap_length2); + // pointer.add inserts the element before the cursor, so there is + // no need to step past the new element. + } + } + thisDiff = pointer.hasNext() ? pointer.next() : null; + } + prevDiff = thisDiff; + thisDiff = pointer.hasNext() ? pointer.next() : null; + } + } + + /** + * Look for single edits surrounded on both sides by equalities + * which can be shifted sideways to align the edit to a word boundary. + * e.g: The cat came. -> The cat came. + * + * @param diffs LinkedList of Diff objects. + */ + public void diff_cleanupSemanticLossless(LinkedList diffs) { + String equality1, edit, equality2; + String commonString; + int commonOffset; + int score, bestScore; + String bestEquality1, bestEdit, bestEquality2; + // Create a new iterator at the start. + ListIterator pointer = diffs.listIterator(); + Diff prevDiff = pointer.hasNext() ? pointer.next() : null; + Diff thisDiff = pointer.hasNext() ? pointer.next() : null; + Diff nextDiff = pointer.hasNext() ? pointer.next() : null; + // Intentionally ignore the first and last element (don't need checking). + while (nextDiff != null) { + if (prevDiff.operation == Operation.EQUAL && + nextDiff.operation == Operation.EQUAL) { + // This is a single edit surrounded by equalities. + equality1 = prevDiff.text; + edit = thisDiff.text; + equality2 = nextDiff.text; + + // First, shift the edit as far left as possible. + commonOffset = diff_commonSuffix(equality1, edit); + if (commonOffset != 0) { + commonString = edit.substring(edit.length() - commonOffset); + equality1 = equality1.substring(0, equality1.length() - commonOffset); + edit = commonString + edit.substring(0, edit.length() - commonOffset); + equality2 = commonString + equality2; + } + + // Second, step character by character right, looking for the best fit. + bestEquality1 = equality1; + bestEdit = edit; + bestEquality2 = equality2; + bestScore = diff_cleanupSemanticScore(equality1, edit) + + diff_cleanupSemanticScore(edit, equality2); + while (edit.length() != 0 && equality2.length() != 0 + && edit.charAt(0) == equality2.charAt(0)) { + equality1 += edit.charAt(0); + edit = edit.substring(1) + equality2.charAt(0); + equality2 = equality2.substring(1); + score = diff_cleanupSemanticScore(equality1, edit) + + diff_cleanupSemanticScore(edit, equality2); + // The >= encourages trailing rather than leading whitespace on edits. + if (score >= bestScore) { + bestScore = score; + bestEquality1 = equality1; + bestEdit = edit; + bestEquality2 = equality2; + } + } + + if (!prevDiff.text.equals(bestEquality1)) { + // We have an improvement, save it back to the diff. + if (bestEquality1.length() != 0) { + prevDiff.text = bestEquality1; + } else { + pointer.previous(); // Walk past nextDiff. + pointer.previous(); // Walk past thisDiff. + pointer.previous(); // Walk past prevDiff. + pointer.remove(); // Delete prevDiff. + pointer.next(); // Walk past thisDiff. + pointer.next(); // Walk past nextDiff. + } + thisDiff.text = bestEdit; + if (bestEquality2.length() != 0) { + nextDiff.text = bestEquality2; + } else { + pointer.remove(); // Delete nextDiff. + nextDiff = thisDiff; + thisDiff = prevDiff; + } + } + } + prevDiff = thisDiff; + thisDiff = nextDiff; + nextDiff = pointer.hasNext() ? pointer.next() : null; + } + } + + /** + * Given two strings, compute a score representing whether the internal + * boundary falls on logical boundaries. + * Scores range from 6 (best) to 0 (worst). + * + * @param one First string. + * @param two Second string. + * @return The score. + */ + private int diff_cleanupSemanticScore(String one, String two) { + if (one.length() == 0 || two.length() == 0) { + // Edges are the best. + return 6; + } + + // Each port of this function behaves slightly differently due to + // subtle differences in each language's definition of things like + // 'whitespace'. Since this function's purpose is largely cosmetic, + // the choice has been made to use each language's native features + // rather than force total conformity. + char char1 = one.charAt(one.length() - 1); + char char2 = two.charAt(0); + boolean nonAlphaNumeric1 = !Character.isLetterOrDigit(char1); + boolean nonAlphaNumeric2 = !Character.isLetterOrDigit(char2); + boolean whitespace1 = nonAlphaNumeric1 && Character.isWhitespace(char1); + boolean whitespace2 = nonAlphaNumeric2 && Character.isWhitespace(char2); + boolean lineBreak1 = whitespace1 + && Character.getType(char1) == Character.CONTROL; + boolean lineBreak2 = whitespace2 + && Character.getType(char2) == Character.CONTROL; + boolean blankLine1 = lineBreak1 && BLANKLINEEND.matcher(one).find(); + boolean blankLine2 = lineBreak2 && BLANKLINESTART.matcher(two).find(); + + if (blankLine1 || blankLine2) { + // Five points for blank lines. + return 5; + } else if (lineBreak1 || lineBreak2) { + // Four points for line breaks. + return 4; + } else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) { + // Three points for end of sentences. + return 3; + } else if (whitespace1 || whitespace2) { + // Two points for whitespace. + return 2; + } else if (nonAlphaNumeric1 || nonAlphaNumeric2) { + // One point for non-alphanumeric. + return 1; + } + return 0; + } + + // Define some regex patterns for matching boundaries. + private Pattern BLANKLINEEND + = Pattern.compile("\\n\\r?\\n\\Z", Pattern.DOTALL); + private Pattern BLANKLINESTART + = Pattern.compile("\\A\\r?\\n\\r?\\n", Pattern.DOTALL); + + /** + * Reduce the number of edits by eliminating operationally trivial equalities. + * + * @param diffs LinkedList of Diff objects. + */ + public void diff_cleanupEfficiency(LinkedList diffs) { + if (diffs.isEmpty()) { + return; + } + boolean changes = false; + Deque equalities = new ArrayDeque(); // Double-ended queue of equalities. + String lastEquality = null; // Always equal to equalities.peek().text + ListIterator pointer = diffs.listIterator(); + // Is there an insertion operation before the last equality. + boolean pre_ins = false; + // Is there a deletion operation before the last equality. + boolean pre_del = false; + // Is there an insertion operation after the last equality. + boolean post_ins = false; + // Is there a deletion operation after the last equality. + boolean post_del = false; + Diff thisDiff = pointer.next(); + Diff safeDiff = thisDiff; // The last Diff that is known to be unsplittable. + while (thisDiff != null) { + if (thisDiff.operation == Operation.EQUAL) { + // Equality found. + if (thisDiff.text.length() < Diff_EditCost && (post_ins || post_del)) { + // Candidate found. + equalities.push(thisDiff); + pre_ins = post_ins; + pre_del = post_del; + lastEquality = thisDiff.text; + } else { + // Not a candidate, and can never become one. + equalities.clear(); + lastEquality = null; + safeDiff = thisDiff; + } + post_ins = post_del = false; + } else { + // An insertion or deletion. + if (thisDiff.operation == Operation.DELETE) { + post_del = true; + } else { + post_ins = true; + } + /* + * Five types to be split: + * ABXYCD + * AXCD + * ABXC + * AXCD + * ABXC + */ + if (lastEquality != null + && ((pre_ins && pre_del && post_ins && post_del) + || ((lastEquality.length() < Diff_EditCost / 2) + && ((pre_ins ? 1 : 0) + (pre_del ? 1 : 0) + + (post_ins ? 1 : 0) + (post_del ? 1 : 0)) == 3))) { + //System.out.println("Splitting: '" + lastEquality + "'"); + // Walk back to offending equality. + while (thisDiff != equalities.peek()) { + thisDiff = pointer.previous(); + } + pointer.next(); + + // Replace equality with a delete. + pointer.set(new Diff(Operation.DELETE, lastEquality)); + // Insert a corresponding an insert. + pointer.add(thisDiff = new Diff(Operation.INSERT, lastEquality)); + + equalities.pop(); // Throw away the equality we just deleted. + lastEquality = null; + if (pre_ins && pre_del) { + // No changes made which could affect previous entry, keep going. + post_ins = post_del = true; + equalities.clear(); + safeDiff = thisDiff; + } else { + if (!equalities.isEmpty()) { + // Throw away the previous equality (it needs to be reevaluated). + equalities.pop(); + } + if (equalities.isEmpty()) { + // There are no previous questionable equalities, + // walk back to the last known safe diff. + thisDiff = safeDiff; + } else { + // There is an equality we can fall back to. + thisDiff = equalities.peek(); + } + while (thisDiff != pointer.previous()) { + // Intentionally empty loop. + } + post_ins = post_del = false; + } + + changes = true; + } + } + thisDiff = pointer.hasNext() ? pointer.next() : null; + } + + if (changes) { + diff_cleanupMerge(diffs); + } + } + + /** + * Reorder and merge like edit sections. Merge equalities. + * Any edit section can move as long as it doesn't cross an equality. + * + * @param diffs LinkedList of Diff objects. + */ + public void diff_cleanupMerge(LinkedList diffs) { + diffs.add(new Diff(Operation.EQUAL, "")); // Add a dummy entry at the end. + ListIterator pointer = diffs.listIterator(); + int count_delete = 0; + int count_insert = 0; + String text_delete = ""; + String text_insert = ""; + Diff thisDiff = pointer.next(); + Diff prevEqual = null; + int commonlength; + while (thisDiff != null) { + switch (thisDiff.operation) { + case INSERT: + count_insert++; + text_insert += thisDiff.text; + prevEqual = null; + break; + case DELETE: + count_delete++; + text_delete += thisDiff.text; + prevEqual = null; + break; + case EQUAL: + if (count_delete + count_insert > 1) { + boolean both_types = count_delete != 0 && count_insert != 0; + // Delete the offending records. + pointer.previous(); // Reverse direction. + while (count_delete-- > 0) { + pointer.previous(); + pointer.remove(); + } + while (count_insert-- > 0) { + pointer.previous(); + pointer.remove(); + } + if (both_types) { + // Factor out any common prefixies. + commonlength = diff_commonPrefix(text_insert, text_delete); + if (commonlength != 0) { + if (pointer.hasPrevious()) { + thisDiff = pointer.previous(); + assert thisDiff.operation == Operation.EQUAL + : "Previous diff should have been an equality."; + thisDiff.text += text_insert.substring(0, commonlength); + pointer.next(); + } else { + pointer.add(new Diff(Operation.EQUAL, + text_insert.substring(0, commonlength))); + } + text_insert = text_insert.substring(commonlength); + text_delete = text_delete.substring(commonlength); + } + // Factor out any common suffixies. + commonlength = diff_commonSuffix(text_insert, text_delete); + if (commonlength != 0) { + thisDiff = pointer.next(); + thisDiff.text = text_insert.substring(text_insert.length() + - commonlength) + thisDiff.text; + text_insert = text_insert.substring(0, text_insert.length() + - commonlength); + text_delete = text_delete.substring(0, text_delete.length() + - commonlength); + pointer.previous(); + } + } + // Insert the merged records. + if (text_delete.length() != 0) { + pointer.add(new Diff(Operation.DELETE, text_delete)); + } + if (text_insert.length() != 0) { + pointer.add(new Diff(Operation.INSERT, text_insert)); + } + // Step forward to the equality. + thisDiff = pointer.hasNext() ? pointer.next() : null; + } else if (prevEqual != null) { + // Merge this equality with the previous one. + prevEqual.text += thisDiff.text; + pointer.remove(); + thisDiff = pointer.previous(); + pointer.next(); // Forward direction + } + count_insert = 0; + count_delete = 0; + text_delete = ""; + text_insert = ""; + prevEqual = thisDiff; + break; + } + thisDiff = pointer.hasNext() ? pointer.next() : null; + } + if (diffs.getLast().text.length() == 0) { + diffs.removeLast(); // Remove the dummy entry at the end. + } + + /* + * Second pass: look for single edits surrounded on both sides by equalities + * which can be shifted sideways to eliminate an equality. + * e.g: ABAC -> ABAC + */ + boolean changes = false; + // Create a new iterator at the start. + // (As opposed to walking the current one back.) + pointer = diffs.listIterator(); + Diff prevDiff = pointer.hasNext() ? pointer.next() : null; + thisDiff = pointer.hasNext() ? pointer.next() : null; + Diff nextDiff = pointer.hasNext() ? pointer.next() : null; + // Intentionally ignore the first and last element (don't need checking). + while (nextDiff != null) { + if (prevDiff.operation == Operation.EQUAL && + nextDiff.operation == Operation.EQUAL) { + // This is a single edit surrounded by equalities. + if (thisDiff.text.endsWith(prevDiff.text)) { + // Shift the edit over the previous equality. + thisDiff.text = prevDiff.text + + thisDiff.text.substring(0, thisDiff.text.length() + - prevDiff.text.length()); + nextDiff.text = prevDiff.text + nextDiff.text; + pointer.previous(); // Walk past nextDiff. + pointer.previous(); // Walk past thisDiff. + pointer.previous(); // Walk past prevDiff. + pointer.remove(); // Delete prevDiff. + pointer.next(); // Walk past thisDiff. + thisDiff = pointer.next(); // Walk past nextDiff. + nextDiff = pointer.hasNext() ? pointer.next() : null; + changes = true; + } else if (thisDiff.text.startsWith(nextDiff.text)) { + // Shift the edit over the next equality. + prevDiff.text += nextDiff.text; + thisDiff.text = thisDiff.text.substring(nextDiff.text.length()) + + nextDiff.text; + pointer.remove(); // Delete nextDiff. + nextDiff = pointer.hasNext() ? pointer.next() : null; + changes = true; + } + } + prevDiff = thisDiff; + thisDiff = nextDiff; + nextDiff = pointer.hasNext() ? pointer.next() : null; + } + // If shifts were made, the diff needs reordering and another shift sweep. + if (changes) { + diff_cleanupMerge(diffs); + } + } + + /** + * loc is a location in text1, compute and return the equivalent location in + * text2. + * e.g. "The cat" vs "The big cat", 1->1, 5->8 + * + * @param diffs List of Diff objects. + * @param loc Location within text1. + * @return Location within text2. + */ + public int diff_xIndex(List diffs, int loc) { + int chars1 = 0; + int chars2 = 0; + int last_chars1 = 0; + int last_chars2 = 0; + Diff lastDiff = null; + for (Diff aDiff : diffs) { + if (aDiff.operation != Operation.INSERT) { + // Equality or deletion. + chars1 += aDiff.text.length(); + } + if (aDiff.operation != Operation.DELETE) { + // Equality or insertion. + chars2 += aDiff.text.length(); + } + if (chars1 > loc) { + // Overshot the location. + lastDiff = aDiff; + break; + } + last_chars1 = chars1; + last_chars2 = chars2; + } + if (lastDiff != null && lastDiff.operation == Operation.DELETE) { + // The location was deleted. + return last_chars2; + } + // Add the remaining character length. + return last_chars2 + (loc - last_chars1); + } + + /** + * Convert a Diff list into a pretty HTML report. + * + * @param diffs List of Diff objects. + * @return HTML representation. + */ + public String diff_prettyHtml(List diffs) { + StringBuilder html = new StringBuilder(); + for (Diff aDiff : diffs) { + String text = aDiff.text.replace("&", "&").replace("<", "<") + .replace(">", ">").replace("\n", "¶
"); + switch (aDiff.operation) { + case INSERT: + html.append("").append(text) + .append(""); + break; + case DELETE: + html.append("").append(text) + .append(""); + break; + case EQUAL: + html.append("").append(text).append(""); + break; + } + } + return html.toString(); + } + + /** + * Compute and return the source text (all equalities and deletions). + * + * @param diffs List of Diff objects. + * @return Source text. + */ + public String diff_text1(List diffs) { + StringBuilder text = new StringBuilder(); + for (Diff aDiff : diffs) { + if (aDiff.operation != Operation.INSERT) { + text.append(aDiff.text); + } + } + return text.toString(); + } + + /** + * Compute and return the destination text (all equalities and insertions). + * + * @param diffs List of Diff objects. + * @return Destination text. + */ + public String diff_text2(List diffs) { + StringBuilder text = new StringBuilder(); + for (Diff aDiff : diffs) { + if (aDiff.operation != Operation.DELETE) { + text.append(aDiff.text); + } + } + return text.toString(); + } + + /** + * Compute the Levenshtein distance; the number of inserted, deleted or + * substituted characters. + * + * @param diffs List of Diff objects. + * @return Number of changes. + */ + public int diff_levenshtein(List diffs) { + int levenshtein = 0; + int insertions = 0; + int deletions = 0; + for (Diff aDiff : diffs) { + switch (aDiff.operation) { + case INSERT: + insertions += aDiff.text.length(); + break; + case DELETE: + deletions += aDiff.text.length(); + break; + case EQUAL: + // A deletion and an insertion is one substitution. + levenshtein += Math.max(insertions, deletions); + insertions = 0; + deletions = 0; + break; + } + } + levenshtein += Math.max(insertions, deletions); + return levenshtein; + } + + /** + * Crush the diff into an encoded string which describes the operations + * required to transform text1 into text2. + * E.g. =3\t-2\t+ing -> Keep 3 chars, delete 2 chars, insert 'ing'. + * Operations are tab-separated. Inserted text is escaped using %xx notation. + * + * @param diffs List of Diff objects. + * @return Delta text. + */ + public String diff_toDelta(List diffs) { + StringBuilder text = new StringBuilder(); + for (Diff aDiff : diffs) { + switch (aDiff.operation) { + case INSERT: + try { + text.append("+").append(URLEncoder.encode(aDiff.text, "UTF-8") + .replace('+', ' ')).append("\t"); + } catch (UnsupportedEncodingException e) { + // Not likely on modern system. + throw new Error("This system does not support UTF-8.", e); + } + break; + case DELETE: + text.append("-").append(aDiff.text.length()).append("\t"); + break; + case EQUAL: + text.append("=").append(aDiff.text.length()).append("\t"); + break; + } + } + String delta = text.toString(); + if (delta.length() != 0) { + // Strip off trailing tab character. + delta = delta.substring(0, delta.length() - 1); + delta = unescapeForEncodeUriCompatability(delta); + } + return delta; + } + + /** + * Given the original text1, and an encoded string which describes the + * operations required to transform text1 into text2, compute the full diff. + * + * @param text1 Source string for the diff. + * @param delta Delta text. + * @return Array of Diff objects or null if invalid. + * @throws IllegalArgumentException If invalid input. + */ + public LinkedList diff_fromDelta(String text1, String delta) + throws IllegalArgumentException { + LinkedList diffs = new LinkedList(); + int pointer = 0; // Cursor in text1 + String[] tokens = delta.split("\t"); + for (String token : tokens) { + if (token.length() == 0) { + // Blank tokens are ok (from a trailing \t). + continue; + } + // Each token begins with a one character parameter which specifies the + // operation of this token (delete, insert, equality). + String param = token.substring(1); + switch (token.charAt(0)) { + case '+': + // decode would change all "+" to " " + param = param.replace("+", "%2B"); + try { + param = URLDecoder.decode(param, "UTF-8"); + } catch (UnsupportedEncodingException e) { + // Not likely on modern system. + throw new Error("This system does not support UTF-8.", e); + } catch (IllegalArgumentException e) { + // Malformed URI sequence. + throw new IllegalArgumentException( + "Illegal escape in diff_fromDelta: " + param, e); + } + diffs.add(new Diff(Operation.INSERT, param)); + break; + case '-': + // Fall through. + case '=': + int n; + try { + n = Integer.parseInt(param); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Invalid number in diff_fromDelta: " + param, e); + } + if (n < 0) { + throw new IllegalArgumentException( + "Negative number in diff_fromDelta: " + param); + } + String text; + try { + text = text1.substring(pointer, pointer += n); + } catch (StringIndexOutOfBoundsException e) { + throw new IllegalArgumentException("Delta length (" + pointer + + ") larger than source text length (" + text1.length() + + ").", e); + } + if (token.charAt(0) == '=') { + diffs.add(new Diff(Operation.EQUAL, text)); + } else { + diffs.add(new Diff(Operation.DELETE, text)); + } + break; + default: + // Anything else is an error. + throw new IllegalArgumentException( + "Invalid diff operation in diff_fromDelta: " + token.charAt(0)); + } + } + if (pointer != text1.length()) { + throw new IllegalArgumentException("Delta length (" + pointer + + ") smaller than source text length (" + text1.length() + ")."); + } + return diffs; + } + + + // MATCH FUNCTIONS + + + /** + * Locate the best instance of 'pattern' in 'text' near 'loc'. + * Returns -1 if no match found. + * + * @param text The text to search. + * @param pattern The pattern to search for. + * @param loc The location to search around. + * @return Best match index or -1. + */ + public int match_main(String text, String pattern, int loc) { + // Check for null inputs. + if (text == null || pattern == null) { + throw new IllegalArgumentException("Null inputs. (match_main)"); + } + + loc = Math.max(0, Math.min(loc, text.length())); + if (text.equals(pattern)) { + // Shortcut (potentially not guaranteed by the algorithm) + return 0; + } else if (text.length() == 0) { + // Nothing to match. + return -1; + } else if (loc + pattern.length() <= text.length() + && text.substring(loc, loc + pattern.length()).equals(pattern)) { + // Perfect match at the perfect spot! (Includes case of null pattern) + return loc; + } else { + // Do a fuzzy compare. + return match_bitap(text, pattern, loc); + } + } + + /** + * Locate the best instance of 'pattern' in 'text' near 'loc' using the + * Bitap algorithm. Returns -1 if no match found. + * + * @param text The text to search. + * @param pattern The pattern to search for. + * @param loc The location to search around. + * @return Best match index or -1. + */ + protected int match_bitap(String text, String pattern, int loc) { + assert (Match_MaxBits == 0 || pattern.length() <= Match_MaxBits) + : "Pattern too long for this application."; + + // Initialise the alphabet. + Map s = match_alphabet(pattern); + + // Highest score beyond which we give up. + double score_threshold = Match_Threshold; + // Is there a nearby exact match? (speedup) + int best_loc = text.indexOf(pattern, loc); + if (best_loc != -1) { + score_threshold = Math.min(match_bitapScore(0, best_loc, loc, pattern), + score_threshold); + // What about in the other direction? (speedup) + best_loc = text.lastIndexOf(pattern, loc + pattern.length()); + if (best_loc != -1) { + score_threshold = Math.min(match_bitapScore(0, best_loc, loc, pattern), + score_threshold); + } + } + + // Initialise the bit arrays. + int matchmask = 1 << (pattern.length() - 1); + best_loc = -1; + + int bin_min, bin_mid; + int bin_max = pattern.length() + text.length(); + // Empty initialization added to appease Java compiler. + int[] last_rd = new int[0]; + for (int d = 0; d < pattern.length(); d++) { + // Scan for the best match; each iteration allows for one more error. + // Run a binary search to determine how far from 'loc' we can stray at + // this error level. + bin_min = 0; + bin_mid = bin_max; + while (bin_min < bin_mid) { + if (match_bitapScore(d, loc + bin_mid, loc, pattern) + <= score_threshold) { + bin_min = bin_mid; + } else { + bin_max = bin_mid; + } + bin_mid = (bin_max - bin_min) / 2 + bin_min; + } + // Use the result from this iteration as the maximum for the next. + bin_max = bin_mid; + int start = Math.max(1, loc - bin_mid + 1); + int finish = Math.min(loc + bin_mid, text.length()) + pattern.length(); + + int[] rd = new int[finish + 2]; + rd[finish + 1] = (1 << d) - 1; + for (int j = finish; j >= start; j--) { + int charMatch; + if (text.length() <= j - 1 || !s.containsKey(text.charAt(j - 1))) { + // Out of range. + charMatch = 0; + } else { + charMatch = s.get(text.charAt(j - 1)); + } + if (d == 0) { + // First pass: exact match. + rd[j] = ((rd[j + 1] << 1) | 1) & charMatch; + } else { + // Subsequent passes: fuzzy match. + rd[j] = (((rd[j + 1] << 1) | 1) & charMatch) + | (((last_rd[j + 1] | last_rd[j]) << 1) | 1) | last_rd[j + 1]; + } + if ((rd[j] & matchmask) != 0) { + double score = match_bitapScore(d, j - 1, loc, pattern); + // This match will almost certainly be better than any existing + // match. But check anyway. + if (score <= score_threshold) { + // Told you so. + score_threshold = score; + best_loc = j - 1; + if (best_loc > loc) { + // When passing loc, don't exceed our current distance from loc. + start = Math.max(1, 2 * loc - best_loc); + } else { + // Already passed loc, downhill from here on in. + break; + } + } + } + } + if (match_bitapScore(d + 1, loc, loc, pattern) > score_threshold) { + // No hope for a (better) match at greater error levels. + break; + } + last_rd = rd; + } + return best_loc; + } + + /** + * Compute and return the score for a match with e errors and x location. + * + * @param e Number of errors in match. + * @param x Location of match. + * @param loc Expected location of match. + * @param pattern Pattern being sought. + * @return Overall score for match (0.0 = good, 1.0 = bad). + */ + private double match_bitapScore(int e, int x, int loc, String pattern) { + float accuracy = (float) e / pattern.length(); + int proximity = Math.abs(loc - x); + if (Match_Distance == 0) { + // Dodge divide by zero error. + return proximity == 0 ? accuracy : 1.0; + } + return accuracy + (proximity / (float) Match_Distance); + } + + /** + * Initialise the alphabet for the Bitap algorithm. + * + * @param pattern The text to encode. + * @return Hash of character locations. + */ + protected Map match_alphabet(String pattern) { + Map s = new HashMap(); + char[] char_pattern = pattern.toCharArray(); + for (char c : char_pattern) { + s.put(c, 0); + } + int i = 0; + for (char c : char_pattern) { + s.put(c, s.get(c) | (1 << (pattern.length() - i - 1))); + i++; + } + return s; + } + + + // PATCH FUNCTIONS + + + /** + * Increase the context until it is unique, + * but don't let the pattern expand beyond Match_MaxBits. + * + * @param patch The patch to grow. + * @param text Source text. + */ + protected void patch_addContext(Patch patch, String text) { + if (text.length() == 0) { + return; + } + String pattern = text.substring(patch.start2, patch.start2 + patch.length1); + int padding = 0; + + // Look for the first and last matches of pattern in text. If two different + // matches are found, increase the pattern length. + while (text.indexOf(pattern) != text.lastIndexOf(pattern) + && pattern.length() < Match_MaxBits - Patch_Margin - Patch_Margin) { + padding += Patch_Margin; + pattern = text.substring(Math.max(0, patch.start2 - padding), + Math.min(text.length(), patch.start2 + patch.length1 + padding)); + } + // Add one chunk for good luck. + padding += Patch_Margin; + + // Add the prefix. + String prefix = text.substring(Math.max(0, patch.start2 - padding), + patch.start2); + if (prefix.length() != 0) { + patch.diffs.addFirst(new Diff(Operation.EQUAL, prefix)); + } + // Add the suffix. + String suffix = text.substring(patch.start2 + patch.length1, + Math.min(text.length(), patch.start2 + patch.length1 + padding)); + if (suffix.length() != 0) { + patch.diffs.addLast(new Diff(Operation.EQUAL, suffix)); + } + + // Roll back the start points. + patch.start1 -= prefix.length(); + patch.start2 -= prefix.length(); + // Extend the lengths. + patch.length1 += prefix.length() + suffix.length(); + patch.length2 += prefix.length() + suffix.length(); + } + + /** + * Compute a list of patches to turn text1 into text2. + * A set of diffs will be computed. + * + * @param text1 Old text. + * @param text2 New text. + * @return LinkedList of Patch objects. + */ + public LinkedList patch_make(String text1, String text2) { + if (text1 == null || text2 == null) { + throw new IllegalArgumentException("Null inputs. (patch_make)"); + } + // No diffs provided, compute our own. + LinkedList diffs = diff_main(text1, text2, true); + if (diffs.size() > 2) { + diff_cleanupSemantic(diffs); + diff_cleanupEfficiency(diffs); + } + return patch_make(text1, diffs); + } + + /** + * Compute a list of patches to turn text1 into text2. + * text1 will be derived from the provided diffs. + * + * @param diffs Array of Diff objects for text1 to text2. + * @return LinkedList of Patch objects. + */ + public LinkedList patch_make(LinkedList diffs) { + if (diffs == null) { + throw new IllegalArgumentException("Null inputs. (patch_make)"); + } + // No origin string provided, compute our own. + String text1 = diff_text1(diffs); + return patch_make(text1, diffs); + } + + /** + * Compute a list of patches to turn text1 into text2. + * text2 is ignored, diffs are the delta between text1 and text2. + * + * @param text1 Old text + * @param text2 Ignored. + * @param diffs Array of Diff objects for text1 to text2. + * @return LinkedList of Patch objects. + * @deprecated Prefer patch_make(String text1, LinkedList diffs). + */ + @Deprecated + public LinkedList patch_make(String text1, String text2, + LinkedList diffs) { + return patch_make(text1, diffs); + } + + /** + * Compute a list of patches to turn text1 into text2. + * text2 is not provided, diffs are the delta between text1 and text2. + * + * @param text1 Old text. + * @param diffs Array of Diff objects for text1 to text2. + * @return LinkedList of Patch objects. + */ + public LinkedList patch_make(String text1, LinkedList diffs) { + if (text1 == null || diffs == null) { + throw new IllegalArgumentException("Null inputs. (patch_make)"); + } + + LinkedList patches = new LinkedList(); + if (diffs.isEmpty()) { + return patches; // Get rid of the null case. + } + Patch patch = new Patch(); + int char_count1 = 0; // Number of characters into the text1 string. + int char_count2 = 0; // Number of characters into the text2 string. + // Start with text1 (prepatch_text) and apply the diffs until we arrive at + // text2 (postpatch_text). We recreate the patches one by one to determine + // context info. + String prepatch_text = text1; + String postpatch_text = text1; + for (Diff aDiff : diffs) { + if (patch.diffs.isEmpty() && aDiff.operation != Operation.EQUAL) { + // A new patch starts here. + patch.start1 = char_count1; + patch.start2 = char_count2; + } + + switch (aDiff.operation) { + case INSERT: + patch.diffs.add(aDiff); + patch.length2 += aDiff.text.length(); + postpatch_text = postpatch_text.substring(0, char_count2) + + aDiff.text + postpatch_text.substring(char_count2); + break; + case DELETE: + patch.length1 += aDiff.text.length(); + patch.diffs.add(aDiff); + postpatch_text = postpatch_text.substring(0, char_count2) + + postpatch_text.substring(char_count2 + aDiff.text.length()); + break; + case EQUAL: + if (aDiff.text.length() <= 2 * Patch_Margin + && !patch.diffs.isEmpty() && aDiff != diffs.getLast()) { + // Small equality inside a patch. + patch.diffs.add(aDiff); + patch.length1 += aDiff.text.length(); + patch.length2 += aDiff.text.length(); + } + + if (aDiff.text.length() >= 2 * Patch_Margin && !patch.diffs.isEmpty()) { + // Time for a new patch. + if (!patch.diffs.isEmpty()) { + patch_addContext(patch, prepatch_text); + patches.add(patch); + patch = new Patch(); + // Unlike Unidiff, our patch lists have a rolling context. + // https://github.com/google/diff-match-patch/wiki/Unidiff + // Update prepatch text & pos to reflect the application of the + // just completed patch. + prepatch_text = postpatch_text; + char_count1 = char_count2; + } + } + break; + } + + // Update the current character count. + if (aDiff.operation != Operation.INSERT) { + char_count1 += aDiff.text.length(); + } + if (aDiff.operation != Operation.DELETE) { + char_count2 += aDiff.text.length(); + } + } + // Pick up the leftover patch if not empty. + if (!patch.diffs.isEmpty()) { + patch_addContext(patch, prepatch_text); + patches.add(patch); + } + + return patches; + } + + /** + * Given an array of patches, return another array that is identical. + * + * @param patches Array of Patch objects. + * @return Array of Patch objects. + */ + public LinkedList patch_deepCopy(LinkedList patches) { + LinkedList patchesCopy = new LinkedList(); + for (Patch aPatch : patches) { + Patch patchCopy = new Patch(); + for (Diff aDiff : aPatch.diffs) { + Diff diffCopy = new Diff(aDiff.operation, aDiff.text); + patchCopy.diffs.add(diffCopy); + } + patchCopy.start1 = aPatch.start1; + patchCopy.start2 = aPatch.start2; + patchCopy.length1 = aPatch.length1; + patchCopy.length2 = aPatch.length2; + patchesCopy.add(patchCopy); + } + return patchesCopy; + } + + /** + * Merge a set of patches onto the text. Return a patched text, as well + * as an array of true/false values indicating which patches were applied. + * + * @param patches Array of Patch objects + * @param text Old text. + * @return Two element Object array, containing the new text and an array of + * boolean values. + */ + public Object[] patch_apply(LinkedList patches, String text) { + if (patches.isEmpty()) { + return new Object[]{text, new boolean[0]}; + } + + // Deep copy the patches so that no changes are made to originals. + patches = patch_deepCopy(patches); + + String nullPadding = patch_addPadding(patches); + text = nullPadding + text + nullPadding; + patch_splitMax(patches); + + int x = 0; + // delta keeps track of the offset between the expected and actual location + // of the previous patch. If there are patches expected at positions 10 and + // 20, but the first patch was found at 12, delta is 2 and the second patch + // has an effective expected position of 22. + int delta = 0; + boolean[] results = new boolean[patches.size()]; + for (Patch aPatch : patches) { + int expected_loc = aPatch.start2 + delta; + String text1 = diff_text1(aPatch.diffs); + int start_loc; + int end_loc = -1; + if (text1.length() > this.Match_MaxBits) { + // patch_splitMax will only provide an oversized pattern in the case of + // a monster delete. + start_loc = match_main(text, + text1.substring(0, this.Match_MaxBits), expected_loc); + if (start_loc != -1) { + end_loc = match_main(text, + text1.substring(text1.length() - this.Match_MaxBits), + expected_loc + text1.length() - this.Match_MaxBits); + if (end_loc == -1 || start_loc >= end_loc) { + // Can't find valid trailing context. Drop this patch. + start_loc = -1; + } + } + } else { + start_loc = match_main(text, text1, expected_loc); + } + if (start_loc == -1) { + // No match found. :( + results[x] = false; + // Subtract the delta for this failed patch from subsequent patches. + delta -= aPatch.length2 - aPatch.length1; + } else { + // Found a match. :) + results[x] = true; + delta = start_loc - expected_loc; + String text2; + if (end_loc == -1) { + text2 = text.substring(start_loc, + Math.min(start_loc + text1.length(), text.length())); + } else { + text2 = text.substring(start_loc, + Math.min(end_loc + this.Match_MaxBits, text.length())); + } + if (text1.equals(text2)) { + // Perfect match, just shove the replacement text in. + text = text.substring(0, start_loc) + diff_text2(aPatch.diffs) + + text.substring(start_loc + text1.length()); + } else { + // Imperfect match. Run a diff to get a framework of equivalent + // indices. + LinkedList diffs = diff_main(text1, text2, false); + if (text1.length() > this.Match_MaxBits + && diff_levenshtein(diffs) / (float) text1.length() + > this.Patch_DeleteThreshold) { + // The end points match, but the content is unacceptably bad. + results[x] = false; + } else { + diff_cleanupSemanticLossless(diffs); + int index1 = 0; + for (Diff aDiff : aPatch.diffs) { + if (aDiff.operation != Operation.EQUAL) { + int index2 = diff_xIndex(diffs, index1); + if (aDiff.operation == Operation.INSERT) { + // Insertion + text = text.substring(0, start_loc + index2) + aDiff.text + + text.substring(start_loc + index2); + } else if (aDiff.operation == Operation.DELETE) { + // Deletion + text = text.substring(0, start_loc + index2) + + text.substring(start_loc + diff_xIndex(diffs, + index1 + aDiff.text.length())); + } + } + if (aDiff.operation != Operation.DELETE) { + index1 += aDiff.text.length(); + } + } + } + } + } + x++; + } + // Strip the padding off. + text = text.substring(nullPadding.length(), text.length() + - nullPadding.length()); + return new Object[]{text, results}; + } + + /** + * Add some padding on text start and end so that edges can match something. + * Intended to be called only from within patch_apply. + * + * @param patches Array of Patch objects. + * @return The padding string added to each side. + */ + public String patch_addPadding(LinkedList patches) { + short paddingLength = this.Patch_Margin; + String nullPadding = ""; + for (short x = 1; x <= paddingLength; x++) { + nullPadding += String.valueOf((char) x); + } + + // Bump all the patches forward. + for (Patch aPatch : patches) { + aPatch.start1 += paddingLength; + aPatch.start2 += paddingLength; + } + + // Add some padding on start of first diff. + Patch patch = patches.getFirst(); + LinkedList diffs = patch.diffs; + if (diffs.isEmpty() || diffs.getFirst().operation != Operation.EQUAL) { + // Add nullPadding equality. + diffs.addFirst(new Diff(Operation.EQUAL, nullPadding)); + patch.start1 -= paddingLength; // Should be 0. + patch.start2 -= paddingLength; // Should be 0. + patch.length1 += paddingLength; + patch.length2 += paddingLength; + } else if (paddingLength > diffs.getFirst().text.length()) { + // Grow first equality. + Diff firstDiff = diffs.getFirst(); + int extraLength = paddingLength - firstDiff.text.length(); + firstDiff.text = nullPadding.substring(firstDiff.text.length()) + + firstDiff.text; + patch.start1 -= extraLength; + patch.start2 -= extraLength; + patch.length1 += extraLength; + patch.length2 += extraLength; + } + + // Add some padding on end of last diff. + patch = patches.getLast(); + diffs = patch.diffs; + if (diffs.isEmpty() || diffs.getLast().operation != Operation.EQUAL) { + // Add nullPadding equality. + diffs.addLast(new Diff(Operation.EQUAL, nullPadding)); + patch.length1 += paddingLength; + patch.length2 += paddingLength; + } else if (paddingLength > diffs.getLast().text.length()) { + // Grow last equality. + Diff lastDiff = diffs.getLast(); + int extraLength = paddingLength - lastDiff.text.length(); + lastDiff.text += nullPadding.substring(0, extraLength); + patch.length1 += extraLength; + patch.length2 += extraLength; + } + + return nullPadding; + } + + /** + * Look through the patches and break up any which are longer than the + * maximum limit of the match algorithm. + * Intended to be called only from within patch_apply. + * + * @param patches LinkedList of Patch objects. + */ + public void patch_splitMax(LinkedList patches) { + short patch_size = Match_MaxBits; + String precontext, postcontext; + Patch patch; + int start1, start2; + boolean empty; + Operation diff_type; + String diff_text; + ListIterator pointer = patches.listIterator(); + Patch bigpatch = pointer.hasNext() ? pointer.next() : null; + while (bigpatch != null) { + if (bigpatch.length1 <= Match_MaxBits) { + bigpatch = pointer.hasNext() ? pointer.next() : null; + continue; + } + // Remove the big old patch. + pointer.remove(); + start1 = bigpatch.start1; + start2 = bigpatch.start2; + precontext = ""; + while (!bigpatch.diffs.isEmpty()) { + // Create one of several smaller patches. + patch = new Patch(); + empty = true; + patch.start1 = start1 - precontext.length(); + patch.start2 = start2 - precontext.length(); + if (precontext.length() != 0) { + patch.length1 = patch.length2 = precontext.length(); + patch.diffs.add(new Diff(Operation.EQUAL, precontext)); + } + while (!bigpatch.diffs.isEmpty() + && patch.length1 < patch_size - Patch_Margin) { + diff_type = bigpatch.diffs.getFirst().operation; + diff_text = bigpatch.diffs.getFirst().text; + if (diff_type == Operation.INSERT) { + // Insertions are harmless. + patch.length2 += diff_text.length(); + start2 += diff_text.length(); + patch.diffs.addLast(bigpatch.diffs.removeFirst()); + empty = false; + } else if (diff_type == Operation.DELETE && patch.diffs.size() == 1 + && patch.diffs.getFirst().operation == Operation.EQUAL + && diff_text.length() > 2 * patch_size) { + // This is a large deletion. Let it pass in one chunk. + patch.length1 += diff_text.length(); + start1 += diff_text.length(); + empty = false; + patch.diffs.add(new Diff(diff_type, diff_text)); + bigpatch.diffs.removeFirst(); + } else { + // Deletion or equality. Only take as much as we can stomach. + diff_text = diff_text.substring(0, Math.min(diff_text.length(), + patch_size - patch.length1 - Patch_Margin)); + patch.length1 += diff_text.length(); + start1 += diff_text.length(); + if (diff_type == Operation.EQUAL) { + patch.length2 += diff_text.length(); + start2 += diff_text.length(); + } else { + empty = false; + } + patch.diffs.add(new Diff(diff_type, diff_text)); + if (diff_text.equals(bigpatch.diffs.getFirst().text)) { + bigpatch.diffs.removeFirst(); + } else { + bigpatch.diffs.getFirst().text = bigpatch.diffs.getFirst().text + .substring(diff_text.length()); + } + } + } + // Compute the head context for the next patch. + precontext = diff_text2(patch.diffs); + precontext = precontext.substring(Math.max(0, precontext.length() + - Patch_Margin)); + // Append the end context for this patch. + if (diff_text1(bigpatch.diffs).length() > Patch_Margin) { + postcontext = diff_text1(bigpatch.diffs).substring(0, Patch_Margin); + } else { + postcontext = diff_text1(bigpatch.diffs); + } + if (postcontext.length() != 0) { + patch.length1 += postcontext.length(); + patch.length2 += postcontext.length(); + if (!patch.diffs.isEmpty() + && patch.diffs.getLast().operation == Operation.EQUAL) { + patch.diffs.getLast().text += postcontext; + } else { + patch.diffs.add(new Diff(Operation.EQUAL, postcontext)); + } + } + if (!empty) { + pointer.add(patch); + } + } + bigpatch = pointer.hasNext() ? pointer.next() : null; + } + } + + /** + * Take a list of patches and return a textual representation. + * + * @param patches List of Patch objects. + * @return Text representation of patches. + */ + public String patch_toText(List patches) { + StringBuilder text = new StringBuilder(); + for (Patch aPatch : patches) { + text.append(aPatch); + } + return text.toString(); + } + + /** + * Parse a textual representation of patches and return a List of Patch + * objects. + * + * @param textline Text representation of patches. + * @return List of Patch objects. + * @throws IllegalArgumentException If invalid input. + */ + public List patch_fromText(String textline) + throws IllegalArgumentException { + List patches = new LinkedList(); + if (textline.length() == 0) { + return patches; + } + List textList = Arrays.asList(textline.split("\n")); + LinkedList text = new LinkedList(textList); + Patch patch; + Pattern patchHeader + = Pattern.compile("^@@ -(\\d+),?(\\d*) \\+(\\d+),?(\\d*) @@$"); + Matcher m; + char sign; + String line; + while (!text.isEmpty()) { + m = patchHeader.matcher(text.getFirst()); + if (!m.matches()) { + throw new IllegalArgumentException( + "Invalid patch string: " + text.getFirst()); + } + patch = new Patch(); + patches.add(patch); + patch.start1 = Integer.parseInt(m.group(1)); + if (m.group(2).length() == 0) { + patch.start1--; + patch.length1 = 1; + } else if (m.group(2).equals("0")) { + patch.length1 = 0; + } else { + patch.start1--; + patch.length1 = Integer.parseInt(m.group(2)); + } + + patch.start2 = Integer.parseInt(m.group(3)); + if (m.group(4).length() == 0) { + patch.start2--; + patch.length2 = 1; + } else if (m.group(4).equals("0")) { + patch.length2 = 0; + } else { + patch.start2--; + patch.length2 = Integer.parseInt(m.group(4)); + } + text.removeFirst(); + + while (!text.isEmpty()) { + try { + sign = text.getFirst().charAt(0); + } catch (IndexOutOfBoundsException e) { + // Blank line? Whatever. + text.removeFirst(); + continue; + } + line = text.getFirst().substring(1); + line = line.replace("+", "%2B"); // decode would change all "+" to " " + try { + line = URLDecoder.decode(line, "UTF-8"); + } catch (UnsupportedEncodingException e) { + // Not likely on modern system. + throw new Error("This system does not support UTF-8.", e); + } catch (IllegalArgumentException e) { + // Malformed URI sequence. + throw new IllegalArgumentException( + "Illegal escape in patch_fromText: " + line, e); + } + if (sign == '-') { + // Deletion. + patch.diffs.add(new Diff(Operation.DELETE, line)); + } else if (sign == '+') { + // Insertion. + patch.diffs.add(new Diff(Operation.INSERT, line)); + } else if (sign == ' ') { + // Minor equality. + patch.diffs.add(new Diff(Operation.EQUAL, line)); + } else if (sign == '@') { + // Start of next patch. + break; + } else { + // WTF? + throw new IllegalArgumentException( + "Invalid patch mode '" + sign + "' in: " + line); + } + text.removeFirst(); + } + } + return patches; + } + + + /** + * Class representing one diff operation. + */ + public static class Diff { + /** + * One of: INSERT, DELETE or EQUAL. + */ + public Operation operation; + /** + * The text associated with this diff operation. + */ + public String text; + + /** + * Constructor. Initializes the diff with the provided values. + * + * @param operation One of INSERT, DELETE or EQUAL. + * @param text The text being applied. + */ + public Diff(Operation operation, String text) { + // Construct a diff with the specified operation and text. + this.operation = operation; + this.text = text; + } + + /** + * Display a human-readable version of this Diff. + * + * @return text version. + */ + public String toString() { + String prettyText = this.text.replace('\n', '\u00b6'); + return "Diff(" + this.operation + ",\"" + prettyText + "\")"; + } + + /** + * Create a numeric hash value for a Diff. + * This function is not used by DMP. + * + * @return Hash value. + */ + @Override + public int hashCode() { + final int prime = 31; + int result = (operation == null) ? 0 : operation.hashCode(); + result += prime * ((text == null) ? 0 : text.hashCode()); + return result; + } + + /** + * Is this Diff equivalent to another Diff? + * + * @param obj Another Diff to compare against. + * @return true or false. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Diff other = (Diff) obj; + if (operation != other.operation) { + return false; + } + if (text == null) { + if (other.text != null) { + return false; + } + } else if (!text.equals(other.text)) { + return false; + } + return true; + } + } + + + /** + * Class representing one patch operation. + */ + public static class Patch { + public LinkedList diffs; + public int start1; + public int start2; + public int length1; + public int length2; + + /** + * Constructor. Initializes with an empty list of diffs. + */ + public Patch() { + this.diffs = new LinkedList(); + } + + /** + * Emulate GNU diff's format. + * Header: @@ -382,8 +481,9 @@ + * Indices are printed as 1-based, not 0-based. + * + * @return The GNU diff string. + */ + public String toString() { + String coords1, coords2; + if (this.length1 == 0) { + coords1 = this.start1 + ",0"; + } else if (this.length1 == 1) { + coords1 = Integer.toString(this.start1 + 1); + } else { + coords1 = (this.start1 + 1) + "," + this.length1; + } + if (this.length2 == 0) { + coords2 = this.start2 + ",0"; + } else if (this.length2 == 1) { + coords2 = Integer.toString(this.start2 + 1); + } else { + coords2 = (this.start2 + 1) + "," + this.length2; + } + StringBuilder text = new StringBuilder(); + text.append("@@ -").append(coords1).append(" +").append(coords2) + .append(" @@\n"); + // Escape the body of the patch with %xx notation. + for (Diff aDiff : this.diffs) { + switch (aDiff.operation) { + case INSERT: + text.append('+'); + break; + case DELETE: + text.append('-'); + break; + case EQUAL: + text.append(' '); + break; + } + try { + text.append(URLEncoder.encode(aDiff.text, "UTF-8").replace('+', ' ')) + .append("\n"); + } catch (UnsupportedEncodingException e) { + // Not likely on modern system. + throw new Error("This system does not support UTF-8.", e); + } + } + return unescapeForEncodeUriCompatability(text.toString()); + } + } + + /** + * Unescape selected chars for compatability with JavaScript's encodeURI. + * In speed critical applications this could be dropped since the + * receiving application will certainly decode these fine. + * Note that this function is case-sensitive. Thus "%3f" would not be + * unescaped. But this is ok because it is only called with the output of + * URLEncoder.encode which returns uppercase hex. + *

+ * Example: "%3F" -> "?", "%24" -> "$", etc. + * + * @param str The string to escape. + * @return The escaped string. + */ + private static String unescapeForEncodeUriCompatability(String str) { + return str.replace("%21", "!").replace("%7E", "~") + .replace("%27", "'").replace("%28", "(").replace("%29", ")") + .replace("%3B", ";").replace("%2F", "/").replace("%3F", "?") + .replace("%3A", ":").replace("%40", "@").replace("%26", "&") + .replace("%3D", "=").replace("%2B", "+").replace("%24", "$") + .replace("%2C", ",").replace("%23", "#"); + } +} diff --git a/src/main/java/org/codedream/epaper/component/datamanager/FileParser.java b/src/main/java/org/codedream/epaper/component/datamanager/FileParser.java new file mode 100644 index 0000000..d17ac77 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/datamanager/FileParser.java @@ -0,0 +1,101 @@ +package org.codedream.epaper.component.datamanager; + +import org.codedream.epaper.exception.innerservererror.RuntimeIOException; +import org.codedream.epaper.model.file.File; +import org.codedream.epaper.repository.file.FileRepository; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; + +/** + * 文件解析器 + */ +@Component +public class FileParser { + + @Resource + private FileRepository fileRepository; + + @Resource + private SHA512Encoder encoder; + + /** + * 查找缓存 + * @param hash 哈希值 + * @return 文件信息对象 + */ + public Optional find(String hash){ + Iterable files = fileRepository.findAllByHash(hash); + Iterator fileIterator = files.iterator(); + if(!fileIterator.hasNext()) return Optional.empty(); + + return Optional.of(fileIterator.next()); + } + + /** + * 数据散列值计算 + * @param bytesData 文件数据 + * @return 散列值 + */ + public String encode(byte[] bytesData){ + return encoder.encode(Base64.getEncoder().encodeToString(bytesData)); + } + + /** + * 读取文件数据 + * @param stream 输入流 + * @return 文件数据字节数组 + */ + public Optional read(InputStream stream){ + ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream(); + try { + // 双重数据写出 + int readBits = 0; + byte[] rawBytes = new byte[1024]; + while ((readBits = stream.read(rawBytes)) != -1) { + arrayOutputStream.write(rawBytes, 0, readBits); + } + + return Optional.of(arrayOutputStream.toByteArray()); + } catch (IOException e){ + return Optional.empty(); + } + } + + /** + * 将文件数据写入到文件系统 + * @param file 文件信息对象 + * @param bytesData 文件数据 + * @return 文件信息对象(更新后) + */ + public File writeOut(File file, byte[] bytesData){ + String storageName = UUID.randomUUID().toString(); + + Path path = Paths.get(file.getPath(), storageName); + try { + Files.createFile(path); + OutputStream outputStream = Files.newOutputStream(path); + ByteArrayInputStream stream = new ByteArrayInputStream(bytesData); + + // 数据写出文件系统 + int readBits = 0; + byte[] rawBytes = new byte[1024]; + while ((readBits = stream.read(rawBytes)) != -1) { + outputStream.write(rawBytes, 0, readBits); + } + outputStream.close(); + stream.close(); + + file.setStorageName(storageName); + file.setSize(bytesData.length); + return file; + } catch (IOException e){ + throw new RuntimeIOException(e.getMessage()); + } + } +} diff --git a/src/main/java/org/codedream/epaper/component/datamanager/JSONParameter.java b/src/main/java/org/codedream/epaper/component/datamanager/JSONParameter.java new file mode 100644 index 0000000..735d229 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/datamanager/JSONParameter.java @@ -0,0 +1,120 @@ +package org.codedream.epaper.component.datamanager; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.fge.jsonpatch.JsonPatch; +import com.github.fge.jsonpatch.JsonPatchException; +import org.codedream.epaper.exception.innerservererror.HandlingErrorsException; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.io.BufferedReader; +import java.util.Optional; + +/** + * JSON请求参数处理器 + */ +@Component +public class JSONParameter { + + /** + * 获得HTTP请求内容 + * @param request HTTP请求 + * @return 内容字符串 + */ + public String getRequestBody(HttpServletRequest request){ + try { + StringBuilder stringBuilder = new StringBuilder(); + BufferedReader reader = request.getReader(); + reader.reset(); + String line; + while ((line = reader.readLine()) != null) + stringBuilder.append(line); + return stringBuilder.toString(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 提取Request内容并解析为JSON对象 + * @param request HTTP请求 + * @return JSON对象 + */ + public Optional getJSONByRequest(HttpServletRequest request){ + try { + JSONObject jsonParam = null; + String content = getRequestBody(request); + jsonParam = JSONObject.parseObject(content); + return Optional.ofNullable(jsonParam); + } catch (Exception e) { + e.printStackTrace(); + return Optional.empty(); + } + + } + + /** + * 根据JSON对象构造JSON字符串用于返回 + * @param json JSON对象 + * @return JSON字符串 + */ + public String getJSONString(JSONObject json){ + return json.toJSONString(); + } + + /** + * 根据Java对象构造JSON字符串用于返回 + * @param object Java对象 + * @return JSON字符串 + */ + public String getJSONString(Object object){ + return JSON.toJSONString(object); + } + + /** + * 由JSON对象获得对应的Java对象 + * @param json JSON对象 + * @param type 对应的Java对象类型 + * @param 对应的Java对象类型 + * @return 指定的Java对象 + */ + public T getJavaObject(JSONObject json, Class type){ + return json.toJavaObject(type); + } + + /** + * 由HTTP请求获得对应的Java对象(一般用于Post请求中) + * @param request HTTP请求 + * @param type 对应的Java对象类型 + * @param 对应的Java对象类型 + * @return 指定的Java对象 + */ + public Optional getJavaObjectByRequest(HttpServletRequest request, Class type){ + Optional json = getJSONByRequest(request); + return json.map(jsonObject -> getJavaObject(jsonObject, type)); + } + + /** + * 将JsonPath对象转换成Java对象(Restful API的Path动词) + * @param patch JsonPath对象 + * @param object Java对象 + * @param 对应的Java对象类型 + * @return 指定的Java对象(更新后) + */ + public T parsePathToObject(JsonPatch patch, T object){ + try { + ObjectMapper mapper = new ObjectMapper(); + JsonNode patched = patch.apply(mapper.convertValue(object, JsonNode.class)); + return (T) mapper.treeToValue(patched, object.getClass()); + } catch (JsonPatchException | JsonProcessingException e) { + throw new HandlingErrorsException(e.getMessage()); + } + + } + +} diff --git a/src/main/java/org/codedream/epaper/component/datamanager/ParagraphDivider.java b/src/main/java/org/codedream/epaper/component/datamanager/ParagraphDivider.java new file mode 100644 index 0000000..cf69cdf --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/datamanager/ParagraphDivider.java @@ -0,0 +1,50 @@ +package org.codedream.epaper.component.datamanager; + +import org.codedream.epaper.configure.PunctuationConfiguration; +import org.codedream.epaper.model.article.Paragraph; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 将一个段落分为几个句子 + */ +@Component +public class ParagraphDivider { + + /** + * 将一个段落划分为若干句,并进行相应的持久化 + * + * @param text 需要划分的段落文本 + * @return 段落中的所有被划分好的句子 + */ + public List divideParagraph(String text) { + + if (text.isEmpty()) return null; + Paragraph paragraph = new Paragraph(); + paragraph.setText(text); + List back = PunctuationConfiguration.getBackPunctuations(); + String[] arr = text.split("。|!|?|……|\\?|:"); + List sentenceTexts = Arrays.asList(arr); + + List sentences = new ArrayList<>(); + Pattern p = Pattern.compile("[\u4e00-\u9fa5]"); + for (int i = 0; i < sentenceTexts.size(); i++) { + String sentenceText = sentenceTexts.get(i); + Matcher m = p.matcher(sentenceText); + int len = sentenceTexts.get(i).length(); + if (!m.find() || len < 15 || len > 510) continue; + if (back.contains(sentenceTexts.get(i).charAt(0)) && sentences.size() > 1) { + sentences.set(i - 1, sentences.get(i - 1) + sentenceTexts.get(i).charAt(0)); + } + + sentences.add(sentenceTexts.get(i)); + } + + return sentences; + } +} diff --git a/src/main/java/org/codedream/epaper/component/datamanager/PdfParser.java b/src/main/java/org/codedream/epaper/component/datamanager/PdfParser.java new file mode 100644 index 0000000..5d735e2 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/datamanager/PdfParser.java @@ -0,0 +1,94 @@ +package org.codedream.epaper.component.datamanager; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.text.PDFTextStripper; +import org.codedream.epaper.exception.innerservererror.HandlingErrorsException; +import org.codedream.epaper.exception.innerservererror.RuntimeIOException; +import org.codedream.epaper.model.article.Article; +import org.codedream.epaper.model.article.Paragraph; +import org.codedream.epaper.model.file.File; +import org.codedream.epaper.repository.article.ParagraphRepository; +import org.codedream.epaper.service.IArticleService; +import org.codedream.epaper.service.IFileService; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.io.IOException; +import java.io.InputStream; +import java.util.Optional; + +@Component +public class PdfParser { + + @Resource + private IFileService fileService; + + @Resource + private IArticleService articleService; + + @Resource + private ParagraphRepository paragraphRepository; + + @Resource + private SHA512Encoder encoder; + + public Article parse(InputStream stream){ + + try { + + Article article = articleService.createArticle(null); + + PDDocument doc = PDDocument.load(stream); + + PDFTextStripper textStripper = new PDFTextStripper(); + + String content = textStripper.getText(doc); + + String regA = "^[\\u4e00-\\u9fa5a-zA-Z0-9。,?!;、:()“”]"; + String regB = "[\\s|.,/?\"%$#@*^~`()+=\\-{}<>\\///_]"; + content = content.replaceAll(regA, ""); + content = content.replaceAll(regB, ""); + + + saveParagraph(article, content); + + doc.close(); + + return article; + + } catch (IOException e) { + throw new RuntimeIOException(e.getMessage()); + } + } + + public Integer parse(Integer fileId){ + File file = fileService.getFileInfo(fileId); + if(file.getType().equals("pdf")){ + Article article = parse(fileService.getFile(fileId)); + + article.setFileId(fileId); + article = articleService.save(article); + return article.getId(); + } + else throw new HandlingErrorsException(file.getType()); + + } + + // 储存段结构,并考虑缓存情况 + private void saveParagraph(Article article, String text){ + String hash = encoder.encode(text); + Paragraph paragraph; + Optional paragraphOptional = paragraphRepository.findBySha512Hash(hash); + if(!paragraphOptional.isPresent()){ + paragraph = articleService.createParagraph(text); + paragraph.setSha512Hash(hash); + paragraph = articleService.save(paragraph); + } + else{ + paragraph = paragraphOptional.get(); + } + + articleService.addParagraph(article, paragraph); + } + +} diff --git a/src/main/java/org/codedream/epaper/component/datamanager/ReportGenerator.java b/src/main/java/org/codedream/epaper/component/datamanager/ReportGenerator.java new file mode 100644 index 0000000..d0488e1 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/datamanager/ReportGenerator.java @@ -0,0 +1,235 @@ +package org.codedream.epaper.component.datamanager; + +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; +import lombok.extern.slf4j.Slf4j; +import org.apache.log4j.helpers.Loader; +import org.codedream.epaper.component.article.GetSentenceFromArticle; +import org.codedream.epaper.component.json.model.JsonableSTNResult; +import org.codedream.epaper.component.json.model.JsonableTaskResult; +import org.codedream.epaper.component.task.JsonableTaskResultGenerator; +import org.codedream.epaper.exception.innerservererror.HandlingErrorsException; +import org.codedream.epaper.exception.innerservererror.InnerDataTransmissionException; +import org.codedream.epaper.exception.innerservererror.RuntimeIOException; +import org.codedream.epaper.model.article.Sentence; +import org.codedream.epaper.model.file.File; +import org.codedream.epaper.model.task.Task; +import org.codedream.epaper.service.FileService; +import org.codedream.epaper.service.IFileService; +import org.codedream.epaper.service.ITaskService; +import org.docx4j.Docx4J; +import org.docx4j.fonts.IdentityPlusMapper; +import org.docx4j.fonts.Mapper; +import org.docx4j.fonts.PhysicalFont; +import org.docx4j.fonts.PhysicalFonts; +import org.docx4j.openpackaging.exceptions.Docx4JException; +import org.docx4j.openpackaging.packages.WordprocessingMLPackage; +import org.springframework.stereotype.Component; +import org.springframework.util.ResourceUtils; + +import javax.annotation.Resource; +import java.io.*; +import java.net.URISyntaxException; +import java.net.URL; +import java.text.SimpleDateFormat; +import java.util.*; + +@Slf4j +@Component +public class ReportGenerator { + @Resource + private ITaskService taskService; + + @Resource + private IFileService fileService; + + @Resource + private JsonableTaskResultGenerator taskResultGenerator; + + @Resource + private GetSentenceFromArticle getSentenceFromArticle; + + Mapper fontMapper = null; + + public byte[] generate(Integer taskId){ + Optional taskOptional =taskService.getTaskInfo(taskId); + + if(!taskOptional.isPresent()) throw new InnerDataTransmissionException(taskId.toString()); + + Task task = taskOptional.get(); + + // 获得数据列表 + Map dataMap = processDataMap(task); + + StringWriter stringWriter = new StringWriter(); + BufferedWriter writer = new BufferedWriter(stringWriter); + Template template = getTemplate("report.ftl"); + + try { + + template.process(dataMap, writer); + + // 转换为Word + String xmlStr = stringWriter.toString(); + ByteArrayInputStream in = new ByteArrayInputStream(xmlStr.getBytes()); + WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(in); + + wordMLPackage.save(new java.io.File("./Report.docx")); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + + wordMLPackage.setFontMapper(generateFrontMapper()); + + // 转换为PDF + Docx4J.toPDF(wordMLPackage, outputStream); + + + + return outputStream.toByteArray(); + + + } catch (IOException | TemplateException | Docx4JException e){ + e.printStackTrace(); + throw new HandlingErrorsException(e.getMessage()); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + private synchronized Mapper generateFrontMapper() throws Exception { + if (this.fontMapper == null) { +// URL simSumUrl = ResourceUtils.getURL("classpath:fonts/simsun.ttc"); + PhysicalFonts.discoverPhysicalFonts(); + URL simSumUrl = ResourceUtils.getURL("./fonts/simsun.ttc"); + PhysicalFonts.addPhysicalFont("SimSun", simSumUrl); + + this.fontMapper = new IdentityPlusMapper(); + fontMapper.put("隶书", PhysicalFonts.get("LiSu")); + fontMapper.put("宋体", PhysicalFonts.get("SimSun")); + fontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft Yahei")); + fontMapper.put("黑体", PhysicalFonts.get("SimHei")); + fontMapper.put("楷体", PhysicalFonts.get("KaiTi")); + fontMapper.put("新宋体", PhysicalFonts.get("NSimSun")); + fontMapper.put("华文行楷", PhysicalFonts.get("STXingkai")); + fontMapper.put("华文仿宋", PhysicalFonts.get("STFangsong")); + fontMapper.put("仿宋", PhysicalFonts.get("FangSong")); + fontMapper.put("幼圆", PhysicalFonts.get("YouYuan")); + fontMapper.put("华文宋体", PhysicalFonts.get("STSong")); + fontMapper.put("华文中宋", PhysicalFonts.get("STZhongsong")); + fontMapper.put("等线", PhysicalFonts.get("SimSun")); + fontMapper.put("等线 Light", PhysicalFonts.get("SimSun")); + fontMapper.put("华文琥珀", PhysicalFonts.get("STHupo")); + fontMapper.put("华文隶书", PhysicalFonts.get("STLiti")); + fontMapper.put("华文新魏", PhysicalFonts.get("STXinwei")); + fontMapper.put("华文彩云", PhysicalFonts.get("STCaiyun")); + fontMapper.put("方正姚体", PhysicalFonts.get("FZYaoti")); + fontMapper.put("方正舒体", PhysicalFonts.get("FZShuTi")); + fontMapper.put("华文细黑", PhysicalFonts.get("STXihei")); + fontMapper.put("宋体扩展", PhysicalFonts.get("simsun-extB")); + fontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312")); + fontMapper.put("新細明體", PhysicalFonts.get("SimSun")); + //解决宋体(正文)和宋体(标题)的乱码问题 + PhysicalFonts.put("PMingLiU", PhysicalFonts.get("SimSun")); + PhysicalFonts.put("新細明體", PhysicalFonts.get("SimSun")); + + return this.fontMapper; + } + return this.fontMapper; + } + + public void saveByFile(String path, byte[] bytes) throws IOException { + FileOutputStream outputStream = new FileOutputStream(path); + outputStream.write(bytes); + outputStream.close(); + } + + public Integer saveByFileService(byte[] byteArray){ + ByteArrayInputStream inputStream = new ByteArrayInputStream(byteArray); + return fileService.saveFile(UUID.randomUUID().toString() + ".pdf", "pdf", inputStream); + } + + private Map processDataMap(Task task){ + + File file = task.getFile(); + if(file == null) throw new InnerDataTransmissionException(task.toString()); + + JsonableTaskResult taskResult = taskResultGenerator.getJsonableTaskResult(task.getId()); + + Map dataMap = new HashMap<>(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm"); + dataMap.put("articleTitle", file.getName()); + dataMap.put("datetime", sdf.format(new Date())); + + if(taskResult.getScore() > 90) + dataMap.put("grade", "A"); + else if(taskResult.getScore() > 80) + dataMap.put("grade", "B"); + else if(taskResult.getScore() > 60) + dataMap.put("grade", "C"); + else + dataMap.put("grade", "D"); + + dataMap.put("wdScore", taskResult.getCorrectionScore()); + dataMap.put("fqScore", taskResult.getDnnScore()); + dataMap.put("flScore", taskResult.getEmotionScore()); + + + List sentences = getSentenceFromArticle.get(task.getArticle()); + + int totalLen = 0; + for(Sentence sentence : sentences) + totalLen += sentence.getText().length(); + + dataMap.put("svgLen", totalLen / sentences.size()); + dataMap.put("stnNum", sentences.size()); + dataMap.put("userId", task.getUser().getId()); + dataMap.put("errStatus", "正态分布"); + dataMap.put("wEum", taskResult.getWrongTextCount()); + dataMap.put("pcsTime", task.getEndDate().getTime() - task.getCreateDate().getTime()); + dataMap.put("fqRum", taskResult.getBrokenSentencesCount()); + dataMap.put("flEum", taskResult.getOralCount()); + dataMap.put("status", "完成"); + + Map stnResults = new HashMap<>(); + + Map sentenceMap = new HashMap<>(); + + for(Sentence sentence : sentences){ + sentenceMap.put(sentence.getId(), sentence); + } + + for(JsonableSTNResult jsonableSTNResult : taskResult.getStnResults()){ + if(jsonableSTNResult.getErrorList().size() == 0) continue; + STNResult stnResult = new STNResult(); + stnResult.setId(jsonableSTNResult.getStnId()); + stnResult.setText(sentenceMap.get(jsonableSTNResult.getStnId()).getText()); + stnResults.put(stnResult.getId(), stnResult); + stnResult.setStnResultList(jsonableSTNResult.getErrorList()); + stnResults.put(stnResult.getId(), stnResult); + } + + List stnResultList = new ArrayList<>(stnResults.values()); + + dataMap.put("errorStnList", stnResultList); + + return dataMap; + + } + + private Template getTemplate(String name){ + try { + Configuration conf = new Configuration(); + +// conf.setDirectoryForTemplateLoading(ResourceUtils.getFile("classpath:templates")); + conf.setDirectoryForTemplateLoading(new java.io.File("./templates/")); + return conf.getTemplate(name); + + } catch (IOException e){ + throw new RuntimeIOException(e.getMessage()); + } + } + +} diff --git a/src/main/java/org/codedream/epaper/component/datamanager/SHA256Encoder.java b/src/main/java/org/codedream/epaper/component/datamanager/SHA256Encoder.java new file mode 100644 index 0000000..af5e40a --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/datamanager/SHA256Encoder.java @@ -0,0 +1,29 @@ +package org.codedream.epaper.component.datamanager; + +import org.apache.commons.codec.digest.DigestUtils; +import org.springframework.stereotype.Component; + +/** + * SHA256编码器 + */ +@Component +public class SHA256Encoder { + /** + * 编码 + * @param charSequence 字符队列 + * @return 密文 + */ + public String encode(CharSequence charSequence){ + return DigestUtils.sha256Hex(charSequence.toString()); + } + + /** + * 验证 + * @param charSequence 字符队列 + * @param s 密文 + * @return 布尔值 + */ + public boolean match (CharSequence charSequence, String s){ + return s.equals(encode(charSequence)); + } +} diff --git a/src/main/java/org/codedream/epaper/component/datamanager/SHA512Encoder.java b/src/main/java/org/codedream/epaper/component/datamanager/SHA512Encoder.java new file mode 100644 index 0000000..7b06b0d --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/datamanager/SHA512Encoder.java @@ -0,0 +1,29 @@ +package org.codedream.epaper.component.datamanager; + +import org.apache.commons.codec.digest.DigestUtils; +import org.springframework.stereotype.Component; + +/** + * SHA256编码器 + */ +@Component +public class SHA512Encoder { + /** + * 编码 + * @param charSequence 字符队列 + * @return 密文 + */ + public String encode(CharSequence charSequence){ + return DigestUtils.sha512Hex(charSequence.toString()); + } + + /** + * 验证 + * @param charSequence 字符队列 + * @param s 密文 + * @return 布尔值 + */ + public boolean match (CharSequence charSequence, String s){ + return s.equals(encode(charSequence)); + } +} diff --git a/src/main/java/org/codedream/epaper/component/datamanager/STNResult.java b/src/main/java/org/codedream/epaper/component/datamanager/STNResult.java new file mode 100644 index 0000000..c51f2c1 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/datamanager/STNResult.java @@ -0,0 +1,15 @@ +package org.codedream.epaper.component.datamanager; + +import lombok.Data; +import org.codedream.epaper.component.json.model.JsonableSTNError; +import org.codedream.epaper.component.json.model.JsonableSTNResult; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class STNResult { + private Integer id; + private String text; + private List stnResultList = new ArrayList<>(); +} diff --git a/src/main/java/org/codedream/epaper/component/datamanager/SentenceDivider.java b/src/main/java/org/codedream/epaper/component/datamanager/SentenceDivider.java new file mode 100644 index 0000000..3c08e8e --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/datamanager/SentenceDivider.java @@ -0,0 +1,122 @@ +package org.codedream.epaper.component.datamanager; + +import com.baidu.aip.nlp.AipNlp; +import com.sun.org.apache.xpath.internal.operations.Bool; +import javafx.util.Pair; +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.configure.SingletonAipNlp; +import org.codedream.epaper.model.article.Phrase; +import org.codedream.epaper.model.article.Sentence; +import org.codedream.epaper.service.ArticleService; +import org.json.JSONArray; +import org.json.JSONObject; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.*; + +/** + * 提供句子分词方法,并通过{@link ArticleService}进行相应持久化操作 + */ +@Slf4j +@Component +public class SentenceDivider { + + @Resource + ArticleService articleService; + + /** + * 将句子分词 + * + * @param text 要进行分词操作的句子 + * @return 一个已经持久化的、分完词的句子 + */ + public List divideSentence(String text) { + + if (text.isEmpty()) return null; + AipNlp client = SingletonAipNlp.getInstance(); + HashMap hashMap = new HashMap<>(); + List phrases = new ArrayList<>(); + String f = ""; + try { + JSONObject res = client.lexer(text, hashMap); + f = "1"; + boolean lexerFlag = true; + Iterator iterator = res.keys(); + while (iterator.hasNext()) { + if (iterator.next().equals("error_msg")) { + lexerFlag = false; + break; + } + } + + if (lexerFlag) { + f = "2"; + JSONArray items = (JSONArray) res.get("items"); + f = "After lexer"; + for (int i = 0; i < items.length(); i++) { + JSONObject item = (JSONObject) items.get(i); + List basic = new ArrayList<>(); + f = "Before basicArray"; + JSONArray basicArray = (JSONArray) item.get("basic_words"); + + for (int j = 0; j < basicArray.length(); j++) { + + f = "In basicArray"; + Pair phrasePair = articleService.savePhrase(basicArray.get(j).toString()); + + basic.add(phrasePair.getValue()); + } + + f = "Before vec"; + JSONObject vecRes = new JSONObject(); + + + Pair phrasePair = articleService.savePhrase(item.get("item").toString()); + Phrase phrase = phrasePair.getValue(); + + if(!phrasePair.getKey()){ + phrase.setText(item.get("item").toString()); + phrase.getBasicPhrase().addAll(basic); + phrase.setPos(item.get("pos").toString()); + phrase = articleService.save(phrase); + } + + + Iterator keys = vecRes.keys(); + boolean flag = true; + while (keys.hasNext()) { + if (keys.next().equals("error_msg")) { + flag = false; + break; + } + } + flag = false; + if (flag) { + f = "Before vecArray"; + JSONArray vec = vecRes.getJSONArray("vec"); + List floatList = new ArrayList<>(); + for (int j = 0; j < vec.length(); j++) { + f = "In vecArray"; + String str = vec.getString(i); + f = "Before parsing to float"; + float vecUnit = Float.parseFloat(str); + floatList.add(vecUnit); + } + phrase.setVec(floatList); + } + + phrases.add(phrase); + + } + } + } catch (Exception e) { + log.error(e.toString() + ": f"); + } + + return phrases; + } + + + +} diff --git a/src/main/java/org/codedream/epaper/component/datamanager/SentenceSmoothnessGetter.java b/src/main/java/org/codedream/epaper/component/datamanager/SentenceSmoothnessGetter.java new file mode 100644 index 0000000..45aa627 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/datamanager/SentenceSmoothnessGetter.java @@ -0,0 +1,38 @@ +package org.codedream.epaper.component.datamanager; + +import com.baidu.aip.nlp.AipNlp; +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.configure.SingletonAipNlp; +import org.json.JSONObject; +import org.springframework.stereotype.Component; + +import java.util.HashMap; + +/** + * 提供句子通顺度获取方法 + */ +@Slf4j +@Component +public class SentenceSmoothnessGetter { + + /** + * 通过调用百度接口获取句子通顺度 + * + * @param text 待处理的文本 + * @return 句子通顺度的值 + */ + public float getSentenceSmoothness(String text) { + AipNlp client = SingletonAipNlp.getInstance(); + HashMap options = new HashMap<>(); + JSONObject jsonObject = client.dnnlmCn(text, options); + + try { + Thread.sleep(500); + float dnn = Float.parseFloat(jsonObject.get("ppl").toString()); + return dnn; + } catch (Exception e) { + log.error(e.toString() + "In dnn: " + jsonObject.toString()); + return (float) 0; + } + } +} diff --git a/src/main/java/org/codedream/epaper/component/datamanager/StringFile.java b/src/main/java/org/codedream/epaper/component/datamanager/StringFile.java new file mode 100644 index 0000000..82a1fc3 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/datamanager/StringFile.java @@ -0,0 +1,25 @@ +package org.codedream.epaper.component.datamanager; + +import lombok.Data; + +/** + * 储存字符串标识的文件(可以将文件直接转换为Json进行传输) + */ +@Data +public class StringFile { + // 文件内容(Base64编码,Gzip算法压缩) + private String strData = null; + + // 散列值 + private String sha256 = null; + + // 文件大小 + private Integer size = null; + + // 文件类型 + private String type = "none"; + + // 文件名 + private String name = null; + +} diff --git a/src/main/java/org/codedream/epaper/component/datamanager/StringFileGenerator.java b/src/main/java/org/codedream/epaper/component/datamanager/StringFileGenerator.java new file mode 100644 index 0000000..9255006 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/datamanager/StringFileGenerator.java @@ -0,0 +1,131 @@ +package org.codedream.epaper.component.datamanager; + +import org.codedream.epaper.exception.innerservererror.StringFileConvertException; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Base64; +import java.util.Optional; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +/** + * 字符串文件生成器 + */ +@Component +public class StringFileGenerator { + + @Resource + private SHA256Encoder encoder; + + /** + * 用过读入流创建一个字符串文件 + * @param name 文件名 + * @param type 文件类型 + * @param stream 输入流 + * @return 字符串文件对象 + */ + public Optional generateStringFile(String name, String type,InputStream stream){ + StringFile file = new StringFile(); + file.setName(name); + file.setType(type); + + // 字符串内容计算 + file.setStrData(generateFile2String(stream)); + if(file.getStrData() == null) return Optional.empty(); + + // 相关校验值计算 + file.setSha256(generateSHA256Checker(file.getStrData())); + file.setSize(file.getStrData().length()); + return Optional.of(file); + } + + /** + * 一次性读取输入流中的文件数据 + * @param stream 输入流 + * @return 文件数据 + */ + private byte[] readSteamAll(InputStream stream) { + try { + byte[] bytes = new byte[stream.available()]; + + //检查文件书否完全读取 + if (stream.read(bytes) != bytes.length) return null; + else return bytes; + } catch (IOException e){ + return null; + } + } + + /** + * 将文件数据压缩(Gzip),然后用Base64编码为字符串 + * @param stream 输入流 + * @return 文件数据编码 + */ + private String generateFile2String(InputStream stream){ + ByteArrayOutputStream zipDataStream = new ByteArrayOutputStream(); + try { + + // 解压缩 + GZIPOutputStream gzipOutputStream = new GZIPOutputStream(zipDataStream); + byte[] bytes = readSteamAll(stream); + if(bytes == null) return null; + gzipOutputStream.write(bytes); + gzipOutputStream.close(); + + // 编码转换 + return Base64.getEncoder().encodeToString(zipDataStream.toByteArray()); + } catch (IOException e) { + return null; + } + } + + /** + * 生成字符串文件的校验码 + * @param str 文件散列值检查 + * @return + */ + private String generateSHA256Checker(String str){ + return encoder.encode(str); + } + + /** + * 检查文件内容是否正确,包括大小与校验码 + * @param file 字符串文件对象 + * @return 布尔值 + */ + public boolean checkStringFile(StringFile file){ + return file.getStrData().length() == file.getSize() + && encoder.match(file.getStrData(), file.getSha256()); + } + + /** + * 从字符串文件中读取真实的文件数据 + * @param file 字符串文件对象 + * @return 输入流 + */ + public InputStream readFileString(StringFile file){ + try { + // 字符串转换为二进制数据 + byte[] bytes = Base64.getDecoder().decode(file.getStrData()); + GZIPInputStream stream = new GZIPInputStream(new ByteArrayInputStream(bytes), bytes.length); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + // 数据解压缩 + int readBits = 0; + byte[] rawBytes = new byte[1024]; + while ((readBits = stream.read(rawBytes)) != -1) { + outputStream.write(rawBytes, 0, readBits); + } + + stream.close(); + return new ByteArrayInputStream(outputStream.toByteArray()); + } catch (IOException e) { + throw new StringFileConvertException("Read FileString Failed"); + } + } +} diff --git a/src/main/java/org/codedream/epaper/component/datamanager/TextCorrector.java b/src/main/java/org/codedream/epaper/component/datamanager/TextCorrector.java new file mode 100644 index 0000000..6ecd7b7 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/datamanager/TextCorrector.java @@ -0,0 +1,70 @@ +package org.codedream.epaper.component.datamanager; + +import com.baidu.aip.nlp.AipNlp; +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.configure.SingletonAipNlp; +import org.codedream.epaper.model.task.CorrectionResult; +import org.json.JSONObject; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + + +/** + * 用于文本纠错 + */ +@Slf4j +@Component +public class TextCorrector { + + /** + * 文本纠错 + * + * @param text 需要纠错的文本 + * @return 一个纠正类的列表,保存句子需要修改的信息 + * 值的位置;corr_text,存放纠错后文本;org_text:存放原文本。 + */ + public List correctText(String text) { + + if (text.isEmpty()) return new ArrayList<>(); + AipNlp client = SingletonAipNlp.getInstance(); + HashMap options = new HashMap<>(); + List correctionResults = new ArrayList<>(); + String correction; + try { + JSONObject res = (JSONObject) client.ecnet(text, options).get("item"); + if (res.get("vec_fragment") == null) { + return correctionResults; + } + correction = (String) res.get("correct_query"); + DiffMatchPatch dmp = new DiffMatchPatch(); + List diffs = dmp.diff_main(text, correction); + int p = 0; + int size = diffs.size(); + for (int i = 0; i < size; i++) { + DiffMatchPatch.Diff diff = diffs.get(i); + if (diff.operation.equals(DiffMatchPatch.Operation.EQUAL) || + diff.operation.equals(DiffMatchPatch.Operation.INSERT)) { + p += diff.text.length(); + continue; + } + CorrectionResult correctionResult = new CorrectionResult(); + correctionResult.setStartPos(p); + correctionResult.setLength(diff.text.length()); + if (i == size - 1 || !diffs.get(i + 1).operation.equals(DiffMatchPatch.Operation.INSERT)) { + correctionResult.setCorrectionText(""); + } else { + DiffMatchPatch.Diff nextDiff = diffs.get(i + 1); + correctionResult.setCorrectionText(nextDiff.text); + } + correctionResults.add(correctionResult); + } + } catch (Exception e) { + log.error(e.toString() + ": Failed to analyze \"" + text + "\""); + } + + return correctionResults; + } +} diff --git a/src/main/java/org/codedream/epaper/component/datamanager/TextParser.java b/src/main/java/org/codedream/epaper/component/datamanager/TextParser.java new file mode 100644 index 0000000..6e776ac --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/datamanager/TextParser.java @@ -0,0 +1,82 @@ +package org.codedream.epaper.component.datamanager; + +import javassist.bytecode.ByteArray; +import org.codedream.epaper.exception.innerservererror.RuntimeIOException; +import org.codedream.epaper.model.article.Article; +import org.codedream.epaper.model.article.Paragraph; +import org.codedream.epaper.model.file.File; +import org.codedream.epaper.repository.article.ParagraphRepository; +import org.codedream.epaper.service.IArticleService; +import org.codedream.epaper.service.IFileService; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Optional; + +@Component +public class TextParser { + @Resource + private IFileService fileService; + + @Resource + private IArticleService articleService; + + @Resource + private ParagraphRepository paragraphRepository; + + @Resource + private SHA512Encoder encoder; + + public Integer parse(Integer fileId) { + File file = fileService.getFileInfo(fileId); + if(file.getType().equals("plain")){ + Article article = articleService.createArticle(null); + InputStream stream = fileService.getFile(fileId); + + ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream(); + + try { + + // 数据内存写出 + int readBits = 0; + byte[] rawBytes = new byte[1024]; + while ((readBits = stream.read(rawBytes)) != -1) { + arrayOutputStream.write(rawBytes, 0, readBits); + } + + saveParagraph(article, new String(arrayOutputStream.toByteArray())); + article.setFileId(fileId); + article = articleService.save(article); + + return article.getId(); + } + catch (IOException e){ + throw new RuntimeIOException(e.getMessage()); + } + + } + + return null; + } + + // 储存段结构,并考虑缓存情况 + private void saveParagraph(Article article, String text){ + String hash = encoder.encode(text); + Paragraph paragraph; + Optional paragraphOptional = paragraphRepository.findBySha512Hash(hash); + if(!paragraphOptional.isPresent()){ + paragraph = articleService.createParagraph(text); + paragraph.setSha512Hash(hash); + paragraph = articleService.save(paragraph); + } + else{ + paragraph = paragraphOptional.get(); + } + + articleService.addParagraph(article, paragraph); + } +} diff --git a/src/main/java/org/codedream/epaper/component/datamanager/WordParser.java b/src/main/java/org/codedream/epaper/component/datamanager/WordParser.java new file mode 100644 index 0000000..aca9d5f --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/datamanager/WordParser.java @@ -0,0 +1,132 @@ +package org.codedream.epaper.component.datamanager; + +import org.apache.poi.hwpf.extractor.WordExtractor; +import org.apache.poi.xwpf.extractor.XWPFWordExtractor; +import org.apache.poi.xwpf.usermodel.XWPFDocument; +import org.apache.poi.xwpf.usermodel.XWPFParagraph; +import org.codedream.epaper.configure.AppConfigure; +import org.codedream.epaper.exception.innerservererror.HandlingErrorsException; +import org.codedream.epaper.exception.innerservererror.RuntimeIOException; +import org.codedream.epaper.model.article.Article; +import org.codedream.epaper.model.article.Paragraph; +import org.codedream.epaper.model.file.File; +import org.codedream.epaper.repository.article.ParagraphRepository; +import org.codedream.epaper.service.IArticleService; +import org.codedream.epaper.service.IFileService; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.io.IOException; +import java.io.InputStream; +import java.util.Iterator; +import java.util.Optional; + +/** + * Word文档解析 + */ +@Component +public class WordParser { + @Resource + private IFileService fileService; + + @Resource + private IArticleService articleService; + + @Resource + private ParagraphRepository paragraphRepository; + + @Resource + private SHA512Encoder encoder; + + @Resource + private AppConfigure configure; + + public Article parse(InputStream stream, String type){ + if(type.equals("doc")){ + return parseDoc(stream); + } + else{ + return parseDocx(stream); + } + } + + public Integer parse(Integer fileId){ + File file = fileService.getFileInfo(fileId); + if(file.getType().equals("doc") || file.getType().equals("docx")){ + Article article = parse(fileService.getFile(fileId), file.getType()); + article.setFileId(fileId); + article = articleService.save(article); + return article.getId(); + } + else throw new HandlingErrorsException(file.getType()); + + } + + public Article parseDoc(InputStream stream) { + try { + WordExtractor extractor = new WordExtractor(stream); + + Article article = articleService.createArticle(null); + + for (String text : extractor.getParagraphText()) { + if (text.length() > configure.getParagraphMinSize() + && text.length() < configure.getParagraphMaxSize()) { + // 储存段结构 + saveParagraph(article ,text); + } + } + + // 保存章 + return articleService.save(article); + + } catch (IOException e){ + throw new RuntimeIOException("Doc Parse Error"); + } + } + + public Article parseDocx(InputStream stream){ + try{ + XWPFDocument doc = new XWPFDocument(stream); + XWPFWordExtractor extractor = new XWPFWordExtractor(doc); + + Article article = articleService.createArticle(null); + + // 遍历段落 + Iterator iterator = doc.getParagraphsIterator(); + while (iterator.hasNext()){ + XWPFParagraph para = iterator.next(); + String text = para.getText(); + if(text.length() > configure.getParagraphMinSize() + && text.length() < configure.getParagraphMaxSize()){ + System.out.println("Paragraph: " + text); + // 储存段结构 + saveParagraph(article ,text); + } + } + + // 保存章 + return articleService.save(article); + + } catch (IOException e){ + throw new RuntimeIOException("Docx Parse Error"); + } + } + + // 储存段结构,并考虑缓存情况 + private void saveParagraph(Article article, String text){ + String hash = encoder.encode(text); + Paragraph paragraph; + Optional paragraphOptional = paragraphRepository.findBySha512Hash(hash); + if(!paragraphOptional.isPresent()){ + paragraph = articleService.createParagraph(text); + paragraph.setSha512Hash(hash); + paragraph = articleService.save(paragraph); + } + else{ + paragraph = paragraphOptional.get(); + } + + articleService.addParagraph(article, paragraph); + } + +} diff --git a/src/main/java/org/codedream/epaper/component/json/JSONBaseObject.java b/src/main/java/org/codedream/epaper/component/json/JSONBaseObject.java new file mode 100644 index 0000000..5ad626a --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/json/JSONBaseObject.java @@ -0,0 +1,14 @@ +package org.codedream.epaper.component.json; + +import lombok.extern.slf4j.Slf4j; + +import java.util.Date; + +/** + * 所有有效的JSON对象模板 + */ +@Slf4j +public class JSONBaseObject { + Date time = new Date(); + +} diff --git a/src/main/java/org/codedream/epaper/component/json/model/JsonableBPT.java b/src/main/java/org/codedream/epaper/component/json/model/JsonableBPT.java new file mode 100644 index 0000000..6312764 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/json/model/JsonableBPT.java @@ -0,0 +1,43 @@ +package org.codedream.epaper.component.json.model; + +import io.swagger.annotations.ApiModel; +import io.swagger.models.auth.In; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.codedream.epaper.model.article.Article; +import org.codedream.epaper.model.article.Paragraph; +import org.codedream.epaper.model.article.Sentence; +import org.codedream.epaper.model.task.BatchProcessingTask; +import org.codedream.epaper.model.task.Task; + +import java.util.ArrayList; +import java.util.List; + +@Data +@ApiModel("批处理任务结构") +@NoArgsConstructor +public class JsonableBPT { + private Integer id; + private Integer stnNumber; + private List stns = new ArrayList<>(); + + public JsonableBPT(BatchProcessingTask bpt){ + this.id = bpt.getId(); + this.stnNumber = bpt.getSentencesNumber(); + + for (Task task : bpt.getTasks()){ + for(Paragraph paragraph : task.getArticle().getParagraphs()){ + for(Sentence sentence : paragraph.getSentences()){ + // 检查是否已经深处理完毕 + if(sentence.isDeepProcess()) continue; + + JsonableSTN stn = new JsonableSTN(); + stn.setStnId(sentence.getId()); + stn.setText(sentence.getText()); + this.stns.add(stn); + } + } + } + + } +} diff --git a/src/main/java/org/codedream/epaper/component/json/model/JsonableBPTResult.java b/src/main/java/org/codedream/epaper/component/json/model/JsonableBPTResult.java new file mode 100644 index 0000000..d7bc5ff --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/json/model/JsonableBPTResult.java @@ -0,0 +1,18 @@ +package org.codedream.epaper.component.json.model; + +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@ApiModel("批处理任务结果返回结构") +@NoArgsConstructor +public class JsonableBPTResult { + // 句子Id + private Integer stnid; + // 标签的预测值 + private List tagPossible = new ArrayList<>(); +} diff --git a/src/main/java/org/codedream/epaper/component/json/model/JsonableCSP.java b/src/main/java/org/codedream/epaper/component/json/model/JsonableCSP.java new file mode 100644 index 0000000..a7ae75b --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/json/model/JsonableCSP.java @@ -0,0 +1,29 @@ +package org.codedream.epaper.component.json.model; + +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.codedream.epaper.model.server.ChildServerPassport; + +import java.util.Date; + +@Data +@ApiModel("子服务器护照") +@NoArgsConstructor +public class JsonableCSP { + + // 身份认证码 + private String identityCode; + + // 最后一次签证日期 + private Date lastUpdateTime; + + // 护照是否过期 + private boolean expired; + + public JsonableCSP(ChildServerPassport csp){ + this.identityCode = csp.getIdentityCode(); + this.lastUpdateTime = csp.getLastUpdateTime(); + this.expired = csp.isExpired(); + } +} diff --git a/src/main/java/org/codedream/epaper/component/json/model/JsonableFile.java b/src/main/java/org/codedream/epaper/component/json/model/JsonableFile.java new file mode 100644 index 0000000..c62a82d --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/json/model/JsonableFile.java @@ -0,0 +1,20 @@ +package org.codedream.epaper.component.json.model; + +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@ApiModel("文件信息结构") +@NoArgsConstructor +public class JsonableFile { + + // 文件ID号 + private Integer fileId; + + // 文件名 + private String filename; + + // 文件类型 + private String type; +} diff --git a/src/main/java/org/codedream/epaper/component/json/model/JsonableSTN.java b/src/main/java/org/codedream/epaper/component/json/model/JsonableSTN.java new file mode 100644 index 0000000..116182a --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/json/model/JsonableSTN.java @@ -0,0 +1,17 @@ +package org.codedream.epaper.component.json.model; + +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@ApiModel("句原文列表结构") +@NoArgsConstructor +public class JsonableSTN { + + // 句子ID号 + private Integer stnId; + + // 句子文本内容 + private String text; +} diff --git a/src/main/java/org/codedream/epaper/component/json/model/JsonableSTNError.java b/src/main/java/org/codedream/epaper/component/json/model/JsonableSTNError.java new file mode 100644 index 0000000..08b8def --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/json/model/JsonableSTNError.java @@ -0,0 +1,23 @@ +package org.codedream.epaper.component.json.model; + +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@ApiModel("句错误结构") +@NoArgsConstructor +public class JsonableSTNError { + + // 序列号(index) + private Integer wordIdx; + + // 词长 + private Integer wordLen; + + // 错误类型 + private Integer type; + + // 错误内容 + private String content; +} diff --git a/src/main/java/org/codedream/epaper/component/json/model/JsonableSTNPage.java b/src/main/java/org/codedream/epaper/component/json/model/JsonableSTNPage.java new file mode 100644 index 0000000..6a3bbe4 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/json/model/JsonableSTNPage.java @@ -0,0 +1,21 @@ +package org.codedream.epaper.component.json.model; + +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@ApiModel("句分页结构") +@NoArgsConstructor +public class JsonableSTNPage { + // 页序号 + Integer page; + + // 页数 + Integer all; + + // 句列表 + List stns; +} diff --git a/src/main/java/org/codedream/epaper/component/json/model/JsonableSTNResult.java b/src/main/java/org/codedream/epaper/component/json/model/JsonableSTNResult.java new file mode 100644 index 0000000..1bb0be7 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/json/model/JsonableSTNResult.java @@ -0,0 +1,28 @@ +package org.codedream.epaper.component.json.model; + +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@ApiModel("句处理结果") +@NoArgsConstructor +public class JsonableSTNResult { + // 句ID号 + Integer stnId; + + // 错误类型 + Integer appear; + + // 分数 + Float score; + + // 是否为书面文本 + boolean isNeutral; + + // 错误列表 + List errorList = new ArrayList<>(); +} diff --git a/src/main/java/org/codedream/epaper/component/json/model/JsonableTask.java b/src/main/java/org/codedream/epaper/component/json/model/JsonableTask.java new file mode 100644 index 0000000..1ed79fa --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/json/model/JsonableTask.java @@ -0,0 +1,42 @@ +package org.codedream.epaper.component.json.model; + +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.codedream.epaper.model.task.Task; + +@Data +@ApiModel("子任务结果") +@NoArgsConstructor +public class JsonableTask { + + // 任务ID号 + private Integer taskId; + + // 用户openid + private String openid; + + // 文件ID号 + private Integer fileId; + + // 文本 + private String text; + + // 任务是否完成 + private boolean finished; + + // 任务进度 + private Float progress; + + // 描述 + private String description; + + public JsonableTask(Task task){ + this.taskId = task.getId(); + this.openid = task.getUser().getUsername(); + this.fileId = task.getFile().getId(); + this.finished = task.isFinished(); + this.progress = task.getProgressRate() / 5.0f; + } + +} diff --git a/src/main/java/org/codedream/epaper/component/json/model/JsonableTaskResult.java b/src/main/java/org/codedream/epaper/component/json/model/JsonableTaskResult.java new file mode 100644 index 0000000..1b9e84f --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/json/model/JsonableTaskResult.java @@ -0,0 +1,43 @@ +package org.codedream.epaper.component.json.model; + +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@ApiModel("任务处理结果") +@NoArgsConstructor +public class JsonableTaskResult { + + // 任务ID号 + private Integer taskId; + + // 任务是否成功 + private boolean success; + + // 文章错误位置数量 + private Integer wrongTextCount = 0; + + // 不通顺的句子数量 + private Integer brokenSentencesCount = 0; + + // 口语化的句子数量 + private Integer oralCount = 0; + + // 文章得分 + private Double score = (double) 0; + + // 通顺度得分 + private Double dnnScore = (double) 0; + + // 感情倾向得分 + private Double emotionScore = (double) 0; + + // 文本纠错得分 + private Double correctionScore = (double) 0; + + private List stnResults = new ArrayList<>(); +} diff --git a/src/main/java/org/codedream/epaper/component/json/model/JsonableUser.java b/src/main/java/org/codedream/epaper/component/json/model/JsonableUser.java new file mode 100644 index 0000000..f6c5840 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/json/model/JsonableUser.java @@ -0,0 +1,33 @@ +package org.codedream.epaper.component.json.model; + +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.codedream.epaper.model.user.User; + +@Data +@ApiModel("用户验证信息") +@NoArgsConstructor +public class JsonableUser { + + // 用户的ID号(数据库) + private Integer id; + + // 用户openid + private String openid; + + // 密码(哈希值) + private String password; + + public JsonableUser(User user){ + this.openid = user.getUsername(); + this.password = user.getPassword(); + this.id = user.getId(); + } + + public User parseObject(User user){ + user.setUsername(this.getOpenid()); + user.setPassword(this.getPassword()); + return user; + } +} diff --git a/src/main/java/org/codedream/epaper/component/json/request/UserLoginChecker.java b/src/main/java/org/codedream/epaper/component/json/request/UserLoginChecker.java new file mode 100644 index 0000000..80c809d --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/json/request/UserLoginChecker.java @@ -0,0 +1,22 @@ +package org.codedream.epaper.component.json.request; + +import lombok.Data; + +/** + * 用户登录请求对象 + */ +@Data +public class UserLoginChecker { + + // 请求类型 + private String checkType; + + // openid + private String openid; + + // 密码 + private String password; + + // 客户端代码 + private String clientCode; +} diff --git a/src/main/java/org/codedream/epaper/component/json/respond/EmptyDataObjectRespond.java b/src/main/java/org/codedream/epaper/component/json/respond/EmptyDataObjectRespond.java new file mode 100644 index 0000000..b01a0fa --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/json/respond/EmptyDataObjectRespond.java @@ -0,0 +1,8 @@ +package org.codedream.epaper.component.json.respond; + +/** + * 空应答 + */ +public class EmptyDataObjectRespond { + +} diff --git a/src/main/java/org/codedream/epaper/component/json/respond/ErrorInfoJSONRespond.java b/src/main/java/org/codedream/epaper/component/json/respond/ErrorInfoJSONRespond.java new file mode 100644 index 0000000..19f3041 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/json/respond/ErrorInfoJSONRespond.java @@ -0,0 +1,15 @@ +package org.codedream.epaper.component.json.respond; + +import lombok.Data; + +import java.util.Date; + +/** + * 错误信息对象 + */ +@Data +public class ErrorInfoJSONRespond { + String exception = null; + String exceptionMessage = null; + Date date = null; +} diff --git a/src/main/java/org/codedream/epaper/component/json/respond/JSONBaseRespondObject.java b/src/main/java/org/codedream/epaper/component/json/respond/JSONBaseRespondObject.java new file mode 100644 index 0000000..dc7881d --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/json/respond/JSONBaseRespondObject.java @@ -0,0 +1,61 @@ +package org.codedream.epaper.component.json.respond; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.codedream.epaper.component.json.JSONBaseObject; + +/** + * 服务端返回的JSON对象标准模板 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class JSONBaseRespondObject extends JSONBaseObject { + + // 存放返回内容 + private Object data = new EmptyDataObjectRespond(); + + // 存放响应信息提示 + private String msg = ""; + + // 额外信息 + private String info = null; + + // 状态 + private Integer status = 200; + + public JSONBaseRespondObject(String msg){ + super(); + this.status = 200; + this.msg = msg; + } + + public JSONBaseRespondObject(Integer status, String msg){ + super(); + this.status = status; + this.msg = msg; + } + + public void setStatusNotFound(){ + this.status = 404; + } + + public void setStatusBadRequest(){ + this.status = 400; + } + + public void setStatusUnauthorized(){ + this.status = 401; + } + + public void setStatusForbidden(){ + this.status = 403; + } + + public void setStatusInternalServerError(){ + this.status = 500; + } + + public void setStatusOK(){ + this.status = 200; + } +} diff --git a/src/main/java/org/codedream/epaper/component/json/respond/UserLoginCheckerJSONRespond.java b/src/main/java/org/codedream/epaper/component/json/respond/UserLoginCheckerJSONRespond.java new file mode 100644 index 0000000..65c8a5c --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/json/respond/UserLoginCheckerJSONRespond.java @@ -0,0 +1,32 @@ +package org.codedream.epaper.component.json.respond; + +import lombok.Data; + +/** + * 用户登录请求应答 + */ +@Data +public class UserLoginCheckerJSONRespond { + + // 用户是否存在 + Boolean userExist = null; + + // 用户是否被封禁 + Boolean userBanned = null; + + // 登录状态 + Boolean loginStatus = null; + + // 返回附加信息 + String respondInformation = null; + + // Token + String token = null; + + // 用户ID号(数据库) + String uid = null; + + // 预验证码 + String pvc = null; + +} diff --git a/src/main/java/org/codedream/epaper/component/server/CSPUpdater.java b/src/main/java/org/codedream/epaper/component/server/CSPUpdater.java new file mode 100644 index 0000000..236b51f --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/server/CSPUpdater.java @@ -0,0 +1,69 @@ +package org.codedream.epaper.component.server; + +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.component.auth.TimestampExpiredChecker; +import org.codedream.epaper.configure.AppConfigure; +import org.codedream.epaper.exception.innerservererror.InnerDataTransmissionException; +import org.codedream.epaper.model.server.ChildServerPassport; +import org.codedream.epaper.model.task.BatchProcessingTask; +import org.codedream.epaper.repository.server.ChildServerPassportRepository; +import org.codedream.epaper.repository.task.BatchProcessingTaskRepository; +import org.codedream.epaper.service.INeuralNetworkModelService; +import org.codedream.epaper.service.ITaskService; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Optional; + +/** + * 子服务器护照管理器 + */ +@Slf4j +@Component +public class CSPUpdater { + + @Resource + private ChildServerPassportRepository cspRepository; + + @Resource + private BatchProcessingTaskRepository bptRepository; + + @Resource + private INeuralNetworkModelService neuralNetworkModelService; + + @Resource + private TimestampExpiredChecker expiredChecker; + + @Resource + private AppConfigure configure; + + /** + * 检查护照状态 + */ + @Scheduled(cron = "0/60 * * * * ?") + public void update(){ + log.info("CSP Updater Started"); + Iterable childServerPassports = cspRepository.findByExpired(false); + for(ChildServerPassport csp : childServerPassports){ + if(expiredChecker + .checkDateBeforeDeterminedTime(csp.getLastUpdateTime(), configure.gerChildServerRegisterTimeout())){ + if(csp.getBptId() != null){ + + // 释放其占用的批处理任务 + Optional bpt = bptRepository.findById(csp.getBptId()); + if(!bpt.isPresent()) throw new InnerDataTransmissionException(); + neuralNetworkModelService.markBPTFailed(bpt.get()); + + log.info(String.format("Unlock BPT: bptId %s", csp.getBptId())); + csp.setBptId(null); + } + csp.setExpired(true); + csp = cspRepository.save(csp); + log.info(String.format("CSP Expired: idcode %s", csp.getIdentityCode())); + + } + + } + } +} diff --git a/src/main/java/org/codedream/epaper/component/task/ArticlePreprocessor.java b/src/main/java/org/codedream/epaper/component/task/ArticlePreprocessor.java new file mode 100644 index 0000000..d8a041b --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/task/ArticlePreprocessor.java @@ -0,0 +1,89 @@ +package org.codedream.epaper.component.task; + +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.component.datamanager.PdfParser; +import org.codedream.epaper.component.datamanager.TextParser; +import org.codedream.epaper.component.datamanager.WordParser; +import org.codedream.epaper.exception.innerservererror.HandlingErrorsException; +import org.codedream.epaper.exception.innerservererror.InnerDataTransmissionException; +import org.codedream.epaper.model.article.Article; +import org.codedream.epaper.model.file.File; +import org.codedream.epaper.model.task.Task; +import org.codedream.epaper.repository.article.ArticleRepository; +import org.codedream.epaper.repository.task.TaskRepository; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Optional; + +/** + * 用于对文章的预处理。 + * 包括对word文档的解析({@link WordParser}) + */ +@Slf4j +@Component +public class ArticlePreprocessor { + + @Resource + private TaskRepository taskRepository; + + @Resource + private ArticleRepository articleRepository; + + @Resource + private WordParser wordParser; + + @Resource + private TextParser textParser; + + @Resource + private PdfParser pdfParser; + + + /** + * 预处理任务中存储的文章并将其持久化 + * + * @param taskId 任务id + */ + public void parse(Integer taskId) { + + // 查找子任务 + Optional taskOptional = taskRepository.findById(taskId); + if (!taskOptional.isPresent()) throw new InnerDataTransmissionException(taskId.toString()); + + Task task = taskOptional.get(); + File file = task.getFile(); + if (file == null) throw new InnerDataTransmissionException(); + + task.setFile(file); + + Integer articleId = null; + + // 章分段处理 + switch (file.getType()) { + case "doc": + case "docx": + articleId = wordParser.parse(file.getId()); + break; + case "plain": + articleId = textParser.parse(file.getId()); + break; + case "pdf": + articleId = pdfParser.parse(file.getId()); + break; + default: + throw new HandlingErrorsException(file.getType()); + } + + if(articleId == null) throw new HandlingErrorsException(file.getId().toString()); + + Optional

optionalArticle = articleRepository.findById(articleId); + if (!optionalArticle.isPresent()) throw new InnerDataTransmissionException(); + + task.setArticle(optionalArticle.get()); + task.setProgressRate(task.getProgressRate() + 1); + log.info(String.format("Article preprocess finished, task progress for now is: %d", task.getProgressRate())); + taskRepository.save(task); + } + +} diff --git a/src/main/java/org/codedream/epaper/component/task/BPTDivider.java b/src/main/java/org/codedream/epaper/component/task/BPTDivider.java new file mode 100644 index 0000000..b0ade5e --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/task/BPTDivider.java @@ -0,0 +1,63 @@ +package org.codedream.epaper.component.task; + +import org.codedream.epaper.component.article.GetSentenceFromArticle; +import org.codedream.epaper.model.article.Sentence; +import org.codedream.epaper.model.task.BatchProcessingTask; +import org.codedream.epaper.model.task.Task; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * 把一个超过limit的BPT按照task数量等分 + */ +@Component +public class BPTDivider { + + @Resource + private GetSentenceFromArticle getSentenceFromArticle; + + /** + * 将一个批处理任务等分 + *

+ * 此方法会将原有的已经持久化的批处理任务从数据库中删除,并换成两个 + * 已经等分了的批处理任务 + * + * @param bpt 一个批处理任务 + * @return 两个封装在列表中的批处理任务 + */ + public List divideBPT(BatchProcessingTask bpt) { + + List batchProcessingTasks = new ArrayList<>(); + BatchProcessingTask bpt1 = new BatchProcessingTask(bpt); + BatchProcessingTask bpt2 = new BatchProcessingTask(bpt); + List tasks = bpt.getTasks(); + List sentenceList = new ArrayList<>(); + bpt1.setTasks(tasks.subList(0, tasks.size() / 2)); + bpt2.setTasks(tasks.subList(tasks.size() / 2, tasks.size())); + Integer sentenceNum = 0; + + divide(batchProcessingTasks, bpt1, sentenceList, sentenceNum); + sentenceList.clear(); + sentenceNum = 0; + + divide(batchProcessingTasks, bpt2, sentenceList, sentenceNum); + return batchProcessingTasks; + } + + private void divide(List batchProcessingTasks, BatchProcessingTask bpt, List sentenceList, Integer sentenceNum) { + for (int i = 0; i < bpt.getTasks().size(); i++) { + Task task = bpt.getTasks().get(i); + sentenceList.addAll(getSentenceFromArticle.get(task.getArticle())); + sentenceNum += task.getArticle().getSentencesNumber(); + } + bpt.setSentences(sentenceList); + bpt.setSentencesNumber(sentenceNum); + bpt.setPriority(sentenceNum); + bpt.setCreateDate(new Date()); + batchProcessingTasks.add(bpt); + } +} diff --git a/src/main/java/org/codedream/epaper/component/task/BPTMonitor.java b/src/main/java/org/codedream/epaper/component/task/BPTMonitor.java new file mode 100644 index 0000000..f997fce --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/task/BPTMonitor.java @@ -0,0 +1,83 @@ +package org.codedream.epaper.component.task; + +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.component.auth.TimestampExpiredChecker; +import org.codedream.epaper.configure.AppConfigure; +import org.codedream.epaper.exception.innerservererror.InnerDataTransmissionException; +import org.codedream.epaper.model.task.BatchProcessingTask; +import org.codedream.epaper.repository.task.BatchProcessingTaskRepository; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Iterator; +import java.util.Optional; + +/** + * 用于提供对批处理任务的监控方法。 + *

+ * 批处理任务在处理时可能会出现分配的GPU服务端突发故障、网络通信故障等问题,从而造成 + * 当前批处理任务在结果等待队列中的无限制等待。为了避免这种问题的发生,我们设计了一个 + * 批处理任务监听器用于监控其计算等待时间,超时后会放入原就绪队列进行重新分配 + */ +@Slf4j +@Component +public class BPTMonitor { + + @Resource + private LockedBPTs lockedBPTs; + + @Resource + private BPTQueue bptQueue; + + @Resource + private BatchProcessingTaskRepository bptRepository; + + @Resource + private TimestampExpiredChecker timestampExpiredChecker; + + @Resource + private AppConfigure configure; + + + private boolean initStatus = true; + + /** + * 一个定时任务,每15秒检测一次批处理任务的等待情况 + */ + @Scheduled(cron = "0/5 * * ? * *") + public void monitorBPTs() { + // 启动自检 + if (initStatus) { + // 查找未完成的批处理任务 + Iterable batchProcessingTasks = bptRepository.findAllByFinished(false); + + for (BatchProcessingTask bpt : batchProcessingTasks) { + bptQueue.addBPT(bpt.getId()); + } + initStatus = false; + } + + log.info("BPT Monitor Started"); + log.info(String.format("Lined BPTs Number For Now: %s", bptQueue.size())); + log.info(String.format("Locked BPTs Number For Now: %s", lockedBPTs.size())); + if (lockedBPTs.isEmpty()) { + return; + } + Iterator bptIterator = lockedBPTs.iterator(); + while (bptIterator.hasNext()) { + Integer bptId = bptIterator.next(); + Optional oBpt = bptRepository.findById(bptId); + if (!oBpt.isPresent()) throw new InnerDataTransmissionException(); + + BatchProcessingTask bpt = oBpt.get(); + if (timestampExpiredChecker.checkDateBeforeDeterminedTime( + bpt.getJoinDate(), configure.gerMaxBPTProcessDelayTime())) { + + bptQueue.addBPT(bpt.getId()); + bptIterator.remove(); + } + } + } + +} diff --git a/src/main/java/org/codedream/epaper/component/task/BPTQueue.java b/src/main/java/org/codedream/epaper/component/task/BPTQueue.java new file mode 100644 index 0000000..be578b8 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/task/BPTQueue.java @@ -0,0 +1,59 @@ +package org.codedream.epaper.component.task; + +import org.codedream.epaper.model.task.BatchProcessingTask; +import org.codedream.epaper.repository.task.BatchProcessingTaskRepository; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Comparator; +import java.util.concurrent.PriorityBlockingQueue; + +/** + * 批处理任务优先级队列 + *

+ * 根据批处理任务待处理的句子数量设置优先级 + * (参见{@link org.codedream.epaper.model.task.BatchProcessingTask#compareTo(BatchProcessingTask)}) + */ +@Component +public class BPTQueue { + + @Resource + private BatchProcessingTaskRepository bptRepository; + + // 优先阻塞队列 + private PriorityBlockingQueue batchProcessingTasks = new PriorityBlockingQueue<>(1, + new Comparator() { + @Override + public int compare(Integer o1, Integer o2) { + BatchProcessingTask bpt1 = bptRepository.findById(o1).get(); + BatchProcessingTask bpt2 = bptRepository.findById(o2).get(); + return bpt1.getPriority() - bpt2.getPriority(); + } + }); + + // 添加批处理任务 + public void addBPT(Integer bptId) { + batchProcessingTasks.offer(bptId); + } + + //移除批处理任务 + public void removeBPT(Integer bptId) { + batchProcessingTasks.remove(bptId); + } + + // 检查队列是否为空 + public boolean checkEmpty(){ + return batchProcessingTasks.isEmpty(); + } + + // 从队列中获得一个批处理任务 + public Integer getBptId() throws InterruptedException { + return batchProcessingTasks.take(); + } + + public Integer size(){ + return batchProcessingTasks.size(); + } + + +} diff --git a/src/main/java/org/codedream/epaper/component/task/JsonableTaskResultGenerator.java b/src/main/java/org/codedream/epaper/component/task/JsonableTaskResultGenerator.java new file mode 100644 index 0000000..840eae0 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/task/JsonableTaskResultGenerator.java @@ -0,0 +1,180 @@ +package org.codedream.epaper.component.task; + +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.component.json.model.JsonableSTNError; +import org.codedream.epaper.component.json.model.JsonableSTNResult; +import org.codedream.epaper.component.json.model.JsonableTaskResult; +import org.codedream.epaper.exception.notfound.NotFoundException; +import org.codedream.epaper.model.task.CorrectionResult; +import org.codedream.epaper.model.task.SentenceResult; +import org.codedream.epaper.model.task.TaskResult; +import org.codedream.epaper.repository.task.TaskResultRepository; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * 提供用于返回前端的结果构造方法 + * + * 原生的结果数据比较混杂,不适合前端后端JSON格式的数据通信,设立此类以整合结果 + */ +@Slf4j +@Component +public class JsonableTaskResultGenerator { + + @Resource + TaskResultRepository taskResultRepository; + + /** + * 根据任务结果的原生数据构造用于返回前端的任务结果 + * @param taskId 任务id + * @return 一个JsonableTaskResult对象,包含构建好的任务结果 + * @see JsonableTaskResult + */ + public JsonableTaskResult getJsonableTaskResult(Integer taskId) { + + log.info(String.format("Generating jsonable result of task %d", taskId)); + Optional OTaskResult = taskResultRepository.findByTaskId(taskId); + double dnnScore = 0; + double correctScore = 0; + double emotionScore = 0; + double e = Math.E; + int dnnCnt = 0; + int wrongCnt = 0; + int oralCnt = 0; + if (OTaskResult.isPresent()) { + TaskResult taskResult = OTaskResult.get(); + JsonableTaskResult jsonableTaskResult = new JsonableTaskResult(); + List jsonableSTNResults = new ArrayList<>(); + jsonableTaskResult.setTaskId(taskResult.getTaskId()); + taskResult.setSuccess(true); + taskResult = taskResultRepository.save(taskResult); + jsonableTaskResult.setSuccess(true); + Map sentenceResultMap = taskResult.getSentenceResultMap(); + for (SentenceResult result : taskResult.getSentenceResultMap().values()) { + JsonableSTNResult jsonableSTNResult = new JsonableSTNResult(); + jsonableSTNResult.setStnId(result.getSentenceId()); + jsonableSTNResult.setAppear(0); + SentenceResult sentenceResult = sentenceResultMap.get(result.getSentenceId()); + + // 中立值设置 + jsonableSTNResult.setNeutral(sentenceResult.isNeutral()); + + List jsonableSTNErrorList = new ArrayList<>(); + + List correctionResultList = result.getCorrectionResults(); + correctScore += correctionResultList.size(); + + log.info("Generating correction result……"); + log.info(String.format("Correction result of sentence %d: %s", sentenceResult.getSentenceId(), + correctionResultList.toString())); + // 寻找该sentence被修改了的位置 + for (CorrectionResult correctionResult : correctionResultList) { + JsonableSTNError jsonableSTNError = new JsonableSTNError(); + jsonableSTNError.setWordIdx(correctionResult.getStartPos()); + jsonableSTNError.setWordLen(correctionResult.getLength()); + jsonableSTNError.setType(1); + wrongCnt++; + if (correctionResult.getCorrectionText().isEmpty()) { + jsonableSTNError.setContent("文本存在错误,建议删除"); + } else { + jsonableSTNError.setContent(String.format("文本存在错误,建议修改为:%s", + correctionResult.getCorrectionText())); + } + jsonableSTNErrorList.add(jsonableSTNError); + } + + // 判断句子情感倾向 + String content; + JsonableSTNError stnError = new JsonableSTNError(); + if (sentenceResult.isNegative() || sentenceResult.isPositive()) { + stnError.setType(2); + stnError.setWordIdx(0); + stnError.setWordLen(0x7fffffff); + if (sentenceResult.isNegative()) { + taskResult.getNegativeEmotionsCount().incrementAndGet(); + float possibility = sentenceResult.getPossibilities().get(0); + emotionScore += Math.pow(e, possibility); + if (possibility < 0.9) { + content = "文本语言较强烈口语化特征,建议修改为书面语。"; + } else { + content = "文本语言极为强烈的口语化特征,建议修改为书面语。"; + } + } else { + taskResult.getPositiveEmotionsCount().incrementAndGet(); + float possibility = sentenceResult.getPossibilities().get(1); + emotionScore += Math.pow(2 * e, possibility); + if (possibility < 0.99) { + content = "文本语言较强烈的口语化特征,建议修改为书面语。"; + } else { + content = "文本语言有极为强烈的口语化特征,建议修改为书面语。"; + } + } + oralCnt++; + stnError.setContent(content); + jsonableSTNErrorList.add(stnError); + } else if (sentenceResult.getPossibilities().get(1) < 0.99) { + stnError.setType(2); + stnError.setWordIdx(0); + stnError.setWordLen(0x7fffffff); + content = "文本疑似存在口语化问题,请注意审查"; + stnError.setContent(content); + jsonableSTNErrorList.add(stnError); + oralCnt++; + } + + if (sentenceResult.getDnn() > 4000) { + dnnScore += sentenceResult.getDnn(); + taskResult.getBrokenSentencesCount().incrementAndGet(); + JsonableSTNError jsonableSTNError = new JsonableSTNError(); + jsonableSTNError.setType(3); + jsonableSTNError.setContent("句子通顺度存在问题,建议修改"); + jsonableSTNError.setWordIdx(0); + jsonableSTNError.setWordLen(0x7fffffff); + jsonableSTNErrorList.add(jsonableSTNError); + dnnCnt++; + } else if (sentenceResult.getDnn() > 2000) { + dnnScore += sentenceResult.getDnn(); + dnnCnt++; + } + jsonableSTNResult.setErrorList(jsonableSTNErrorList); + jsonableSTNResults.add(jsonableSTNResult); + } + taskResultRepository.save(taskResult); + + jsonableTaskResult.setWrongTextCount(wrongCnt); + jsonableTaskResult.setBrokenSentencesCount(dnnCnt); + jsonableTaskResult.setOralCount(oralCnt); + jsonableTaskResult.setStnResults(jsonableSTNResults); + + wrongCnt += 4; + correctScore = Math.PI * Math.log(Math.atan(1.1 * wrongCnt - 10) + 2) / Math.log(e) + 10; + correctScore = correctScore * 6.3489125794718040652210611515526 + 12.305831203372289317158476382609; + + dnnCnt += 1; + dnnScore = Math.PI * Math.log(-Math.atan(2 * dnnCnt - 20) + 2) / Math.log(e) + 10; + dnnScore = dnnScore * 5.8066270155791913101469156905197 + 19.001197895698319575318511547571; + + + oralCnt += 1; + emotionScore = Math.PI * Math.log(-Math.atan(0.9 * oralCnt - 12) + 2) / Math.log(e) + 10; + emotionScore = emotionScore * 6.4684248579558173553422908337857 + 9.969074019579274789063012724827; + + jsonableTaskResult.setDnnScore(dnnScore); + jsonableTaskResult.setEmotionScore(emotionScore); + jsonableTaskResult.setCorrectionScore(correctScore); + + dnnScore *= 0.34482758620689655172413793103448; + correctScore *= 0.17241379310344827586206896551724; + emotionScore *= 0.48275862068965517241379310344828; + jsonableTaskResult.setScore(dnnScore + emotionScore + correctScore); + return jsonableTaskResult; + } else { + throw new NotFoundException("This task has no result yet."); + } + } +} diff --git a/src/main/java/org/codedream/epaper/component/task/LockedBPTs.java b/src/main/java/org/codedream/epaper/component/task/LockedBPTs.java new file mode 100644 index 0000000..85ed256 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/task/LockedBPTs.java @@ -0,0 +1,35 @@ +package org.codedream.epaper.component.task; + +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * 用于提供{@link org.codedream.epaper.model.task.BatchProcessingTask}的计算等待队列以及相关操作 + */ +@Component +public class LockedBPTs { + private List bptIdList = new ArrayList<>(); + + public void add(Integer bptId){ + bptIdList.add(bptId); + } + + public Iterator iterator(){ + return bptIdList.iterator(); + } + + public boolean isEmpty(){ + return bptIdList.isEmpty(); + } + + public boolean contains(Integer integer){ + return bptIdList.contains(integer); + } + + public Integer size(){ + return bptIdList.size(); + } +} diff --git a/src/main/java/org/codedream/epaper/component/task/ParagraphProcessor.java b/src/main/java/org/codedream/epaper/component/task/ParagraphProcessor.java new file mode 100644 index 0000000..1b9985a --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/task/ParagraphProcessor.java @@ -0,0 +1,116 @@ +package org.codedream.epaper.component.task; + +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.component.datamanager.ParagraphDivider; +import org.codedream.epaper.component.datamanager.SHA512Encoder; +import org.codedream.epaper.exception.innerservererror.InnerDataTransmissionException; +import org.codedream.epaper.model.article.Article; +import org.codedream.epaper.model.article.Paragraph; +import org.codedream.epaper.model.article.Sentence; +import org.codedream.epaper.model.task.Task; +import org.codedream.epaper.repository.article.ArticleRepository; +import org.codedream.epaper.repository.article.ParagraphRepository; +import org.codedream.epaper.repository.article.SentenceRepository; +import org.codedream.epaper.repository.task.TaskRepository; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.*; + +/** + * 提供对段落的预处理方法 + */ +@Slf4j +@Component +public class ParagraphProcessor { + + @Resource + private TaskRepository taskRepository; + + @Resource + private ParagraphRepository paragraphRepository; + + @Resource + private ParagraphDivider paragraphDivider; + + @Resource + private SentenceRepository sentenceRepository; + + @Resource + private ArticleRepository articleRepository; + + @Resource + private SHA512Encoder encoder; + + /** + * 对段落进行分句处理并进行SHA512编码,以便缓存识别 + * + * @param taskId 任务id,用于获取待处理的段落 + * @see ParagraphDivider + * @see SHA512Encoder + */ + public void parse(Integer taskId) { + // 查找子任务 + Optional taskOptional = taskRepository.findById(taskId); + if (!taskOptional.isPresent()) throw new InnerDataTransmissionException(taskId.toString()); + + Article article = taskOptional.get().getArticle(); + if (article == null) throw new InnerDataTransmissionException(taskId.toString()); + + int stnNum = 0; + + // 段分句及段结构更新 + for (Paragraph paragraph : article.getParagraphs()) { + // 跳过预处理过的段落 + if (paragraph.isPreprocess()) continue; + + // 段分句处理 + List sentenceTexts = paragraphDivider.divideParagraph(paragraph.getText()); + List sentences = new ArrayList<>(); + for (String text : sentenceTexts) { + Sentence sentence; + String hash = encoder.encode(text); + + // 查找句数据库缓存 + Optional sentenceOptional = sentenceRepository.findBySha512Hash(hash); + if (!sentenceOptional.isPresent()) { + // 创建新的句 + sentence = new Sentence(); + sentence.setText(text); + sentence.setSha512Hash(hash); + sentence = sentenceRepository.save(sentence); + } else { + sentence = sentenceOptional.get(); + } + sentences.add(sentence); + } + + stnNum += sentences.size(); + + // 设置句集合 + Set sentenceSet = new HashSet<>(sentences); + paragraph.setSentences(sentenceSet); + + // 设置句列表 + for (Sentence sentence : sentences) { + paragraph.getSentenceList().add(sentence.getId()); + } + + // 设置预处理状态 + paragraph.setPreprocess(true); + + // 更新段落信息 + paragraphRepository.save(paragraph); + } + + // 更新文章总句数 + taskOptional.get().getArticle().setSentencesNumber(stnNum); + taskOptional.get().setProgressRate(taskOptional.get().getProgressRate() + 1); + log.info(String.format("Paragraph preprocess finished, task progress for now is: %d", + taskOptional.get().getProgressRate())); + articleRepository.save(taskOptional.get().getArticle()); + taskRepository.save(taskOptional.get()); + + } + +} diff --git a/src/main/java/org/codedream/epaper/component/task/SentenceAnalyser.java b/src/main/java/org/codedream/epaper/component/task/SentenceAnalyser.java new file mode 100644 index 0000000..0922acf --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/task/SentenceAnalyser.java @@ -0,0 +1,62 @@ +package org.codedream.epaper.component.task; + +import org.codedream.epaper.component.datamanager.SentenceSmoothnessGetter; +import org.codedream.epaper.component.datamanager.TextCorrector; +import org.codedream.epaper.model.article.Sentence; +import org.codedream.epaper.model.task.CorrectionResult; +import org.codedream.epaper.model.task.SentenceResult; +import org.codedream.epaper.repository.task.SentenceResultRepository; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 句分析器,提供句子分析相关方法以及持久化方法 + * + * @see TextCorrector + * @see SentenceSmoothnessGetter + */ +@Component +public class SentenceAnalyser { + + @Resource + private TextCorrector textCorrector; + + @Resource + private SentenceSmoothnessGetter dnnGetter; + + @Resource + private SentenceResultRepository sentenceResultRepository; + + public SentenceResult analyse(Sentence sentence) { + // 创建与预填写句子与处理结果结构 + SentenceResult sentenceResult = new SentenceResult(); + sentenceResult.setSentenceId(sentence.getId()); + sentenceResult.setDnn(dnnParse(sentence)); + correct(sentenceResult, sentence); + // 储存句分析结果 + return sentenceResultRepository.save(sentenceResult); + } + + /** + * 获取句子的文本纠错结果 + * + * @param sentence 句子 + */ + private void correct(SentenceResult sentenceResult, Sentence sentence) { + List correctionResultList = textCorrector.correctText(sentence.getText()); + sentenceResult.setCorrectionResults(correctionResultList); + } + + /** + * 获取DNN处理结果 + * + * @param sentence 待处理句子 + * @return DNN处理结果 + * @see SentenceSmoothnessGetter#getSentenceSmoothness(String) + */ + private synchronized float dnnParse(Sentence sentence) { + return dnnGetter.getSentenceSmoothness(sentence.getText()); + } +} diff --git a/src/main/java/org/codedream/epaper/component/task/SentencePreprocessor.java b/src/main/java/org/codedream/epaper/component/task/SentencePreprocessor.java new file mode 100644 index 0000000..3a27bb3 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/task/SentencePreprocessor.java @@ -0,0 +1,78 @@ +package org.codedream.epaper.component.task; + +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.component.article.GetSentenceFromArticle; +import org.codedream.epaper.component.datamanager.SentenceDivider; +import org.codedream.epaper.exception.innerservererror.InnerDataTransmissionException; +import org.codedream.epaper.model.article.Article; +import org.codedream.epaper.model.article.Phrase; +import org.codedream.epaper.model.article.Sentence; +import org.codedream.epaper.model.task.Task; +import org.codedream.epaper.repository.article.SentenceRepository; +import org.codedream.epaper.repository.task.TaskRepository; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Optional; + +/** + * 用于提供句子预处理相关方法,以及对应的持久化方法 + */ +@Slf4j +@Component +public class SentencePreprocessor { + + @Resource + private TaskRepository taskRepository; + + @Resource + private SentenceDivider sentenceDivider; + + @Resource + private SentenceRepository sentenceRepository; + + @Resource + private GetSentenceFromArticle getSentenceFromArticle; + + + /** + * 取出任务中的句子并进行预处理 + * + * @param taskId 任务id + * @see SentenceDivider#divideSentence(String) + * @see GetSentenceFromArticle#get(Article) + */ + public void parse(Integer taskId) { + // 查找子任务 + Optional taskOptional = taskRepository.findById(taskId); + if (!taskOptional.isPresent()) throw new InnerDataTransmissionException(taskId.toString()); + + Task task = taskOptional.get(); + Article article = task.getArticle(); + if (article == null) throw new InnerDataTransmissionException(taskId.toString()); + + List sentences = getSentenceFromArticle.get(article); + + for (Sentence sentence : sentences) { + // 跳过缓存句 + if (sentence.isPreprocess()) continue; + + List phrases = sentenceDivider.divideSentence(sentence.getText()); + + sentence.getPhrases().addAll(phrases); + sentence.setPreprocess(true); + + for (Phrase phrase : phrases) { + sentence.getPhraseList().add(phrase.getId()); + } + + sentenceRepository.save(sentence); + } + task.setProgressRate(task.getProgressRate() + 1); + log.info(String.format("Sentence preprocess finished, task progress for now is: %d", task.getProgressRate())); + taskRepository.save(task); + } + + +} diff --git a/src/main/java/org/codedream/epaper/component/task/TaskAnalyser.java b/src/main/java/org/codedream/epaper/component/task/TaskAnalyser.java new file mode 100644 index 0000000..e32cdc7 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/task/TaskAnalyser.java @@ -0,0 +1,110 @@ +package org.codedream.epaper.component.task; + +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.component.article.GetSentenceFromArticle; +import org.codedream.epaper.exception.innerservererror.InnerDataTransmissionException; +import org.codedream.epaper.model.article.Sentence; +import org.codedream.epaper.model.task.SentenceResult; +import org.codedream.epaper.model.task.Task; +import org.codedream.epaper.model.task.TaskResult; +import org.codedream.epaper.repository.task.SentenceResultRepository; +import org.codedream.epaper.repository.task.TaskRepository; +import org.codedream.epaper.repository.task.TaskResultRepository; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + + +/** + * 子任务预分析器,用于精确到句子的任务分析以及持久化子任务结果 + * + * @see SentenceAnalyser + */ +@Slf4j +@Component +public class TaskAnalyser { + + @Resource + private TaskResultRepository taskResultRepository; + + @Resource + private TaskRepository taskRepository; + + @Resource + private GetSentenceFromArticle getSentenceFromArticle; + + @Resource + private SentenceAnalyser sentenceAnalyser; + + @Resource + private SentenceResultRepository sentenceResultRepository; + + /** + * 分析一个task,并持久化其结果 + * 持久化结果的时候,会先查找缓存 + * + * @param taskId 任务id + * @return 一个TaskResult对象 + */ + public TaskResult analyse(Integer taskId) { + + log.info(String.format("Analysing task: %d", taskId)); + + // 查找子任务 + Optional taskOptional = taskRepository.findById(taskId); + if (!taskOptional.isPresent()) throw new InnerDataTransmissionException(taskId.toString()); + + Task task = taskOptional.get(); + + // 构建预处理的任务结果存储结构 + TaskResult taskResult = new TaskResult(); + taskResult.setTaskId(task.getId()); + + Map dnnRes = new HashMap<>(); + Map sentenceMap = new HashMap<>(); + // 句分析 + for(Sentence sentence : getSentenceFromArticle.get(task.getArticle())){ + SentenceResult sentenceResult; + log.info(String.format("Analysing sentence: %d", sentence.getId())); + // 查找缓存 + Optional sentenceResultOptional = + sentenceResultRepository.findBySentenceId(sentence.getId()); + + if (sentenceResultOptional.isPresent()) { + sentenceResult = sentenceResultOptional.get(); + + dnnRes.put(sentence.getId(), sentenceResult.getDnn()); + sentenceMap.put(sentence.getId(), sentenceResult); + log.info(String.format("DNN result of sentence %d: %f", sentence.getId(), sentenceResult.getDnn())); + log.info(String.format("Correction result of sentence %d: %s", sentence.getId(), + sentenceResult.getCorrectionResults().toString())); + continue; + } else { + sentenceResult = sentenceAnalyser.analyse(sentence); + } + log.info(String.format("DNN result of sentence %d: %f", sentence.getId(), sentenceResult.getDnn())); + log.info(String.format("Correction result of sentence %d: %s", sentence.getId(), + sentenceResult.getCorrectionResults().toString())); + dnnRes.put(sentence.getId(), sentenceResult.getDnn()); + sentenceMap.put(sentence.getId(), sentenceResult); + } + + taskResult.setDnnMap(dnnRes); + taskResult.setSentenceResultMap(sentenceMap); + + taskResult = taskResultRepository.save(taskResult); + + + // 更新任务 + taskOptional.get().setProgressRate(taskOptional.get().getProgressRate()+1); + task.setResult(taskResult); + taskRepository.save(task); + log.info(String.format("Analyse preprocess finished, task progress for now is: %d", task.getProgressRate())); + + log.info(String.format("Analysing result of task %d: %s", taskId, task.getResult())); + return taskResult; + } +} diff --git a/src/main/java/org/codedream/epaper/component/task/TaskMonitor.java b/src/main/java/org/codedream/epaper/component/task/TaskMonitor.java new file mode 100644 index 0000000..fe6d38a --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/task/TaskMonitor.java @@ -0,0 +1,83 @@ +package org.codedream.epaper.component.task; + +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.component.auth.TimestampExpiredChecker; +import org.codedream.epaper.configure.AppConfigure; +import org.codedream.epaper.model.task.BatchProcessingTask; +import org.codedream.epaper.model.task.Task; +import org.codedream.epaper.repository.task.BatchProcessingTaskRepository; +import org.codedream.epaper.service.TaskService; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +/** + * 子任务监视器 + */ +@Slf4j +@Component +public class TaskMonitor { + + @Resource + private TaskQueue taskQueue; + + @Resource + private TaskService taskService; + + @Resource + private TimestampExpiredChecker timestampExpiredChecker; + + @Resource + private BatchProcessingTaskRepository bptRepository; + + @Resource + private AppConfigure configure; + + @Scheduled(cron = "0/3 * * ? * *") + public void monitorTasks() { + log.info("Tasks Monitor Started."); + log.info(String.format("Tasks Number For Now: %s", taskQueue.size())); + if (taskQueue.checkEmpty()) { + return; + } + + List tasks = new ArrayList<>(); + Integer sentenceNum = 0; + Iterator iterator = taskQueue.getIterator(); + while (iterator.hasNext()) { + Task task = iterator.next(); + + if (null == task) { + log.error("Task null value."); + continue; + } + //超时MaxTaskDelayTime秒即添加到一个批处理队列中 + if (timestampExpiredChecker.checkDateBeforeDeterminedTime( + task.getCreateDate(), configure.gerMaxTaskDelayTime())) { + + tasks.add(task); + iterator.remove(); + sentenceNum += task.getArticle().getSentencesNumber(); + } + } + + if (tasks.isEmpty()) return; + + // 对等待超时的task立即新建一个bpt,将之放入就绪队列,等待调用 + BatchProcessingTask bpt = new BatchProcessingTask(); + bpt.setTasks(tasks); + bpt.setCreateDate(new Date()); + bpt.setPriority(sentenceNum); + bpt.setSentencesNumber(sentenceNum); + bpt = bptRepository.save(bpt); + + // 注册批处理任务 + taskService.registerBPTTask(bpt); + } + +} diff --git a/src/main/java/org/codedream/epaper/component/task/TaskQueue.java b/src/main/java/org/codedream/epaper/component/task/TaskQueue.java new file mode 100644 index 0000000..f14da83 --- /dev/null +++ b/src/main/java/org/codedream/epaper/component/task/TaskQueue.java @@ -0,0 +1,55 @@ +package org.codedream.epaper.component.task; + +import lombok.Data; +import org.codedream.epaper.model.task.Task; +import org.springframework.stereotype.Component; + +import java.util.*; + +/** + * 任务优先队列,用于给BPT中的任务进行优先级排序 + */ +@Data +@Component +public class TaskQueue { + + Comparator comparator = Comparator.comparingInt(Task::getPriority); + + private Queue tasks = new PriorityQueue<>(comparator); + + private Integer sentenceNumber = 0; + + public boolean addTask(Task task) { + if (this.tasks.offer(task)) { + sentenceNumber += task.getArticle().getSentencesNumber(); + return true; + } else return false; + } + + public Integer size(){ + return tasks.size(); + } + + public void removeTask(Task task){ + tasks.remove(task); + } + + public Iterator getIterator(){ + return tasks.iterator(); + } + + public List getTaskAsList() { + List taskList = new ArrayList<>(this.tasks); + return taskList; + } + + public void clear() { + this.tasks.clear(); + this.setSentenceNumber(0); + } + + public boolean checkEmpty() { + return this.tasks.isEmpty(); + } + +} diff --git a/src/main/java/org/codedream/epaper/configure/AppConfigure.java b/src/main/java/org/codedream/epaper/configure/AppConfigure.java new file mode 100644 index 0000000..1c82333 --- /dev/null +++ b/src/main/java/org/codedream/epaper/configure/AppConfigure.java @@ -0,0 +1,125 @@ +package org.codedream.epaper.configure; + +import org.springframework.stereotype.Component; + +/** + * 应用程序常用配置信息 + * 用于常见的应用程序本身的相关信息的引用 + */ +@Component +public class AppConfigure { + /** + * 获得应用程序的中文名 + * @return 返回包含完整内容的字符串 + */ + public String getName() { + return "智慧学术论文行文指导服务端"; + } + + /** + * 获得应用程序的版本号 + * @return 返回版本号内容的字符串 + */ + public String getVersion() { + return "0.0.1_200204"; + } + + /** + * 获得应用程序的英文名 + * @return 返回包含完整内容的字符串 + */ + public String getEnglishName() { + return "ePaper"; + } + + /** + * 获得开发小组的名称 + * @return 包含完整内容的字符串 + */ + public String getOrganization() { + return "码梦工坊"; + } + + /** + * 文件服务储存路径 + * @return 字符串 + */ + public String getFilePath(){ + return "./FILES/"; + } + + /** + * 上传的文件的大小限制(字节) + * 预设值:16MB + * @return 数值 + */ + public Integer getFileMaxSize(){ + return 16000000; + } + + /** + * 单段的最小字数阈值 + * @return 数值 + */ + public Integer getParagraphMinSize(){ + return 16; + } + + /** + * 单段的最大字数阈值 + * @return 数值 + */ + public Integer getParagraphMaxSize(){ + return 2048; + } + + /** + * 批处理任务最大句数目阈值 + * @return 数值 + */ + public Long getBPTMaxSentenceNumber() { + return 32768L; + } + + /** + * 批处理任务最少句数目阈值 + * @return 数值 + */ + public Long getBPTMinSentenceNumber(){ + return 1024L; + } + + /** + * 子服务器失联等待时间 + * @return 数值 + */ + public Integer gerChildServerRegisterTimeout(){ + return 300; + } + + /** + * 子任务等待被加入批处理任务最长时间 + * @return 数值 + */ + public Integer gerMaxTaskDelayTime(){ + return 30; + } + + /** + * 批处理任务处理最长时间 + * @return 数值 + */ + public Integer gerMaxBPTProcessDelayTime(){ + return 300; + } + + + /** + * 单页句数 + * @return 数值 + */ + public Integer getSentencePrePage(){ + return 10; + } + +} diff --git a/src/main/java/org/codedream/epaper/configure/BatchTaskConfiguration.java b/src/main/java/org/codedream/epaper/configure/BatchTaskConfiguration.java new file mode 100644 index 0000000..8eedd7b --- /dev/null +++ b/src/main/java/org/codedream/epaper/configure/BatchTaskConfiguration.java @@ -0,0 +1,46 @@ +package org.codedream.epaper.configure; + +import lombok.Data; +import org.springframework.context.annotation.Configuration; + +/** + * 批处理任务配置,用于配置线程相关参数 + */ +@Configuration +@Data +public class BatchTaskConfiguration { + + //保留的线程池大小 + private static int corePoolSize = 15; + + //线程池最大大小 + private static int maxPoolSize = 30; + + //线程最大空闲时间 + private static int keepAliveTime = 1000; + + //阻塞队列大小 + private static int workQueueSize = 200; + + private static Long limit = 20000L; + + public static int getCorePoolSize() { + return corePoolSize; + } + + public static int getMaxPoolSize() { + return maxPoolSize; + } + + public static int getKeepAliveTime() { + return keepAliveTime; + } + + public static int getWorkQueueSize() { + return workQueueSize; + } + + public static Long getLimit() { + return limit; + } +} diff --git a/src/main/java/org/codedream/epaper/configure/ComponentsConfigure.java b/src/main/java/org/codedream/epaper/configure/ComponentsConfigure.java new file mode 100644 index 0000000..5a99379 --- /dev/null +++ b/src/main/java/org/codedream/epaper/configure/ComponentsConfigure.java @@ -0,0 +1,9 @@ +package org.codedream.epaper.configure; + +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ComponentsConfigure { + + +} diff --git a/src/main/java/org/codedream/epaper/configure/CustomWebSecurityConfig.java b/src/main/java/org/codedream/epaper/configure/CustomWebSecurityConfig.java new file mode 100644 index 0000000..9556d6c --- /dev/null +++ b/src/main/java/org/codedream/epaper/configure/CustomWebSecurityConfig.java @@ -0,0 +1,162 @@ +package org.codedream.epaper.configure; + +import org.codedream.epaper.component.auth.*; +import org.codedream.epaper.service.EPUserDetailsService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.session.SessionRegistry; +import org.springframework.security.core.session.SessionRegistryImpl; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy; +import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +import javax.annotation.Resource; + +/** + * Spring Security 配置类 + * 用于Spring Security相关参数的配置 + */ +@Configuration +@EnableWebSecurity +public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter { + + @Resource + EPUserDetailsService aseUserDetailService; + + @Resource + EPPasswordEncoder EPPasswordEncoder; + + @Resource + EPSecurityAuthenticationProvider EPSecurityAuthenticationProvider; + + @Resource + EPAuthenticationSuccessHandler successHandler; + + @Resource + EPAuthenticationFailureHandler failureHandler; + + @Resource + EPAuthenticationEntryPoint authenticationEntryPoint; + + @Resource + EPAccessDeniedHandler accessDeniedHandler; + + /** + * HTTP服务器安全配置 + * @param http HTTP服务器安全对象 + * @throws Exception 异常 + */ + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .anyRequest().authenticated() + .and() + .csrf().disable() + .logout().permitAll(); + + http.exceptionHandling() + .authenticationEntryPoint(authenticationEntryPoint) + .accessDeniedHandler(accessDeniedHandler); + + // 替换掉原有的UsernamePasswordAuthenticationFilter + http.addFilterAt(epUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(jsonTokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); + + http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); + + } + + /** + * 认证器设置 + * @param auth 认证对象 + * @throws Exception 异常 + */ + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.authenticationProvider(EPSecurityAuthenticationProvider) + .userDetailsService(aseUserDetailService) + .passwordEncoder(EPPasswordEncoder); + } + + /** + * 安全配置 + * @param web web对象 + * @throws Exception 异常 + */ + @Override + public void configure(WebSecurity web) throws Exception { + web + .ignoring() + .antMatchers( + "/assets/**", + "/forget/**", + "/not_found/**", + "/error/**", + "/user/**", + "/swagger-ui.html", + "/webjars/**", + "/swagger-resources/**", + "/v2/api-docs", + "/configuration/ui", + "/configuration/security"); + } + + /** + * 注册自定义的UsernamePasswordAuthenticationFilter + * @return UsernamePasswordAuthenticationFilter + * @throws Exception 异常 + */ + @Bean + EPJSONTokenAuthenticationFilter jsonTokenAuthenticationFilter() throws Exception { + return new EPJSONTokenAuthenticationFilter(); + } + + /** + * 注册自定义的UsernamePasswordAuthenticationFilter + * @return UsernamePasswordAuthenticationFilter + * @throws Exception 异常 + */ + @Bean + EPUsernamePasswordAuthenticationFilter epUsernamePasswordAuthenticationFilter() throws Exception { + EPUsernamePasswordAuthenticationFilter filter = new EPUsernamePasswordAuthenticationFilter(); + filter.setAuthenticationSuccessHandler(successHandler); + filter.setAuthenticationFailureHandler(failureHandler); + filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy(sessionRegistry())); + filter.setAllowSessionCreation(true); + filter.setRequiresAuthenticationRequestMatcher( + new AntPathRequestMatcher("/user/login", "POST")); + + filter.setAuthenticationManager(authenticationManagerBean()); + return filter; + } + + /** + * 注册Session会话储存器 + * @return SessionRegistry + */ + @Bean + public SessionRegistry sessionRegistry() { + return new SessionRegistryImpl(); + } + + /** + * 登记sessionAuthenticationStrategy + * @param sessionRegistry sessionRegistry + * @return SessionAuthenticationStrategy + */ + @Bean + public SessionAuthenticationStrategy sessionAuthenticationStrategy(SessionRegistry sessionRegistry){ + return new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry){{ + setMaximumSessions(1); + }}; + } + +} diff --git a/src/main/java/org/codedream/epaper/configure/EPApplicationContextInitializer.java b/src/main/java/org/codedream/epaper/configure/EPApplicationContextInitializer.java new file mode 100644 index 0000000..3348863 --- /dev/null +++ b/src/main/java/org/codedream/epaper/configure/EPApplicationContextInitializer.java @@ -0,0 +1,24 @@ +package org.codedream.epaper.configure; + +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.component.task.BPTQueue; +import org.codedream.epaper.model.task.BatchProcessingTask; +import org.codedream.epaper.repository.task.BatchProcessingTaskRepository; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.Resource; + +/** + * 服务端程序初始化检查 + */ +@Slf4j +public class EPApplicationContextInitializer implements ApplicationContextInitializer { + + @Override + public void initialize(ConfigurableApplicationContext configurableApplicationContext) { + log.info("EPApplicationContextInitializer Started"); + + } +} diff --git a/src/main/java/org/codedream/epaper/configure/EPExecutorConfigure.java b/src/main/java/org/codedream/epaper/configure/EPExecutorConfigure.java new file mode 100644 index 0000000..53c17d1 --- /dev/null +++ b/src/main/java/org/codedream/epaper/configure/EPExecutorConfigure.java @@ -0,0 +1,39 @@ +package org.codedream.epaper.configure; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 线程池配置 + */ +@Slf4j +@Configuration +public class EPExecutorConfigure { + + @Bean(value = "PaPoolExecutor") + public Executor asyncServiceExecutor() { + ThreadPoolTaskExecutor executor =new ThreadPoolTaskExecutor(); + // 核心线程数 + executor.setCorePoolSize(4); + // 最大线程数 + executor.setMaxPoolSize(8); + // 队列大小 + executor.setQueueCapacity(100); + // 空闲线程等待工作的超时时间 + executor.setKeepAliveSeconds(60); + // 线程池中的线程的名称前缀 + executor.setThreadNamePrefix("pa-pool"); + + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + // 线程池初始化 + executor.initialize(); + + return executor; + } +} diff --git a/src/main/java/org/codedream/epaper/configure/EPSwaggerConfigure.java b/src/main/java/org/codedream/epaper/configure/EPSwaggerConfigure.java new file mode 100644 index 0000000..9910eff --- /dev/null +++ b/src/main/java/org/codedream/epaper/configure/EPSwaggerConfigure.java @@ -0,0 +1,59 @@ +package org.codedream.epaper.configure; + +import com.google.common.collect.Sets; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.ParameterBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.schema.ModelRef; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.Parameter; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * Swagger2配置类 + */ +@Configuration +@EnableSwagger2 +public class EPSwaggerConfigure { + @Bean + public Docket createRestApi() { + + List pars = new ArrayList(); + + pars.add(new ParameterBuilder().name("openid").description("账号openid").hidden(true).order(1) + .modelRef(new ModelRef("string")).parameterType("header") + .required(false).defaultValue("24310760d8bb8b6542e5a3f16a0d67253214e01ee7ab0e96a1").build()); + pars.add(new ParameterBuilder().name("signed").description("客户端签名").hidden(true).order(2) + .modelRef(new ModelRef("string")).parameterType("header") + .required(false).defaultValue("6d4923fca4dcb51f67b85e54a23a8d763d9e02af").build()); + pars.add(new ParameterBuilder().name("timestamp").description("时间戳").hidden(true).order(3) + .modelRef(new ModelRef("string")).parameterType("header") + .required(false).defaultValue(Long.toString(new Date().getTime())).build()); + + return new Docket(DocumentationType.SWAGGER_2) + .protocols(Sets.newHashSet("http")) + .apiInfo(apiInfo()) + .select() + .apis(RequestHandlerSelectors.basePackage("org.codedream.epaper.controller")) + .paths(PathSelectors.any()) + .build() + .globalOperationParameters(pars); + } + + private ApiInfo apiInfo() { + return new ApiInfoBuilder() + .title("智慧学术论文行文指导服务端接口定义") + .version("0.0.1") + .description("用于对服务端接口进行说明") + .build(); + } +} diff --git a/src/main/java/org/codedream/epaper/configure/GlobalConfigure.java b/src/main/java/org/codedream/epaper/configure/GlobalConfigure.java new file mode 100644 index 0000000..29ffd29 --- /dev/null +++ b/src/main/java/org/codedream/epaper/configure/GlobalConfigure.java @@ -0,0 +1,15 @@ +package org.codedream.epaper.configure; + + +import org.springframework.context.annotation.Configuration; + +/** + * Spring 框架全局配置类 + * 主要用于注册或者管理Bean + */ +@Configuration +public class GlobalConfigure { + + + +} diff --git a/src/main/java/org/codedream/epaper/configure/NLPConfigure.java b/src/main/java/org/codedream/epaper/configure/NLPConfigure.java new file mode 100644 index 0000000..a6b0264 --- /dev/null +++ b/src/main/java/org/codedream/epaper/configure/NLPConfigure.java @@ -0,0 +1,26 @@ +package org.codedream.epaper.configure; + +import org.springframework.context.annotation.Configuration; + +/** + * 百度API接口所需信息 + */ +@Configuration +public class NLPConfigure { + + private static final String APP_ID = "18006539"; + private static final String APP_KEY = "5sdgAnjElUhfuzH1eHFDnWpz"; + private static final String SECRET_KEY = "7DYgD3j0KEO3h0Lxrwq16QUWWShvsvKV"; + + public static String getAPPId() { + return APP_ID; + } + + public static String getAppKey() { + return APP_KEY; + } + + public static String getSecretKey() { + return SECRET_KEY; + } +} diff --git a/src/main/java/org/codedream/epaper/configure/PunctuationConfiguration.java b/src/main/java/org/codedream/epaper/configure/PunctuationConfiguration.java new file mode 100644 index 0000000..dffcdd0 --- /dev/null +++ b/src/main/java/org/codedream/epaper/configure/PunctuationConfiguration.java @@ -0,0 +1,75 @@ +package org.codedream.epaper.configure; + +import org.springframework.context.annotation.Configuration; + +import java.util.ArrayList; +import java.util.List; +/** + * 句末标点、引用标点等配置 + */ +@Configuration +public class PunctuationConfiguration { + + private static final List expPunctuations = new ArrayList() {{ + add("("); + add(")"); + add("("); + add(")"); + }}; + + private static final List endOfSentencePunctuations = new ArrayList() {{ + add("。"); + add("?"); + add("!"); + }}; + + private static final List warningQuotes = new ArrayList() {{ + add("\""); + add("\'"); + }}; + + private static final List frontPunctuations = new ArrayList() {{ + add("“"); + add("《"); + }}; + + private static final List backPunctuations = new ArrayList() {{ + add("”"); + add("》"); + add(")"); + add(")"); + }}; + + private static final List quotesPunctuations = new ArrayList() {{ + add("“"); + add("《"); + add("”"); + add("》"); + }}; + + + static public List getExpPunctuations() { + return expPunctuations; + } + + public static List getEndOfSentencePunctuations() { + return endOfSentencePunctuations; + } + + public static List getWarningQuotes() { + return warningQuotes; + } + + public static List getFrontPunctuations() { + return frontPunctuations; + } + + public static List getBackPunctuations() { + return backPunctuations; + } + + public static List getQuotesPunctuations() { + return quotesPunctuations; + } + +} diff --git a/src/main/java/org/codedream/epaper/configure/SingletonAipNlp.java b/src/main/java/org/codedream/epaper/configure/SingletonAipNlp.java new file mode 100644 index 0000000..5dd2f9c --- /dev/null +++ b/src/main/java/org/codedream/epaper/configure/SingletonAipNlp.java @@ -0,0 +1,32 @@ +package org.codedream.epaper.configure; + +import com.baidu.aip.nlp.AipNlp; +import org.codedream.epaper.configure.NLPConfigure; +import org.springframework.stereotype.Component; + +/** + * 百度{@link AipNlp}的接口单例设计,提供线程安全的单例接口调用 + */ +@Component +public class SingletonAipNlp { + private static AipNlp aipNlp; + + public SingletonAipNlp() { + } + + /** + * 调用一个单例{@link AipNlp} + * + * @return 一个单例的AipNlp对象 + */ + public static AipNlp getInstance() { + if (null == aipNlp) { + synchronized (AipNlp.class) { + if (null == aipNlp) { + aipNlp = new AipNlp(NLPConfigure.getAPPId(), NLPConfigure.getAppKey(), NLPConfigure.getSecretKey()); + } + } + } + return aipNlp; + } +} diff --git a/src/main/java/org/codedream/epaper/controller/EPControllerAdvice.java b/src/main/java/org/codedream/epaper/controller/EPControllerAdvice.java new file mode 100644 index 0000000..5c62519 --- /dev/null +++ b/src/main/java/org/codedream/epaper/controller/EPControllerAdvice.java @@ -0,0 +1,116 @@ +package org.codedream.epaper.controller; + +import io.swagger.annotations.Api; +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.component.api.QuickJSONRespond; +import org.codedream.epaper.component.json.respond.ErrorInfoJSONRespond; +import org.codedream.epaper.exception.badrequest.AlreadyExistException; +import org.codedream.epaper.exception.badrequest.IllegalException; +import org.codedream.epaper.exception.conflict.RelatedObjectsExistException; +import org.codedream.epaper.exception.innerservererror.FormatException; +import org.codedream.epaper.exception.innerservererror.HandlingErrorsException; +import org.codedream.epaper.exception.innerservererror.RuntimeIOException; +import org.codedream.epaper.exception.notfound.NotFoundException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import javax.annotation.Resource; +import java.util.Date; + +/** + * 错误/异常管理机制控制 + */ +@Slf4j +@RestControllerAdvice +@Api(hidden = true) +public class EPControllerAdvice { + + @Resource + private QuickJSONRespond quickJSONRespond; + + /** + * 非法请求类异常处理 + * @param ex 异常 + * @return 返回对象 + */ + @ExceptionHandler(value = { + NullPointerException.class, + AlreadyExistException.class, + IllegalException.class + }) + public ResponseEntity handleBadRequest(Exception ex) { + ex.printStackTrace(); + return getResponse(HttpStatus.BAD_REQUEST, ex); + } + + /** + * 未找到类异常处理 + * @param ex 异常 + * @return 返回对象 + */ + @ExceptionHandler(value = {NotFoundException.class}) + public ResponseEntity handleNotFound(Exception ex) { + + return getResponse(HttpStatus.NOT_FOUND, ex); + } + + /** + * 不可接受类异常处理 + * @param ex 异常 + * @return 返回对象 + */ + @ExceptionHandler(value = {}) + public ResponseEntity handleNotAcceptable(Exception ex) { + return getResponse(HttpStatus.NOT_ACCEPTABLE, ex); + } + + /** + * 冲突类异常处理 + * @param ex 异常 + * @return 返回对象 + */ + @ExceptionHandler(value = {RelatedObjectsExistException.class}) + public ResponseEntity handleConflict(Exception ex) { + return getResponse(HttpStatus.CONFLICT, ex); + } + + /** + * 内部错误类异常处理 + * @param ex 异常 + * @return 返回对象 + */ + @ExceptionHandler(value = { + HandlingErrorsException.class, + FormatException.class, + RuntimeIOException.class}) + public ResponseEntity handleInnerServerError(Exception ex){ + return getResponse(HttpStatus.INTERNAL_SERVER_ERROR, ex); + } + + /** + * 构造JSON填充的返回对象 + * @param status HTTP状态码 + * @param ex 异常 + * @return 返回对象 + */ + private ResponseEntity getResponse(HttpStatus status, Exception ex){ + return ResponseEntity.status(status).body(getJSON(status, ex)); + + } + + private String getJSON(HttpStatus status, Exception ex){ + return quickJSONRespond.getJSONStandardRespond(status, getJSONRespondObject(ex)); + } + + private Object getJSONRespondObject(Exception ex){ + ErrorInfoJSONRespond errorInfoJSONRespond = new ErrorInfoJSONRespond(); + errorInfoJSONRespond.setException(ex.getClass().getName()); + errorInfoJSONRespond.setExceptionMessage(ex.getMessage()); + errorInfoJSONRespond.setDate(new Date()); + return errorInfoJSONRespond; + } + + +} diff --git a/src/main/java/org/codedream/epaper/controller/FileController.java b/src/main/java/org/codedream/epaper/controller/FileController.java new file mode 100644 index 0000000..c99d560 --- /dev/null +++ b/src/main/java/org/codedream/epaper/controller/FileController.java @@ -0,0 +1,122 @@ +package org.codedream.epaper.controller; + + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.hdgf.streams.StringsStream; +import org.codedream.epaper.component.json.model.JsonableFile; +import org.codedream.epaper.configure.AppConfigure; +import org.codedream.epaper.exception.badrequest.IllegalException; +import org.codedream.epaper.exception.innerservererror.HandlingErrorsException; +import org.codedream.epaper.exception.innerservererror.RuntimeIOException; +import org.codedream.epaper.exception.notfound.NotFoundException; +import org.codedream.epaper.model.file.File; +import org.codedream.epaper.service.FileService; +import org.codedream.epaper.service.IFileService; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.URLEncoder; +import java.util.Optional; + +@Slf4j +@RestController +@Api("文件服务类接口") +@RequestMapping("file") +public class FileController { + + @Resource + private IFileService fileService; + + @Resource + private AppConfigure configure; + + @PostMapping("text") + @ResponseStatus(HttpStatus.CREATED) + @ApiOperation("文本上传接口") + public JsonableFile uploadText(@RequestBody String text) { + if(text == null || text.length() < 300) throw new IllegalArgumentException(); + ByteArrayInputStream stream = new ByteArrayInputStream(text.getBytes()); + + Integer fileId = fileService.saveFile(text.substring(0, 12), "plain", stream); + + JsonableFile jsonableFile = new JsonableFile(); + jsonableFile.setFileId(fileId); + jsonableFile.setFilename(text.substring(0, 12)); + jsonableFile.setType("plain"); + return jsonableFile; + } + + @PostMapping("") + @ResponseStatus(HttpStatus.CREATED) + @ApiOperation("文件上传接口") + public JsonableFile uploadFile(@RequestParam("file") MultipartFile file){ + String filename = file.getOriginalFilename(); + + String[] strArray = filename.split("\\."); + int suffixIndex = strArray.length -1; + String fileType = strArray[suffixIndex]; + + log.info(String.format("File Upload filename %s", filename)); + log.info(String.format("File Upload fileType %s", fileType)); + + // 检查文件大小 + if(file.getSize() > configure.getFileMaxSize()) throw new IllegalException(Long.toString(file.getSize())); + + if(fileType.equals("doc") || fileType.equals("docx") || fileType.equals("pdf")){ + try { + byte[] fileData = file.getBytes(); + ByteArrayInputStream stream = new ByteArrayInputStream(fileData); + Integer fileId = fileService.saveFile(filename, fileType, stream); + + // 填写返回JSON + JsonableFile jsonableFile = new JsonableFile(); + jsonableFile.setFileId(fileId); + jsonableFile.setFilename(filename); + jsonableFile.setType(fileType); + return jsonableFile; + + } catch (IOException e){ + throw new RuntimeIOException(filename); + } + + } + else throw new IllegalException(fileType); + } + + @GetMapping("download") + public void downloadFile(@RequestParam("fileId") Integer fileId, HttpServletResponse response){ + File file = fileService.getFileInfo(fileId); + if(file == null) throw new NotFoundException(fileId.toString()); + + try { + response.setHeader("content-type", "application/octet-stream"); + response.setContentType("application/octet-stream"); + response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e.getMessage()); + } + + InputStream stream = fileService.getFile(fileId); + + try{ + OutputStream outputStream = response.getOutputStream(); + int readBits; + byte[] rawBytes = new byte[1024]; + while ((readBits = stream.read(rawBytes)) != -1) { + outputStream.write(rawBytes, 0, readBits); + } + outputStream.close(); + stream.close(); + } catch (Exception e){ + throw new HandlingErrorsException(e.getMessage()); + } + + } +} diff --git a/src/main/java/org/codedream/epaper/controller/GPUServerController.java b/src/main/java/org/codedream/epaper/controller/GPUServerController.java new file mode 100644 index 0000000..25e1d6e --- /dev/null +++ b/src/main/java/org/codedream/epaper/controller/GPUServerController.java @@ -0,0 +1,202 @@ +package org.codedream.epaper.controller; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.models.auth.In; +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.component.json.model.JsonableBPT; +import org.codedream.epaper.component.json.model.JsonableBPTResult; +import org.codedream.epaper.component.json.model.JsonableCSP; +import org.codedream.epaper.component.json.model.JsonableSTN; +import org.codedream.epaper.configure.AppConfigure; +import org.codedream.epaper.exception.badrequest.AuthExpiredException; +import org.codedream.epaper.exception.badrequest.IllegalException; +import org.codedream.epaper.exception.innerservererror.HandlingErrorsException; +import org.codedream.epaper.exception.notfound.NotFoundException; +import org.codedream.epaper.model.article.Sentence; +import org.codedream.epaper.model.server.ChildServerPassport; +import org.codedream.epaper.model.task.BatchProcessingTask; +import org.codedream.epaper.model.user.User; +import org.codedream.epaper.repository.task.BatchProcessingTaskRepository; +import org.codedream.epaper.service.IChildServerService; +import org.codedream.epaper.service.INeuralNetworkModelService; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.*; + + +@Slf4j +@RestController +@Api("GPU服务器分布式计算接口") +@RequestMapping("cs") +public class GPUServerController { + + @Resource + private IChildServerService childServerService; + + @Resource + private INeuralNetworkModelService modelService; + + @Resource + private BatchProcessingTaskRepository bptRepository; + + @Resource + private AppConfigure configure; + + @PostMapping("") + @ApiOperation("获得子服务器护照") + @ResponseStatus(HttpStatus.OK) + public JsonableCSP registerCSP(Authentication authentication){ + User user = (User) authentication.getPrincipal(); + if(!user.getUserAuth().getRole().equals("ChildServer")) + throw new IllegalException(authentication.getName()); + + return new JsonableCSP(childServerService.createCSP(user)); + } + + @PutMapping("") + @ApiOperation("更新子服务器签证") + public JsonableCSP updateCSP(@RequestParam(value = "idcode") String idcode){ + if(!childServerService.checkCSP(idcode)) throw new IllegalException(idcode); + + ChildServerPassport csp = childServerService.updateCSP(idcode); + + // 签证过期 + if(csp == null) { + JsonableCSP jsonableCSP = new JsonableCSP(); + jsonableCSP.setExpired(true); + jsonableCSP.setIdentityCode(idcode); + return jsonableCSP; + } + + return new JsonableCSP(csp); + } + + @GetMapping("bpt") + @ApiOperation("获得一个合适的批处理任务") + @ResponseStatus(HttpStatus.OK) + public JsonableBPT getBPT(@RequestParam(value = "idcode") String idcode, + @RequestParam(value = "maxStnNum") String maxSTNNumberStr){ + + log.info(String.format("Get BPT Request From %s Max Sentence Number %s.", idcode, maxSTNNumberStr)); + + if(childServerService.checkCSPExpired(idcode)){ + log.info(String.format("Found CSP %s Expired.", idcode)); + throw new AuthExpiredException(idcode); + } + + float maxSTNNumber = Float.parseFloat(maxSTNNumberStr); + if(configure.getBPTMinSentenceNumber() > maxSTNNumber){ + log.info(String.format("Found maxSTNNumber %f is Too Few.", maxSTNNumber)); + throw new IllegalException ("Too Few maxSTNNumber"); + } + + log.info("PreCheck Succeed."); + + // 标记签证地点 + ChildServerPassport csp = childServerService.getCSPInfo(idcode); + + // 存在被该CSP锁住的BPT + if(csp.getBptId() != null){ + log.info(String.format("Locked BPT Found %d.", csp.getBptId())); + + Optional bpt = bptRepository.findById(csp.getBptId()); + if(!bpt.isPresent()) throw new IllegalException(); + modelService.markBPTFailed(bpt.get()); + csp.setBptId(null); + + log.info(String.format("Marked Locked BPT Failed %d.", csp.getBptId())); + } + + try { + log.info("Trying To Get New BPT..."); + Optional bpt = modelService.getBPTTaskAndLock((int) maxSTNNumber); + + if (!bpt.isPresent()) { + log.info("Available BPT Not Found."); + return new JsonableBPT(); + } + + log.info(String.format("New BPT Got %d.", bpt.get().getId())); + List sentences = modelService.calculateSentenceList(bpt.get()); + + // 所有句已处理 + if(sentences.size() == 0){ + log.info("None Sentence Must Be Processing"); + // 标记任务已完成 + modelService.markBPTSuccess(bpt.get(), new ArrayList<>()); + log.info("Marked BPT Successful."); + log.info("Available BPT Not Found."); + return new JsonableBPT(); + } + + log.info(String.format("Record BPT %d To CSP %s.", bpt.get().getId(), csp.getIdentityCode())); + csp.setBptId(bpt.get().getId()); + // 更新数据库 + childServerService.update(csp); + + return modelService.getJsonableBPT(bpt.get(), sentences); + + } catch(Exception e){ + log.error(e.getMessage()); + e.printStackTrace(); + throw new HandlingErrorsException(e.getMessage()); + } + + } + + + @PutMapping("bpt") + @ApiOperation("更新BPT状态为已完成") + @ResponseStatus(HttpStatus.CREATED) + public void setBPTFinished(@RequestParam(value = "idcode") String idcode, + @RequestParam(value = "bptId") Integer bptId, + @RequestParam(value = "status") boolean status, + @RequestBody List results){ + + log.info(String.format("Get BPT Result Upload Request From %s For BPT %d", idcode, bptId)); + + if(childServerService.checkCSPExpired(idcode)){ + log.info(String.format("Found CSP %s Expired.", idcode)); + throw new AuthExpiredException(idcode); + } + + ChildServerPassport csp = childServerService.getCSPInfo(idcode); + if(csp.getBptId() == null || !csp.getBptId().equals(bptId)){ + log.info(String.format("Found CSP Status %s Illegal.", idcode)); + throw new IllegalException(bptId.toString()); + } + + log.info("PreCheck Succeed."); + + Optional bpt = bptRepository.findById(bptId); + + if(!bpt.isPresent()){ + log.info(String.format("Found BPT ID %d Illegal.", bptId)); + throw new NotFoundException(bptId.toString()); + } + + if(status) { + log.info("Child Server Process Status Successful."); + modelService.markBPTSuccess(bpt.get(), results); + } + else{ + log.info("Child Server Process Status Failed."); + modelService.markBPTFailed(bpt.get()); + } + + csp.setBptId(null); + // 更新数据库 + childServerService.update(csp); + + log.info(String.format("BPT %d Result Processing Succeed For CSP %s", bptId, idcode)); + + } + + + + +} diff --git a/src/main/java/org/codedream/epaper/controller/ReportController.java b/src/main/java/org/codedream/epaper/controller/ReportController.java new file mode 100644 index 0000000..891edbe --- /dev/null +++ b/src/main/java/org/codedream/epaper/controller/ReportController.java @@ -0,0 +1,46 @@ +package org.codedream.epaper.controller; + +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.component.datamanager.ReportGenerator; +import org.codedream.epaper.exception.badrequest.IllegalException; +import org.codedream.epaper.exception.notfound.NotFoundException; +import org.codedream.epaper.model.file.File; +import org.codedream.epaper.model.task.Task; +import org.codedream.epaper.model.user.User; +import org.codedream.epaper.service.ITaskService; +import org.springframework.security.core.Authentication; +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 javax.annotation.Resource; +import java.util.Optional; + +@Slf4j +@RestController +@RequestMapping("report") +public class ReportController { + + @Resource + private ITaskService taskService; + + @Resource + private ReportGenerator reportGenerator; + + @GetMapping("generate") + Integer generateReport(Authentication authentication, @RequestParam("taskId") Integer taskId){ + + User user = (User) authentication.getPrincipal(); + Optional taskOptional = taskService.getTaskInfo(taskId); + if(!taskOptional.isPresent()) throw new NotFoundException(taskId.toString()); + if (taskOptional.get().getUser().getId() != user.getId()) throw new IllegalException(taskId.toString()); + + log.info(String.format("Start Generate Report For TaskID %d ...", taskId)); + + Integer fileId = reportGenerator.saveByFileService(reportGenerator.generate(taskId)); + log.info(String.format("Generate Report For TaskID %d Done.", taskId)); + + return fileId; + } +} diff --git a/src/main/java/org/codedream/epaper/controller/TaskController.java b/src/main/java/org/codedream/epaper/controller/TaskController.java new file mode 100644 index 0000000..5047acf --- /dev/null +++ b/src/main/java/org/codedream/epaper/controller/TaskController.java @@ -0,0 +1,249 @@ +package org.codedream.epaper.controller; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.codedream.epaper.component.article.GetSentenceFromArticle; +import org.codedream.epaper.component.datamanager.DiffMatchPatch; +import org.codedream.epaper.component.json.model.JsonableSTN; +import org.codedream.epaper.component.json.model.JsonableSTNPage; +import org.codedream.epaper.component.json.model.JsonableTask; +import org.codedream.epaper.component.json.model.JsonableTaskResult; +import org.codedream.epaper.configure.AppConfigure; +import org.codedream.epaper.exception.badrequest.IllegalException; +import org.codedream.epaper.exception.innerservererror.InnerDataTransmissionException; +import org.codedream.epaper.exception.notfound.NotFoundException; +import org.codedream.epaper.model.article.Article; +import org.codedream.epaper.model.article.Paragraph; +import org.codedream.epaper.model.article.Sentence; +import org.codedream.epaper.model.file.File; +import org.codedream.epaper.model.task.Task; +import org.codedream.epaper.model.task.TaskResult; +import org.codedream.epaper.model.user.User; +import org.codedream.epaper.repository.task.TaskResultRepository; +import org.codedream.epaper.service.IFileService; +import org.codedream.epaper.service.ITaskService; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.*; + +@RestController +@Api("任务类接口") +@RequestMapping("task") +public class TaskController { + + @Resource + private ITaskService taskService; + + @Resource + private IFileService fileService; + + @Resource + private GetSentenceFromArticle getSentenceFromArticle; + + @Resource + private AppConfigure configure; + + @Resource + private TaskResultRepository taskResultRepository; + + @PostMapping("") + @ApiOperation("创建任务") + @ResponseStatus(HttpStatus.CREATED) + public JsonableTask createTask(@RequestBody JsonableTask jsonableTask, Authentication authentication) { + // 获得当前认证用户的身份 + User user = (User) authentication.getPrincipal(); + + // 文件序号检查 + File file = fileService.getFileInfo(jsonableTask.getFileId()); + if(file == null) throw new NotFoundException(jsonableTask.getFileId().toString()); + + // 注册子任务 + Integer taskId = taskService.registerTask(user.getId(), file.getId(), "normal"); + jsonableTask.setTaskId(taskId); + + return jsonableTask; + } + + @GetMapping("history") + @ApiOperation("查询用户历史记录") + @ResponseStatus(HttpStatus.OK) + public List taskHistory(Authentication authentication){ + User user = (User) authentication.getPrincipal(); + Iterable tasks = taskService.findHistoryTaskId(user.getId()); + List taskIds = new ArrayList<>(); + + for(Task task : tasks){ + taskIds.add(task.getId()); + } + + return taskIds; + + } + + @GetMapping("") + @ApiOperation("查询任务状态接口") + @ResponseStatus(HttpStatus.OK) + public JsonableTask checkTask(@RequestParam(value = "taskId") Integer taskId){ + Optional task = taskService.getTaskInfo(taskId); + if(!task.isPresent()) throw new NotFoundException(taskId.toString()); + + JsonableTask jsonableTask = new JsonableTask(task.get()); + + if (task.get().isFinished()) { + Article article = task.get().getArticle(); + Iterator paragraphIterator = article.getParagraphs().iterator(); + StringBuilder builder = new StringBuilder(); + while (builder.length() < 30 && paragraphIterator.hasNext()) { + builder.append(paragraphIterator.next().getText()); + } + String str = builder.toString().trim(); + + if(str.length() > 30) str = str.substring(0, 30); + jsonableTask.setDescription(str); + } + + return jsonableTask; + } + + @GetMapping("result") + @ApiOperation("获得任务结果") + @ResponseStatus(HttpStatus.OK) + public JsonableTaskResult getTaskResult(@RequestParam(value = "taskId") Integer taskId){ + // 验证子任务Id + Optional task = taskService.getTaskInfo(taskId); + if (!task.isPresent()) throw new NotFoundException(taskId.toString()); + if (!task.get().isFinished()) + throw new NotFoundException(String.format("Task Not Finished : Task Id %d", taskId)); + + + Optional taskResult = taskService.getTaskResult(taskId); + if(!taskResult.isPresent()) throw new NotFoundException(String.format("Result Not Found : Task Id %d",taskId)); + + return taskService.getJsonableTaskResult(taskId); + } + + @GetMapping("stnlist") + @ApiOperation("获取句子原文接口") + @ResponseStatus(HttpStatus.OK) + public JsonableSTNPage getSTNList(@RequestParam(value = "taskId") Integer taskId, + @RequestParam(value = "page") Integer page){ + // 验证子任务Id + Optional task = taskService.getTaskInfo(taskId); + if (!task.isPresent()) throw new NotFoundException(taskId.toString()); + if (!task.get().isFinished()) + throw new NotFoundException(String.format("Task Not Finished : Task Id %d", taskId)); + if (page == null) page = 1; + if(page < 1) throw new IllegalException(); + + + Optional taskResult = taskService.getTaskResult(taskId); + if(!taskResult.isPresent()) throw new InnerDataTransmissionException(); + + if(!taskResult.get().isSuccess()){ + throw new IllegalException("Task Failed"); + } + + List sentences = getSentenceFromArticle.get(task.get().getArticle()); + + + int stnPrePage = configure.getSentencePrePage(); + + // 求所有分页数 + int pageAll = new Double(Math.ceil(sentences.size() / 10.0)).intValue(); + if(page > pageAll) page = pageAll; + + + // 按照ID 排序 + sentences.sort(Comparator.comparing(Sentence::getId)); + + List sentencePage = new ArrayList<>(); + + // 获取分页数据 + for(int i = (page-1) * stnPrePage ; i < page * stnPrePage && i < sentences.size(); i++){ + sentencePage.add(sentences.get(i)); + } + + List jsonableSTNList = new ArrayList<>(); + + for(Sentence sentence : sentencePage){ + JsonableSTN jsonableSTN = new JsonableSTN(); + jsonableSTN.setStnId(sentence.getId()); + jsonableSTN.setText(sentence.getText()); + jsonableSTNList.add(jsonableSTN); + } + + JsonableSTNPage stnPage = new JsonableSTNPage(); + stnPage.setAll(pageAll); + stnPage.setPage(page); + stnPage.setStns(jsonableSTNList); + + return stnPage; + } + + @GetMapping("test") + @ResponseStatus(HttpStatus.OK) + public String test() { + DiffMatchPatch dmp = new DiffMatchPatch(); + String text1 = "我们预备了缓存数据库功能,将论文种常见的行文错误进行特征编码后缓存。"; + String text2 = "我们预备了缓存数据库功能,将论文中常见的行文错误进行特征编码后缓存。"; + LinkedList diff = dmp.diff_main(text1, text2); + List show = new ArrayList<>(); + System.out.println(diff.toString()); + for (DiffMatchPatch.Diff diff1 : diff) { + DiffMatchPatch.Operation operation = diff1.operation; + System.out.println(operation.compareTo(DiffMatchPatch.Operation.INSERT)); + show.add(operation.compareTo(DiffMatchPatch.Operation.INSERT)); + } + return show.toString() + "\n" + diff.toString(); + } + + @GetMapping("test2") + @ResponseStatus(HttpStatus.OK) + public String test2(@RequestParam("str") String test) { + System.out.println("aha?"); + List taskResults = taskResultRepository.findAll(); + /*for (TaskResult taskResult : taskResults) { + System.out.println(taskResult.getTaskId() + ": " + taskResult.getBrokenSentencesCount() + + " " + taskResult.getWrongTextCount() + " " + + taskResult.getPositiveEmotionsCount().addAndGet(taskResult.getNegativeEmotionsCount().intValue())); + }*/ + StringTokenizer stringTokenizer = new StringTokenizer(test, ","); + int cnt = 0; + int[] args = new int[4]; + while (stringTokenizer.hasMoreTokens()) { + args[cnt++] = Integer.parseInt(stringTokenizer.nextToken()); + } + args[1] += 4; + double e = Math.E; + double res1 = Math.PI * Math.log(-Math.atan(1.3 * args[1] - 10) + 2) / Math.log(e) + 10; + res1 *= 6.3489125794718040652210611515526; + res1 += 12.305831203372289317158476382609; + + //0: 13.949372309773254 + //10: 9.643946813538529 + // times: 5.8066270155791913101469156905197 + /// tmp: 80.998802104301680424681488452429 + // delta: 19.001197895698319575318511547571 + args[0] += 1; + double res0 = Math.PI * Math.log(-Math.atan(2 * args[0] - 20) + 2) / Math.log(e) + 10; + res0 *= 5.8066270155791913101469156905197; + res0 += 19.001197895698319575318511547571; + + //0 :13.918523899939487 + //14:10.053595335568621 + //times:6.4684248579558173553422908337857 + // tmp:90.030925980420725210936987275173 + // delta:9.969074019579274789063012724827 + args[2] += 1; + double res2 = Math.PI * Math.log(-Math.atan(0.9 * args[2] - 12) + 2) / Math.log(e) + 10; + res2 *= 6.4684248579558173553422908337857; + res2 += 9.969074019579274789063012724827; + + return res0 + " " + res1 + " " + res2; + } + + +} diff --git a/src/main/java/org/codedream/epaper/controller/UserController.java b/src/main/java/org/codedream/epaper/controller/UserController.java new file mode 100644 index 0000000..200e759 --- /dev/null +++ b/src/main/java/org/codedream/epaper/controller/UserController.java @@ -0,0 +1,70 @@ +package org.codedream.epaper.controller; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.codedream.epaper.component.json.model.JsonableUser; +import org.codedream.epaper.exception.badrequest.AlreadyExistException; +import org.codedream.epaper.exception.badrequest.IllegalException; +import org.codedream.epaper.exception.notfound.NotFoundException; +import org.codedream.epaper.model.user.User; +import org.codedream.epaper.service.IUserService; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import springfox.documentation.spring.web.json.Json; + +import javax.annotation.Resource; +import java.util.Optional; + +@RestController +@RequestMapping("user") +@Api("用户验证类接口") +public class UserController { + + @Resource + private IUserService userService; + + @PostMapping("") + @ResponseStatus(HttpStatus.CREATED) + @ApiOperation("用户注册接口") + public JsonableUser createUser(@RequestBody JsonableUser jsonableUser){ + if(jsonableUser.getOpenid() == null) throw new IllegalAccessError("Null Value Openid"); + if(userService.findUserByOpenid(jsonableUser.getOpenid()).isPresent()) + throw new AlreadyExistException(jsonableUser.getOpenid()); + + User user = userService.getDefaultUser(); + return new JsonableUser(userService.save(jsonableUser.parseObject(user))); + } + + @PostMapping("cs") + @ResponseStatus(HttpStatus.CREATED) + @ApiOperation("子服务器注册接口") + public JsonableUser createChildServerUser(@RequestParam(value = "clientCode") String clientCode, + @RequestBody JsonableUser jsonableUser){ + if(jsonableUser.getOpenid() == null) throw new IllegalAccessError("Null Value Openid"); + if(userService.findUserByOpenid(jsonableUser.getOpenid()).isPresent()) + throw new AlreadyExistException(jsonableUser.getOpenid()); + + if(!clientCode.equals("dc9fbb4f4f0b84fa903058991af60e73556494af8a02ef69fb6a93217729f04b")) + throw new IllegalException("Illegal Child Server"); + + User user = userService.getDefaultUser(); + user.getUserAuth().setRole("ChildServer"); + return new JsonableUser(userService.save(jsonableUser.parseObject(user))); + } + + @GetMapping("") + @ResponseStatus(HttpStatus.OK) + @ApiOperation("检查用户是否存在接口") + public JsonableUser getUser(@RequestParam(value = "openid") String openid){ + Optional user = userService.findUserByOpenid(openid); + if(!user.isPresent()) + throw new NotFoundException(openid); + + JsonableUser jsonableUser = new JsonableUser(); + jsonableUser.setId(user.get().getId()); + jsonableUser.setOpenid(user.get().getUsername()); + return jsonableUser; + } + + +} diff --git a/src/main/java/org/codedream/epaper/exception/badrequest/AlreadyExistException.java b/src/main/java/org/codedream/epaper/exception/badrequest/AlreadyExistException.java new file mode 100644 index 0000000..21158c8 --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/badrequest/AlreadyExistException.java @@ -0,0 +1,14 @@ +package org.codedream.epaper.exception.badrequest; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@EqualsAndHashCode(callSuper = true) +@Data +@NoArgsConstructor +public class AlreadyExistException extends RuntimeException { + public AlreadyExistException(String msg){ + super(msg); + } +} diff --git a/src/main/java/org/codedream/epaper/exception/badrequest/AuthExpiredException.java b/src/main/java/org/codedream/epaper/exception/badrequest/AuthExpiredException.java new file mode 100644 index 0000000..b39d2bb --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/badrequest/AuthExpiredException.java @@ -0,0 +1,15 @@ +package org.codedream.epaper.exception.badrequest; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 认证信息过期 + */ +@Data +@NoArgsConstructor +public class AuthExpiredException extends IllegalException { + public AuthExpiredException(String msg){ + super(msg); + } +} diff --git a/src/main/java/org/codedream/epaper/exception/badrequest/BaseInformationAlreadyExistException.java b/src/main/java/org/codedream/epaper/exception/badrequest/BaseInformationAlreadyExistException.java new file mode 100644 index 0000000..0924efa --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/badrequest/BaseInformationAlreadyExistException.java @@ -0,0 +1,17 @@ +package org.codedream.epaper.exception.badrequest; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class BaseInformationAlreadyExistException extends AlreadyExistException { + private String className; + private String value; + + public BaseInformationAlreadyExistException(Class aClass, String value){ + super(String.format("%s: %s", aClass.getName(), value)); + this.className = aClass.getName(); + this.value = value; + } +} diff --git a/src/main/java/org/codedream/epaper/exception/badrequest/BaseInformationIllegalException.java b/src/main/java/org/codedream/epaper/exception/badrequest/BaseInformationIllegalException.java new file mode 100644 index 0000000..56cb70d --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/badrequest/BaseInformationIllegalException.java @@ -0,0 +1,17 @@ +package org.codedream.epaper.exception.badrequest; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class BaseInformationIllegalException extends IllegalException { + String type; + String value; + + public BaseInformationIllegalException(Class aClass, String value){ + super(); + this.type = aClass.getName(); + this.value = value; + } +} diff --git a/src/main/java/org/codedream/epaper/exception/badrequest/IllegalException.java b/src/main/java/org/codedream/epaper/exception/badrequest/IllegalException.java new file mode 100644 index 0000000..17d1076 --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/badrequest/IllegalException.java @@ -0,0 +1,10 @@ +package org.codedream.epaper.exception.badrequest; + +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class IllegalException extends RuntimeException { + public IllegalException(String msg){ + super(msg); + } +} diff --git a/src/main/java/org/codedream/epaper/exception/badrequest/UserInformationIllegalException.java b/src/main/java/org/codedream/epaper/exception/badrequest/UserInformationIllegalException.java new file mode 100644 index 0000000..c7b9341 --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/badrequest/UserInformationIllegalException.java @@ -0,0 +1,17 @@ +package org.codedream.epaper.exception.badrequest; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class UserInformationIllegalException extends IllegalException { + + String username; + + public UserInformationIllegalException(String username){ + super(); + this.username = username; + } + +} diff --git a/src/main/java/org/codedream/epaper/exception/badrequest/UsernameAlreadyExistException.java b/src/main/java/org/codedream/epaper/exception/badrequest/UsernameAlreadyExistException.java new file mode 100644 index 0000000..0601e17 --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/badrequest/UsernameAlreadyExistException.java @@ -0,0 +1,16 @@ +package org.codedream.epaper.exception.badrequest; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class UsernameAlreadyExistException extends AlreadyExistException { + + String username; + + public UsernameAlreadyExistException(String username){ + super(username); + this.username = username; + } +} diff --git a/src/main/java/org/codedream/epaper/exception/conflict/RelatedObjectsExistException.java b/src/main/java/org/codedream/epaper/exception/conflict/RelatedObjectsExistException.java new file mode 100644 index 0000000..10713a4 --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/conflict/RelatedObjectsExistException.java @@ -0,0 +1,13 @@ +package org.codedream.epaper.exception.conflict; + +import lombok.NoArgsConstructor; + +/** + * 存在与之相关联的对象 + */ +@NoArgsConstructor +public class RelatedObjectsExistException extends RuntimeException { + public RelatedObjectsExistException(String msg){ + super(msg); + } +} diff --git a/src/main/java/org/codedream/epaper/exception/innerservererror/DataIOException.java b/src/main/java/org/codedream/epaper/exception/innerservererror/DataIOException.java new file mode 100644 index 0000000..3e80c5d --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/innerservererror/DataIOException.java @@ -0,0 +1,9 @@ +package org.codedream.epaper.exception.innerservererror; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class DataIOException extends RuntimeIOException { +} diff --git a/src/main/java/org/codedream/epaper/exception/innerservererror/DataIllegalTableFormatException.java b/src/main/java/org/codedream/epaper/exception/innerservererror/DataIllegalTableFormatException.java new file mode 100644 index 0000000..871c509 --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/innerservererror/DataIllegalTableFormatException.java @@ -0,0 +1,9 @@ +package org.codedream.epaper.exception.innerservererror; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class DataIllegalTableFormatException extends FormatException { +} diff --git a/src/main/java/org/codedream/epaper/exception/innerservererror/DataInvalidFormatException.java b/src/main/java/org/codedream/epaper/exception/innerservererror/DataInvalidFormatException.java new file mode 100644 index 0000000..59233fe --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/innerservererror/DataInvalidFormatException.java @@ -0,0 +1,19 @@ +package org.codedream.epaper.exception.innerservererror; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class DataInvalidFormatException extends FormatException { + String information; + + public DataInvalidFormatException(Exception e){ + super(); + information = e.getMessage(); + } + + public DataInvalidFormatException(){ + super(); + } +} diff --git a/src/main/java/org/codedream/epaper/exception/innerservererror/FormatException.java b/src/main/java/org/codedream/epaper/exception/innerservererror/FormatException.java new file mode 100644 index 0000000..96c268e --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/innerservererror/FormatException.java @@ -0,0 +1,10 @@ +package org.codedream.epaper.exception.innerservererror; + +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class FormatException extends RuntimeException { + public FormatException(String msg){ + super(msg); + } +} diff --git a/src/main/java/org/codedream/epaper/exception/innerservererror/HandlingErrorsException.java b/src/main/java/org/codedream/epaper/exception/innerservererror/HandlingErrorsException.java new file mode 100644 index 0000000..8ea5dec --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/innerservererror/HandlingErrorsException.java @@ -0,0 +1,15 @@ +package org.codedream.epaper.exception.innerservererror; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +// 处理错误对应的异常类 +@EqualsAndHashCode(callSuper = true) +@Data +@NoArgsConstructor +public class HandlingErrorsException extends RuntimeException { + public HandlingErrorsException(String msg){ + super(msg); + } +} diff --git a/src/main/java/org/codedream/epaper/exception/innerservererror/InnerDataTransmissionException.java b/src/main/java/org/codedream/epaper/exception/innerservererror/InnerDataTransmissionException.java new file mode 100644 index 0000000..0f883a3 --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/innerservererror/InnerDataTransmissionException.java @@ -0,0 +1,13 @@ +package org.codedream.epaper.exception.innerservererror; + +import lombok.NoArgsConstructor; + +/** + * 内部接口数据传递异常 + */ +@NoArgsConstructor +public class InnerDataTransmissionException extends HandlingErrorsException { + public InnerDataTransmissionException(String msg){ + super(msg); + } +} diff --git a/src/main/java/org/codedream/epaper/exception/innerservererror/InvalidFormFormatException.java b/src/main/java/org/codedream/epaper/exception/innerservererror/InvalidFormFormatException.java new file mode 100644 index 0000000..c40db56 --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/innerservererror/InvalidFormFormatException.java @@ -0,0 +1,19 @@ +package org.codedream.epaper.exception.innerservererror; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class InvalidFormFormatException extends FormatException { + + private String message = "Invalid form format"; + + public InvalidFormFormatException(){ + super(); + } + + public InvalidFormFormatException(String message){ + this.message = message; + } +} diff --git a/src/main/java/org/codedream/epaper/exception/innerservererror/LowBatchLimitException.java b/src/main/java/org/codedream/epaper/exception/innerservererror/LowBatchLimitException.java new file mode 100644 index 0000000..1f9ee1a --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/innerservererror/LowBatchLimitException.java @@ -0,0 +1,19 @@ +package org.codedream.epaper.exception.innerservererror; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class LowBatchLimitException extends RuntimeException { + + private String message = "Low batch limit, please set a higher one."; + + public LowBatchLimitException() { + super(); + } + + public LowBatchLimitException(String message) { + this.message = message; + } +} diff --git a/src/main/java/org/codedream/epaper/exception/innerservererror/RuntimeIOException.java b/src/main/java/org/codedream/epaper/exception/innerservererror/RuntimeIOException.java new file mode 100644 index 0000000..aae4af1 --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/innerservererror/RuntimeIOException.java @@ -0,0 +1,10 @@ +package org.codedream.epaper.exception.innerservererror; + +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class RuntimeIOException extends RuntimeException { + public RuntimeIOException(String msg){ + super(msg); + } +} diff --git a/src/main/java/org/codedream/epaper/exception/innerservererror/StringFileConvertException.java b/src/main/java/org/codedream/epaper/exception/innerservererror/StringFileConvertException.java new file mode 100644 index 0000000..2a42504 --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/innerservererror/StringFileConvertException.java @@ -0,0 +1,12 @@ +package org.codedream.epaper.exception.innerservererror; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class StringFileConvertException extends HandlingErrorsException { + public StringFileConvertException(String msg){ + super(msg); + } +} diff --git a/src/main/java/org/codedream/epaper/exception/notfound/BaseInformationNotFoundException.java b/src/main/java/org/codedream/epaper/exception/notfound/BaseInformationNotFoundException.java new file mode 100644 index 0000000..7116d51 --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/notfound/BaseInformationNotFoundException.java @@ -0,0 +1,17 @@ +package org.codedream.epaper.exception.notfound; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class BaseInformationNotFoundException extends NotFoundException { + private String className; + private String value; + + public BaseInformationNotFoundException(Class baseInformationClass, String value){ + super(String.format("%s: %s", baseInformationClass.getName(), value)); + this.className = baseInformationClass.getName(); + this.value = value; + } +} diff --git a/src/main/java/org/codedream/epaper/exception/notfound/DataFileNotFoundException.java b/src/main/java/org/codedream/epaper/exception/notfound/DataFileNotFoundException.java new file mode 100644 index 0000000..93fb8c4 --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/notfound/DataFileNotFoundException.java @@ -0,0 +1,15 @@ +package org.codedream.epaper.exception.notfound; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class DataFileNotFoundException extends NotFoundException { + private String path; + + public DataFileNotFoundException(String msg){ + super(msg); + this.path = msg; + } +} diff --git a/src/main/java/org/codedream/epaper/exception/notfound/NotFoundException.java b/src/main/java/org/codedream/epaper/exception/notfound/NotFoundException.java new file mode 100644 index 0000000..364070c --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/notfound/NotFoundException.java @@ -0,0 +1,12 @@ +package org.codedream.epaper.exception.notfound; + + +public class NotFoundException extends RuntimeException { + public NotFoundException(String msg){ + super(msg); + } + + public NotFoundException(){ + super(); + } +} diff --git a/src/main/java/org/codedream/epaper/exception/notfound/TagNotFoundException.java b/src/main/java/org/codedream/epaper/exception/notfound/TagNotFoundException.java new file mode 100644 index 0000000..d05cd1c --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/notfound/TagNotFoundException.java @@ -0,0 +1,11 @@ +package org.codedream.epaper.exception.notfound; + + +public class TagNotFoundException extends NotFoundException { + String tagName; + + public TagNotFoundException(String tagName){ + super(tagName); + this.tagName = tagName; + } +} diff --git a/src/main/java/org/codedream/epaper/exception/notfound/UserNotFoundException.java b/src/main/java/org/codedream/epaper/exception/notfound/UserNotFoundException.java new file mode 100644 index 0000000..ecab7b0 --- /dev/null +++ b/src/main/java/org/codedream/epaper/exception/notfound/UserNotFoundException.java @@ -0,0 +1,20 @@ +package org.codedream.epaper.exception.notfound; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class UserNotFoundException extends NotFoundException { + Integer id; + String username; + public UserNotFoundException(Integer id, String username){ + super(); + this.id = id; + this.username = username; + } + + public UserNotFoundException(String msg){ + super(msg); + } +} diff --git a/src/main/java/org/codedream/epaper/model/article/Article.java b/src/main/java/org/codedream/epaper/model/article/Article.java new file mode 100644 index 0000000..a25fc6d --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/article/Article.java @@ -0,0 +1,54 @@ +package org.codedream.epaper.model.article; + +import lombok.Data; +import org.codedream.epaper.model.user.User; +import org.springframework.security.core.parameters.P; +import org.springframework.web.bind.annotation.RestController; + +import javax.persistence.*; +import java.lang.invoke.LambdaConversionException; +import java.util.*; + +/** + * 章处理结构 + */ +@Data +@Entity +@Table(name = "article") +public class Article { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + // 创建时间 + private Date createTime = new Date(); + + // 对应的文件 + private Integer fileId = null; + + private Integer totalLength = 0; + + // 待处理的句的数目 + private Integer sentencesNumber = 0; + + // 段列表 + @ElementCollection + private List paragraphList = new ArrayList<>(); + + // 段集合 + @ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY) + private Set paragraphs = new HashSet<>(); + + + @ManyToOne(cascade = CascadeType.MERGE, fetch = FetchType.LAZY) + private User user = null; + + // 是否预处理 + private boolean preprocess = false; + + // 是否深度处理 + private boolean deepProcess = false; + + +} diff --git a/src/main/java/org/codedream/epaper/model/article/LongText.java b/src/main/java/org/codedream/epaper/model/article/LongText.java new file mode 100644 index 0000000..8985fbf --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/article/LongText.java @@ -0,0 +1,30 @@ +package org.codedream.epaper.model.article; + +import lombok.Data; + +import javax.persistence.*; +import java.util.Date; + +/** + * 长文本统一储存片段(用于数据库容量自动管理) + */ +@Data +@Entity +@Table(name = "long_text") +public class LongText { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + // 创建时间 + private Date createTime = new Date(); + + // SHA512 哈希值 + private String sha512Hash; + + // 长文本内容 + @Column(columnDefinition = "LONGTEXT") + private String text = ""; + +} diff --git a/src/main/java/org/codedream/epaper/model/article/Paragraph.java b/src/main/java/org/codedream/epaper/model/article/Paragraph.java new file mode 100644 index 0000000..aff2c0f --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/article/Paragraph.java @@ -0,0 +1,56 @@ +package org.codedream.epaper.model.article; + +import lombok.Data; + +import javax.persistence.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * 段处理结构(中间结构) + */ +@Data +@Entity +@Table(name = "paragraph") +public class Paragraph { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + // 文本 + @Column(columnDefinition = "LONGTEXT") + private String text = ""; + + // 哈希值 + private String sha512Hash; + + // 句列表 + @ElementCollection + private List sentenceList = new ArrayList<>(); + + // 句集合 + @ManyToMany(cascade = {}, fetch = FetchType.LAZY) + private Set sentences = new HashSet<>(); + + // 预处理标志位 + private boolean preprocess = false; + + // 深度处理标志位 + private boolean deepProcess = false; + + @Override + public int hashCode(){ + return this.id; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Paragraph paragraph = (Paragraph) o; + return this.id.equals(paragraph.id); + } + +} diff --git a/src/main/java/org/codedream/epaper/model/article/Phrase.java b/src/main/java/org/codedream/epaper/model/article/Phrase.java new file mode 100644 index 0000000..f8396e4 --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/article/Phrase.java @@ -0,0 +1,54 @@ +package org.codedream.epaper.model.article; + +import lombok.Data; + +import javax.persistence.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * 词处理结构 + */ +@Data +@Entity +@Table(name = "phrase") +public class Phrase { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + // 文本 + private String text; + + // 词性 + private String pos; + + // 词向量 + @ElementCollection + private List vec = new ArrayList<>(); + + // 语素列表 + @ElementCollection + private List basicPhraseList = new ArrayList<>(); + + // 语素集合 + @ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY) + private Set basicPhrase = new HashSet<>(); + + + @Override + public int hashCode(){ + return this.id; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Phrase phrase = (Phrase) o; + return this.id.equals(phrase.id); + } + +} diff --git a/src/main/java/org/codedream/epaper/model/article/Sentence.java b/src/main/java/org/codedream/epaper/model/article/Sentence.java new file mode 100644 index 0000000..6720a2b --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/article/Sentence.java @@ -0,0 +1,55 @@ +package org.codedream.epaper.model.article; + +import lombok.Data; + +import javax.persistence.*; +import java.util.*; + +/** + * 句处理结构 + */ +@Data +@Entity +@Table(name = "sentence") +public class Sentence { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + // 原始文本 + private String text = ""; + + // 文本哈希值(用于缓存) + private String sha512Hash = ""; + + // 创建时间 + private Date createTime = new Date(); + + // 分词表 + @ElementCollection + private List phraseList = new ArrayList<>(); + + // 词集合 + @ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY) + private Set phrases = new HashSet<>(); + + // 预处理标志位 + private boolean preprocess = false; + + // 深度处理标志位 + private boolean deepProcess = false; + + @Override + public int hashCode(){ + return this.id; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Sentence sentence = (Sentence) o; + return this.id.equals(sentence.id); + } +} diff --git a/src/main/java/org/codedream/epaper/model/auth/JSONToken.java b/src/main/java/org/codedream/epaper/model/auth/JSONToken.java new file mode 100644 index 0000000..2911cb1 --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/auth/JSONToken.java @@ -0,0 +1,31 @@ +package org.codedream.epaper.model.auth; + +import lombok.Data; + +import javax.persistence.*; +import java.util.Date; + +// token记录 +@Data +@Entity +@Table(name = "json_tokens") +public class JSONToken { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + // token对应的用户 + @Column(unique = true) + private String username; + + // token值 + @Column(unique = true) + private String token; + + // 客户端标识口令 + private String clientCode; + + // token过期时间 + private Date expiredDate; +} diff --git a/src/main/java/org/codedream/epaper/model/auth/PreValidationCode.java b/src/main/java/org/codedream/epaper/model/auth/PreValidationCode.java new file mode 100644 index 0000000..22e994f --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/auth/PreValidationCode.java @@ -0,0 +1,20 @@ +package org.codedream.epaper.model.auth; + +import lombok.Data; + +import javax.persistence.*; +import java.util.Date; + +@Data +@Entity +@Table(name = "pre_validation_code") +public class PreValidationCode { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private int id; + + private String value; + + private Date date = new Date(); + +} diff --git a/src/main/java/org/codedream/epaper/model/cache/Cache.java b/src/main/java/org/codedream/epaper/model/cache/Cache.java new file mode 100644 index 0000000..12d9239 --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/cache/Cache.java @@ -0,0 +1,30 @@ +package org.codedream.epaper.model.cache; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import javax.persistence.*; +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name = "cache") +public class Cache { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private int id; + + @Column(unique = true) + private String hashCode; + + @JsonFormat(locale = "zh", timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime recordTime; + + private String result; + + public Cache(String result) { + this.result = result; + } + +} diff --git a/src/main/java/org/codedream/epaper/model/file/File.java b/src/main/java/org/codedream/epaper/model/file/File.java new file mode 100644 index 0000000..9b3913e --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/file/File.java @@ -0,0 +1,36 @@ +package org.codedream.epaper.model.file; + +import lombok.Data; + +import javax.persistence.*; +import java.util.Date; + +@Data +@Entity +@Table(name = "file") +public class File { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + // 文件名 + private String name; + + // 文件哈希码 + private String hash; + + // 文件类型 + private String type; + + // 文件大小 + private Integer size; + + // 储存名 + private String storageName; + + // 文件路径 + private String path = "/"; + + // 文件上传时间 + private Date date = new Date(); +} diff --git a/src/main/java/org/codedream/epaper/model/record/BPTRecord.java b/src/main/java/org/codedream/epaper/model/record/BPTRecord.java new file mode 100644 index 0000000..10b31a7 --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/record/BPTRecord.java @@ -0,0 +1,32 @@ +package org.codedream.epaper.model.record; + +import lombok.Data; + +import javax.persistence.*; +import java.util.Date; + +/** + * 批处理任务变动记录 + */ +@Data +@Entity +@Table(name = "bpt_record") +public class BPTRecord { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + // 批处理任务序号 + private Integer bptId; + + // 记录时间 + private Date time = new Date(); + + // 操作类型 + private String operationType; + + // 记录内容 + private String message = ""; + +} diff --git a/src/main/java/org/codedream/epaper/model/record/FileRecord.java b/src/main/java/org/codedream/epaper/model/record/FileRecord.java new file mode 100644 index 0000000..3547a02 --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/record/FileRecord.java @@ -0,0 +1,31 @@ +package org.codedream.epaper.model.record; + +import lombok.Data; + +import javax.persistence.*; +import java.util.Date; + +/** + * 文件变动记录 + */ +@Data +@Entity +@Table(name = "file_record") +public class FileRecord { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + private Integer fileId; + + // 记录生成时间 + private Date time = new Date(); + + // 操作类型 + private String operationType; + + // 记录内容 + private String message = ""; + +} diff --git a/src/main/java/org/codedream/epaper/model/record/TaskRecord.java b/src/main/java/org/codedream/epaper/model/record/TaskRecord.java new file mode 100644 index 0000000..ad942db --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/record/TaskRecord.java @@ -0,0 +1,33 @@ +package org.codedream.epaper.model.record; + +import io.swagger.models.auth.In; +import lombok.Data; + +import javax.persistence.*; +import java.util.Date; + +/** + * 子任务变动记录 + */ +@Data +@Entity +@Table(name = "task_record") +public class TaskRecord { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + // 子任务序号 + private Integer taskId; + + // 记录生成时间 + private Date time = new Date(); + + // 操作类型 + private String operationType; + + // 记录内容 + private String msg = ""; + +} diff --git a/src/main/java/org/codedream/epaper/model/record/UserRecord.java b/src/main/java/org/codedream/epaper/model/record/UserRecord.java new file mode 100644 index 0000000..7f2b582 --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/record/UserRecord.java @@ -0,0 +1,24 @@ +package org.codedream.epaper.model.record; + +import javax.persistence.*; +import java.util.Date; + +@Entity +@Table(name = "user_record") +public class UserRecord { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + // 用户Id + private Integer userId; + + // 记录创建时间 + private Date time = new Date(); + + // 操作类型 + private String operationType; + + // 记录内容 + private String msg = ""; +} diff --git a/src/main/java/org/codedream/epaper/model/server/ChildServerPassport.java b/src/main/java/org/codedream/epaper/model/server/ChildServerPassport.java new file mode 100644 index 0000000..4da173c --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/server/ChildServerPassport.java @@ -0,0 +1,40 @@ +package org.codedream.epaper.model.server; + +import lombok.Data; +import org.codedream.epaper.model.user.User; + +import javax.persistence.*; +import java.util.Date; +import java.util.UUID; + +/** + * 分布式计算业务子服务器护照 + */ +@Data +@Entity +@Table(name="csp") +public class ChildServerPassport { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + // 认证代码 + private String identityCode = UUID.randomUUID().toString(); + + // 关联用户 + @ManyToOne(cascade = CascadeType.MERGE, fetch = FetchType.LAZY) + private User user = null; + + // 创建信息 + private Date createTime = new Date(); + + // 是否过期 + private boolean expired = false; + + // 子服务器最后一次状态更新时间 + private Date lastUpdateTime = new Date(); + + // 正在处理的批处理任务Id + private Integer bptId = null; + +} diff --git a/src/main/java/org/codedream/epaper/model/task/BatchProcessingTask.java b/src/main/java/org/codedream/epaper/model/task/BatchProcessingTask.java new file mode 100644 index 0000000..b47ea40 --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/task/BatchProcessingTask.java @@ -0,0 +1,99 @@ +package org.codedream.epaper.model.task; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.model.article.Sentence; + +import javax.persistence.*; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * 批处理任务 + */ +@Slf4j +@Data +@Entity +@Table(name="batch_processing_task") +public class BatchProcessingTask implements Comparable { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + // 任务流水号 + private String serialNumber; + + // 任务号 + @OneToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY) + private List tasks = new ArrayList<>(); + + // 句集合 + @ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY) + private List sentences = new ArrayList<>(); + + + // 待处理的句的数目 + private Integer sentencesNumber = 0; + + // 加入队列时间 + private Date joinDate = new Date(); + + // 创建时间 + private Date createDate = new Date(); + + // 完成时间 + private Date finishDate = null; + + // 运行次数 + private Integer tryNumber = 0; + + // 是否成功 + private boolean success = false; + + // 是否完成 + private boolean finished = false; + + // 任务优先级 + private Integer priority = 1; + + @Override + public int compareTo(BatchProcessingTask o) { + if (this.getPriority() > o.getPriority()) { + return 1; + } else if (this.getPriority() < o.getPriority()) { + return -1; + } + return 0; + } + + public void setSuccess(boolean ifSuccess) { + this.success = ifSuccess; + } + + public void setFinished(boolean ifFinished) { + this.finished = ifFinished; + } + + public BatchProcessingTask(BatchProcessingTask batchProcessingTask) { + batchProcessingTask.setCreateDate(this.createDate); + batchProcessingTask.setFinishDate(this.finishDate); + batchProcessingTask.setFinished(this.finished); + batchProcessingTask.setTryNumber(this.tryNumber); + batchProcessingTask.setSuccess(this.success); + } + + public BatchProcessingTask() { + } + + public void setTaskFinished() { + for (Task task : this.tasks) { + task.setFinished(true); + task.setProgressRate(task.getProgressRate() + 1); + log.info(String.format("BPT progress finished, task progress for now is: %d", task.getProgressRate())); + task.setEndDate(new Date()); + } + } + + +} diff --git a/src/main/java/org/codedream/epaper/model/task/BatchProcessingTaskResult.java b/src/main/java/org/codedream/epaper/model/task/BatchProcessingTaskResult.java new file mode 100644 index 0000000..660533f --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/task/BatchProcessingTaskResult.java @@ -0,0 +1,26 @@ +package org.codedream.epaper.model.task; + +import lombok.Data; + +import javax.persistence.*; +import java.util.Date; + +@Data +@Entity +@Table(name = "bpt_result") +public class BatchProcessingTaskResult { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private BatchProcessingTask batchProcessingTask = null; + + // 创建时间 + private Date createDate = new Date(); + + // 是否成功 + private boolean success = false; + + +} diff --git a/src/main/java/org/codedream/epaper/model/task/CorrectionResult.java b/src/main/java/org/codedream/epaper/model/task/CorrectionResult.java new file mode 100644 index 0000000..3f46f94 --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/task/CorrectionResult.java @@ -0,0 +1,22 @@ +package org.codedream.epaper.model.task; + +import lombok.Data; + +import javax.persistence.*; + +@Data +@Entity +@Table +public class CorrectionResult { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + Integer startPos; + + Integer length; + + String correctionText; + +} diff --git a/src/main/java/org/codedream/epaper/model/task/SentenceResult.java b/src/main/java/org/codedream/epaper/model/task/SentenceResult.java new file mode 100644 index 0000000..97a4339 --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/task/SentenceResult.java @@ -0,0 +1,36 @@ +package org.codedream.epaper.model.task; + +import lombok.Data; + +import javax.persistence.*; +import java.util.ArrayList; +import java.util.List; + +@Data +@Entity +@Table(name = "sentence_result") +public class SentenceResult { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + private Integer sentenceId; + + private boolean isNeutral = false; + + private boolean isPositive = false; + + private boolean isNegative = false; + + private Float dnn; + + @ElementCollection + private List possibilities = new ArrayList<>(); + + @OneToMany(cascade = CascadeType.ALL) + private List correctionResults = new ArrayList<>(); + + private boolean initStatus = true; + +} diff --git a/src/main/java/org/codedream/epaper/model/task/Task.java b/src/main/java/org/codedream/epaper/model/task/Task.java new file mode 100644 index 0000000..e0e9bee --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/task/Task.java @@ -0,0 +1,68 @@ +package org.codedream.epaper.model.task; + +import io.swagger.models.auth.In; +import lombok.Data; +import org.codedream.epaper.model.article.Article; +import org.codedream.epaper.model.file.File; +import org.codedream.epaper.model.user.User; +import org.codedream.epaper.repository.task.TaskRepository; + +import javax.persistence.*; +import javax.xml.transform.Result; +import java.util.Date; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 子任务 + */ +@Data +@Entity +@Table(name = "task") +public class Task { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + private Date createDate = new Date(); + + // 处理时间 + private Date dealingDate; + + // 加入队列时间 + private Date joinDate = new Date(); + + // 结束时间 + private Date endDate; + + private Integer priority; + + // 关联文件 + @ManyToOne(cascade = CascadeType.MERGE, fetch = FetchType.LAZY) + private File file = null; + + // 关联文章 + @ManyToOne(cascade = CascadeType.MERGE, fetch = FetchType.LAZY) + private Article article = null; + + // 关联用户 + @ManyToOne(cascade = CascadeType.MERGE, fetch = FetchType.LAZY) + private User user = null; + + @OneToOne(cascade = CascadeType.MERGE, fetch = FetchType.LAZY) + private TaskResult result; + + // 异常结束 + private boolean failed = false; + + // 是否结束 + private boolean finished = false; + + // 已加入批处理任务 + private boolean scheduled = false; + + // 任务类型 + private String type; + + // 处理进度标识,五位分别表示:章预处理、段预处理、句预处理、预分析、BERT计算 + private Integer progressRate = 0; +} diff --git a/src/main/java/org/codedream/epaper/model/task/TaskResult.java b/src/main/java/org/codedream/epaper/model/task/TaskResult.java new file mode 100644 index 0000000..026737c --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/task/TaskResult.java @@ -0,0 +1,55 @@ +package org.codedream.epaper.model.task; + +import lombok.Data; + +import javax.persistence.*; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 任务结果 + */ +@Data +@Entity +@Table(name = "task_result") +public class TaskResult { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Integer id; + + // 结果类型 + private String type; + + // 对应任务id + private Integer taskId; + + // 文章错误位置数量 + private AtomicInteger wrongTextCount = new AtomicInteger(0); + + // 不通顺的句子数量 + private AtomicInteger brokenSentencesCount = new AtomicInteger(0); + + // 消极情感倾向严重的句子数量 + private AtomicInteger negativeEmotionsCount = new AtomicInteger(0); + + // 积极情感倾向严重的句子数量 + private AtomicInteger positiveEmotionsCount = new AtomicInteger(0); + + @ElementCollection + private List sentenceIds = new ArrayList<>(); + + // 句子通顺度 + @ElementCollection + private Map dnnMap = new HashMap<>(); + + // 句处理结果查找表 + @ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY) + private Map sentenceResultMap = new HashMap<>(); + + + // 生成日期 + private Date createDate = new Date(); + + // 处理成功 + private boolean success = false; +} diff --git a/src/main/java/org/codedream/epaper/model/user/User.java b/src/main/java/org/codedream/epaper/model/user/User.java new file mode 100644 index 0000000..37fe370 --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/user/User.java @@ -0,0 +1,75 @@ +package org.codedream.epaper.model.user; + +import lombok.Data; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import javax.persistence.*; +import java.util.Collection; + +@Data +@Entity +@Table(name = "user") +public class User implements UserDetails { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private int id; + + // 用户名(openid) + @Column(unique = true, nullable = false) + private String username; + + // 密码(必须以哈希值sha256储存) + @Column(nullable = false) + private String password; + + // 账号是否过期 + private boolean accountNonExpired = true; + + // 账号是否被封禁 + private boolean accountNonLocked = true; + + // 证书是否过期 + private boolean credentialsNonExpired = true; + + // 账号是否激活 + private boolean enabled = true; + + // 是否删除 + @Column(nullable = false) + private boolean deleted = false; + + // 访问控制角色(不在数据表中) Spring Security + private transient Collection authorities; + + // 用户详细信息 + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private UserDetail userDetail = new UserDetail(); + + // 用户认证表 + @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + private UserAuth userAuth = new UserAuth(); + + + public User(String username, String password) { + this.username = username; + this.password = password; + + initDefault(); + } + + public User() { + this.username = null; + this.password = null; + this.deleted = false; + + initDefault(); + } + + // 用默认的方式初始化User对象的值 + private void initDefault(){ + + } + +} diff --git a/src/main/java/org/codedream/epaper/model/user/UserAuth.java b/src/main/java/org/codedream/epaper/model/user/UserAuth.java new file mode 100644 index 0000000..72f4985 --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/user/UserAuth.java @@ -0,0 +1,19 @@ +package org.codedream.epaper.model.user; + +import lombok.Data; + +import javax.persistence.*; + +@Data +@Entity +@Table(name = "user_auth") +public class UserAuth { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private int id; + + // 用户身份(普通用户、子服务器) + private String role = "User"; + +} diff --git a/src/main/java/org/codedream/epaper/model/user/UserDetail.java b/src/main/java/org/codedream/epaper/model/user/UserDetail.java new file mode 100644 index 0000000..b6e3ff4 --- /dev/null +++ b/src/main/java/org/codedream/epaper/model/user/UserDetail.java @@ -0,0 +1,32 @@ +package org.codedream.epaper.model.user; + +import lombok.Data; +import org.codedream.epaper.model.file.File; +import org.codedream.epaper.model.task.Task; +import org.hibernate.annotations.CollectionType; + +import javax.persistence.*; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * 用户相关详细信息 + */ +@Data +@Entity +@Table(name = "user_detail") +public class UserDetail { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private int id; + + // 上传的文件记录 + @ElementCollection + private List recentFileIds = new LinkedList<>(); + + // 发起子任务记录 + @OneToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY) + private List recentTasks = new ArrayList<>(); + +} diff --git a/src/main/java/org/codedream/epaper/repository/article/ArticleRepository.java b/src/main/java/org/codedream/epaper/repository/article/ArticleRepository.java new file mode 100644 index 0000000..a5aeed4 --- /dev/null +++ b/src/main/java/org/codedream/epaper/repository/article/ArticleRepository.java @@ -0,0 +1,11 @@ +package org.codedream.epaper.repository.article; + +import io.swagger.models.auth.In; +import org.codedream.epaper.model.article.Article; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ArticleRepository extends JpaRepository { + +} diff --git a/src/main/java/org/codedream/epaper/repository/article/CacheRepository.java b/src/main/java/org/codedream/epaper/repository/article/CacheRepository.java new file mode 100644 index 0000000..4f8a412 --- /dev/null +++ b/src/main/java/org/codedream/epaper/repository/article/CacheRepository.java @@ -0,0 +1,15 @@ +package org.codedream.epaper.repository.article; + +import org.codedream.epaper.model.cache.Cache; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.time.LocalDateTime; +import java.util.Optional; + +public interface CacheRepository extends JpaRepository { + + Optional findByHashCode(String hashCode); + + void deleteByRecordTime(LocalDateTime recordTime); + +} diff --git a/src/main/java/org/codedream/epaper/repository/article/ParagraphRepository.java b/src/main/java/org/codedream/epaper/repository/article/ParagraphRepository.java new file mode 100644 index 0000000..7e89083 --- /dev/null +++ b/src/main/java/org/codedream/epaper/repository/article/ParagraphRepository.java @@ -0,0 +1,15 @@ +package org.codedream.epaper.repository.article; + +import org.codedream.epaper.model.article.Paragraph; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import javax.transaction.Transactional; +import java.util.Optional; + +@Repository +public interface ParagraphRepository extends JpaRepository { + Optional findBySha512Hash(String hash); +} diff --git a/src/main/java/org/codedream/epaper/repository/article/PhraseRepository.java b/src/main/java/org/codedream/epaper/repository/article/PhraseRepository.java new file mode 100644 index 0000000..4f13bdb --- /dev/null +++ b/src/main/java/org/codedream/epaper/repository/article/PhraseRepository.java @@ -0,0 +1,14 @@ +package org.codedream.epaper.repository.article; + +import io.swagger.models.auth.In; +import org.codedream.epaper.model.article.Phrase; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface PhraseRepository extends JpaRepository { + boolean existsByText(String text); + Optional findByText(String text); +} diff --git a/src/main/java/org/codedream/epaper/repository/article/SentenceRepository.java b/src/main/java/org/codedream/epaper/repository/article/SentenceRepository.java new file mode 100644 index 0000000..05ea94e --- /dev/null +++ b/src/main/java/org/codedream/epaper/repository/article/SentenceRepository.java @@ -0,0 +1,14 @@ +package org.codedream.epaper.repository.article; + +import io.swagger.models.auth.In; +import org.codedream.epaper.model.article.Sentence; +import org.python.antlr.ast.Str; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface SentenceRepository extends JpaRepository { + Optional findBySha512Hash(String hash); +} diff --git a/src/main/java/org/codedream/epaper/repository/auth/JSONTokenRepository.java b/src/main/java/org/codedream/epaper/repository/auth/JSONTokenRepository.java new file mode 100644 index 0000000..40b21b4 --- /dev/null +++ b/src/main/java/org/codedream/epaper/repository/auth/JSONTokenRepository.java @@ -0,0 +1,12 @@ +package org.codedream.epaper.repository.auth; + +import org.codedream.epaper.model.auth.JSONToken; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface JSONTokenRepository extends CrudRepository { + Optional findByUsername(String username); +} diff --git a/src/main/java/org/codedream/epaper/repository/auth/PreValidationCodeRepository.java b/src/main/java/org/codedream/epaper/repository/auth/PreValidationCodeRepository.java new file mode 100644 index 0000000..c4fed88 --- /dev/null +++ b/src/main/java/org/codedream/epaper/repository/auth/PreValidationCodeRepository.java @@ -0,0 +1,12 @@ +package org.codedream.epaper.repository.auth; + +import org.codedream.epaper.model.auth.PreValidationCode; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface PreValidationCodeRepository extends CrudRepository { + Optional findByValue(String value); +} diff --git a/src/main/java/org/codedream/epaper/repository/file/FileRepository.java b/src/main/java/org/codedream/epaper/repository/file/FileRepository.java new file mode 100644 index 0000000..59dcc72 --- /dev/null +++ b/src/main/java/org/codedream/epaper/repository/file/FileRepository.java @@ -0,0 +1,12 @@ +package org.codedream.epaper.repository.file; + +import org.codedream.epaper.model.file.File; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface FileRepository extends JpaRepository { + Iterable findAllByHash(String hash); +} diff --git a/src/main/java/org/codedream/epaper/repository/record/BPTRecordRepository.java b/src/main/java/org/codedream/epaper/repository/record/BPTRecordRepository.java new file mode 100644 index 0000000..e6911a4 --- /dev/null +++ b/src/main/java/org/codedream/epaper/repository/record/BPTRecordRepository.java @@ -0,0 +1,10 @@ +package org.codedream.epaper.repository.record; + +import org.codedream.epaper.model.record.BPTRecord; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface BPTRecordRepository extends JpaRepository { + +} diff --git a/src/main/java/org/codedream/epaper/repository/record/FileRecordRepository.java b/src/main/java/org/codedream/epaper/repository/record/FileRecordRepository.java new file mode 100644 index 0000000..3650744 --- /dev/null +++ b/src/main/java/org/codedream/epaper/repository/record/FileRecordRepository.java @@ -0,0 +1,10 @@ +package org.codedream.epaper.repository.record; + +import org.codedream.epaper.model.record.FileRecord; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface FileRecordRepository extends JpaRepository { + +} diff --git a/src/main/java/org/codedream/epaper/repository/record/TaskRecordRepository.java b/src/main/java/org/codedream/epaper/repository/record/TaskRecordRepository.java new file mode 100644 index 0000000..feec9e2 --- /dev/null +++ b/src/main/java/org/codedream/epaper/repository/record/TaskRecordRepository.java @@ -0,0 +1,10 @@ +package org.codedream.epaper.repository.record; + +import org.codedream.epaper.model.record.TaskRecord; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface TaskRecordRepository extends JpaRepository { + +} diff --git a/src/main/java/org/codedream/epaper/repository/record/UserRecordRepository.java b/src/main/java/org/codedream/epaper/repository/record/UserRecordRepository.java new file mode 100644 index 0000000..0e4777c --- /dev/null +++ b/src/main/java/org/codedream/epaper/repository/record/UserRecordRepository.java @@ -0,0 +1,10 @@ +package org.codedream.epaper.repository.record; + +import org.codedream.epaper.model.record.UserRecord; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserRecordRepository extends JpaRepository { + +} diff --git a/src/main/java/org/codedream/epaper/repository/server/ChildServerPassportRepository.java b/src/main/java/org/codedream/epaper/repository/server/ChildServerPassportRepository.java new file mode 100644 index 0000000..e38c388 --- /dev/null +++ b/src/main/java/org/codedream/epaper/repository/server/ChildServerPassportRepository.java @@ -0,0 +1,13 @@ +package org.codedream.epaper.repository.server; + +import org.codedream.epaper.model.server.ChildServerPassport; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface ChildServerPassportRepository extends JpaRepository { + Optional findByIdentityCode(String identityCode); + Iterable findByExpired(boolean expired); +} diff --git a/src/main/java/org/codedream/epaper/repository/task/BatchProcessingTaskRepository.java b/src/main/java/org/codedream/epaper/repository/task/BatchProcessingTaskRepository.java new file mode 100644 index 0000000..4ca000f --- /dev/null +++ b/src/main/java/org/codedream/epaper/repository/task/BatchProcessingTaskRepository.java @@ -0,0 +1,12 @@ +package org.codedream.epaper.repository.task; + +import org.codedream.epaper.model.task.BatchProcessingTask; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Iterator; + +@Repository +public interface BatchProcessingTaskRepository extends JpaRepository { + Iterable findAllByFinished(boolean finished); +} diff --git a/src/main/java/org/codedream/epaper/repository/task/SentenceResultRepository.java b/src/main/java/org/codedream/epaper/repository/task/SentenceResultRepository.java new file mode 100644 index 0000000..8984180 --- /dev/null +++ b/src/main/java/org/codedream/epaper/repository/task/SentenceResultRepository.java @@ -0,0 +1,13 @@ +package org.codedream.epaper.repository.task; + +import org.codedream.epaper.model.task.SentenceResult; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + + +@Repository +public interface SentenceResultRepository extends JpaRepository { + Optional findBySentenceId(Integer sentenceId); +} diff --git a/src/main/java/org/codedream/epaper/repository/task/TaskRepository.java b/src/main/java/org/codedream/epaper/repository/task/TaskRepository.java new file mode 100644 index 0000000..c7a997c --- /dev/null +++ b/src/main/java/org/codedream/epaper/repository/task/TaskRepository.java @@ -0,0 +1,13 @@ +package org.codedream.epaper.repository.task; + +import org.codedream.epaper.model.task.Task; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface TaskRepository extends JpaRepository { + + Iterable findByFinished(boolean finish); + + Iterable findAllByUserId(Integer userId); +} diff --git a/src/main/java/org/codedream/epaper/repository/task/TaskResultRepository.java b/src/main/java/org/codedream/epaper/repository/task/TaskResultRepository.java new file mode 100644 index 0000000..774f4e3 --- /dev/null +++ b/src/main/java/org/codedream/epaper/repository/task/TaskResultRepository.java @@ -0,0 +1,13 @@ +package org.codedream.epaper.repository.task; + +import org.codedream.epaper.model.task.Task; +import org.codedream.epaper.model.task.TaskResult; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface TaskResultRepository extends JpaRepository { + Optional findByTaskId(Integer taskId); +} diff --git a/src/main/java/org/codedream/epaper/repository/user/UserRepository.java b/src/main/java/org/codedream/epaper/repository/user/UserRepository.java new file mode 100644 index 0000000..78e529b --- /dev/null +++ b/src/main/java/org/codedream/epaper/repository/user/UserRepository.java @@ -0,0 +1,14 @@ +package org.codedream.epaper.repository.user; + +import org.codedream.epaper.model.user.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + + +@Repository +public interface UserRepository extends JpaRepository { + Optional findByUsername(String openid); +} diff --git a/src/main/java/org/codedream/epaper/service/ArticleService.java b/src/main/java/org/codedream/epaper/service/ArticleService.java new file mode 100644 index 0000000..a2f8926 --- /dev/null +++ b/src/main/java/org/codedream/epaper/service/ArticleService.java @@ -0,0 +1,239 @@ +package org.codedream.epaper.service; + +import javafx.util.Pair; +import org.codedream.epaper.model.article.Article; +import org.codedream.epaper.model.article.Paragraph; +import org.codedream.epaper.model.article.Phrase; +import org.codedream.epaper.model.article.Sentence; +import org.codedream.epaper.repository.article.ArticleRepository; +import org.codedream.epaper.repository.article.ParagraphRepository; +import org.codedream.epaper.repository.article.PhraseRepository; +import org.codedream.epaper.repository.article.SentenceRepository; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Optional; + +/** + * 文章服务层,提供包括文章解析、创建、存储等方法在内的有关文章处理的服务。 + *

+ * 除了{@code Article}对象的DAO相关方法外,也提供更细粒度对象的DAO相关方法也基于此服务, + * 如 {@code Phrase}、{@code Paragraph}以及{@code Sentence} + *

+ * 所有涉及的持久化操作都会进行缓存校验 + */ +@Service +public class ArticleService implements IArticleService { + + @Resource + ArticleRepository articleRepository; + + @Resource + PhraseRepository phraseRepository; + + @Resource + SentenceRepository sentenceRepository; + + @Resource + ParagraphRepository paragraphRepository; + + /** + * 创建一个新的{@link Article}对象 + * + * @param title 文章标题 + * @return 一个新建的 + */ + @Override + public Article createArticle(String title) { + return new Article(); + } + + /** + * 根据文本创建一个新的{@link Paragraph}对象 + * + * @param text 文本内容 + * @return 一个新的{@link Paragraph}对象 + */ + @Override + public Paragraph createParagraph(String text) { + Paragraph paragraph = new Paragraph(); + paragraph.setText(text); + return paragraph; + } + + /** + * 把段落加入到一篇文章中,即建立起一个{@link Article}对象和一个{@link Paragraph}对象之间的关联 + * + * @param article 待加入段落文章 + * @param paragraph 待加入到文章的段落 + */ + @Override + public void addParagraph(Article article, Paragraph paragraph) { + article.getParagraphs().add(paragraph); + article.setTotalLength(article.getTotalLength() + paragraph.getText().length()); + } + + /** + * 查询基本词是否存在 + * + * @param text 词文本 + * @return 一个布尔值表示是否存在 + * @see PhraseRepository#existsByText(String) + */ + @Override + public boolean checkPhraseExist(String text) { + return phraseRepository.existsByText(text); + } + + /** + * 根据句子的哈希值判断是否存储了相应句子 + * + * @param hash 句子hash值 + * @return 一个封装在Optional中的句子对象 + */ + @Override + public Optional findSentenceByHash(String hash) { + return sentenceRepository.findBySha512Hash(hash); + } + + /** + * 保存基本词,并返回是否存在于数据库中 + * + * @param text 基本词文本 + * @return 一个Pair类型的对象,键值表示是否存在于数据库,值表示基本词构建的Phrase对象 + */ + @Override + public Pair savePhrase(String text) { + Phrase phrase; + Optional phraseOptional = phraseRepository.findByText(text); + if (!phraseOptional.isPresent()) { + phrase = new Phrase(); + phrase.setText(text); + return new Pair<>(false, phraseRepository.save(phrase)); + } else { + return new Pair<>(true, phraseOptional.get()); + } + } + + /** + * 根据文本持久化一个{@link Sentence}对象,暂未启用 + * + * @param text 句子文本 + * @return 空 + */ + @Override + public Sentence saveSentence(String text) { + return null; + } + + /** + * 根据文本持久化一个{@link Paragraph}对象,暂未启用 + * + * @param text 段落文本 + * @return 空 + */ + @Override + public Paragraph saveParagraph(String text) { + return null; + } + + /** + * 根据文本查询基本词是否已经持久化 + * + * @param text 文本 + * @return Optional封装的Phrase对象 + */ + @Override + public Optional findPhrase(String text) { + return phraseRepository.findByText(text); + } + + + /** + * 持久化一个{@link Phrase}对象 + * + * @param phrase 要存储的{@link Phrase}对象 + * @return 持久化的Phrase对象 + */ + @Override + public Phrase save(Phrase phrase) { + return phraseRepository.save(phrase); + } + + /** + * 持久化一个{@link Sentence}对象 + * + * @param sentence 要存储的{@link Phrase}对象 + * @return 持久化的Sentence对象 + */ + @Override + public Sentence save(Sentence sentence) { + return sentenceRepository.save(sentence); + } + + /** + * 持久化一个{@link Article}对象 + * + * @param article 要存储的{@link Article}对象 + * @return 持久化的Article对象 + */ + @Override + public Article save(Article article) { + return articleRepository.save(article); + } + + /** + * 持久化一个{@link Paragraph}对象 + * + * @param paragraph 要存储的{@link Paragraph}对象 + * @return 持久化的Paragraph对象 + */ + @Override + public Paragraph save(Paragraph paragraph) { + return paragraphRepository.save(paragraph); + } + + /** + * 通过id寻找一个{@link Phrase}对象 + * + * @param id 一个{@link Phrase}对线的id + * @return 封装在Optional内部的Phrase对象 + */ + @Override + public Optional findPhraseById(int id) { + return phraseRepository.findById(id); + } + + /** + * 通过id寻找一个{@link Paragraph}对象 + * + * @param id 一个{@link Paragraph}对线的id + * @return 封装在Optional内部的Paragraph对象 + */ + @Override + public Optional findParagraphById(int id) { + return paragraphRepository.findById(id); + } + + /** + * 通过id寻找一个{@link Sentence}对象 + * + * @param id 一个{@link Sentence}对线的id + * @return 封装在Optional内部的Sentence对象 + */ + @Override + public Optional findSentenceById(int id) { + return sentenceRepository.findById(id); + } + + /** + * 通过id寻找一个{@link Article}对象 + * + * @param id 一个{@link Article}对线的id + * @return 封装在Optional内部的Article对象 + */ + @Override + public Optional

findArticleById(int id) { + return articleRepository.findById(id); + } +} diff --git a/src/main/java/org/codedream/epaper/service/AsyncTaskServer.java b/src/main/java/org/codedream/epaper/service/AsyncTaskServer.java new file mode 100644 index 0000000..b3dd8f2 --- /dev/null +++ b/src/main/java/org/codedream/epaper/service/AsyncTaskServer.java @@ -0,0 +1,128 @@ +package org.codedream.epaper.service; + +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.component.task.*; +import org.codedream.epaper.configure.BatchTaskConfiguration; +import org.codedream.epaper.exception.innerservererror.InnerDataTransmissionException; +import org.codedream.epaper.model.task.BatchProcessingTask; +import org.codedream.epaper.model.task.Task; +import org.codedream.epaper.repository.task.BatchProcessingTaskRepository; +import org.codedream.epaper.repository.task.TaskRepository; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.Optional; + +/** + * 此类提供任务异步调取相关的服务,用以支持分布式多线程的任务批处理机制,以及处理结果的持久化(参见{@link TaskAnalyser}) + * 包括对文章(参见{@link ArticlePreprocessor})、段落(参见{@link ParagraphProcessor}) + * 以及句子(参见{@link SentencePreprocessor})的异步预处理和预分析 + */ +@Slf4j +@Service +public class AsyncTaskServer implements IAsyncTaskServer { + + @Resource + private ArticlePreprocessor articlePreprocessor; + + @Resource + private ParagraphProcessor paragraphProcessor; + + @Resource + private SentencePreprocessor sentencePreprocessor; + + @Resource + private TaskAnalyser taskAnalyser; + + @Resource + private TaskQueue taskQueue; + + @Resource + private TaskRepository taskRepository; + + @Resource + private TaskService taskService; + + @Resource + private BatchProcessingTaskRepository bptRepository; + + private BatchProcessingTask newlyCreatedBpt = new BatchProcessingTask(); + + /** + * 子任务预处理以及分析,用于将子任务进行分词分句分段处理,并初步得到文本纠错结果、DNN处理结果 + * + * @param taskId 任务id + */ + @Override + @Async("PaPoolExecutor") + public void preprocessorAndAnalyse(Integer taskId) { + + // 子任务预处理 + preprocess(taskId); + + // 设定子任务优先级 + Optional oTask = taskRepository.findById(taskId); + if (!oTask.isPresent()) throw new InnerDataTransmissionException(taskId.toString()); + Task pTask = oTask.get(); + pTask.setPriority(pTask.getArticle().getSentencesNumber()); + taskRepository.save(pTask); + + // 子任务预分析 + analyse(taskId); + log.info(String.format("Task analysed: %s", taskId)); + + // 查找子任务 + Optional taskOptional = taskRepository.findById(taskId); + if (!taskOptional.isPresent()) throw new InnerDataTransmissionException(taskId.toString()); + + Task task = taskOptional.get(); + + if (taskQueue.getSentenceNumber() + task.getArticle().getSentencesNumber() > BatchTaskConfiguration.getLimit()) { + newlyCreatedBpt.setTasks(taskQueue.getTaskAsList()); + newlyCreatedBpt.setCreateDate(new Date()); + newlyCreatedBpt.setPriority(taskQueue.getSentenceNumber()); + newlyCreatedBpt.setSentencesNumber(taskQueue.getSentenceNumber()); + newlyCreatedBpt = bptRepository.save(newlyCreatedBpt); + taskService.registerBPTTask(newlyCreatedBpt); + newlyCreatedBpt = new BatchProcessingTask(); + taskQueue.clear(); + } + + // 否则继续加入任务等待队列 + task.setJoinDate(new Date()); + task = taskRepository.save(task); + taskQueue.addTask(task); + } + + /** + * 子任务预分析,用于获取并持久化任务初步处理结果 + * + * @param taskId 任务id + * @see {@link TaskAnalyser#analyse(Integer)} + */ + private void analyse(Integer taskId) { + // 子任务预分析 + taskAnalyser.analyse(taskId); + } + + /** + * @param taskId 子任务预处理,用于完成并持久化文章的分段{@link ArticlePreprocessor#parse(Integer)}、 + * 分句{@link ParagraphProcessor#parse(Integer)}、分词{@link SentencePreprocessor#parse(Integer)}结果 + */ + private void preprocess(Integer taskId) { + + // 章预处理 + articlePreprocessor.parse(taskId); + + // 段预处理 + paragraphProcessor.parse(taskId); + + // 句预处理 + sentencePreprocessor.parse(taskId); + + } + + +} diff --git a/src/main/java/org/codedream/epaper/service/AuthService.java b/src/main/java/org/codedream/epaper/service/AuthService.java new file mode 100644 index 0000000..da9e276 --- /dev/null +++ b/src/main/java/org/codedream/epaper/service/AuthService.java @@ -0,0 +1,119 @@ +package org.codedream.epaper.service; + +import javafx.util.Pair; +import org.codedream.epaper.component.auth.AuthTokenGenerator; +import org.codedream.epaper.component.auth.TimestampExpiredChecker; +import org.codedream.epaper.model.auth.JSONToken; +import org.codedream.epaper.model.auth.PreValidationCode; +import org.codedream.epaper.model.user.User; +import org.codedream.epaper.repository.auth.JSONTokenRepository; +import org.codedream.epaper.repository.auth.PreValidationCodeRepository; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.Optional; +import java.util.UUID; + +/** + * 身份验证服务类 + */ +@Service +public class AuthService implements IAuthService { + + @Resource + private JSONTokenRepository jsonTokenRepository; + + @Resource + private IUserService userService; + + @Resource + private AuthTokenGenerator authTokenGenerator; + + @Resource + private PreValidationCodeRepository preValidationCodeRepository; + + @Resource + private TimestampExpiredChecker timestampExpiredChecker; + + /** + * 通过用户名查找Token有效 + * @param username 用户名 + * @return Token对象 + */ + @Override + public Optional findTokenByUserName(String username) { + return jsonTokenRepository.findByUsername(username); + } + + /** + * 检查Token是否过期 + * @param token Token对象 + * @return 布尔值 + */ + @Override + public boolean checkTokenIfExpired(JSONToken token) { + return token.getExpiredDate().compareTo(new Date()) <= 0; + } + + /** + * 获得新的有效Token + * @param username 用户名 + * @param clientCode 客户端代码 + * @return Token值 + */ + @Override + public Optional userNewTokenGetter(String username, String clientCode) { + Pair userPair = userService.checkIfUserExists(username); + if(userPair.getKey()){ + Optional jsonTokenOptional = jsonTokenRepository.findByUsername(username); + JSONToken token = jsonTokenOptional.orElseGet(JSONToken::new); + + // 过期时间设置为三十分钟后 + long currentTime = System.currentTimeMillis(); + currentTime +=30*60*1000; + token.setExpiredDate(new Date(currentTime)); + token.setToken(authTokenGenerator.generateAuthToken(username)); + + + // 设置用户名 + token.setUsername(username); + // 设置客户端代号 + token.setClientCode(clientCode); + + // 在数据库中更新新的token + token = jsonTokenRepository.save(token); + return Optional.ofNullable(token.getToken()); + } + else return Optional.empty(); + } + + /** + * 预验证码生成 + * @return 预验证码 + */ + @Override + public String preValidationCodeGetter() { + PreValidationCode preValidationCode = new + PreValidationCode(); + preValidationCode.setValue(UUID.randomUUID().toString()); + preValidationCode = preValidationCodeRepository.save(preValidationCode); + return preValidationCode.getValue(); + } + + /** + * 预验证码检查 + * @param pvc 预验证码 + * @return 布尔值 + */ + @Override + public boolean preValidationCodeChecker(String pvc) { + Optional preValidationCode = + preValidationCodeRepository.findByValue(pvc); + if(preValidationCode.filter(validationCode -> timestampExpiredChecker.checkDateBeforeMaxTime(validationCode.getDate(), 60)).isPresent()){ + preValidationCodeRepository.delete(preValidationCode.get()); + return true; + } + else return false; + } +} diff --git a/src/main/java/org/codedream/epaper/service/CacheService.java b/src/main/java/org/codedream/epaper/service/CacheService.java new file mode 100644 index 0000000..ee86b64 --- /dev/null +++ b/src/main/java/org/codedream/epaper/service/CacheService.java @@ -0,0 +1,75 @@ +package org.codedream.epaper.service; + +import org.codedream.epaper.component.datamanager.SHA256Encoder; +import org.codedream.epaper.component.datamanager.SHA512Encoder; +import org.codedream.epaper.model.cache.Cache; +import org.codedream.epaper.repository.article.CacheRepository; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.time.LocalDateTime; +import java.util.Optional; + +/** + * 文本缓存服务。 + *

+ * 用于通过SHA256码寻找当前要持久化的文本({@link org.codedream.epaper.model.article.Article}、 + * {@link org.codedream.epaper.model.article.Paragraph}) + */ +@Service +public class CacheService implements ICacheService { + + @Resource + CacheRepository cacheRepository; + + @Resource + SHA512Encoder sha512Encoder; + + /** + * 在缓存中搜索是否存在相同文本。 + * + * @param hashCode 文本散列值 + * @return 被Optional封装的Cache对象 + */ + @Override + public Optional searchCache(String hashCode) { + + return cacheRepository.findByHashCode(hashCode); + } + + /** + * 在数据库中创建缓存 + * + * @param text 待持久化的文本 + * @return 已经持久化的缓存对象 + */ + @Override + public Cache create(String text) { + + Cache cache = new Cache(text); + cache.setRecordTime(LocalDateTime.now()); + cache.setHashCode(this.encode(text)); + return cacheRepository.save(cache); + } + + /** + * 清空缓存。 + *

+ * 用于定时清理 + */ + @Override + public void evictCache() { + cacheRepository.deleteAll(); + } + + /** + * 文本编码(参见{@link SHA256Encoder}) + * + * @param text 待编码的文本 + * @return 文本编码 + */ + @Override + public String encode(String text) { + return sha512Encoder.encode(text); + } +} diff --git a/src/main/java/org/codedream/epaper/service/ChildServerService.java b/src/main/java/org/codedream/epaper/service/ChildServerService.java new file mode 100644 index 0000000..b6401a6 --- /dev/null +++ b/src/main/java/org/codedream/epaper/service/ChildServerService.java @@ -0,0 +1,101 @@ +package org.codedream.epaper.service; + +import org.codedream.epaper.exception.innerservererror.InnerDataTransmissionException; +import org.codedream.epaper.exception.notfound.NotFoundException; +import org.codedream.epaper.model.server.ChildServerPassport; +import org.codedream.epaper.model.user.User; +import org.codedream.epaper.repository.server.ChildServerPassportRepository; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.Optional; + +/** + * 子服务器管理服务实例 + */ +@Service +public class ChildServerService implements IChildServerService { + + @Resource + private ChildServerPassportRepository cspRepository; + + /** + * 获取新的子服务器护照 + * @param user 子服务器账号 + * @return 子服务器护照 + */ + @Override + public ChildServerPassport createCSP(User user) { + if(user == null) throw new InnerDataTransmissionException(); + ChildServerPassport csp = new ChildServerPassport(); + + // 检查账号身份 + if(!user.getUserAuth().getRole().equals("ChildServer")) throw new InnerDataTransmissionException(); + csp.setUser(user); + return cspRepository.save(csp); + } + + /** + * 获取新的签证 + * @param idcode 护照身份认证码 + * @return 子服务器护照(更新后) + */ + @Override + public ChildServerPassport updateCSP(String idcode) { + Optional csp = cspRepository.findByIdentityCode(idcode); + if(!csp.isPresent()) throw new NotFoundException(idcode); + + if(csp.get().isExpired()) return null; + + csp.get().setLastUpdateTime(new Date()); + + return cspRepository.save(csp.get()); + } + + /** + * 检查护照身份认证码是否有效 + * @param idcode 护照身份认证码 + * @return 布尔值 + */ + @Override + public boolean checkCSP(String idcode) { + return cspRepository.findByIdentityCode(idcode).isPresent(); + } + + /** + * 检查护照是否过期 + * @param idcode 护照身份认证码 + * @return 布尔值 + */ + @Override + public boolean checkCSPExpired(String idcode) { + Optional csp = cspRepository.findByIdentityCode(idcode); + if(!csp.isPresent()) throw new NotFoundException(idcode); + + return csp.get().isExpired(); + } + + /** + * 获得子服务器护照详细信息 + * @param idcode 护照身份认证码 + * @return 子服务器护照对象 + */ + @Override + public ChildServerPassport getCSPInfo(String idcode) { + Optional csp = cspRepository.findByIdentityCode(idcode); + if(!csp.isPresent()) throw new NotFoundException(idcode); + return csp.get(); + } + + /** + * 更新子服务器护照对象到数据库中 + * @param csp 子服务器护照对象 + * @return 子服务器护照对象(更新后) + */ + @Override + public ChildServerPassport update(ChildServerPassport csp) { + if(!cspRepository.findById(csp.getId()).isPresent()) throw new NotFoundException(csp.getIdentityCode()); + return cspRepository.save(csp); + } +} diff --git a/src/main/java/org/codedream/epaper/service/EPUserDetailsService.java b/src/main/java/org/codedream/epaper/service/EPUserDetailsService.java new file mode 100644 index 0000000..2728a2a --- /dev/null +++ b/src/main/java/org/codedream/epaper/service/EPUserDetailsService.java @@ -0,0 +1,39 @@ +package org.codedream.epaper.service; + +import org.codedream.epaper.exception.notfound.UserNotFoundException; +import org.codedream.epaper.model.user.User; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import javax.transaction.Transactional; +import java.util.ArrayList; +import java.util.Optional; + +/** + * 用户对象获取服务实例 + * (Spring Security依赖) + */ +@Service +public class EPUserDetailsService implements UserDetailsService { + + @Resource + IUserService userService; + + @Override + @Transactional + public UserDetails loadUserByUsername(String s) { + try { + Optional userOptional = userService.findUserByOpenid(s); + if(!userOptional.isPresent()) throw new UserNotFoundException(s); + User user = userOptional.get(); + user.setAuthorities(new ArrayList<>()); + return user; + } catch (UserNotFoundException e){ + throw new AuthenticationServiceException("User Not Exist"); + } + + } +} diff --git a/src/main/java/org/codedream/epaper/service/FileService.java b/src/main/java/org/codedream/epaper/service/FileService.java new file mode 100644 index 0000000..1d91f44 --- /dev/null +++ b/src/main/java/org/codedream/epaper/service/FileService.java @@ -0,0 +1,175 @@ +package org.codedream.epaper.service; + +import org.codedream.epaper.component.datamanager.FileParser; +import org.codedream.epaper.component.datamanager.StringFile; +import org.codedream.epaper.component.datamanager.StringFileGenerator; +import org.codedream.epaper.configure.AppConfigure; +import org.codedream.epaper.exception.innerservererror.RuntimeIOException; +import org.codedream.epaper.exception.innerservererror.StringFileConvertException; +import org.codedream.epaper.exception.notfound.NotFoundException; +import org.codedream.epaper.model.file.File; +import org.codedream.epaper.repository.file.FileRepository; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; + +/** + * 文件服务实例 + */ +@Service +public class FileService implements IFileService { + + @Resource + private AppConfigure configure; + + + @Resource + private StringFileGenerator stringFileGenerator; + + @Resource + private FileRepository fileRepository; + + @Resource + private FileParser fileParser; + + /** + * 储存文件 + * @param name 文件名 + * @param type 文件类型 + * @param stream 输入流 + * @return 文件ID号 + */ + @Override + public Integer saveFile(String name, String type, InputStream stream) { + Path filePath = Paths.get(configure.getFilePath()); + if(!Files.exists(filePath)){ + try { + Files.createDirectory(filePath); + } catch (IOException e){ + throw new RuntimeIOException(e.getMessage()); + } + } + + File file = new File(); + file.setName(name); + file.setPath(configure.getFilePath()); + file.setType(type); + + Optional bytes = fileParser.read(stream); + if(!bytes.isPresent()) return null; + + String hash = fileParser.encode(bytes.get()); + + // 运用缓存机制 + Optional existFile = fileParser.find(hash); + if (existFile.isPresent()){ + file.setStorageName(existFile.get().getStorageName()); + + file.setSize(existFile.get().getSize()); + } + else{ + file = fileParser.writeOut(file, bytes.get()); + } + + file.setHash(hash); + + return fileRepository.save(file).getId(); + + } + + /** + * 储存文件 + * @param stringFile 字符串文件对象 + * @return 文件ID号 + */ + @Override + public Integer saveFile(StringFile stringFile) { + saveFile(stringFile.getName(), + stringFile.getType(), + stringFileGenerator.readFileString(stringFile)); + return null; + } + + /** + * 获得文件的输入流 + * @param fileId 文件ID号 + * @return 输入流 + */ + @Override + public InputStream getFile(Integer fileId) { + Optional optionalFile = fileRepository.findById(fileId); + if(!optionalFile.isPresent()) + throw new NotFoundException(fileId.toString()); + + File file = optionalFile.get(); + Path path = Paths.get(file.getPath(), file.getStorageName()); + try { + return Files.newInputStream(path); + } catch (IOException e){ + throw new RuntimeException(e.getMessage()); + } + } + + /** + * 获得字符串文件对象 + * @param fileId 文件ID号 + * @return 字符串文件对象 + */ + @Override + public StringFile getStringFile(Integer fileId) { + Optional optionalFile = fileRepository.findById(fileId); + if(!optionalFile.isPresent()) + throw new NotFoundException(fileId.toString()); + File file = optionalFile.get(); + + Optional stringFile = + stringFileGenerator.generateStringFile( + file.getName(), + file.getType(), + getFile(fileId) + ); + if(!stringFile.isPresent()) throw new StringFileConvertException(file.getName()); + return stringFile.get(); + } + + /** + * 删除文件(不推荐使用) + * @param fileId 文件ID号 + * @return 布尔值 + */ + @Override + public boolean deleteFile(Integer fileId) { + Optional optionalFile = fileRepository.findById(fileId); + if(!optionalFile.isPresent()) + throw new NotFoundException(fileId.toString()); + File file = optionalFile.get(); + + Path path = Paths.get(file.getPath(), file.getStorageName()); + try { + Files.delete(path); + fileRepository.delete(file); + return true; + } catch (IOException e){ + throw new RuntimeException(e.getMessage()); + } + } + + /** + * 获取文件信息对象 + * @param fileId 文件ID号 + * @return 文件信息对象 + */ + @Override + public File getFileInfo(Integer fileId) { + Optional optionalFile = fileRepository.findById(fileId); + if(!optionalFile.isPresent()) + throw new NotFoundException(fileId.toString()); + + return optionalFile.get(); + } +} diff --git a/src/main/java/org/codedream/epaper/service/IArticleService.java b/src/main/java/org/codedream/epaper/service/IArticleService.java new file mode 100644 index 0000000..62282ba --- /dev/null +++ b/src/main/java/org/codedream/epaper/service/IArticleService.java @@ -0,0 +1,154 @@ +package org.codedream.epaper.service; + +import javafx.util.Pair; +import org.codedream.epaper.model.article.Article; +import org.codedream.epaper.model.article.Paragraph; +import org.codedream.epaper.model.article.Phrase; +import org.codedream.epaper.model.article.Sentence; +import org.codedream.epaper.repository.article.PhraseRepository; + +import javax.swing.text.html.Option; +import java.util.Optional; + +/** + * 文本处理服务接口 + */ +public interface IArticleService { + + /** + * 创建一个新的{@link Article}对象 + * + * @param title 文章标题 + * @return 一个新建的 + */ + Article createArticle(String title); + + /** + * 根据文本创建一个新的{@link Paragraph}对象 + * + * @param text 文本内容 + * @return 一个新的{@link Paragraph}对象 + */ + Paragraph createParagraph(String text); + + /** + * 把段落加入到一篇文章中,即建立起一个{@link Article}对象和一个{@link Paragraph}对象之间的关联 + * + * @param article 待加入段落文章 + * @param paragraph 待加入到文章的段落 + */ + void addParagraph(Article article, Paragraph paragraph); + + /** + * 查询基本词是否存在 + * + * @param text 词文本 + * @return 一个布尔值表示是否存在 + * @see PhraseRepository#existsByText(String) + */ + boolean checkPhraseExist(String text); + + /** + * 保存基本词,并返回是否存在于数据库中 + * + * @param text 基本词文本 + * @return 一个Pair类型的对象,键值表示是否存在于数据库,值表示基本词构建的Phrase对象 + */ + Pair savePhrase(String text); + + /** + * 根据文本持久化一个{@link Sentence}对象,暂未启用 + * + * @param text 句子文本 + * @return 空 + */ + Sentence saveSentence(String text); + + /** + * 根据文本持久化一个{@link Paragraph}对象,暂未启用 + * + * @param text 段落文本 + * @return 空 + */ + Paragraph saveParagraph(String text); + + /** + * 根据句子的哈希值判断是否存储了相应句子 + * + * @param hash 句子hash值 + * @return 一个封装在Optional中的句子对象 + */ + Optional findSentenceByHash(String hash); + + /** + * 根据文本查询基本词是否已经持久化 + * + * @param text 文本 + * @return Optional封装的Phrase对象 + */ + Optional findPhrase(String text); + + /** + * 持久化一个{@link Phrase}对象 + * + * @param phrase 要存储的{@link Phrase}对象 + * @return 持久化的Phrase对象 + */ + Phrase save(Phrase phrase); + + /** + * 持久化一个{@link Sentence}对象 + * + * @param sentence 要存储的{@link Phrase}对象 + * @return 持久化的Sentence对象 + */ + Sentence save(Sentence sentence); + + /** + * 持久化一个{@link Article}对象 + * + * @param article 要存储的{@link Article}对象 + * @return 持久化的Article对象 + */ + Article save(Article article); + + /** + * 持久化一个{@link Paragraph}对象 + * + * @param paragraph 要存储的{@link Paragraph}对象 + * @return 持久化的Paragraph对象 + */ + Paragraph save(Paragraph paragraph); + + /** + * 通过id寻找一个{@link Phrase}对象 + * + * @param id 一个{@link Phrase}对线的id + * @return 封装在Optional内部的Phrase对象 + */ + Optional findPhraseById(int id); + + /** + * 通过id寻找一个{@link Paragraph}对象 + * + * @param id 一个{@link Paragraph}对线的id + * @return 封装在Optional内部的Paragraph对象 + */ + Optional findParagraphById(int id); + + /** + * 通过id寻找一个{@link Sentence}对象 + * + * @param id 一个{@link Sentence}对线的id + * @return 封装在Optional内部的Sentence对象 + */ + Optional findSentenceById(int id); + + /** + * 通过id寻找一个{@link Article}对象 + * + * @param id 一个{@link Article}对线的id + * @return 封装在Optional内部的Article对象 + */ + Optional

findArticleById(int id); +} diff --git a/src/main/java/org/codedream/epaper/service/IAsyncTaskServer.java b/src/main/java/org/codedream/epaper/service/IAsyncTaskServer.java new file mode 100644 index 0000000..a5f5205 --- /dev/null +++ b/src/main/java/org/codedream/epaper/service/IAsyncTaskServer.java @@ -0,0 +1,13 @@ +package org.codedream.epaper.service; + +/** + * 异步任务处理服务层 + */ +public interface IAsyncTaskServer { + + /** + * 执行预处理与预分析任务 + * @param taskId 子任务ID号 + */ + void preprocessorAndAnalyse(Integer taskId); +} diff --git a/src/main/java/org/codedream/epaper/service/IAuthService.java b/src/main/java/org/codedream/epaper/service/IAuthService.java new file mode 100644 index 0000000..b7a2a43 --- /dev/null +++ b/src/main/java/org/codedream/epaper/service/IAuthService.java @@ -0,0 +1,47 @@ +package org.codedream.epaper.service; + + +import org.codedream.epaper.model.auth.JSONToken; + +import java.util.Optional; + +/** + * 身份验证服务接口 + */ +public interface IAuthService { + + /** + * 通过用户名查找与对应用户相关联的token + * @param username 用户名(openid) + * @return Token对象 + */ + Optional findTokenByUserName(String username); + + /** + * 检查token是否过期 + * @param token Token对象 + * @return 布尔值 + */ + boolean checkTokenIfExpired(JSONToken token); + + /** + * 为用户获得一个新的API Token + * @param username 用户名(openid) + * @param clientCode 客户端代码 + * @return Token值 + */ + Optional userNewTokenGetter(String username, String clientCode); + + /** + * 获得一个新的预验证码 + * @return 预验证码 + */ + String preValidationCodeGetter(); + + /** + * 检验预验证码是否有效 + * @param pvc 预验证码 + * @return 布尔值 + */ + boolean preValidationCodeChecker(String pvc); +} diff --git a/src/main/java/org/codedream/epaper/service/ICacheService.java b/src/main/java/org/codedream/epaper/service/ICacheService.java new file mode 100644 index 0000000..854594e --- /dev/null +++ b/src/main/java/org/codedream/epaper/service/ICacheService.java @@ -0,0 +1,37 @@ +package org.codedream.epaper.service; + +import org.codedream.epaper.model.cache.Cache; + +import java.util.Optional; + +/** + * 缓存服务接口 + */ +public interface ICacheService { + + /** + * 根据句子的hash值查询cache + * @param hashCode 缓存代码 + * @return 缓存对象 + */ + Optional searchCache(String hashCode); + + /** + * 创建cache + * @param text 文本 + * @return 缓存对象 + */ + Cache create(String text); + + /** + * 清空cache + */ + void evictCache(); + + /** + * 对传入文本进行hash编码 + * @param text 文本 + * @return 密文 + */ + String encode(String text); +} diff --git a/src/main/java/org/codedream/epaper/service/IChildServerService.java b/src/main/java/org/codedream/epaper/service/IChildServerService.java new file mode 100644 index 0000000..c8cd134 --- /dev/null +++ b/src/main/java/org/codedream/epaper/service/IChildServerService.java @@ -0,0 +1,52 @@ +package org.codedream.epaper.service; + +import org.codedream.epaper.model.server.ChildServerPassport; +import org.codedream.epaper.model.user.User; + +/** + * 子服务器服务接口 + */ +public interface IChildServerService { + + /** + * 创建子服务器护照 + * @param user 用户对象 + * @return 子服务器护照对象 + */ + ChildServerPassport createCSP(User user); + + /** + * 更新子服务器签证 + * @param idcode 子服务器护照份认证码 + * @return 子服务器护照对象 + */ + ChildServerPassport updateCSP(String idcode); + + /** + * 检查子服务器对象是否合法 + * @param idcode 子服务器护照身份认证码 + * @return 布尔值 + */ + boolean checkCSP(String idcode); + + /** + * 检查子服务器护照是否过期 + * @param idcode 子服务器护照身份认证码 + * @return 布尔值 + */ + boolean checkCSPExpired(String idcode); + + /** + * 获得子服务器护照详细信息 + * @param idcode 子服务器护照身份认证码 + * @return 子服务器护照对象 + */ + ChildServerPassport getCSPInfo(String idcode); + + /** + * 同步子服务器护照与数据库中的值 + * @param csp 子服务器护照对象 + * @return 子服务器护照对象(更新后) + */ + ChildServerPassport update(ChildServerPassport csp); +} diff --git a/src/main/java/org/codedream/epaper/service/IFileService.java b/src/main/java/org/codedream/epaper/service/IFileService.java new file mode 100644 index 0000000..6030e70 --- /dev/null +++ b/src/main/java/org/codedream/epaper/service/IFileService.java @@ -0,0 +1,54 @@ +package org.codedream.epaper.service; + +import org.codedream.epaper.component.datamanager.StringFile; +import org.codedream.epaper.model.file.File; + +import java.io.InputStream; + +public interface IFileService { + + /** + * 储存文件 + * @param name 文件名 + * @param type 文件类型 + * @param stream 输入流 + * @return 文件ID号 + */ + Integer saveFile(String name, String type, InputStream stream); + + /** + * 储存文件 + * @param stringFile 字符串文件对象 + * @return 文件ID号 + */ + Integer saveFile(StringFile stringFile); + + /** + * 获得文件的输入流 + * @param fileId 文件ID号 + * @return 输入流 + */ + InputStream getFile(Integer fileId); + + /** + * 获得字符串文件对象 + * @param fileId 文件ID号 + * @return 字符串文件对象 + */ + StringFile getStringFile(Integer fileId); + + /** + * 删除文件(不推荐使用) + * @param fileId 文件ID号 + * @return 布尔值 + */ + boolean deleteFile(Integer fileId); + + /** + * 获取文件信息对象 + * @param fileId 文件ID号 + * @return 文件信息对象 + */ + File getFileInfo(Integer fileId); + +} diff --git a/src/main/java/org/codedream/epaper/service/INeuralNetworkModelService.java b/src/main/java/org/codedream/epaper/service/INeuralNetworkModelService.java new file mode 100644 index 0000000..1599adb --- /dev/null +++ b/src/main/java/org/codedream/epaper/service/INeuralNetworkModelService.java @@ -0,0 +1,47 @@ +package org.codedream.epaper.service; + +import org.codedream.epaper.component.json.model.JsonableBPT; +import org.codedream.epaper.component.json.model.JsonableBPTResult; +import org.codedream.epaper.model.article.Sentence; +import org.codedream.epaper.model.task.BatchProcessingTask; + +import java.util.List; +import java.util.Optional; + +public interface INeuralNetworkModelService { + + /** + * 标记批处理任务成功执行 + * @param batchProcessingTask 批处理任务对象 + * @param results 批处理任务结果 + */ + void markBPTSuccess(BatchProcessingTask batchProcessingTask, List results); + + /** + * 标记批处理任务执行失败 + * @param batchProcessingTask 批处理任务对象 + */ + void markBPTFailed(BatchProcessingTask batchProcessingTask); + + /** + * 获得批处理任务并锁定 + * @param limit 计算端单次批处理上限 + * @return 批处理任务对象 + */ + Optional getBPTTaskAndLock(Integer limit); + + /** + * 计算批处理任务原文句子列表(启用缓存筛选) + * @param bpt 批处理任务对象 + * @return 原文句子列表 + */ + List calculateSentenceList(BatchProcessingTask bpt); + + /** + * 获得可转换为JSON的批处理任务对象 + * @param bpt 批处理任务对象 + * @param sentences 任务原文句子列表 + * @return 批处理任务JSON设计对象 + */ + JsonableBPT getJsonableBPT(BatchProcessingTask bpt, List sentences); +} diff --git a/src/main/java/org/codedream/epaper/service/ITaskService.java b/src/main/java/org/codedream/epaper/service/ITaskService.java new file mode 100644 index 0000000..b39374e --- /dev/null +++ b/src/main/java/org/codedream/epaper/service/ITaskService.java @@ -0,0 +1,98 @@ +package org.codedream.epaper.service; + +import org.codedream.epaper.component.json.model.JsonableBPTResult; +import org.codedream.epaper.component.json.model.JsonableTaskResult; +import org.codedream.epaper.model.task.BatchProcessingTask; +import org.codedream.epaper.model.task.Task; +import org.codedream.epaper.model.task.TaskResult; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * 任务服务接口 + */ +public interface ITaskService { + + /** + * 注册子任务 + * @param userId 用户ID号 + * @param fileId 文件ID号 + * @param type 子任务类型 + * @return 子任务ID号 + */ + Integer registerTask(Integer userId, Integer fileId, String type); + + /** + * 查询子任务是否结束 + * @param taskId 子任务ID号 + * @return 布尔值 + */ + boolean checkTaskFinished(Integer taskId); + + /** + * 获得子任务的详细信息 + * @param taskId 子任务ID号 + * @return 子任务对象 + */ + Optional getTaskInfo(Integer taskId); + + /** + * 获得子任务结果 + * @param taskId 子任务ID号 + * @return 任务结果对象 + */ + Optional getTaskResult(Integer taskId); + + /** + * 获得一个批处理任务 + * @param limit 批处理任务计算量上限 + * @return 处理任务对象 + */ + BatchProcessingTask getBatchProcessingTask(Integer limit); + + /** + * 获取子任务用于返回前端的结果 + * @param taskId 子任务ID号 + * @return 子任务结果JSON设计对象 + */ + JsonableTaskResult getJsonableTaskResult(Integer taskId); + + /** + * 标记一个批处理任务已完成 + * @param bptId 批处理任务ID号 + * @param bptResults 批处理任务计算结果 + */ + void markBatchProcessingTaskFinished(Integer bptId, Map bptResults); + + /** + * 获得一个新的批处理任务 + * @return 批处理任务对象 + */ + BatchProcessingTask getDefaultBPTTask(); + + /** + * 将子任务添加到批处理任务中 + * @param bpt 批处理任务对象 + */ + void registerBPTTask(BatchProcessingTask bpt); + + + /** + * 为当前bpt加锁 + * @param id 批处理任务ID号 + */ + void lockBPT(Integer id); + + /** + * 为当前bpt解锁 + * @param id 批处理任务ID号 + */ + void unlockBPT(Integer id); + + + Iterable findHistoryTaskId(Integer userId); + + +} diff --git a/src/main/java/org/codedream/epaper/service/IUserService.java b/src/main/java/org/codedream/epaper/service/IUserService.java new file mode 100644 index 0000000..2a47f42 --- /dev/null +++ b/src/main/java/org/codedream/epaper/service/IUserService.java @@ -0,0 +1,106 @@ +package org.codedream.epaper.service; + +import javafx.util.Pair; +import org.codedream.epaper.model.user.User; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +/** + * 用户服务接口 + */ +public interface IUserService { + + /** + * 获得一个空的默认用户(已激活) + * @return 用户对象 + */ + User getDefaultUser(); + + /** + * 获得列出所有用户 + * @return 用户对象列表 + */ + List findAll(); + + /** + * 通过用户ID号(数据库)查找用户对象 + * @param id 用户ID号 + * @return 用户对象 + */ + Optional findUserById(int id); + + /** + * 通过openid查找用户对象 + * @param openid 用户openid + * @return + */ + Optional findUserByOpenid(String openid); + + /** + * 查询用户是否存在 + * @param openid 用户openid + * @return 用户存在状态标记与openid对应的用户对象(如果存在) + */ + public Pair checkIfUserExists(String openid); + + /** + * 获得用户所有的权限角色 + * @param user 用户对象 + * @return 用户权限列表 + */ + Collection getUserAuthorities(User user); + + /** + * 更新用户的密码(过程中自动计算密码散列值) + * @param user 用户对象 + * @param password 用户密码 + * @return 用户对象(更新后) + */ + User updatePassword(User user, String password); + + /** + * 封禁用户 + * @param user 用户对象 + * @return 用户对象(更新后) + */ + User disableUser(User user); + + /** + * 通过用户ID号列表(数据库)获得用户对象列表 + * @param usersId 用户ID号列表 + * @return 用户对象列表 + */ + Set findUsersById(Set usersId); + + /** + * 随机生成一个用户名 + * @param user 用户对象 + */ + void generateRandomUsername(User user); + + /** + * 在数据库中保存一个新的用户对象 + * @param user 用户对象 + * @return 用户对象(更新后) + */ + User save(User user); + + /** + * 更新一个已有的用户对象在数据库中的信息 + * @param user 用户对象 + * @return 用户对象(更新后) + */ + User update(User user); + + /** + * 删除用一个在数据库中已有的用户 + * @param user 用户对象 + */ + void delete(User user); + + +} diff --git a/src/main/java/org/codedream/epaper/service/NeuralNetworkModelService.java b/src/main/java/org/codedream/epaper/service/NeuralNetworkModelService.java new file mode 100644 index 0000000..1b66e25 --- /dev/null +++ b/src/main/java/org/codedream/epaper/service/NeuralNetworkModelService.java @@ -0,0 +1,118 @@ +package org.codedream.epaper.service; + +import org.codedream.epaper.component.json.model.JsonableBPT; +import org.codedream.epaper.component.json.model.JsonableBPTResult; +import org.codedream.epaper.component.json.model.JsonableSTN; +import org.codedream.epaper.exception.innerservererror.InnerDataTransmissionException; +import org.codedream.epaper.model.article.Paragraph; +import org.codedream.epaper.model.article.Sentence; +import org.codedream.epaper.model.task.BatchProcessingTask; +import org.codedream.epaper.model.task.Task; +import org.codedream.epaper.repository.article.SentenceRepository; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.*; + +/** + * 神经网络服务层 + */ +@Service +public class NeuralNetworkModelService implements INeuralNetworkModelService { + + @Resource + private ITaskService taskService; + + @Resource + private SentenceRepository sentenceRepository; + + /** + * 标记批处理任务成功执行 + * @param batchProcessingTask 批处理任务对象 + * @param results 批处理任务结果 + */ + @Override + public void markBPTSuccess(BatchProcessingTask batchProcessingTask, List results) { + + // 标记深处理 + for(JsonableBPTResult jsonableBPTResult : results){ + Optional sentence = sentenceRepository.findById(jsonableBPTResult.getStnid()); + if(!sentence.isPresent()) throw new InnerDataTransmissionException(); + sentence.get().setDeepProcess(true); + } + + + Map bptResultMap = new HashMap<>(); + for(JsonableBPTResult bptResult : results){ + bptResultMap.put(bptResult.getStnid(), bptResult); + } + taskService.markBatchProcessingTaskFinished(batchProcessingTask.getId(), bptResultMap); + // 解锁BPT + taskService.unlockBPT(batchProcessingTask.getId()); + } + + /** + * 标记批处理任务执行失败 + * @param batchProcessingTask 批处理任务对象 + */ + @Override + public void markBPTFailed(BatchProcessingTask batchProcessingTask) { + taskService.unlockBPT(batchProcessingTask.getId()); + } + + /** + * 获得批处理任务并锁定 + * @param limit 计算端单次批处理上限 + * @return 批处理任务对象 + */ + @Override + public Optional getBPTTaskAndLock(Integer limit) { + BatchProcessingTask bpt = taskService.getBatchProcessingTask(limit); + if(bpt == null) return Optional.empty(); + else{ + taskService.lockBPT(bpt.getId()); + return Optional.of(bpt); + } + } + + /** + * 计算批处理任务原文句子列表(启用缓存筛选) + * @param bpt 批处理任务对象 + * @return 原文句子列表 + */ + @Override + public List calculateSentenceList(BatchProcessingTask bpt) { + List sentenceList = new ArrayList<>(); + for(Task task : bpt.getTasks()) { + for (Paragraph paragraph : task.getArticle().getParagraphs()) { + for (Sentence sentence : paragraph.getSentences()) { + // 检查是否已经深处理完毕 + if (sentence.isDeepProcess()) continue; + // 添加到列表 + sentenceList.add(sentence); + } + } + } + return sentenceList; + } + + /** + * 获得可转换为JSON的批处理任务对象 + * @param bpt 批处理任务对象 + * @param sentences 任务原文句子列表 + * @return 批处理任务JSON设计对象 + */ + @Override + public JsonableBPT getJsonableBPT(BatchProcessingTask bpt, List sentences) { + JsonableBPT jsonableBPT = new JsonableBPT(); + jsonableBPT.setId(bpt.getId()); + jsonableBPT.setStnNumber(sentences.size()); + for(Sentence sentence : sentences) { + JsonableSTN jsonableSTN = new JsonableSTN(); + jsonableSTN.setStnId(sentence.getId()); + jsonableSTN.setText(sentence.getText()); + jsonableBPT.getStns().add(jsonableSTN); + } + return jsonableBPT; + } +} diff --git a/src/main/java/org/codedream/epaper/service/TaskService.java b/src/main/java/org/codedream/epaper/service/TaskService.java new file mode 100644 index 0000000..18310f6 --- /dev/null +++ b/src/main/java/org/codedream/epaper/service/TaskService.java @@ -0,0 +1,386 @@ +package org.codedream.epaper.service; + +import lombok.extern.slf4j.Slf4j; +import org.codedream.epaper.component.article.GetSentenceFromArticle; +import org.codedream.epaper.component.json.model.JsonableBPTResult; +import org.codedream.epaper.component.json.model.JsonableTaskResult; +import org.codedream.epaper.component.task.BPTDivider; +import org.codedream.epaper.component.task.BPTQueue; +import org.codedream.epaper.component.task.JsonableTaskResultGenerator; +import org.codedream.epaper.component.task.LockedBPTs; +import org.codedream.epaper.exception.innerservererror.InnerDataTransmissionException; +import org.codedream.epaper.exception.innerservererror.LowBatchLimitException; +import org.codedream.epaper.exception.notfound.NotFoundException; +import org.codedream.epaper.model.article.Sentence; +import org.codedream.epaper.model.file.File; +import org.codedream.epaper.model.task.BatchProcessingTask; +import org.codedream.epaper.model.task.SentenceResult; +import org.codedream.epaper.model.task.Task; +import org.codedream.epaper.model.task.TaskResult; +import org.codedream.epaper.model.user.User; +import org.codedream.epaper.repository.task.BatchProcessingTaskRepository; +import org.codedream.epaper.repository.task.SentenceResultRepository; +import org.codedream.epaper.repository.task.TaskRepository; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.*; + +/** + * 批处理任务及子任务管理服务层,提供了任务相关的功能服务 + * + * @see AsyncTaskServer + * @see UserService + * @see FileService + * @see BPTQueue + */ +@Slf4j +@EnableScheduling +@Service +public class TaskService implements ITaskService { + + @Resource + private IUserService userService; + + @Resource + private IFileService fileService; + + @Resource + private TaskRepository taskRepository; + + @Resource + private BatchProcessingTaskRepository bptRepository; + + @Resource + private BPTQueue bptQueue; + + @Resource + private BPTDivider bptDivider; + + @Resource + private SentenceResultRepository sentenceResultRepository; + + @Resource + private GetSentenceFromArticle getSentenceFromArticle; + + @Resource + private JsonableTaskResultGenerator resultGenerator; + + @Resource + private IAsyncTaskServer asyncTaskServer; + + @Resource + private LockedBPTs lockedBPTs; + + + /** + * 此函数用于创建一个新的任务。此过程中会对任务进行预处理,以得到包括DNN语言模型处理、文本纠错、段落划分等结果并将之持久化。 + * + * @param userId 用户id + * @param fileId 文件id + * @param type 文件类型,目前只接受.doc/.docx + * @return 创建好的任务的id + */ + @Override + public Integer registerTask(Integer userId, Integer fileId, String type) { + log.info(String.format("Registering task(UserId:%d FileId:%d Type:%s)……", userId, fileId, type)); + + Optional user; + Optional file; + + Task task = new Task(); + + if (userId != null) { + user = userService.findUserById(userId); + user.ifPresent(task::setUser); + } + + if (fileId != null) { + file = Optional.ofNullable(fileService.getFileInfo(fileId)); + file.ifPresent(task::setFile); + } + + task.setType(type); + task = taskRepository.save(task); + + // 异步执行子任务预处理与预分析 + asyncTaskServer.preprocessorAndAnalyse(task.getId()); + + log.info(String.format("Task registered(UserId:%d FileId:%d Type:%s).", userId, fileId, type)); + return task.getId(); + } + + /** + * 检查任务是否完成 + * + * @param taskId 任务id + * @return 一个布尔值,表示任务是否完成 + */ + @Override + public boolean checkTaskFinished(Integer taskId) { + Optional task = taskRepository.findById(taskId); + if (!task.isPresent()) { + throw new NotFoundException(taskId.toString()); + } + return task.get().isFailed(); + } + + /** + * 获取任务信息,亦即通过任务id查找任务 + * + * @param taskId 任务id + * @return 封装在Optional中的任务 + */ + @Override + public Optional getTaskInfo(Integer taskId) { + Optional task = taskRepository.findById(taskId); + if (!task.isPresent()) throw new NotFoundException(taskId.toString()); + + return task; + } + + /** + * 获取任务结果,亦即通过任务id查找任务结果 + * + * @param taskId 任务id + * @return 封装在Optional中的任务结果 + */ + @Override + public Optional getTaskResult(Integer taskId) { + Optional task = taskRepository.findById(taskId); + if (!task.isPresent()) throw new NotFoundException(taskId.toString()); + + if (task.get().isFailed()) return Optional.empty(); + else return Optional.of(task.get().getResult()); + } + + /** + * 线程安全地获取一个批处理任务。确保调用的批处理任务在调用后被标记已锁,并放入另一个队列中等待计算完毕。 + * 若在优先队列队首的任务都无法满足限制,则会将此批处理任务等分,并重新加入队列 + * + * @param limit 发起调用请求的子服务器所接收的最大待处理的句子数量 + * @return 一个可用的批处理任务 + */ + @Override + public BatchProcessingTask getBatchProcessingTask(Integer limit) { + + BatchProcessingTask bpt = null; + try { + // 检查队列是否为空,防止阻塞 + if (bptQueue.checkEmpty()) return null; + Optional oBpt = bptRepository.findById(bptQueue.getBptId()); + + if (!oBpt.isPresent()) throw new InnerDataTransmissionException(); + bpt = oBpt.get(); + while (true) { + // 批处理任务 + if (bpt.getSentences().size() > limit) { + if (bpt.getTasks().size() <= 1) { + throw new LowBatchLimitException(); + } else { + divide(bpt); + } + oBpt = bptRepository.findById(bptQueue.getBptId()); + if (!oBpt.isPresent()) { + throw new NotFoundException("BPT not found."); + } + bpt = oBpt.get(); + } else { + break; + } + } + // 尝试次数递增 1 + bpt.setTryNumber(bpt.getTryNumber() + 1); + } catch (Exception e) { + e.printStackTrace(); + } + + if (null != bpt) { + bpt.setJoinDate(new Date()); + bpt = bptRepository.save(bpt); + lockedBPTs.add(bpt.getId()); + bptQueue.removeBPT(bpt.getId()); + } + return bpt; + } + + /** + * 生成用于返回前端的任务结果,以支持json格式的数据传递 + * + * @param taskId 任务id + * @return 封装好的任务结果 + * @see JsonableTaskResultGenerator#getJsonableTaskResult(Integer) + */ + @Override + public JsonableTaskResult getJsonableTaskResult(Integer taskId) { + log.info(String.format("Getting task(Id: %d) result……", taskId)); + return resultGenerator.getJsonableTaskResult(taskId); + } + + + /** + * 将一个批处理任务标记为已经完成。此过程将把之前锁住的批处理任务解锁,并持久化返回的结果 + * + * @param bptId 批处理任务id + * @param bptResults 批处理任务的结果,键值为句子的id,值为句子的处理结果。 + */ + @Override + public void markBatchProcessingTaskFinished(Integer bptId, Map bptResults) { + + Optional batchProcessingTask = bptRepository.findById(bptId); + if (!batchProcessingTask.isPresent()) throw new NotFoundException(); + BatchProcessingTask bptTask = batchProcessingTask.get(); + + // 处理bpt中每个task的返回结果 + for (Task task : bptTask.getTasks()) { + List sentences = getSentenceFromArticle.get(task.getArticle()); + task.getResult().setSuccess(true); + for (Sentence sentence : sentences) { + + Optional optionalSentenceResult = sentenceResultRepository.findBySentenceId(sentence.getId()); + if (!optionalSentenceResult.isPresent()) { + throw new InnerDataTransmissionException("Sentence result not found"); + } + SentenceResult sentenceResult = optionalSentenceResult.get(); + + // 检查句结果是否是初始值 + if (sentenceResult.isInitStatus()) { + JsonableBPTResult sentenceRes = bptResults.get(sentence.getId()); + int flag = 0; + float max = -1; + List res = sentenceRes.getTagPossible(); + sentenceResult.setPossibilities(res); + for (int i = 0; i < 3; i++) { + float possibility = res.get(i); + if (max < possibility) { + max = possibility; + flag = i + 1; + } + } + + switch (flag) { + case 1: { + sentenceResult.setNegative(true); + break; + } + case 2: { + sentenceResult.setNeutral(true); + break; + } + case 3: { + sentenceResult.setPositive(true); + break; + } + default: { + } + } + sentenceResult.setInitStatus(false); + sentenceResultRepository.save(sentenceResult); + } + } + } + + bptTask.setTaskFinished(); + // 将批处理任务标记完结 + bptTask.setFinished(true); + bptTask.setFinishDate(new Date()); + bptRepository.save(bptTask); + } + + /** + * 获取一个默认的批处理任务 + * + * @return 一个默认的批处理任务 + */ + @Override + public BatchProcessingTask getDefaultBPTTask() { + return new BatchProcessingTask(); + } + + /** + * 注册一个批处理任务,将之放入就绪队列中,等待来自计算端的调用 + * + * @param bpt 需要注册的批处理任务 + */ + @Override + public void registerBPTTask(BatchProcessingTask bpt) { + bptQueue.addBPT(bpt.getId()); + } + + /** + * 为一个批处理任务加锁,并放入处理等待队列,以便监听等待时间以及保证线程安全 + * + * @param bptId 批处理任务id + */ + @Override + public void lockBPT(Integer bptId) { + Optional bpt = bptRepository.findById(bptId); + if (bpt.isPresent()) { + if (lockedBPTs.contains(bptId)) return; + + lockedBPTs.add(bpt.get().getId()); + } + } + + /** + * 为一个批处理任务解锁,通常要求子服务器先调用{@link #markBatchProcessingTaskFinished(Integer, Map)}, + * 再调用此方法。如果此批处理任务未完成,并且尝试次数已经超过了3次,会将此批处理任务等分。 + * 参照{@link #divide(BatchProcessingTask)} + * + * @param id 批处理任务id + */ + @Override + public void unlockBPT(Integer id) { + Iterator iterator = lockedBPTs.iterator(); + while (iterator.hasNext()) { + Integer bptId = iterator.next(); + if (bptId.equals(id)) { + + // 查找BPT + Optional bptOptional = bptRepository.findById(bptId); + if (!bptOptional.isPresent()) throw new InnerDataTransmissionException(); + + BatchProcessingTask bpt = bptOptional.get(); + + // 如果BPT已被完成 + if (bpt.isFinished()) { + iterator.remove(); + bpt.setSuccess(true); + bptRepository.save(bpt); + } else { + if (bpt.getTryNumber() > 3) { + try { + if (bpt.getTasks().size() <= 1) { + return; + } + divide(bpt); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + break; + } + } + } + + @Override + public Iterable findHistoryTaskId(Integer userId) { + return taskRepository.findAllByUserId(userId); + } + + // 等分bpt + private void divide(BatchProcessingTask batchProcessingTask) { + List batchProcessingTasks = bptDivider.divideBPT(batchProcessingTask); + BatchProcessingTask bpt1 = batchProcessingTasks.get(0); + BatchProcessingTask bpt2 = batchProcessingTasks.get(1); + bptRepository.delete(batchProcessingTask); + bptRepository.save(batchProcessingTasks.get(0)); + bptRepository.save(batchProcessingTasks.get(1)); + bptQueue.addBPT(bpt1.getId()); + bptQueue.addBPT(bpt2.getId()); + bptQueue.removeBPT(batchProcessingTask.getId()); + } + +} diff --git a/src/main/java/org/codedream/epaper/service/UserService.java b/src/main/java/org/codedream/epaper/service/UserService.java new file mode 100644 index 0000000..369ad08 --- /dev/null +++ b/src/main/java/org/codedream/epaper/service/UserService.java @@ -0,0 +1,179 @@ +package org.codedream.epaper.service; + +import javafx.util.Pair; +import org.codedream.epaper.component.auth.EPPasswordEncoder; +import org.codedream.epaper.component.auth.EPUsernameEncoder; +import org.codedream.epaper.exception.badrequest.UsernameAlreadyExistException; +import org.codedream.epaper.exception.notfound.UserNotFoundException; +import org.codedream.epaper.model.user.User; +import org.codedream.epaper.repository.user.UserRepository; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.*; + +@Service +public class UserService implements IUserService { + @Resource + private UserRepository userRepository; + + @Resource + private EPPasswordEncoder passwordEncoder; + + @Resource + private EPUsernameEncoder usernameEncoder; + + /** + * 获得列出所有用户 + * @return 用户对象列表 + */ + @Override + public List findAll() { + return (List) userRepository.findAll(); + } + + /** + * 通过用户ID号(数据库)查找用户对象 + * @param id 用户ID号 + * @return 用户对象 + */ + @Override + public Optional findUserById(int id) { + return userRepository.findById(id); + } + + /** + * 通过openid查找用户对象 + * @param openid 用户openid + * @return + */ + @Override + public Optional findUserByOpenid(String openid) { + Optional user = userRepository.findByUsername(openid); + return user; + } + + /** + * 查询用户是否存在 + * @param openid 用户openid + * @return 用户存在状态标记与openid对应的用户对象(如果存在) + */ + @Override + public Pair checkIfUserExists(String openid){ + Optional user = userRepository.findByUsername(openid); + return user.map(value -> new Pair<>(true, value)).orElseGet(() -> new Pair<>(false, null)); + } + + /** + * 获得用户所有的权限角色 + * @param user 用户对象 + * @return 用户权限列表 + */ + @Override + public Collection getUserAuthorities(User user) { + return new ArrayList<>(); + } + + /** + * 更新用户的密码(过程中自动计算密码散列值) + * @param user 用户对象 + * @param password 用户密码 + * @return 用户对象(更新后) + */ + @Override + public User updatePassword(User user, String password) { + user.setPassword(passwordEncoder.encode(password)); + return update(user); + } + + /** + * 封禁用户 + * @param user 用户对象 + * @return 用户对象(更新后) + */ + @Override + public User disableUser(User user){ + user.setEnabled(false); + return update(user); + } + + /** + * 通过用户ID号列表(数据库)获得用户对象列表 + * @param usersId 用户ID号列表 + * @return 用户对象列表 + */ + @Override + public Set findUsersById(Set usersId) { + Set userSet = new HashSet<>(); + for(Integer id : usersId){ + Optional user = findUserById(id); + if(!user.isPresent()) throw new UserNotFoundException(String.format("ID: %d", id)); + userSet.add(user.get()); + } + return userSet; + } + + /** + * 随机生成一个用户名 + * @param user 用户对象 + */ + @Override + public void generateRandomUsername(User user) { + user.setUsername(usernameEncoder.encode(UUID.randomUUID().toString())); + } + + /** + * 在数据库中保存一个新的用户对象 + * @param user 用户对象 + * @return 用户对象(更新后) + */ + @Override + public User save(User user) { + // 查找用户名是否已经被注册 + if(userRepository.findByUsername(user.getUsername()).isPresent()) + throw new UsernameAlreadyExistException(user.getUsername()); + + // 强制以哈希值(sha256)保存密码 + user.setPassword(passwordEncoder.encode(user.getPassword())); + return userRepository.save(user); + } + + /** + * 更新一个已有的用户对象在数据库中的信息 + * @param user 用户对象 + * @return 用户对象(更新后) + */ + @Override + public User update(User user) { + // 执行前检查 + if(!userRepository.findById(user.getId()).isPresent()) + throw new UserNotFoundException(user.getId(), user.getUsername()); + return userRepository.save(user); + + } + + /** + * 删除用一个在数据库中已有的用户 + * @param user 用户对象 + */ + @Override + public void delete(User user) { + // 执行前检查 + if(!userRepository.findById(user.getId()).isPresent()) + throw new UserNotFoundException(user.getId(), user.getUsername()); + userRepository.delete(user); + } + + /** + * 获得一个空的默认用户(已激活) + * @return 用户对象 + */ + @Override + public User getDefaultUser() { + return new User(); + } + + +} diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties new file mode 100644 index 0000000..56ead79 --- /dev/null +++ b/src/main/resources/application-dev.properties @@ -0,0 +1,18 @@ +server.port=8083 + +spring.jpa.generate-ddl=false +spring.jpa.show-sql=true +spring.jpa.hibernate.ddl-auto=update +spring.jooq.sql-dialect=org.hibernate.dialect.MariaDB102Dialect +spring.jpa.open-in-view=true +spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true + +spring.datasource.url=jdbc:mariadb://localhost:3306/epaper?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC +spring.datasource.username=root +spring.datasource.password=#a9b9fa6422 +spring.datasource.driver-class-name=org.mariadb.jdbc.Driver + +server.error.whitelabel.enabled=false + +logging.level.root=info +logging.level.org.springframework.security=info \ No newline at end of file diff --git a/src/main/resources/application-lcj.properties b/src/main/resources/application-lcj.properties new file mode 100644 index 0000000..27919eb --- /dev/null +++ b/src/main/resources/application-lcj.properties @@ -0,0 +1,14 @@ +server.port=8083 +spring.jpa.generate-ddl=false +spring.jpa.show-sql=false +spring.jpa.hibernate.ddl-auto=create +spring.jooq.sql-dialect=org.hibernate.dialect.MariaDB102Dialect +spring.jpa.open-in-view=true +spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true +spring.datasource.url=jdbc:mariadb://39.100.94.111:3306/epaper?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC +spring.datasource.username=epaper +spring.datasource.password=X5764WHiMLfYr5dc +spring.datasource.driver-class-name=org.mariadb.jdbc.Driver +server.error.whitelabel.enabled=false +logging.level.root=info +logging.level.org.springframework.security=info diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties new file mode 100644 index 0000000..031ce24 --- /dev/null +++ b/src/main/resources/application-test.properties @@ -0,0 +1,18 @@ +server.port=8083 + +spring.jpa.generate-ddl=false +spring.jpa.show-sql=true +spring.jpa.hibernate.ddl-auto=update +spring.jooq.sql-dialect=org.hibernate.dialect.MariaDB102Dialect +spring.jpa.open-in-view=true +spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true + +spring.datasource.url=jdbc:h2:mem:test +spring.datasource.username= +spring.datasource.password= +spring.datasource.driver-class-name=org.h2.Driver + +server.error.whitelabel.enabled=false + +logging.level.root=debug +logging.level.org.springframework.security=info diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..2d6c860 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,18 @@ +server.port=8083 + +spring.jpa.generate-ddl=false +spring.jpa.show-sql=false +spring.jpa.hibernate.ddl-auto=update +spring.jooq.sql-dialect=org.hibernate.dialect.MariaDB102Dialect +spring.jpa.open-in-view=true +spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true + +spring.datasource.url=jdbc:mariadb://127.0.0.1:3306/epaper?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC +spring.datasource.username=epaper +spring.datasource.password=X5764WHiMLfYr5dc +spring.datasource.driver-class-name=org.mariadb.jdbc.Driver + +server.error.whitelabel.enabled=false + +logging.level.root=info +logging.level.org.springframework.security=info diff --git a/src/main/resources/fonts/simsun.ttc b/src/main/resources/fonts/simsun.ttc new file mode 100644 index 0000000..5f22ce3 Binary files /dev/null and b/src/main/resources/fonts/simsun.ttc differ diff --git a/src/main/resources/templates/report.ftl b/src/main/resources/templates/report.ftl new file mode 100644 index 0000000..0c96ade --- /dev/null +++ b/src/main/resources/templates/report.ftl @@ -0,0 +1,3102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${articleTitle} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 学术论文初级评审报告 + + + + + + + + + + + + + + + + + + + + ${datetime} + + + + + + + + + + + + + + + + ----------------------------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 维度 + + + + + + + + + + + + + + + + + + + + + + 词语 + + + + + + + + + + + + + + + + + + + + + + 通顺度 + + + + + + + + + + + + + + + + + + + + + + 书面语 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 得分 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${wdScore} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${fqScore} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${flScore} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 占位符 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 处理句数 + + + + + + + + + + + + + + + + + + + + + + + + + + ${stnNum} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 用户ID + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 平均句长 + + + + + + + + + + + + + + + + + + + + + + + + + + + ${svgLen} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${userId} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 错误分布 + + + + + + + + + + + + + + + + + + + + + + + + + + ${errStatus} + + + + + + + + + + + + + + + + + + + + + + + + + + + + 任务周转时间 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 词语错误 + + + + + + + + + + + + + + + + + + + + + + + + + + ${wEum} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${pcsTime} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 通顺度问题 + + + + + + + + + + + + + + + + + + + + + + + + + + ${fqRum} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 完成状态 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 口语 + + + + + + + 化表达 + + + + + + + + + + + + + + + + + + + + + + + + + + + ${flEum} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + status + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <#list errorStnList as error> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${error.text} + + + + + + + + + + + + + + + + + + + + + + + + + + + + 建议 + + + + + <#list error.stnResultList as result> + + + + + + + + + + + + + + + + + + + + + + + + ${result_index} + + + + + + + + + + + + + + + + + + + + + + + + + + + <#if result.type == 1> + 错字 + <#elseif result.type == 2> + 口语化 + <#elseif result.type == 3> + 通顺度 + <#else> + 无类型 + + + + + + + + + + + + + + + + + + + + + + + + + ${result.content} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 报告 + + Saturn Eric + + + Saturn Eric + 8 + 2020-07-03T13:48:00Z + 2020-07-03T14:04:00Z + + + + + + + + 14 + 1 + 76 + 434 + Microsoft Office Word + 0 + 3 + 1 + false + + false + 509 + false + false + 16.0000 + + + + \ No newline at end of file diff --git a/src/test/java/org/codedream/epaper/DiffMatchPatchTest.java b/src/test/java/org/codedream/epaper/DiffMatchPatchTest.java new file mode 100644 index 0000000..11889cb --- /dev/null +++ b/src/test/java/org/codedream/epaper/DiffMatchPatchTest.java @@ -0,0 +1,23 @@ +package org.codedream.epaper; + +import org.codedream.epaper.component.datamanager.DiffMatchPatch; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import javax.annotation.Resource; +import java.util.LinkedList; + +@SpringBootTest +public class DiffMatchPatchTest { + + @Resource + DiffMatchPatch dmp = new DiffMatchPatch(); + + @Test + public void test() { + String text1 = "我们预备了缓存数据库功能,将论文种常见的行文错误进行特征编码后缓存。"; + String text2 = "我们预备了缓存数据库功能,将论文中常见的行文错误进行特征编码后缓存。"; + LinkedList diff = dmp.diff_main(text1, text2); + System.out.println(diff.toString()); + } +} diff --git a/src/test/java/org/codedream/epaper/DividerTest.java b/src/test/java/org/codedream/epaper/DividerTest.java new file mode 100644 index 0000000..749d1a9 --- /dev/null +++ b/src/test/java/org/codedream/epaper/DividerTest.java @@ -0,0 +1,114 @@ +package org.codedream.epaper; + +import com.baidu.aip.nlp.AipNlp; +import org.codedream.epaper.component.EPSpringUtil; +import org.codedream.epaper.component.datamanager.ParagraphDivider; +import org.codedream.epaper.configure.NLPConfigure; +import org.codedream.epaper.model.article.Paragraph; +import org.codedream.epaper.model.article.Phrase; +import org.codedream.epaper.service.ArticleService; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import javax.annotation.Resource; +import java.io.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +@SpringBootTest +public class DividerTest { + + @Resource + ArticleService articleService; + + @Resource + EPSpringUtil epSpringUtil; + + @Test + public void divideSentence() throws JSONException { + + String a = "也只好,通过它了解真相"; + AipNlp client = new AipNlp(NLPConfigure.getAPPId(), NLPConfigure.getAppKey(), NLPConfigure.getSecretKey()); + HashMap hashMap = new HashMap<>(); + JSONObject res = client.lexer(a, hashMap); + HashMap hashMap1 = new HashMap<>(); + JSONArray items = (JSONArray) res.get("items"); + List phrases = new ArrayList<>(); + for (int i = 0; i < items.length() - 1; i++) { + JSONObject item = (JSONObject) items.get(i); + Phrase phrase = new Phrase(); + List basic = new ArrayList<>(); + JSONArray basicArray = (JSONArray) item.get("basic_words"); + System.out.println(basicArray); + for (int j = 0; j < basicArray.length(); j++) { + + Phrase phrase1 = new Phrase(); + phrase1.setText(basicArray.get(j).toString()); + phrase1 = articleService.save(phrase1); + basic.add(phrase1); + } + phrase.setText(item.get("item").toString()); + phrase.getBasicPhrase().addAll(basic); + phrase.setPos(item.get("pos").toString()); + } + System.out.println(items); + } + + @Test + public void divideParagraph() throws FileNotFoundException { + +// String path = "D:\\Contests\\SIC\\log.txt"; +// FileOutputStream fileOutputStream = new FileOutputStream(path); +// String text = "1111aaaaaaaaaaaaaaaaaaaaaa我。Smart Guiding of Academic Paper Writing。" + +// "Version 0.4.2.191231_alpha。" + +// "All Rights Reserved。2019-12-1 ~ 2019-12-15\n" + +// "2019-12-16 ~ 2019-12-31。"; +// ParagraphDivider paragraphDivider = epSpringUtil.getBean(ParagraphDivider.class); +// Paragraph paragraph = paragraphDivider.divideParagraph(text); +// BufferedWriter out = null; + /** + try { + out = new BufferedWriter(new OutputStreamWriter( + new FileOutputStream(path, true))); + for (int i = 0; i < paragraph.getSentences().size(); i++) { + out.write(paragraph.getSentences().get(i).getText()); + out.write("\n"); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + */ + + } + + @Test + public void littleTest() throws JSONException { + + String text = "alpha_asdfadfafsadfafs"; + AipNlp client = new AipNlp(NLPConfigure.getAPPId(), NLPConfigure.getAppKey(), NLPConfigure.getSecretKey()); + HashMap hashMap = new HashMap<>(); + System.out.println(client.lexer(text, hashMap)); + /*JSONObject res = client.wordEmbedding(text, hashMap); + System.out.println(res.toString()); + JSONArray vec = res.getJSONArray("vec"); + List floatList = new ArrayList<>(); + for (int i = 0; i < vec.length(); i++) { + + String str = vec.getString(i); + float vecUnit = Float.parseFloat(str); + floatList.add(vecUnit); + } + + System.out.println(floatList);*/ + } +} diff --git a/src/test/java/org/codedream/epaper/DnnTest.java b/src/test/java/org/codedream/epaper/DnnTest.java new file mode 100644 index 0000000..d767341 --- /dev/null +++ b/src/test/java/org/codedream/epaper/DnnTest.java @@ -0,0 +1,53 @@ +package org.codedream.epaper; + +import com.baidu.aip.nlp.AipNlp; +import org.codedream.epaper.component.datamanager.SentenceSmoothnessGetter; +import org.codedream.epaper.configure.SingletonAipNlp; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.HashMap; + +@SpringBootTest +public class DnnTest { + + @Test + public void testDnn() throws JSONException { + + String text = "阿斯顿发射点"; + AipNlp client = SingletonAipNlp.getInstance(); + HashMap options = new HashMap<>(); + JSONObject res = client.dnnlmCn(text, options); + + + System.out.println(res.get("ppl")); + } + + @Test + public void testSingleton() { + Thread2[] ThreadArr = new Thread2[10]; + for (int i = 0; i < ThreadArr.length; i++) { + ThreadArr[i] = new Thread2(); + ThreadArr[i].start(); + } + } + + @Test + public void testSentenceSmoothnessGetter() { + + String text = "该项目的软件产品的客户端功能主要分为三大类:启发模式、评价模式与一键修改。"; + SentenceSmoothnessGetter sentenceSmoothnessGetter = new SentenceSmoothnessGetter(); + float smoothness = sentenceSmoothnessGetter.getSentenceSmoothness(text); + System.out.println(smoothness); + } +} + +// 测试线程 +class Thread2 extends Thread { + @Override + public void run() { + System.out.println(SingletonAipNlp.getInstance().hashCode()); + } +} \ No newline at end of file diff --git a/src/test/java/org/codedream/epaper/EpaperApplicationTests.java b/src/test/java/org/codedream/epaper/EpaperApplicationTests.java new file mode 100644 index 0000000..d37633d --- /dev/null +++ b/src/test/java/org/codedream/epaper/EpaperApplicationTests.java @@ -0,0 +1,13 @@ +package org.codedream.epaper; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class EpaperApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/src/test/java/org/codedream/epaper/FileTest.java b/src/test/java/org/codedream/epaper/FileTest.java new file mode 100644 index 0000000..3e01df4 --- /dev/null +++ b/src/test/java/org/codedream/epaper/FileTest.java @@ -0,0 +1,31 @@ +package org.codedream.epaper; + +import org.codedream.epaper.service.IFileService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.annotation.Resource; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +@RunWith(SpringRunner.class) +@SpringBootTest +@ActiveProfiles("dev") +public class FileTest { + @Resource + private IFileService fileService; + + @Test + public void addFile() throws IOException { + Path path = Paths.get("文件管理子系统测试.docx"); + InputStream stream = Files.newInputStream(path); + fileService.saveFile("文件管理子系统测试","docx", stream); + stream.close(); + } +} diff --git a/src/test/java/org/codedream/epaper/ParserTest.java b/src/test/java/org/codedream/epaper/ParserTest.java new file mode 100644 index 0000000..71dc7ff --- /dev/null +++ b/src/test/java/org/codedream/epaper/ParserTest.java @@ -0,0 +1,58 @@ +package org.codedream.epaper; + +import org.codedream.epaper.component.datamanager.PdfParser; +import org.codedream.epaper.component.datamanager.WordParser; +import org.codedream.epaper.model.article.Paragraph; +import org.codedream.epaper.service.IArticleService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.annotation.Resource; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +@RunWith(SpringRunner.class) +@SpringBootTest +@ActiveProfiles("dev") +public class ParserTest { + @Resource + private WordParser wordParser; + + @Resource + private PdfParser pdfParser; + + @Resource + private IArticleService articleService; + + @Test + public void docxTest() throws IOException { + Path path = Paths.get("大量文字测试.docx"); + InputStream stream = Files.newInputStream(path); + wordParser.parse(stream, "docx"); + stream.close(); + } + + @Test + public void pdfTest() throws IOException { + Path path = Paths.get("大量文字测试.pdf"); + InputStream stream = Files.newInputStream(path); + pdfParser.parse(stream); + stream.close(); + } + + + @Test + public void unitTest() { + Paragraph paragraph = new Paragraph(); + paragraph.setText(""); + paragraph.setPreprocess(false); + paragraph.setSentences(null); + paragraph = articleService.save(paragraph); + } +} diff --git a/src/test/java/org/codedream/epaper/ReportTest.java b/src/test/java/org/codedream/epaper/ReportTest.java new file mode 100644 index 0000000..507a591 --- /dev/null +++ b/src/test/java/org/codedream/epaper/ReportTest.java @@ -0,0 +1,25 @@ +package org.codedream.epaper; + +import org.codedream.epaper.component.datamanager.ReportGenerator; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.annotation.Resource; +import java.io.IOException; + +@RunWith(SpringRunner.class) +@SpringBootTest +@ActiveProfiles("dev") +public class ReportTest { + + @Resource + private ReportGenerator reportGenerator; + + @Test + public void simpleGenerate() throws IOException { + reportGenerator.saveByFile("Report.pdf", reportGenerator.generate(4009)); + } +} diff --git a/src/test/java/org/codedream/epaper/ResultGeneratorTest.java b/src/test/java/org/codedream/epaper/ResultGeneratorTest.java new file mode 100644 index 0000000..b84ed28 --- /dev/null +++ b/src/test/java/org/codedream/epaper/ResultGeneratorTest.java @@ -0,0 +1,25 @@ +package org.codedream.epaper; + +import org.codedream.epaper.repository.task.TaskRepository; +import org.codedream.epaper.service.TaskService; +import org.hibernate.validator.constraints.pl.REGON; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import javax.annotation.Resource; + +@SpringBootTest +public class ResultGeneratorTest { + + @Resource + TaskRepository taskRepository; + + @Resource + TaskService taskService; + + + @Test + public void test() { + + } +} diff --git a/src/test/java/org/codedream/epaper/TaskServiceTest.java b/src/test/java/org/codedream/epaper/TaskServiceTest.java new file mode 100644 index 0000000..b8fc83d --- /dev/null +++ b/src/test/java/org/codedream/epaper/TaskServiceTest.java @@ -0,0 +1,24 @@ +package org.codedream.epaper; + +import lombok.Data; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.concurrent.atomic.AtomicInteger; + +@SpringBootTest +public class TaskServiceTest { + @Test + public void littleTest() { + LittleTest littleTest = new LittleTest(); + for (int i = 0; i < 5; i++) { + littleTest.getCnt().incrementAndGet(); + } + System.out.println(littleTest.getCnt()); + } +} + +@Data +class LittleTest { + private AtomicInteger cnt = new AtomicInteger(0); +} diff --git a/src/test/java/org/codedream/epaper/TextCorrectorTest.java b/src/test/java/org/codedream/epaper/TextCorrectorTest.java new file mode 100644 index 0000000..d77017a --- /dev/null +++ b/src/test/java/org/codedream/epaper/TextCorrectorTest.java @@ -0,0 +1,65 @@ +package org.codedream.epaper; + +import com.alibaba.fastjson.JSON; +import com.baidu.aip.nlp.AipNlp; +import org.codedream.epaper.component.EPSpringUtil; +import org.codedream.epaper.component.datamanager.TextCorrector; +import org.codedream.epaper.configure.NLPConfigure; +import org.codedream.epaper.configure.SingletonAipNlp; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import javax.annotation.Resource; +import javax.xml.soap.Text; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +@SpringBootTest +public class TextCorrectorTest { + + @Resource + EPSpringUtil epSpringUtil; + + @Test + public void testCorrector() throws JSONException { + + /* + TextCorrector textCorrector = epSpringUtil.getBean(TextCorrector.class); + String text = "百度是一家人工只能公司"; + HashMap hashMap = textCorrector.correctText(text); + System.out.println(hashMap.get("org_text").toString()); + System.out.println(hashMap.get("corr_text").toString()); + System.out.println(hashMap.get("org_pos").toString()); + System.out.println(hashMap.get("mod_pos").toString()); + */ + AipNlp client = SingletonAipNlp.getInstance(); + HashMap hashMap = new HashMap<>(); + String text = "一座美腻的城堡"; + System.out.println(client.ecnet(text, hashMap)); + } + + @Test + public void littleTest() { + + HashSet hashSet = new HashSet<>(); + String text = "你是一个好人"; + String text2 = "你不是一个好人"; + List poses1 = new ArrayList<>(); + List poses2 = new ArrayList<>(); + for (int i = 0; i < text.length(); i++) { + hashSet.add(text.charAt(i)); + } + + for (int i = 0; i < text2.length(); i++) { + if (hashSet.contains(text2.charAt(i))) continue; + poses1.add(i); + } + + System.out.println(poses1.toString()); + } +}