diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..f5b6393 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..85544b3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,147 @@ +### @author 程序员鱼皮 ### +### @from 编程导航知识星球 ### + +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +### Java template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Maven template +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +# https://github.com/takari/maven-wrapper#usage-without-binary-jar +.mvn/wrapper/maven-wrapper.jar + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..3f266f4 --- /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.8.1/apache-maven-3.8.1-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..414cd3a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +# Docker 镜像构建 +# @author 程序员鱼皮 +# @from 编程导航知识星球 +FROM maven:3.8.1-jdk-8-slim as builder + +# Copy local code to the container image. +WORKDIR /app +COPY pom.xml . +COPY src ./src + +# Build a release artifact. +RUN mvn package -DskipTests + +# Run the web service on container startup. +CMD ["java","-jar","/app/target/hebi-backend-0.0.1-SNAPSHOT.jar","--spring.profiles.active=prod"] \ No newline at end of file diff --git a/doc/swagger.png b/doc/swagger.png new file mode 100644 index 0000000..0518706 Binary files /dev/null and b/doc/swagger.png differ diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..8a8fb22 --- /dev/null +++ b/mvnw @@ -0,0 +1,316 @@ +#!/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 /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + 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="`\\unset -f command; \\command -v 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/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.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" || rm -f "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$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 \ + $MAVEN_DEBUG_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..1d8ab01 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,188 @@ +@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 "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\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/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + +FOR /F "usebackq 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%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.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 "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\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% + +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..6b45750 --- /dev/null +++ b/pom.xml @@ -0,0 +1,156 @@ + + + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.2 + + + com.yupi + hebi-backend + 0.0.1-SNAPSHOT + hebi-backend + + 1.8 + + + + + + + + io.github.briqt + xunfei-spark4j + 1.3.0 + + + + + org.redisson + redisson + 3.21.3 + + + + org.springframework.boot + spring-boot-starter-freemarker + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-aop + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.2.2 + + + + com.baomidou + mybatis-plus-boot-starter + 3.5.2 + + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.session + spring-session-data-redis + + + + org.springframework.boot + spring-boot-starter-data-elasticsearch + + + + com.github.binarywang + wx-java-mp-spring-boot-starter + 4.4.0 + + + + com.github.xiaoymin + knife4j-openapi2-spring-boot-starter + 4.4.0 + + + + com.qcloud + cos_api + 5.6.89 + + + + org.apache.commons + commons-lang3 + + + + com.alibaba + easyexcel + 3.1.1 + + + + cn.hutool + hutool-all + 5.8.8 + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + mysql + mysql-connector-java + runtime + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/sql/create_table.sql b/sql/create_table.sql new file mode 100644 index 0000000..f18851c --- /dev/null +++ b/sql/create_table.sql @@ -0,0 +1,40 @@ + + +-- 创建库 +create database if not exists hebi; + +-- 切换库 +use hebi; + +-- 用户表 +create table if not exists user +( + id bigint auto_increment comment 'id' primary key, + userAccount varchar(256) not null comment '账号', + userPassword varchar(512) not null comment '密码', + userName varchar(256) null comment '用户昵称', + userAvatar varchar(1024) null comment '用户头像', + userRole varchar(256) default 'user' not null comment '用户角色:user/admin', + createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间', + updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间', + isDelete tinyint default 0 not null comment '是否删除', + index idx_userAccount (userAccount) + ) comment '用户' collate = utf8mb4_unicode_ci; + +-- 图表信息表 +create table if not exists chart +( + id bigint auto_increment comment 'id' primary key, + goal text null comment '分析目标', + `name` varchar(128) null comment '图标名称', + chartData text null comment '图表数据', + chartType varchar(128) null comment '图表类型', + genChart text null comment '生成的图表数据', + genResult text null comment '生成的分析结论', + status varchar(128) not null default 'wait' comment'wait,running,succeed,failed', + execMessage text null comment '执行信息', + userId bigint null comment '创建用户 id', + createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间', + updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间', + isDelete tinyint default 0 not null comment '是否删除' + ) comment '图表信息表' collate = utf8mb4_unicode_ci; \ No newline at end of file diff --git a/sql/post_es_mapping.json b/sql/post_es_mapping.json new file mode 100644 index 0000000..655272d --- /dev/null +++ b/sql/post_es_mapping.json @@ -0,0 +1,52 @@ +{ + "aliases": { + "post": {} + }, + "mappings": { + "properties": { + "title": { + "type": "text", + "analyzer": "ik_max_word", + "search_analyzer": "ik_smart", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "content": { + "type": "text", + "analyzer": "ik_max_word", + "search_analyzer": "ik_smart", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "tags": { + "type": "keyword" + }, + "thumbNum": { + "type": "long" + }, + "favourNum": { + "type": "long" + }, + "userId": { + "type": "keyword" + }, + "createTime": { + "type": "date" + }, + "updateTime": { + "type": "date" + }, + "isDelete": { + "type": "keyword" + } + } + } +} \ No newline at end of file diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000..c8d5200 Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/main/java/com/yupi/springbootinit/MainApplication.java b/src/main/java/com/yupi/springbootinit/MainApplication.java new file mode 100644 index 0000000..712c0b0 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/MainApplication.java @@ -0,0 +1,27 @@ +package com.yupi.springbootinit; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * 主类(项目启动入口) + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +// todo 如需开启 Redis,须移除 exclude 中的内容 +@SpringBootApplication(exclude = {RedisAutoConfiguration.class}) +@MapperScan("com.yupi.springbootinit.mapper") +@EnableScheduling +@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true) +public class MainApplication { + + public static void main(String[] args) { + SpringApplication.run(MainApplication.class, args); + } + +} diff --git a/src/main/java/com/yupi/springbootinit/annotation/AuthCheck.java b/src/main/java/com/yupi/springbootinit/annotation/AuthCheck.java new file mode 100644 index 0000000..b06c232 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/annotation/AuthCheck.java @@ -0,0 +1,26 @@ +package com.yupi.springbootinit.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 权限校验 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface AuthCheck { + + /** + * 必须有某个角色 + * + * @return + */ + String mustRole() default ""; + +} + diff --git a/src/main/java/com/yupi/springbootinit/aop/AuthInterceptor.java b/src/main/java/com/yupi/springbootinit/aop/AuthInterceptor.java new file mode 100644 index 0000000..adc3673 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/aop/AuthInterceptor.java @@ -0,0 +1,72 @@ +package com.yupi.springbootinit.aop; + +import com.yupi.springbootinit.annotation.AuthCheck; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.model.enums.UserRoleEnum; +import com.yupi.springbootinit.service.UserService; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; + +/** + * 权限校验 AOP + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Aspect +@Component +public class AuthInterceptor { + + @Resource + private UserService userService; + + /** + * 执行拦截 + * + * @param joinPoint + * @param authCheck + * @return + */ + @Around("@annotation(authCheck)") + public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable { + String mustRole = authCheck.mustRole(); + RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); + HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest(); + // 当前登录用户 + User loginUser = userService.getLoginUser(request); + UserRoleEnum mustRoleEnum = UserRoleEnum.getEnumByValue(mustRole); + // 不需要权限,放行 + if (mustRoleEnum == null) { + return joinPoint.proceed(); + } + // 必须有该权限才通过 + UserRoleEnum userRoleEnum = UserRoleEnum.getEnumByValue(loginUser.getUserRole()); + if (userRoleEnum == null) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + // 如果被封号,直接拒绝 + if (UserRoleEnum.BAN.equals(userRoleEnum)) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + // 必须有管理员权限 + if (UserRoleEnum.ADMIN.equals(mustRoleEnum)) { + // 用户没有管理员权限,拒绝 + if (!UserRoleEnum.ADMIN.equals(userRoleEnum)) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + } + // 通过权限校验,放行 + return joinPoint.proceed(); + } +} + diff --git a/src/main/java/com/yupi/springbootinit/aop/LogInterceptor.java b/src/main/java/com/yupi/springbootinit/aop/LogInterceptor.java new file mode 100644 index 0000000..5b804b9 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/aop/LogInterceptor.java @@ -0,0 +1,56 @@ +package com.yupi.springbootinit.aop; + +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; +import org.springframework.util.StopWatch; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * 请求响应日志 AOP + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + **/ +@Aspect +@Component +@Slf4j +public class LogInterceptor { + + /** + * 执行拦截 + */ + @Around("execution(* com.yupi.springbootinit.controller.*.*(..))") + public Object doInterceptor(ProceedingJoinPoint point) throws Throwable { + // 计时 + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + // 获取请求路径 + RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); + HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest(); + // 生成请求唯一 id + String requestId = UUID.randomUUID().toString(); + String url = httpServletRequest.getRequestURI(); + // 获取请求参数 + Object[] args = point.getArgs(); + String reqParam = "[" + StringUtils.join(args, ", ") + "]"; + // 输出请求日志 + log.info("request start,id: {}, path: {}, ip: {}, params: {}", requestId, url, + httpServletRequest.getRemoteHost(), reqParam); + // 执行原方法 + Object result = point.proceed(); + // 输出响应日志 + stopWatch.stop(); + long totalTimeMillis = stopWatch.getTotalTimeMillis(); + log.info("request end, id: {}, cost: {}ms", requestId, totalTimeMillis); + return result; + } +} + diff --git a/src/main/java/com/yupi/springbootinit/api/OpenAiApi.java b/src/main/java/com/yupi/springbootinit/api/OpenAiApi.java new file mode 100644 index 0000000..5466cdd --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/api/OpenAiApi.java @@ -0,0 +1,38 @@ +package com.yupi.springbootinit.api; + +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +/** + * 调用 AI 平台的示例代码 + */ +@Service +public class OpenAiApi { + + public static void main(String[] args) { + +// /** +// * AI 对话(需要自己创建请求响应对象) +// * +// * @param request +// * @param openAiApiKey +// * @return +// */ +// public CreateChatCompletionResponse createChatCompletion(CreateChatCompletionRequest request, String openAiApiKey) { +// if (StringUtils.isBlank(openAiApiKey)) { +// throw new BusinessException(ErrorCode.PARAMS_ERROR, "未传 openAiApiKey"); +// } +// String url = "https://api.openai.com/v1/chat/completions"; +// String json = JSONUtil.toJsonStr(request); +// String result = HttpRequest.post(url) +// .header("Authorization", "Bearer " + openAiApiKey) +// .body(json) +// .execute() +// .body(); +// return JSONUtil.toBean(result, CreateChatCompletionResponse.class); +// } + + } +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/common/BaseResponse.java b/src/main/java/com/yupi/springbootinit/common/BaseResponse.java new file mode 100644 index 0000000..c1ca661 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/common/BaseResponse.java @@ -0,0 +1,35 @@ +package com.yupi.springbootinit.common; + +import java.io.Serializable; +import lombok.Data; + +/** + * 通用返回类 + * + * @param + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class BaseResponse implements Serializable { + + private int code; + + private T data; + + private String message; + + public BaseResponse(int code, T data, String message) { + this.code = code; + this.data = data; + this.message = message; + } + + public BaseResponse(int code, T data) { + this(code, data, ""); + } + + public BaseResponse(ErrorCode errorCode) { + this(errorCode.getCode(), null, errorCode.getMessage()); + } +} diff --git a/src/main/java/com/yupi/springbootinit/common/DeleteRequest.java b/src/main/java/com/yupi/springbootinit/common/DeleteRequest.java new file mode 100644 index 0000000..0109023 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/common/DeleteRequest.java @@ -0,0 +1,21 @@ +package com.yupi.springbootinit.common; + +import java.io.Serializable; +import lombok.Data; + +/** + * 删除请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class DeleteRequest implements Serializable { + + /** + * id + */ + private Long id; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/common/ErrorCode.java b/src/main/java/com/yupi/springbootinit/common/ErrorCode.java new file mode 100644 index 0000000..8880472 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/common/ErrorCode.java @@ -0,0 +1,44 @@ +package com.yupi.springbootinit.common; + +/** + * 自定义错误码 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public enum ErrorCode { + + SUCCESS(0, "ok"), + PARAMS_ERROR(40000, "请求参数错误"), + NOT_LOGIN_ERROR(40100, "未登录"), + NO_AUTH_ERROR(40101, "无权限"), + NOT_FOUND_ERROR(40400, "请求数据不存在"), + TOO_MANY_REQUEST(42900, "请求过于频繁"), + FORBIDDEN_ERROR(40300, "禁止访问"), + SYSTEM_ERROR(50000, "系统内部异常"), + OPERATION_ERROR(50001, "操作失败"); + + /** + * 状态码 + */ + private final int code; + + /** + * 信息 + */ + private final String message; + + ErrorCode(int code, String message) { + this.code = code; + this.message = message; + } + + public int getCode() { + return code; + } + + public String getMessage() { + return message; + } + +} diff --git a/src/main/java/com/yupi/springbootinit/common/PageRequest.java b/src/main/java/com/yupi/springbootinit/common/PageRequest.java new file mode 100644 index 0000000..f17de55 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/common/PageRequest.java @@ -0,0 +1,34 @@ +package com.yupi.springbootinit.common; + +import com.yupi.springbootinit.constant.CommonConstant; +import lombok.Data; + +/** + * 分页请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class PageRequest { + + /** + * 当前页号 + */ + private int current = 1; + + /** + * 页面大小 + */ + private int pageSize = 10; + + /** + * 排序字段 + */ + private String sortField; + + /** + * 排序顺序(默认升序) + */ + private String sortOrder = CommonConstant.SORT_ORDER_ASC; +} diff --git a/src/main/java/com/yupi/springbootinit/common/ResultUtils.java b/src/main/java/com/yupi/springbootinit/common/ResultUtils.java new file mode 100644 index 0000000..5544068 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/common/ResultUtils.java @@ -0,0 +1,52 @@ +package com.yupi.springbootinit.common; + +/** + * 返回工具类 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public class ResultUtils { + + /** + * 成功 + * + * @param data + * @param + * @return + */ + public static BaseResponse success(T data) { + return new BaseResponse<>(0, data, "ok"); + } + + /** + * 失败 + * + * @param errorCode + * @return + */ + public static BaseResponse error(ErrorCode errorCode) { + return new BaseResponse<>(errorCode); + } + + /** + * 失败 + * + * @param code + * @param message + * @return + */ + public static BaseResponse error(int code, String message) { + return new BaseResponse(code, null, message); + } + + /** + * 失败 + * + * @param errorCode + * @return + */ + public static BaseResponse error(ErrorCode errorCode, String message) { + return new BaseResponse(errorCode.getCode(), null, message); + } +} diff --git a/src/main/java/com/yupi/springbootinit/config/CorsConfig.java b/src/main/java/com/yupi/springbootinit/config/CorsConfig.java new file mode 100644 index 0000000..23184b5 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/config/CorsConfig.java @@ -0,0 +1,28 @@ +package com.yupi.springbootinit.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 全局跨域配置 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Configuration +public class CorsConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + // 覆盖所有请求 + registry.addMapping("/**") + // 允许发送 Cookie + .allowCredentials(true) + // 放行哪些域名(必须用 patterns,否则 * 会和 allowCredentials 冲突) + .allowedOriginPatterns("*") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .exposedHeaders("*"); + } +} diff --git a/src/main/java/com/yupi/springbootinit/config/CosClientConfig.java b/src/main/java/com/yupi/springbootinit/config/CosClientConfig.java new file mode 100644 index 0000000..f01b26a --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/config/CosClientConfig.java @@ -0,0 +1,53 @@ +package com.yupi.springbootinit.config; + +import com.qcloud.cos.COSClient; +import com.qcloud.cos.ClientConfig; +import com.qcloud.cos.auth.BasicCOSCredentials; +import com.qcloud.cos.auth.COSCredentials; +import com.qcloud.cos.region.Region; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 腾讯云对象存储客户端 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Configuration +@ConfigurationProperties(prefix = "cos.client") +@Data +public class CosClientConfig { + + /** + * accessKey + */ + private String accessKey; + + /** + * secretKey + */ + private String secretKey; + + /** + * 区域 + */ + private String region; + + /** + * 桶名 + */ + private String bucket; + + @Bean + public COSClient cosClient() { + // 初始化用户身份信息(secretId, secretKey) + COSCredentials cred = new BasicCOSCredentials(accessKey, secretKey); + // 设置bucket的区域, COS地域的简称请参照 https://www.qcloud.com/document/product/436/6224 + ClientConfig clientConfig = new ClientConfig(new Region(region)); + // 生成cos客户端 + return new COSClient(cred, clientConfig); + } +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/config/JsonConfig.java b/src/main/java/com/yupi/springbootinit/config/JsonConfig.java new file mode 100644 index 0000000..86ad8a8 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/config/JsonConfig.java @@ -0,0 +1,31 @@ +package com.yupi.springbootinit.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import org.springframework.boot.jackson.JsonComponent; +import org.springframework.context.annotation.Bean; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; + +/** + * Spring MVC Json 配置 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@JsonComponent +public class JsonConfig { + + /** + * 添加 Long 转 json 精度丢失的配置 + */ + @Bean + public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { + ObjectMapper objectMapper = builder.createXmlMapper(false).build(); + SimpleModule module = new SimpleModule(); + module.addSerializer(Long.class, ToStringSerializer.instance); + module.addSerializer(Long.TYPE, ToStringSerializer.instance); + objectMapper.registerModule(module); + return objectMapper; + } +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/config/MyBatisPlusConfig.java b/src/main/java/com/yupi/springbootinit/config/MyBatisPlusConfig.java new file mode 100644 index 0000000..a5f3b9b --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/config/MyBatisPlusConfig.java @@ -0,0 +1,31 @@ +package com.yupi.springbootinit.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * MyBatis Plus 配置 + * + * @author https://github.com/liyupi + */ +@Configuration +@MapperScan("com.yupi.springbootinit.mapper") +public class MyBatisPlusConfig { + + /** + * 拦截器配置 + * + * @return + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + // 分页插件 + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/config/RedissonConfig.java b/src/main/java/com/yupi/springbootinit/config/RedissonConfig.java new file mode 100644 index 0000000..9927cbb --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/config/RedissonConfig.java @@ -0,0 +1,34 @@ +package com.yupi.springbootinit.config; + +import lombok.Data; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "spring.redis") +@Data +public class RedissonConfig { + + private Integer database; + + private String host; + + private Integer port; + + private String password; + + @Bean + public RedissonClient redissonClient() { + Config config = new Config(); + config.useSingleServer() + .setDatabase(database) + .setAddress("redis://" + host + ":" + port); +// .setPassword("123456"); + RedissonClient redisson = Redisson.create(config); + return redisson; + } +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/config/WxOpenConfig.java b/src/main/java/com/yupi/springbootinit/config/WxOpenConfig.java new file mode 100644 index 0000000..b0d0dc1 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/config/WxOpenConfig.java @@ -0,0 +1,51 @@ +package com.yupi.springbootinit.config; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * 微信开放平台配置 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Slf4j +@Configuration +@ConfigurationProperties(prefix = "wx.open") +@Data +public class WxOpenConfig { + + private String appId; + + private String appSecret; + + private WxMpService wxMpService; + + /** + * 单例模式(不用 @Bean 是为了防止和公众号的 service 冲突) + * + * @return + */ + public WxMpService getWxMpService() { + if (wxMpService != null) { + return wxMpService; + } + synchronized (this) { + if (wxMpService != null) { + return wxMpService; + } + WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl(); + config.setAppId(appId); + config.setSecret(appSecret); + WxMpService service = new WxMpServiceImpl(); + service.setWxMpConfigStorage(config); + wxMpService = service; + return wxMpService; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/config/XingHuoConfig.java b/src/main/java/com/yupi/springbootinit/config/XingHuoConfig.java new file mode 100644 index 0000000..5fab7a8 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/config/XingHuoConfig.java @@ -0,0 +1,30 @@ +package com.yupi.springbootinit.config; + + +import io.github.briqt.spark4j.SparkClient; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author leikooo + * @Description 星火 AI 配置类 + */ +@Configuration +@ConfigurationProperties(prefix = "xun-fei.client") +@Data +public class XingHuoConfig { + private String appId; + private String apiSecret; + private String apiKey; + + @Bean + public SparkClient sparkClient() { + SparkClient sparkClient = new SparkClient(); + sparkClient.apiKey = apiKey; + sparkClient.apiSecret = apiSecret; + sparkClient.appid = appId; + return sparkClient; + } +} diff --git a/src/main/java/com/yupi/springbootinit/constant/CommonConstant.java b/src/main/java/com/yupi/springbootinit/constant/CommonConstant.java new file mode 100644 index 0000000..d7a1a54 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/constant/CommonConstant.java @@ -0,0 +1,21 @@ +package com.yupi.springbootinit.constant; + +/** + * 通用常量 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface CommonConstant { + + /** + * 升序 + */ + String SORT_ORDER_ASC = "ascend"; + + /** + * 降序 + */ + String SORT_ORDER_DESC = " descend"; + +} diff --git a/src/main/java/com/yupi/springbootinit/constant/FileConstant.java b/src/main/java/com/yupi/springbootinit/constant/FileConstant.java new file mode 100644 index 0000000..8781d56 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/constant/FileConstant.java @@ -0,0 +1,16 @@ +package com.yupi.springbootinit.constant; + +/** + * 文件常量 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface FileConstant { + + /** + * COS 访问地址 + * todo 需替换配置 + */ + String COS_HOST = "https://yupi.icu"; +} diff --git a/src/main/java/com/yupi/springbootinit/constant/UserConstant.java b/src/main/java/com/yupi/springbootinit/constant/UserConstant.java new file mode 100644 index 0000000..eeddd9b --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/constant/UserConstant.java @@ -0,0 +1,34 @@ +package com.yupi.springbootinit.constant; + +/** + * 用户常量 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface UserConstant { + + /** + * 用户登录态键 + */ + String USER_LOGIN_STATE = "user_login"; + + // region 权限 + + /** + * 默认角色 + */ + String DEFAULT_ROLE = "user"; + + /** + * 管理员角色 + */ + String ADMIN_ROLE = "admin"; + + /** + * 被封号 + */ + String BAN_ROLE = "ban"; + + // endregion +} diff --git a/src/main/java/com/yupi/springbootinit/controller/ChartController.java b/src/main/java/com/yupi/springbootinit/controller/ChartController.java new file mode 100644 index 0000000..93a407b --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/controller/ChartController.java @@ -0,0 +1,631 @@ +package com.yupi.springbootinit.controller; + +import cn.hutool.core.io.FileUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yupi.springbootinit.annotation.AuthCheck; +import com.yupi.springbootinit.common.BaseResponse; +import com.yupi.springbootinit.common.DeleteRequest; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.common.ResultUtils; +import com.yupi.springbootinit.constant.CommonConstant; +import com.yupi.springbootinit.constant.FileConstant; +import com.yupi.springbootinit.constant.UserConstant; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.exception.ThrowUtils; +import com.yupi.springbootinit.manager.AiManager; +import com.yupi.springbootinit.manager.RedisLimiterManager; +import com.yupi.springbootinit.model.dto.chart.*; +import com.yupi.springbootinit.model.dto.file.UploadFileRequest; +import com.yupi.springbootinit.model.entity.Chart; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.model.enums.FileUploadBizEnum; +import com.yupi.springbootinit.model.vo.BiResponse; +import com.yupi.springbootinit.service.ChartService; +import com.yupi.springbootinit.service.UserService; +import com.yupi.springbootinit.utils.ExcelUtils; +import com.yupi.springbootinit.utils.SqlUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 帖子接口 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@RestController +@RequestMapping("/chart") +@Slf4j +public class ChartController { + + @Resource + private ChartService chartService; + + @Resource + private UserService userService; + + // region 增删改查 + + @Resource + private AiManager aiManager; + + @Resource + private RedisLimiterManager redisLimiterManager; + + @Resource + private ThreadPoolExecutor threadPoolExecutor; + + /** + * 创建 + * + * @param chartAddRequest + * @param request + * @return + */ + @PostMapping("/add") + public BaseResponse addChart(@RequestBody ChartAddRequest chartAddRequest, HttpServletRequest request) { + if (chartAddRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Chart chart = new Chart(); + BeanUtils.copyProperties(chartAddRequest, chart); + User loginUser = userService.getLoginUser(request); + chart.setUserId(loginUser.getId()); + boolean result = chartService.save(chart); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + long newChartId = chart.getId(); + return ResultUtils.success(newChartId); + } + + /** + * 删除 + * + * @param deleteRequest + * @param request + * @return + */ + @PostMapping("/delete") + public BaseResponse deleteChart(@RequestBody DeleteRequest deleteRequest, HttpServletRequest request) { + if (deleteRequest == null || deleteRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User user = userService.getLoginUser(request); + long id = deleteRequest.getId(); + // 判断是否存在 + Chart oldChart = chartService.getById(id); + ThrowUtils.throwIf(oldChart == null, ErrorCode.NOT_FOUND_ERROR); + // 仅本人或管理员可删除 + if (!oldChart.getUserId().equals(user.getId()) && !userService.isAdmin(request)) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + boolean b = chartService.removeById(id); + return ResultUtils.success(b); + } + + /** + * 更新(仅管理员) + * + * @param chartUpdateRequest + * @return + */ + @PostMapping("/update") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public BaseResponse updateChart(@RequestBody ChartUpdateRequest chartUpdateRequest) { + if (chartUpdateRequest == null || chartUpdateRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Chart chart = new Chart(); + BeanUtils.copyProperties(chartUpdateRequest, chart); + long id = chartUpdateRequest.getId(); + // 判断是否存在 + Chart oldChart = chartService.getById(id); + ThrowUtils.throwIf(oldChart == null, ErrorCode.NOT_FOUND_ERROR); + boolean result = chartService.updateById(chart); + return ResultUtils.success(result); + } + + /** + * 根据 id 获取 + * + * @param id + * @return + */ + @GetMapping("/get") + public BaseResponse getChartById(long id, HttpServletRequest request) { + if (id <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Chart chart = chartService.getById(id); + if (chart == null) { + throw new BusinessException(ErrorCode.NOT_FOUND_ERROR); + } + return ResultUtils.success(chart); + } + + /** + * 分页获取列表(封装类) + * + * @param chartQueryRequest + * @param request + * @return + */ + @PostMapping("/list/page") + public BaseResponse> listChartByPage(@RequestBody ChartQueryRequest chartQueryRequest, + HttpServletRequest request) { + long current = chartQueryRequest.getCurrent(); + long size = chartQueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + Page chartPage = chartService.page(new Page<>(current, size), + getQueryWrapper(chartQueryRequest)); + return ResultUtils.success(chartPage); + } + + /** + * 分页获取当前用户创建的资源列表 + * + * @param chartQueryRequest + * @param request + * @return + */ + @PostMapping("/my/list/page") + public BaseResponse> listMyChartByPage(@RequestBody ChartQueryRequest chartQueryRequest, + HttpServletRequest request) { + if (chartQueryRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User loginUser = userService.getLoginUser(request); + chartQueryRequest.setUserId(loginUser.getId()); + long current = chartQueryRequest.getCurrent(); + long size = chartQueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + Page chartPage = chartService.page(new Page<>(current, size), + getQueryWrapper(chartQueryRequest)); + return ResultUtils.success(chartPage); + } + + // endregion + + /** + * 编辑(用户) + * + * @param chartEditRequest + * @param request + * @return + */ + @PostMapping("/edit") + public BaseResponse editChart(@RequestBody ChartEditRequest chartEditRequest, HttpServletRequest request) { + if (chartEditRequest == null || chartEditRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Chart chart = new Chart(); + BeanUtils.copyProperties(chartEditRequest, chart); + User loginUser = userService.getLoginUser(request); + long id = chartEditRequest.getId(); + // 判断是否存在 + Chart oldChart = chartService.getById(id); + ThrowUtils.throwIf(oldChart == null, ErrorCode.NOT_FOUND_ERROR); + // 仅本人或管理员可编辑 + if (!oldChart.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + boolean result = chartService.updateById(chart); + return ResultUtils.success(result); + } + + + + /** + * 智能分析(同步) + * + * @param multipartFile + * @param genChartByAiRequest + * @param request + * @return + */ + @PostMapping("/gen") + public BaseResponse genChartByAi(@RequestPart("file") MultipartFile multipartFile, + GenChartByAiRequest genChartByAiRequest, HttpServletRequest request) { + String name = genChartByAiRequest.getName(); + String goal = genChartByAiRequest.getGoal(); + String chartType = genChartByAiRequest.getChartType(); + // 校验 + ThrowUtils.throwIf(StringUtils.isBlank(goal), ErrorCode.PARAMS_ERROR, "目标为空"); + ThrowUtils.throwIf(StringUtils.isNotBlank(name) && name.length() > 100, ErrorCode.PARAMS_ERROR, "名称过长"); + + //校验文件 + long size =multipartFile.getSize(); + String originalFilename = multipartFile.getOriginalFilename(); + //校验文件大小 + final long ONE_MB = 10 *1024*1024L; + ThrowUtils.throwIf(size>ONE_MB,ErrorCode.PARAMS_ERROR,"文件超过10M"); + //校验文件后缀 + String suffix = FileUtil.getSuffix(originalFilename); + final List validFileSuffixList = Arrays.asList("xlsx","xls"); + ThrowUtils.throwIf(!validFileSuffixList.contains(suffix),ErrorCode.PARAMS_ERROR,"文件后缀不合法"); + + + +// final String prompt = "你是一个数据分析师和前端开发专家,接下来我会按照以下固定格式给你提供内容:\n" + +// "分析需求:\n" + +// "{数据分析的需求或者目标}\n" + +// "原始数据:\n" + +// "{csv格式的原始数据,用,作为分隔符}\n" + +// "请根据这两部分内容,按照以下指定格式生成内容(此外不要输出任何多余的开头、结尾、注释)\n" + +// "'【【【【'\n" + +// "{前端 Echarts V5 的 option 配置对象js代码,合理地将数据进行可视化,不要生成任何多余的内容,比如注释}\n" + +// "'【【【【'\n" + +// "{明确的数据分析结论、越详细越好,不要生成多余的注释}"; + + User loginUser = userService.getLoginUser(request); + // 限流判断,每个用户一个限流器 + redisLimiterManager.doRateLimit("genChartByAi_" + loginUser.getId()); + + + // 分析需求: + // 分析网站用户的增长情况 + // 原始数据: + // 日期,用户数 + // 1号,10 + // 2号,20 + // 3号,30 + + // 构造用户输入 + StringBuilder userInput = new StringBuilder(); + userInput.append("分析需求:").append("\n"); + + // 拼接分析目标 + String userGoal = goal; + if (StringUtils.isNotBlank(chartType)) { + userGoal += ",请使用" + chartType; + } + userInput.append(userGoal).append("\n"); + userInput.append("原始数据:").append("\n"); + // 压缩后的数据 + String csvData = ExcelUtils.excelToCsv(multipartFile); + userInput.append(csvData).append("\n"); + + String result = aiManager.sendMsgToXingHuo(true,userInput.toString()); + + + String[] splits = result.split("'【【【【'"); + if (splits.length < 3) { + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "AI 生成错误"); + } + + String genChart = splits[1].trim(); + String genResult = splits[2].trim(); + + + // 插入到数据库 + Chart chart = new Chart(); + chart.setName(name); + chart.setGoal(goal); + chart.setChartData(csvData); + chart.setChartType(chartType); + chart.setGenChart(genChart); + chart.setGenResult(genResult); + chart.setUserId(loginUser.getId()); + boolean saveResult = chartService.save(chart); + ThrowUtils.throwIf(!saveResult, ErrorCode.SYSTEM_ERROR, "图表保存失败"); + + BiResponse biResponse = new BiResponse(); + biResponse.setGenChart(genChart); + biResponse.setGenResult(genResult); + biResponse.setChartId(chart.getId()); + return ResultUtils.success(biResponse); + +// // 校验文件 +// long size = multipartFile.getSize(); +// String originalFilename = multipartFile.getOriginalFilename(); +// // 校验文件大小 +// final long ONE_MB = 1024 * 1024L; +// ThrowUtils.throwIf(size > ONE_MB, ErrorCode.PARAMS_ERROR, "文件超过 1M"); +// // 校验文件后缀 aaa.png +// String suffix = FileUtil.getSuffix(originalFilename); +// final List validFileSuffixList = Arrays.asList("xlsx"); +// ThrowUtils.throwIf(!validFileSuffixList.contains(suffix), ErrorCode.PARAMS_ERROR, "文件后缀非法"); +// +// User loginUser = userService.getLoginUser(request); +// +// +// return ResultUtils.success(""); + } + + + /** + * 智能分析(异步) + * + * @param multipartFile + * @param genChartByAiRequest + * @param request + * @return + */ + @PostMapping("/gen/async") + public BaseResponse genChartByAiAsync(@RequestPart("file") MultipartFile multipartFile, + GenChartByAiRequest genChartByAiRequest, HttpServletRequest request) { + String name = genChartByAiRequest.getName(); + String goal = genChartByAiRequest.getGoal(); + String chartType = genChartByAiRequest.getChartType(); + // 校验 + ThrowUtils.throwIf(StringUtils.isBlank(goal), ErrorCode.PARAMS_ERROR, "目标为空"); + ThrowUtils.throwIf(StringUtils.isNotBlank(name) && name.length() > 100, ErrorCode.PARAMS_ERROR, "名称过长"); + // 校验文件 + long size = multipartFile.getSize(); + String originalFilename = multipartFile.getOriginalFilename(); + // 校验文件大小 + final long ONE_MB = 1024 * 1024L; + ThrowUtils.throwIf(size > ONE_MB, ErrorCode.PARAMS_ERROR, "文件超过 1M"); + // 校验文件后缀 aaa.png + String suffix = FileUtil.getSuffix(originalFilename); + final List validFileSuffixList = Arrays.asList("xlsx", "xls"); + ThrowUtils.throwIf(!validFileSuffixList.contains(suffix), ErrorCode.PARAMS_ERROR, "文件后缀非法"); + + User loginUser = userService.getLoginUser(request); + // 限流判断,每个用户一个限流器 + redisLimiterManager.doRateLimit("genChartByAi_" + loginUser.getId()); + + + // 构造用户输入 + StringBuilder userInput = new StringBuilder(); + userInput.append("分析需求:").append("\n"); + + // 拼接分析目标 + String userGoal = goal; + if (StringUtils.isNotBlank(chartType)) { + userGoal += ",请使用" + chartType; + } + userInput.append(userGoal).append("\n"); + userInput.append("原始数据:").append("\n"); + // 压缩后的数据 + String csvData = ExcelUtils.excelToCsv(multipartFile); + userInput.append(csvData).append("\n"); + + // 插入到数据库 + Chart chart = new Chart(); + chart.setName(name); + chart.setGoal(goal); + chart.setChartData(csvData); + chart.setChartType(chartType); + chart.setStatus("wait"); + chart.setUserId(loginUser.getId()); + boolean saveResult = chartService.save(chart); // 直接插入 + ThrowUtils.throwIf(!saveResult, ErrorCode.SYSTEM_ERROR, "图表保存失败"); + + // todo 建议处理任务队列满了后,抛异常的情况 + try{ + CompletableFuture.runAsync(() -> { + // 先修改图表任务状态为 “执行中”。等执行成功后,修改为 “已完成”、保存执行结果;执行失败后,状态修改为 “失败”,记录任务失败信息。 + Chart updateChart = new Chart(); + updateChart.setId(chart.getId()); + updateChart.setStatus("running"); + boolean b = chartService.updateById(updateChart); + if (!b) { + handleChartUpdateError(chart.getId(), "更新图表执行中状态失败"); + return; + } + // 调用 AI + String result = aiManager.sendMsgToXingHuo(true,userInput.toString()); + String[] splits = result.split("'【【【【'"); + if (splits.length < 3) { + handleChartUpdateError(chart.getId(), "AI 生成错误"); + return; + } + String genChart = splits[1].trim(); + String genResult = splits[2].trim(); + Chart updateChartResult = new Chart(); + updateChartResult.setId(chart.getId()); + updateChartResult.setGenChart(genChart); + updateChartResult.setGenResult(genResult); + // todo 建议定义状态为枚举值 + updateChartResult.setStatus("succeed"); + boolean updateResult = chartService.updateById(updateChartResult); + if (!updateResult) { + handleChartUpdateError(chart.getId(), "更新图表成功状态失败"); + } + }, threadPoolExecutor); + }catch (RejectedExecutionException e) { + log.error("任务队列已满,无法提交异步任务", e); + // 更新图表状态为失败 + handleChartUpdateError(chart.getId(), "任务队列已满,无法处理"); + } + + + BiResponse biResponse = new BiResponse(); + biResponse.setChartId(chart.getId()); + return ResultUtils.success(biResponse); + } + + + + //工具类 + private void handleChartUpdateError(long chartId, String execMessage) { + Chart updateChartResult = new Chart(); + updateChartResult.setId(chartId); + updateChartResult.setStatus("failed"); + updateChartResult.setExecMessage("execMessage"); + boolean updateResult = chartService.updateById(updateChartResult); + if (!updateResult) { + log.error("更新图表失败状态失败" + chartId + "," + execMessage); + } + } + + +// /** +// * 智能分析 +// * +// * @param multipartFile +// * @param genChartByAiRequest +// * @param request +// * @return +// */ +// @PostMapping("/gen") +// public BaseResponse genChartByAi(@RequestPart("file") MultipartFile multipartFile, +// GenChartByAiRequest genChartByAiRequest, HttpServletRequest request) { +// String name = genChartByAiRequest.getName(); +// String goal = genChartByAiRequest.getGoal(); +// String chartType = genChartByAiRequest.getChartType(); +// //校验 +// ThrowUtils.throwIf(StringUtils.isBlank(goal),ErrorCode.PARAMS_ERROR,"目标为空"); +// ThrowUtils.throwIf(StringUtils.isNotBlank(name) && name.length()>100,ErrorCode.PARAMS_ERROR,"名称过长"); +// +// //校验文件 +// long size =multipartFile.getSize(); +// String originalFilename = multipartFile.getOriginalFilename(); +// //校验文件大小 +// final long ONE_MB = 10 *1024*1024L; +// ThrowUtils.throwIf(size>ONE_MB,ErrorCode.PARAMS_ERROR,"文件超过10M"); +// //校验文件后缀 +// String suffix = FileUtil.getSuffix(originalFilename); +// final List validFileSuffixList = Arrays.asList("xlsx","xls"); +// ThrowUtils.throwIf(!validFileSuffixList.contains(suffix),ErrorCode.PARAMS_ERROR,"文件后缀不合法"); +// +// //获取用户信息 +// User loginUser = userService.getLoginUser(request); +// //限流判断,每个用户一个限流器(方法名+用户id) +// redisLimiterManager.doRateLimit("genChartByAi_"+loginUser.getId()); +// +// +// //系统预设 +//// final String prompt = "你是一个数据分析师和前端开发专家,接下来我会按照以下固定格式给你提供内容:\n" + +//// "分析需求:\n" + +//// "(数据分析的需求或者目标}\n" + +//// "原始数据:\n" + +//// "{csv格式的原始数据,用,作为分隔符]\n" + +//// "请根据这两部分内容,按照以下指定格式生成内容(此外不要输出任何多余的开头、结尾、注释)\n" + +//// "'【【【【'\n" + +//// "{前端Echarts V5的option配置对象js代码,合理地将数据进行可视化,不要生成任何多余的内容,比如注释}\n" + +//// "'【【【【'\n" + +//// "(明确的数据分析结论、越详细越好,不要生成多余的注释}"; +// +// +// //构造用户输入 +// StringBuilder userInput = new StringBuilder(); +// userInput.append("分析需求:").append("\n"); +// +// //拼接分析目标 +// String userGoal =goal; +// if(StringUtils.isNotBlank(chartType)){ +// userGoal+=",请使用"+chartType; +// } +// userInput.append(userGoal).append("\n"); +// userInput.append("原始数据:").append("\n"); +// +// //压缩后的数据 +// String csvData=ExcelUtils.excelToCsv(multipartFile); +// userInput.append(csvData).append("\n"); +// +// //插入到数据库中 +// Chart chart = new Chart(); +// chart.setName(name); +// chart.setGoal(goal); +// chart.setChartData(csvData); +// chart.setChartType(chartType); +// chart.setStatus("wait"); +// chart.setUserId(loginUser.getId()); +// boolean saveResult = chartService.save(chart); +// ThrowUtils.throwIf(!saveResult,ErrorCode.SYSTEM_ERROR,"图表保存失败"); +// +// //处理任务队列满了后,抛异常的情况 +// try { +// CompletableFuture.runAsync(() -> { +// // 先修改图表任务状态为 “执行中”。等执行成功后,修改为 “已完成”、保存执行结果;执行失败后,状态修改为 “失败”,记录任务失败信息。 +// Chart updateChart = new Chart(); +// updateChart.setId(chart.getId()); +// updateChart.setStatus("running"); +// boolean b = chartService.updateById(updateChart); +// if (!b) { +// handleChartUpdateError(chart.getId(), "更新图表执行中状态失败"); +// return; +// } +// //调用ai +// String result = aiManager.sendMsgToXingHuo(true,userInput.toString()); +// String[] splits = result.split("'【【【【'"); +// if(splits.length<3){ +// handleChartUpdateError(chart.getId(), "AI 生成错误"); +// return; +// } +// String genChart =splits[1].trim(); +// String genResult = splits[2].trim(); +// Chart updateChartResult = new Chart(); +// updateChartResult.setId(chart.getId()); +// updateChartResult.setGenChart(genChart); +// updateChartResult.setGenResult(genResult); +// updateChartResult.setStatus("success"); +// boolean updateResult = chartService.updateById(updateChartResult); +// if (!updateResult) { +// handleChartUpdateError(chart.getId(), "更新图表成功状态失败"); +// } +// },threadPoolExecutor); +// }catch (RejectedExecutionException e) { +// System.err.println("任务被拒绝,线程池任务队列已满: " + e.getMessage()); +// } +// +// BiResponse biResponse =new BiResponse(); +// biResponse.setChartId(chart.getId()); +// return ResultUtils.success(biResponse); +// +// } +// +// +// //工具类 +// private void handleChartUpdateError(long chartId, String execMessage) { +// Chart updateChartResult = new Chart(); +// updateChartResult.setId(chartId); +// updateChartResult.setStatus("failed"); +// updateChartResult.setExecMessage("execMessage"); +// boolean updateResult = chartService.updateById(updateChartResult); +// if (!updateResult) { +// log.error("更新图表失败状态失败" + chartId + "," + execMessage); +// } +// } + + + + /** + * 获取查询包装类 + * + * @param chartQueryRequest + * @return + */ + private QueryWrapper getQueryWrapper(ChartQueryRequest chartQueryRequest) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (chartQueryRequest == null) { + return queryWrapper; + } + Long id = chartQueryRequest.getId(); + String name = chartQueryRequest.getName(); + String goal = chartQueryRequest.getGoal(); + String chartType = chartQueryRequest.getChartType(); + Long userId = chartQueryRequest.getUserId(); + String sortField = chartQueryRequest.getSortField(); + String sortOrder = chartQueryRequest.getSortOrder(); + + queryWrapper.eq(id!=null && id>0,"id",id); + queryWrapper.like(StringUtils.isNotBlank(name),"name",name); + queryWrapper.eq(StringUtils.isNotBlank(goal),"goal",goal); + queryWrapper.eq(StringUtils.isNotBlank(chartType),"chartType",chartType); + queryWrapper.eq(ObjectUtils.isNotEmpty(userId), "userId", userId); + queryWrapper.eq("isDelete",false); + queryWrapper.orderBy(SqlUtils.validSortField(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC), + sortField); + return queryWrapper; + } + +} diff --git a/src/main/java/com/yupi/springbootinit/controller/FileController.java b/src/main/java/com/yupi/springbootinit/controller/FileController.java new file mode 100644 index 0000000..cd3b060 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/controller/FileController.java @@ -0,0 +1,108 @@ +package com.yupi.springbootinit.controller; + +import cn.hutool.core.io.FileUtil; +import com.yupi.springbootinit.common.BaseResponse; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.common.ResultUtils; +import com.yupi.springbootinit.constant.FileConstant; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.manager.CosManager; +import com.yupi.springbootinit.model.dto.file.UploadFileRequest; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.model.enums.FileUploadBizEnum; +import com.yupi.springbootinit.service.UserService; +import java.io.File; +import java.util.Arrays; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +/** + * 文件接口 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@RestController +@RequestMapping("/file") +@Slf4j +public class FileController { + + @Resource + private UserService userService; + + @Resource + private CosManager cosManager; + + /** + * 文件上传 + * + * @param multipartFile + * @param uploadFileRequest + * @param request + * @return + */ + @PostMapping("/upload") + public BaseResponse uploadFile(@RequestPart("file") MultipartFile multipartFile, + UploadFileRequest uploadFileRequest, HttpServletRequest request) { + String biz = uploadFileRequest.getBiz(); + FileUploadBizEnum fileUploadBizEnum = FileUploadBizEnum.getEnumByValue(biz); + if (fileUploadBizEnum == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + validFile(multipartFile, fileUploadBizEnum); + User loginUser = userService.getLoginUser(request); + // 文件目录:根据业务、用户来划分 + String uuid = RandomStringUtils.randomAlphanumeric(8); + String filename = uuid + "-" + multipartFile.getOriginalFilename(); + String filepath = String.format("/%s/%s/%s", fileUploadBizEnum.getValue(), loginUser.getId(), filename); + File file = null; + try { + // 上传文件 + file = File.createTempFile(filepath, null); + multipartFile.transferTo(file); + cosManager.putObject(filepath, file); + // 返回可访问地址 + return ResultUtils.success(FileConstant.COS_HOST + filepath); + } catch (Exception e) { + log.error("file upload error, filepath = " + filepath, e); + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "上传失败"); + } finally { + if (file != null) { + // 删除临时文件 + boolean delete = file.delete(); + if (!delete) { + log.error("file delete error, filepath = {}", filepath); + } + } + } + } + + /** + * 校验文件 + * + * @param multipartFile + * @param fileUploadBizEnum 业务类型 + */ + private void validFile(MultipartFile multipartFile, FileUploadBizEnum fileUploadBizEnum) { + // 文件大小 + long fileSize = multipartFile.getSize(); + // 文件后缀 + String fileSuffix = FileUtil.getSuffix(multipartFile.getOriginalFilename()); + final long ONE_M = 1024 * 1024L; + if (FileUploadBizEnum.USER_AVATAR.equals(fileUploadBizEnum)) { + if (fileSize > ONE_M) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件大小不能超过 1M"); + } + if (!Arrays.asList("jpeg", "jpg", "svg", "png", "webp").contains(fileSuffix)) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件类型错误"); + } + } + } +} diff --git a/src/main/java/com/yupi/springbootinit/controller/PostController.java b/src/main/java/com/yupi/springbootinit/controller/PostController.java new file mode 100644 index 0000000..676f173 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/controller/PostController.java @@ -0,0 +1,263 @@ +package com.yupi.springbootinit.controller; + +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yupi.springbootinit.annotation.AuthCheck; +import com.yupi.springbootinit.common.BaseResponse; +import com.yupi.springbootinit.common.DeleteRequest; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.common.ResultUtils; +import com.yupi.springbootinit.constant.UserConstant; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.exception.ThrowUtils; +import com.yupi.springbootinit.model.dto.post.PostAddRequest; +import com.yupi.springbootinit.model.dto.post.PostEditRequest; +import com.yupi.springbootinit.model.dto.post.PostQueryRequest; +import com.yupi.springbootinit.model.dto.post.PostUpdateRequest; +import com.yupi.springbootinit.model.entity.Post; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.model.vo.PostVO; +import com.yupi.springbootinit.service.PostService; +import com.yupi.springbootinit.service.UserService; +import java.util.List; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 帖子接口 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@RestController +@RequestMapping("/post") +@Slf4j +public class PostController { + + @Resource + private PostService postService; + + @Resource + private UserService userService; + + // region 增删改查 + + /** + * 创建 + * + * @param postAddRequest + * @param request + * @return + */ + @PostMapping("/add") + public BaseResponse addPost(@RequestBody PostAddRequest postAddRequest, HttpServletRequest request) { + if (postAddRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Post post = new Post(); + BeanUtils.copyProperties(postAddRequest, post); + List tags = postAddRequest.getTags(); + if (tags != null) { + post.setTags(JSONUtil.toJsonStr(tags)); + } + postService.validPost(post, true); + User loginUser = userService.getLoginUser(request); + post.setUserId(loginUser.getId()); + post.setFavourNum(0); + post.setThumbNum(0); + boolean result = postService.save(post); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + long newPostId = post.getId(); + return ResultUtils.success(newPostId); + } + + /** + * 删除 + * + * @param deleteRequest + * @param request + * @return + */ + @PostMapping("/delete") + public BaseResponse deletePost(@RequestBody DeleteRequest deleteRequest, HttpServletRequest request) { + if (deleteRequest == null || deleteRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User user = userService.getLoginUser(request); + long id = deleteRequest.getId(); + // 判断是否存在 + Post oldPost = postService.getById(id); + ThrowUtils.throwIf(oldPost == null, ErrorCode.NOT_FOUND_ERROR); + // 仅本人或管理员可删除 + if (!oldPost.getUserId().equals(user.getId()) && !userService.isAdmin(request)) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + boolean b = postService.removeById(id); + return ResultUtils.success(b); + } + + /** + * 更新(仅管理员) + * + * @param postUpdateRequest + * @return + */ + @PostMapping("/update") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public BaseResponse updatePost(@RequestBody PostUpdateRequest postUpdateRequest) { + if (postUpdateRequest == null || postUpdateRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Post post = new Post(); + BeanUtils.copyProperties(postUpdateRequest, post); + List tags = postUpdateRequest.getTags(); + if (tags != null) { + post.setTags(JSONUtil.toJsonStr(tags)); + } + // 参数校验 + postService.validPost(post, false); + long id = postUpdateRequest.getId(); + // 判断是否存在 + Post oldPost = postService.getById(id); + ThrowUtils.throwIf(oldPost == null, ErrorCode.NOT_FOUND_ERROR); + boolean result = postService.updateById(post); + return ResultUtils.success(result); + } + + /** + * 根据 id 获取 + * + * @param id + * @return + */ + @GetMapping("/get/vo") + public BaseResponse getPostVOById(long id, HttpServletRequest request) { + if (id <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Post post = postService.getById(id); + if (post == null) { + throw new BusinessException(ErrorCode.NOT_FOUND_ERROR); + } + return ResultUtils.success(postService.getPostVO(post, request)); + } + + /** + * 分页获取列表(仅管理员) + * + * @param postQueryRequest + * @return + */ + @PostMapping("/list/page") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public BaseResponse> listPostByPage(@RequestBody PostQueryRequest postQueryRequest) { + long current = postQueryRequest.getCurrent(); + long size = postQueryRequest.getPageSize(); + Page postPage = postService.page(new Page<>(current, size), + postService.getQueryWrapper(postQueryRequest)); + return ResultUtils.success(postPage); + } + + /** + * 分页获取列表(封装类) + * + * @param postQueryRequest + * @param request + * @return + */ + @PostMapping("/list/page/vo") + public BaseResponse> listPostVOByPage(@RequestBody PostQueryRequest postQueryRequest, + HttpServletRequest request) { + long current = postQueryRequest.getCurrent(); + long size = postQueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + Page postPage = postService.page(new Page<>(current, size), + postService.getQueryWrapper(postQueryRequest)); + return ResultUtils.success(postService.getPostVOPage(postPage, request)); + } + + /** + * 分页获取当前用户创建的资源列表 + * + * @param postQueryRequest + * @param request + * @return + */ + @PostMapping("/my/list/page/vo") + public BaseResponse> listMyPostVOByPage(@RequestBody PostQueryRequest postQueryRequest, + HttpServletRequest request) { + if (postQueryRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User loginUser = userService.getLoginUser(request); + postQueryRequest.setUserId(loginUser.getId()); + long current = postQueryRequest.getCurrent(); + long size = postQueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + Page postPage = postService.page(new Page<>(current, size), + postService.getQueryWrapper(postQueryRequest)); + return ResultUtils.success(postService.getPostVOPage(postPage, request)); + } + + // endregion + + /** + * 分页搜索(从 ES 查询,封装类) + * + * @param postQueryRequest + * @param request + * @return + */ + @PostMapping("/search/page/vo") + public BaseResponse> searchPostVOByPage(@RequestBody PostQueryRequest postQueryRequest, + HttpServletRequest request) { + long size = postQueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + Page postPage = postService.searchFromEs(postQueryRequest); + return ResultUtils.success(postService.getPostVOPage(postPage, request)); + } + + /** + * 编辑(用户) + * + * @param postEditRequest + * @param request + * @return + */ + @PostMapping("/edit") + public BaseResponse editPost(@RequestBody PostEditRequest postEditRequest, HttpServletRequest request) { + if (postEditRequest == null || postEditRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Post post = new Post(); + BeanUtils.copyProperties(postEditRequest, post); + List tags = postEditRequest.getTags(); + if (tags != null) { + post.setTags(JSONUtil.toJsonStr(tags)); + } + // 参数校验 + postService.validPost(post, false); + User loginUser = userService.getLoginUser(request); + long id = postEditRequest.getId(); + // 判断是否存在 + Post oldPost = postService.getById(id); + ThrowUtils.throwIf(oldPost == null, ErrorCode.NOT_FOUND_ERROR); + // 仅本人或管理员可编辑 + if (!oldPost.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + boolean result = postService.updateById(post); + return ResultUtils.success(result); + } + +} diff --git a/src/main/java/com/yupi/springbootinit/controller/PostFavourController.java b/src/main/java/com/yupi/springbootinit/controller/PostFavourController.java new file mode 100644 index 0000000..767d4ef --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/controller/PostFavourController.java @@ -0,0 +1,109 @@ +package com.yupi.springbootinit.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yupi.springbootinit.common.BaseResponse; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.common.ResultUtils; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.exception.ThrowUtils; +import com.yupi.springbootinit.model.dto.post.PostQueryRequest; +import com.yupi.springbootinit.model.dto.postfavour.PostFavourAddRequest; +import com.yupi.springbootinit.model.dto.postfavour.PostFavourQueryRequest; +import com.yupi.springbootinit.model.entity.Post; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.model.vo.PostVO; +import com.yupi.springbootinit.service.PostFavourService; +import com.yupi.springbootinit.service.PostService; +import com.yupi.springbootinit.service.UserService; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 帖子收藏接口 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@RestController +@RequestMapping("/post_favour") +@Slf4j +public class PostFavourController { + + @Resource + private PostFavourService postFavourService; + + @Resource + private PostService postService; + + @Resource + private UserService userService; + + /** + * 收藏 / 取消收藏 + * + * @param postFavourAddRequest + * @param request + * @return resultNum 收藏变化数 + */ + @PostMapping("/") + public BaseResponse doPostFavour(@RequestBody PostFavourAddRequest postFavourAddRequest, + HttpServletRequest request) { + if (postFavourAddRequest == null || postFavourAddRequest.getPostId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + // 登录才能操作 + final User loginUser = userService.getLoginUser(request); + long postId = postFavourAddRequest.getPostId(); + int result = postFavourService.doPostFavour(postId, loginUser); + return ResultUtils.success(result); + } + + /** + * 获取我收藏的帖子列表 + * + * @param postQueryRequest + * @param request + */ + @PostMapping("/my/list/page") + public BaseResponse> listMyFavourPostByPage(@RequestBody PostQueryRequest postQueryRequest, + HttpServletRequest request) { + if (postQueryRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User loginUser = userService.getLoginUser(request); + long current = postQueryRequest.getCurrent(); + long size = postQueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + Page postPage = postFavourService.listFavourPostByPage(new Page<>(current, size), + postService.getQueryWrapper(postQueryRequest), loginUser.getId()); + return ResultUtils.success(postService.getPostVOPage(postPage, request)); + } + + /** + * 获取用户收藏的帖子列表 + * + * @param postFavourQueryRequest + * @param request + */ + @PostMapping("/list/page") + public BaseResponse> listFavourPostByPage(@RequestBody PostFavourQueryRequest postFavourQueryRequest, + HttpServletRequest request) { + if (postFavourQueryRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + long current = postFavourQueryRequest.getCurrent(); + long size = postFavourQueryRequest.getPageSize(); + Long userId = postFavourQueryRequest.getUserId(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20 || userId == null, ErrorCode.PARAMS_ERROR); + Page postPage = postFavourService.listFavourPostByPage(new Page<>(current, size), + postService.getQueryWrapper(postFavourQueryRequest.getPostQueryRequest()), userId); + return ResultUtils.success(postService.getPostVOPage(postPage, request)); + } +} diff --git a/src/main/java/com/yupi/springbootinit/controller/PostThumbController.java b/src/main/java/com/yupi/springbootinit/controller/PostThumbController.java new file mode 100644 index 0000000..faaaa3c --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/controller/PostThumbController.java @@ -0,0 +1,56 @@ +package com.yupi.springbootinit.controller; + +import com.yupi.springbootinit.common.BaseResponse; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.common.ResultUtils; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.model.dto.postthumb.PostThumbAddRequest; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.service.PostThumbService; +import com.yupi.springbootinit.service.UserService; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 帖子点赞接口 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@RestController +@RequestMapping("/post_thumb") +@Slf4j +public class PostThumbController { + + @Resource + private PostThumbService postThumbService; + + @Resource + private UserService userService; + + /** + * 点赞 / 取消点赞 + * + * @param postThumbAddRequest + * @param request + * @return resultNum 本次点赞变化数 + */ + @PostMapping("/") + public BaseResponse doThumb(@RequestBody PostThumbAddRequest postThumbAddRequest, + HttpServletRequest request) { + if (postThumbAddRequest == null || postThumbAddRequest.getPostId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + // 登录才能点赞 + final User loginUser = userService.getLoginUser(request); + long postId = postThumbAddRequest.getPostId(); + int result = postThumbService.doPostThumb(postId, loginUser); + return ResultUtils.success(result); + } + +} diff --git a/src/main/java/com/yupi/springbootinit/controller/QueueController.java b/src/main/java/com/yupi/springbootinit/controller/QueueController.java new file mode 100644 index 0000000..9591eeb --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/controller/QueueController.java @@ -0,0 +1,56 @@ +package com.yupi.springbootinit.controller; + +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Profile; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 队列测试 + * + * @author Ding Jiaxiong + */ +@RestController +@RequestMapping("/queue") +@Slf4j +@Profile({ "dev", "local" }) +public class QueueController { + + @Resource + private ThreadPoolExecutor threadPoolExecutor; + + @GetMapping("/add") + public void add(String name) { + CompletableFuture.runAsync(() -> { +// System.out.println("任务执行中:" + name + ",执行人:" + Thread.currentThread().getName()); + log.info("任务执行中:" + name + ",执行人:" + Thread.currentThread().getName()); + try { + Thread.sleep(600000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + }, threadPoolExecutor); + } + + @GetMapping("/get") + public String get() { + Map map = new HashMap<>(); + int size = threadPoolExecutor.getQueue().size(); + map.put("队列长度", size); + long taskCount = threadPoolExecutor.getTaskCount(); + map.put("任务总数", taskCount); + long completedTaskCount = threadPoolExecutor.getCompletedTaskCount(); + map.put("已完成任务数", completedTaskCount); + int activeCount = threadPoolExecutor.getActiveCount(); + map.put("正在工作的线程数", activeCount); + return JSONUtil.toJsonStr(map); + } +} diff --git a/src/main/java/com/yupi/springbootinit/controller/UserController.java b/src/main/java/com/yupi/springbootinit/controller/UserController.java new file mode 100644 index 0000000..d13d448 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/controller/UserController.java @@ -0,0 +1,293 @@ +package com.yupi.springbootinit.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yupi.springbootinit.annotation.AuthCheck; +import com.yupi.springbootinit.common.BaseResponse; +import com.yupi.springbootinit.common.DeleteRequest; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.common.ResultUtils; +import com.yupi.springbootinit.config.WxOpenConfig; +import com.yupi.springbootinit.constant.UserConstant; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.exception.ThrowUtils; +import com.yupi.springbootinit.model.dto.user.UserAddRequest; +import com.yupi.springbootinit.model.dto.user.UserLoginRequest; +import com.yupi.springbootinit.model.dto.user.UserQueryRequest; +import com.yupi.springbootinit.model.dto.user.UserRegisterRequest; +import com.yupi.springbootinit.model.dto.user.UserUpdateMyRequest; +import com.yupi.springbootinit.model.dto.user.UserUpdateRequest; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.model.vo.LoginUserVO; +import com.yupi.springbootinit.model.vo.UserVO; +import com.yupi.springbootinit.service.UserService; + +import java.util.List; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.util.DigestUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static com.yupi.springbootinit.service.impl.UserServiceImpl.SALT; + +/** + * 用户接口 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@RestController +@RequestMapping("/user") +@Slf4j +public class UserController { + + @Resource + private UserService userService; + + @Resource + private WxOpenConfig wxOpenConfig; + + // region 登录相关 + + /** + * 用户注册 + * + * @param userRegisterRequest + * @return + */ + @PostMapping("/register") + public BaseResponse userRegister(@RequestBody UserRegisterRequest userRegisterRequest) { + if (userRegisterRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + String userAccount = userRegisterRequest.getUserAccount(); + String userPassword = userRegisterRequest.getUserPassword(); + String checkPassword = userRegisterRequest.getCheckPassword(); + if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword)) { + return null; + } + long result = userService.userRegister(userAccount, userPassword, checkPassword); + return ResultUtils.success(result); + } + + /** + * 用户登录 + * + * @param userLoginRequest + * @param request + * @return + */ + @PostMapping("/login") + public BaseResponse userLogin(@RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request) { + if (userLoginRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + String userAccount = userLoginRequest.getUserAccount(); + String userPassword = userLoginRequest.getUserPassword(); + if (StringUtils.isAnyBlank(userAccount, userPassword)) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + LoginUserVO loginUserVO = userService.userLogin(userAccount, userPassword, request); + return ResultUtils.success(loginUserVO); + } + + /** + * 用户注销 + * + * @param request + * @return + */ + @PostMapping("/logout") + public BaseResponse userLogout(HttpServletRequest request) { + if (request == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + boolean result = userService.userLogout(request); + return ResultUtils.success(result); + } + + /** + * 获取当前登录用户 + * + * @param request + * @return + */ + @GetMapping("/get/login") + public BaseResponse getLoginUser(HttpServletRequest request) { + User user = userService.getLoginUser(request); + return ResultUtils.success(userService.getLoginUserVO(user)); + } + + // endregion + + // region 增删改查 + + /** + * 创建用户 + * + * @param userAddRequest + * @param request + * @return + */ + @PostMapping("/add") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public BaseResponse addUser(@RequestBody UserAddRequest userAddRequest, HttpServletRequest request) { + if (userAddRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User user = new User(); + BeanUtils.copyProperties(userAddRequest, user); + // 默认密码 12345678 + String defaultPassword = "12345678"; + String encryptPassword = DigestUtils.md5DigestAsHex((SALT + defaultPassword).getBytes()); + user.setUserPassword(encryptPassword); + boolean result = userService.save(user); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + return ResultUtils.success(user.getId()); + } + + /** + * 删除用户 + * + * @param deleteRequest + * @param request + * @return + */ + @PostMapping("/delete") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public BaseResponse deleteUser(@RequestBody DeleteRequest deleteRequest, HttpServletRequest request) { + if (deleteRequest == null || deleteRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + boolean b = userService.removeById(deleteRequest.getId()); + return ResultUtils.success(b); + } + + /** + * 更新用户 + * + * @param userUpdateRequest + * @param request + * @return + */ + @PostMapping("/update") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public BaseResponse updateUser(@RequestBody UserUpdateRequest userUpdateRequest, + HttpServletRequest request) { + if (userUpdateRequest == null || userUpdateRequest.getId() == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User user = new User(); + BeanUtils.copyProperties(userUpdateRequest, user); + boolean result = userService.updateById(user); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + return ResultUtils.success(true); + } + + /** + * 根据 id 获取用户(仅管理员) + * + * @param id + * @param request + * @return + */ + @GetMapping("/get") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public BaseResponse getUserById(long id, HttpServletRequest request) { + if (id <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User user = userService.getById(id); + ThrowUtils.throwIf(user == null, ErrorCode.NOT_FOUND_ERROR); + return ResultUtils.success(user); + } + + /** + * 根据 id 获取包装类 + * + * @param id + * @param request + * @return + */ + @GetMapping("/get/vo") + public BaseResponse getUserVOById(long id, HttpServletRequest request) { + BaseResponse response = getUserById(id, request); + User user = response.getData(); + return ResultUtils.success(userService.getUserVO(user)); + } + + /** + * 分页获取用户列表(仅管理员) + * + * @param userQueryRequest + * @param request + * @return + */ + @PostMapping("/list/page") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public BaseResponse> listUserByPage(@RequestBody UserQueryRequest userQueryRequest, + HttpServletRequest request) { + long current = userQueryRequest.getCurrent(); + long size = userQueryRequest.getPageSize(); + Page userPage = userService.page(new Page<>(current, size), + userService.getQueryWrapper(userQueryRequest)); + return ResultUtils.success(userPage); + } + + /** + * 分页获取用户封装列表 + * + * @param userQueryRequest + * @param request + * @return + */ + @PostMapping("/list/page/vo") + public BaseResponse> listUserVOByPage(@RequestBody UserQueryRequest userQueryRequest, + HttpServletRequest request) { + if (userQueryRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + long current = userQueryRequest.getCurrent(); + long size = userQueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + Page userPage = userService.page(new Page<>(current, size), + userService.getQueryWrapper(userQueryRequest)); + Page userVOPage = new Page<>(current, size, userPage.getTotal()); + List userVO = userService.getUserVO(userPage.getRecords()); + userVOPage.setRecords(userVO); + return ResultUtils.success(userVOPage); + } + + // endregion + + /** + * 更新个人信息 + * + * @param userUpdateMyRequest + * @param request + * @return + */ + @PostMapping("/update/my") + public BaseResponse updateMyUser(@RequestBody UserUpdateMyRequest userUpdateMyRequest, + HttpServletRequest request) { + if (userUpdateMyRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User loginUser = userService.getLoginUser(request); + User user = new User(); + BeanUtils.copyProperties(userUpdateMyRequest, user); + user.setId(loginUser.getId()); + boolean result = userService.updateById(user); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + return ResultUtils.success(true); + } +} diff --git a/src/main/java/com/yupi/springbootinit/esdao/PostEsDao.java b/src/main/java/com/yupi/springbootinit/esdao/PostEsDao.java new file mode 100644 index 0000000..8565f53 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/esdao/PostEsDao.java @@ -0,0 +1,16 @@ +package com.yupi.springbootinit.esdao; + +import com.yupi.springbootinit.model.dto.post.PostEsDTO; +import java.util.List; +import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; + +/** + * 帖子 ES 操作 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface PostEsDao extends ElasticsearchRepository { + + List findByUserId(Long userId); +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/exception/BusinessException.java b/src/main/java/com/yupi/springbootinit/exception/BusinessException.java new file mode 100644 index 0000000..6e7dd1c --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/exception/BusinessException.java @@ -0,0 +1,36 @@ +package com.yupi.springbootinit.exception; + +import com.yupi.springbootinit.common.ErrorCode; + +/** + * 自定义异常类 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public class BusinessException extends RuntimeException { + + /** + * 错误码 + */ + private final int code; + + public BusinessException(int code, String message) { + super(message); + this.code = code; + } + + public BusinessException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.code = errorCode.getCode(); + } + + public BusinessException(ErrorCode errorCode, String message) { + super(message); + this.code = errorCode.getCode(); + } + + public int getCode() { + return code; + } +} diff --git a/src/main/java/com/yupi/springbootinit/exception/GlobalExceptionHandler.java b/src/main/java/com/yupi/springbootinit/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..610ca49 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/exception/GlobalExceptionHandler.java @@ -0,0 +1,31 @@ +package com.yupi.springbootinit.exception; + +import com.yupi.springbootinit.common.BaseResponse; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.common.ResultUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * 全局异常处理器 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@RestControllerAdvice +@Slf4j +public class GlobalExceptionHandler { + + @ExceptionHandler(BusinessException.class) + public BaseResponse businessExceptionHandler(BusinessException e) { + log.error("BusinessException", e); + return ResultUtils.error(e.getCode(), e.getMessage()); + } + + @ExceptionHandler(RuntimeException.class) + public BaseResponse runtimeExceptionHandler(RuntimeException e) { + log.error("RuntimeException", e); + return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "系统错误"); + } +} diff --git a/src/main/java/com/yupi/springbootinit/exception/ThrowUtils.java b/src/main/java/com/yupi/springbootinit/exception/ThrowUtils.java new file mode 100644 index 0000000..e25468a --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/exception/ThrowUtils.java @@ -0,0 +1,45 @@ +package com.yupi.springbootinit.exception; + +import com.yupi.springbootinit.common.ErrorCode; + +/** + * 抛异常工具类 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public class ThrowUtils { + + /** + * 条件成立则抛异常 + * + * @param condition + * @param runtimeException + */ + public static void throwIf(boolean condition, RuntimeException runtimeException) { + if (condition) { + throw runtimeException; + } + } + + /** + * 条件成立则抛异常 + * + * @param condition + * @param errorCode + */ + public static void throwIf(boolean condition, ErrorCode errorCode) { + throwIf(condition, new BusinessException(errorCode)); + } + + /** + * 条件成立则抛异常 + * + * @param condition + * @param errorCode + * @param message + */ + public static void throwIf(boolean condition, ErrorCode errorCode, String message) { + throwIf(condition, new BusinessException(errorCode, message)); + } +} diff --git a/src/main/java/com/yupi/springbootinit/generate/CodeGenerator.java b/src/main/java/com/yupi/springbootinit/generate/CodeGenerator.java new file mode 100644 index 0000000..c379215 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/generate/CodeGenerator.java @@ -0,0 +1,128 @@ +package com.yupi.springbootinit.generate; + +import cn.hutool.core.io.FileUtil; +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.io.FileWriter; +import java.io.Writer; + +/** + * 代码生成器 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +public class CodeGenerator { + + /** + * 用法:修改生成参数和生成路径,注释掉不需要的生成逻辑,然后运行即可 + * + * @param args + * @throws TemplateException + * @throws IOException + */ + public static void main(String[] args) throws TemplateException, IOException { + // 指定生成参数 + String packageName = "com.yupi.springbootinit"; + String dataName = "用户评论"; + String dataKey = "userComment"; + String upperDataKey = "UserComment"; + + // 封装生成参数 + Map dataModel = new HashMap<>(); + dataModel.put("packageName", packageName); + dataModel.put("dataName", dataName); + dataModel.put("dataKey", dataKey); + dataModel.put("upperDataKey", upperDataKey); + + // 生成路径默认值 + String projectPath = System.getProperty("user.dir"); + // 参考路径,可以自己调整下面的 outputPath + String inputPath = projectPath + File.separator + "src/main/resources/templates/模板名称.java.ftl"; + String outputPath = String.format("%s/generator/包名/%s类后缀.java", projectPath, upperDataKey); + + // 1、生成 Controller + // 指定生成路径 + inputPath = projectPath + File.separator + "src/main/resources/templates/TemplateController.java.ftl"; + outputPath = String.format("%s/generator/controller/%sController.java", projectPath, upperDataKey); + // 生成 + doGenerate(inputPath, outputPath, dataModel); + System.out.println("生成 Controller 成功,文件路径:" + outputPath); + + // 2、生成 Service 接口和实现类 + // 生成 Service 接口 + inputPath = projectPath + File.separator + "src/main/resources/templates/TemplateService.java.ftl"; + outputPath = String.format("%s/generator/service/%sService.java", projectPath, upperDataKey); + doGenerate(inputPath, outputPath, dataModel); + System.out.println("生成 Service 接口成功,文件路径:" + outputPath); + // 生成 Service 实现类 + inputPath = projectPath + File.separator + "src/main/resources/templates/TemplateServiceImpl.java.ftl"; + outputPath = String.format("%s/generator/service/impl/%sServiceImpl.java", projectPath, upperDataKey); + doGenerate(inputPath, outputPath, dataModel); + System.out.println("生成 Service 实现类成功,文件路径:" + outputPath); + + // 3、生成数据模型封装类(包括 DTO 和 VO) + // 生成 DTO + inputPath = projectPath + File.separator + "src/main/resources/templates/model/TemplateAddRequest.java.ftl"; + outputPath = String.format("%s/generator/model/dto/%sAddRequest.java", projectPath, upperDataKey); + doGenerate(inputPath, outputPath, dataModel); + inputPath = projectPath + File.separator + "src/main/resources/templates/model/TemplateQueryRequest.java.ftl"; + outputPath = String.format("%s/generator/model/dto/%sQueryRequest.java", projectPath, upperDataKey); + doGenerate(inputPath, outputPath, dataModel); + inputPath = projectPath + File.separator + "src/main/resources/templates/model/TemplateEditRequest.java.ftl"; + outputPath = String.format("%s/generator/model/dto/%sEditRequest.java", projectPath, upperDataKey); + doGenerate(inputPath, outputPath, dataModel); + inputPath = projectPath + File.separator + "src/main/resources/templates/model/TemplateUpdateRequest.java.ftl"; + outputPath = String.format("%s/generator/model/dto/%sUpdateRequest.java", projectPath, upperDataKey); + doGenerate(inputPath, outputPath, dataModel); + System.out.println("生成 DTO 成功,文件路径:" + outputPath); + // 生成 VO + inputPath = projectPath + File.separator + "src/main/resources/templates/model/TemplateVO.java.ftl"; + outputPath = String.format("%s/generator/model/vo/%sVO.java", projectPath, upperDataKey); + doGenerate(inputPath, outputPath, dataModel); + System.out.println("生成 VO 成功,文件路径:" + outputPath); + } + + /** + * 生成文件 + * + * @param inputPath 模板文件输入路径 + * @param outputPath 输出路径 + * @param model 数据模型 + * @throws IOException + * @throws TemplateException + */ + public static void doGenerate(String inputPath, String outputPath, Object model) throws IOException, TemplateException { + // new 出 Configuration 对象,参数为 FreeMarker 版本号 + Configuration configuration = new Configuration(Configuration.VERSION_2_3_31); + + // 指定模板文件所在的路径 + File templateDir = new File(inputPath).getParentFile(); + configuration.setDirectoryForTemplateLoading(templateDir); + + // 设置模板文件使用的字符集 + configuration.setDefaultEncoding("utf-8"); + + // 创建模板对象,加载指定模板 + String templateName = new File(inputPath).getName(); + Template template = configuration.getTemplate(templateName); + + // 文件不存在则创建文件和父目录 + if (!FileUtil.exist(outputPath)) { + FileUtil.touch(outputPath); + } + + // 生成 + Writer out = new FileWriter(outputPath); + template.process(model, out); + + // 生成文件后别忘了关闭哦 + out.close(); + } +} diff --git a/src/main/java/com/yupi/springbootinit/job/cycle/IncSyncPostToEs.java b/src/main/java/com/yupi/springbootinit/job/cycle/IncSyncPostToEs.java new file mode 100644 index 0000000..9a24326 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/job/cycle/IncSyncPostToEs.java @@ -0,0 +1,57 @@ +package com.yupi.springbootinit.job.cycle; + +import com.yupi.springbootinit.esdao.PostEsDao; +import com.yupi.springbootinit.mapper.PostMapper; +import com.yupi.springbootinit.model.dto.post.PostEsDTO; +import com.yupi.springbootinit.model.entity.Post; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import cn.hutool.core.collection.CollUtil; +import org.springframework.scheduling.annotation.Scheduled; + +/** + * 增量同步帖子到 es + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +// todo 取消注释开启任务 +//@Component +@Slf4j +public class IncSyncPostToEs { + + @Resource + private PostMapper postMapper; + + @Resource + private PostEsDao postEsDao; + + /** + * 每分钟执行一次 + */ + @Scheduled(fixedRate = 60 * 1000) + public void run() { + // 查询近 5 分钟内的数据 + Date fiveMinutesAgoDate = new Date(new Date().getTime() - 5 * 60 * 1000L); + List postList = postMapper.listPostWithDelete(fiveMinutesAgoDate); + if (CollUtil.isEmpty(postList)) { + log.info("no inc post"); + return; + } + List postEsDTOList = postList.stream() + .map(PostEsDTO::objToDto) + .collect(Collectors.toList()); + final int pageSize = 500; + int total = postEsDTOList.size(); + log.info("IncSyncPostToEs start, total {}", total); + for (int i = 0; i < total; i += pageSize) { + int end = Math.min(i + pageSize, total); + log.info("sync from {} to {}", i, end); + postEsDao.saveAll(postEsDTOList.subList(i, end)); + } + log.info("IncSyncPostToEs end, total {}", total); + } +} diff --git a/src/main/java/com/yupi/springbootinit/job/once/FullSyncPostToEs.java b/src/main/java/com/yupi/springbootinit/job/once/FullSyncPostToEs.java new file mode 100644 index 0000000..e7b8793 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/job/once/FullSyncPostToEs.java @@ -0,0 +1,48 @@ +package com.yupi.springbootinit.job.once; + +import com.yupi.springbootinit.esdao.PostEsDao; +import com.yupi.springbootinit.model.dto.post.PostEsDTO; +import com.yupi.springbootinit.model.entity.Post; +import com.yupi.springbootinit.service.PostService; +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import cn.hutool.core.collection.CollUtil; +import org.springframework.boot.CommandLineRunner; + +/** + * 全量同步帖子到 es + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +// todo 取消注释开启任务 +//@Component +@Slf4j +public class FullSyncPostToEs implements CommandLineRunner { + + @Resource + private PostService postService; + + @Resource + private PostEsDao postEsDao; + + @Override + public void run(String... args) { + List postList = postService.list(); + if (CollUtil.isEmpty(postList)) { + return; + } + List postEsDTOList = postList.stream().map(PostEsDTO::objToDto).collect(Collectors.toList()); + final int pageSize = 500; + int total = postEsDTOList.size(); + log.info("FullSyncPostToEs start, total {}", total); + for (int i = 0; i < total; i += pageSize) { + int end = Math.min(i + pageSize, total); + log.info("sync from {} to {}", i, end); + postEsDao.saveAll(postEsDTOList.subList(i, end)); + } + log.info("FullSyncPostToEs end, total {}", total); + } +} diff --git a/src/main/java/com/yupi/springbootinit/manager/AiManager.java b/src/main/java/com/yupi/springbootinit/manager/AiManager.java new file mode 100644 index 0000000..d8dcd14 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/manager/AiManager.java @@ -0,0 +1,85 @@ +package com.yupi.springbootinit.manager; + +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.exception.BusinessException; +import io.github.briqt.spark4j.SparkClient; +import io.github.briqt.spark4j.constant.SparkApiVersion; +import io.github.briqt.spark4j.model.SparkMessage; +import io.github.briqt.spark4j.model.request.SparkRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +@Service +@Slf4j +public class AiManager { + + @Resource + private SparkClient sparkClient; + + + /** + * 向 AI 发送请求 + * + * @param isNeedTemplate 是否使用模板,进行 AI 生成; true 使用 、false 不使用 ,false 的情况是只想用 AI 不只是生成前端代码 + * @param content 内容 + * 分析需求: + * 分析网站用户的增长情况 + * 原始数据: + * 日期,用户数 + * 1号,10 + * 2号,20 + * 3号,30 + * @return AI 返回的内容 + * '【【【【' + *

+ * '【【【【' + */ + public String sendMsgToXingHuo(boolean isNeedTemplate, String content) { + List messages = new ArrayList<>(); + if (isNeedTemplate) { + // AI 生成问题的预设条件 + String predefinedInformation = "请严格按照下面的输出格式生成结果,且不得添加任何多余内容(例如无关文字、注释、代码块标记或反引号):\n" + + "\n" + + "'【【【【'" + + "{ 生成 Echarts V5 的 option 配置对象 JSON 代码,要求为合法 JSON 格式且不含任何额外内容(如注释或多余字符) } '【【【【' 结论: {\n" + + "提供对数据的详细分析结论,内容应尽可能准确、详细,不允许添加其他无关文字或注释 }\n" + + "\n" + + "示例: 输入: 分析需求: 分析网站用户增长情况,请使用柱状图展示 原始数据: 日期,用户数 1号,10 2号,20 3号,30\n" + + "\n" + + "期望输出: '【【【【' { \"title\": { \"text\": \"分析网站用户增长情况\" }, \"xAxis\": { \"type\": \"category\", \"data\": [\"1号\", \"2号\", \"3号\"] }, \"yAxis\": { \"type\": \"value\" }, \"series\": [ { \"name\": \"用户数\", \"type\": \"bar\", \"data\": [10, 20, 30] } ] } '【【【【' 从数据看,网站用户数由1号的10人增长到2号的20人,再到3号的30人,呈现出明显的上升趋势。这表明在这段时间内网站用户吸引力增强,可能与推广活动、内容更新或其他外部因素有关。"; + messages.add(SparkMessage.systemContent(predefinedInformation + "\n" + "----------------------------------")); + } + messages.add(SparkMessage.userContent(content)); + // 构造请求 + SparkRequest sparkRequest = SparkRequest.builder() + // 消息列表 + .messages(messages) + // 模型回答的tokens的最大长度,非必传,取值为[1,4096],默认为2048 + .maxTokens(2048) + // 核采样阈值。用于决定结果随机性,取值越高随机性越强即相同的问题得到的不同答案的可能性越高 非必传,取值为[0,1],默认为0.5 + .temperature(0.6) + // 指定请求版本 + .apiVersion(SparkApiVersion.V4_0) + .build(); + // 同步调用 + String responseContent = sparkClient.chatSync(sparkRequest).getContent().trim(); + if (!isNeedTemplate) { + return responseContent; + } + log.info("星火 AI 返回的结果 {}", responseContent); + AtomicInteger atomicInteger = new AtomicInteger(1); + while (responseContent.split("'【【【【'").length < 3) { + responseContent = sparkClient.chatSync(sparkRequest).getContent().trim(); + if (atomicInteger.incrementAndGet() >= 4) { + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "星火 AI 生成失败"); + } + } + return responseContent; + } +} + diff --git a/src/main/java/com/yupi/springbootinit/manager/CosManager.java b/src/main/java/com/yupi/springbootinit/manager/CosManager.java new file mode 100644 index 0000000..a556cc2 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/manager/CosManager.java @@ -0,0 +1,51 @@ +package com.yupi.springbootinit.manager; + +import com.qcloud.cos.COSClient; +import com.qcloud.cos.model.PutObjectRequest; +import com.qcloud.cos.model.PutObjectResult; +import com.yupi.springbootinit.config.CosClientConfig; +import java.io.File; +import javax.annotation.Resource; +import org.springframework.stereotype.Component; + +/** + * Cos 对象存储操作 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Component +public class CosManager { + + @Resource + private CosClientConfig cosClientConfig; + + @Resource + private COSClient cosClient; + + /** + * 上传对象 + * + * @param key 唯一键 + * @param localFilePath 本地文件路径 + * @return + */ + public PutObjectResult putObject(String key, String localFilePath) { + PutObjectRequest putObjectRequest = new PutObjectRequest(cosClientConfig.getBucket(), key, + new File(localFilePath)); + return cosClient.putObject(putObjectRequest); + } + + /** + * 上传对象 + * + * @param key 唯一键 + * @param file 文件 + * @return + */ + public PutObjectResult putObject(String key, File file) { + PutObjectRequest putObjectRequest = new PutObjectRequest(cosClientConfig.getBucket(), key, + file); + return cosClient.putObject(putObjectRequest); + } +} diff --git a/src/main/java/com/yupi/springbootinit/manager/RedisLimiterManager.java b/src/main/java/com/yupi/springbootinit/manager/RedisLimiterManager.java new file mode 100644 index 0000000..9c39070 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/manager/RedisLimiterManager.java @@ -0,0 +1,37 @@ +package com.yupi.springbootinit.manager; + +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.exception.BusinessException; +import org.redisson.api.RRateLimiter; +import org.redisson.api.RateIntervalUnit; +import org.redisson.api.RateType; +import org.redisson.api.RedissonClient; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +/** + * 专门提供 RedisLimiter 限流基础服务的(提供了通用的能力) + */ +@Service +public class RedisLimiterManager { + + @Resource + private RedissonClient redissonClient; + + /** + * 限流操作 + * + * @param key 区分不同的限流器,比如不同的用户 id 应该分别统计 + */ + public void doRateLimit(String key) { + // 创建一个名称为user_limiter的限流器,每秒最多访问 2 次 + RRateLimiter rateLimiter = redissonClient.getRateLimiter(key); + rateLimiter.trySetRate(RateType.OVERALL, 2, 1, RateIntervalUnit.SECONDS); + // 每当一个操作来了后,请求一个令牌 + boolean canOp = rateLimiter.tryAcquire(1); + if (!canOp) { + throw new BusinessException(ErrorCode.TOO_MANY_REQUEST); + } + } +} diff --git a/src/main/java/com/yupi/springbootinit/manager/ThreadPoolExecutorConfig.java b/src/main/java/com/yupi/springbootinit/manager/ThreadPoolExecutorConfig.java new file mode 100644 index 0000000..662e329 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/manager/ThreadPoolExecutorConfig.java @@ -0,0 +1,45 @@ +package com.yupi.springbootinit.manager; + +import org.apache.poi.ss.formula.functions.T; +import org.jetbrains.annotations.NotNull; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +@Configuration +public class ThreadPoolExecutorConfig { + + @Bean + public ThreadPoolExecutor threadPoolExecutor() { + ThreadFactory threadFactory = new ThreadFactory() { + private int count = 1; + + @Override + public Thread newThread(@NotNull Runnable r) { + Thread thread = new Thread(r); + thread.setName("线程" + count); + count++; + return thread; + } + }; + +// corePoolSize(核心线程数=>正式员工数):正常情况下,我们的系统应该同时工作的线程数(随时就绪的状态) +// maximumPoolSize(最大线程数=>哪怕任务再多,你也最多招这些人):极限情况下,我们的线程池最多有多少个线程? +// keepAliveTime(空闲线程存活时间):非核心线程在没有任务的情况下,过多久要删除(理解为开除临时工),从而释放无用的线程资源。 +// TimeUnit unit(空闲线程存活时间的单位):分钟、秒 +// workQueue(工作队列):用于存放给线程执行的任务,存在一个队列的长度(一定要设置,不要说队列长度无限,因为也会占用资源) +// threadFactory(线程工厂):控制每个线程的生成、线程的属性(比如线程名) + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( + 2, + 4, + 100, + TimeUnit.SECONDS, + new ArrayBlockingQueue<>(4), + threadFactory); + return threadPoolExecutor; + } +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/mapper/ChartMapper.java b/src/main/java/com/yupi/springbootinit/mapper/ChartMapper.java new file mode 100644 index 0000000..89b467e --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/mapper/ChartMapper.java @@ -0,0 +1,18 @@ +package com.yupi.springbootinit.mapper; + +import com.yupi.springbootinit.model.entity.Chart; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** +* @author HeSun +* @description 针对表【chart(图表信息表)】的数据库操作Mapper +* @createDate 2025-02-20 20:52:40 +* @Entity com.yupi.springbootinit.model.entity.Chart +*/ +public interface ChartMapper extends BaseMapper { + +} + + + + diff --git a/src/main/java/com/yupi/springbootinit/mapper/PostFavourMapper.java b/src/main/java/com/yupi/springbootinit/mapper/PostFavourMapper.java new file mode 100644 index 0000000..c57cf5e --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/mapper/PostFavourMapper.java @@ -0,0 +1,35 @@ +package com.yupi.springbootinit.mapper; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yupi.springbootinit.model.entity.Post; +import com.yupi.springbootinit.model.entity.PostFavour; +import org.apache.ibatis.annotations.Param; + +/** + * 帖子收藏数据库操作 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface PostFavourMapper extends BaseMapper { + + /** + * 分页查询收藏帖子列表 + * + * @param page + * @param queryWrapper + * @param favourUserId + * @return + */ + Page listFavourPostByPage(IPage page, @Param(Constants.WRAPPER) Wrapper queryWrapper, + long favourUserId); + +} + + + + diff --git a/src/main/java/com/yupi/springbootinit/mapper/PostMapper.java b/src/main/java/com/yupi/springbootinit/mapper/PostMapper.java new file mode 100644 index 0000000..2975b84 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/mapper/PostMapper.java @@ -0,0 +1,25 @@ +package com.yupi.springbootinit.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yupi.springbootinit.model.entity.Post; +import java.util.Date; +import java.util.List; + +/** + * 帖子数据库操作 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface PostMapper extends BaseMapper { + + /** + * 查询帖子列表(包括已被删除的数据) + */ + List listPostWithDelete(Date minUpdateTime); + +} + + + + diff --git a/src/main/java/com/yupi/springbootinit/mapper/PostThumbMapper.java b/src/main/java/com/yupi/springbootinit/mapper/PostThumbMapper.java new file mode 100644 index 0000000..fe39c82 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/mapper/PostThumbMapper.java @@ -0,0 +1,18 @@ +package com.yupi.springbootinit.mapper; + +import com.yupi.springbootinit.model.entity.PostThumb; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 帖子点赞数据库操作 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface PostThumbMapper extends BaseMapper { + +} + + + + diff --git a/src/main/java/com/yupi/springbootinit/mapper/UserMapper.java b/src/main/java/com/yupi/springbootinit/mapper/UserMapper.java new file mode 100644 index 0000000..247bf3b --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/mapper/UserMapper.java @@ -0,0 +1,18 @@ +package com.yupi.springbootinit.mapper; + +import com.yupi.springbootinit.model.entity.User; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** +* @author HeSun +* @description 针对表【user(用户)】的数据库操作Mapper +* @createDate 2025-02-20 20:52:40 +* @Entity com.yupi.springbootinit.model.entity.User +*/ +public interface UserMapper extends BaseMapper { + +} + + + + diff --git a/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartAddRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartAddRequest.java new file mode 100644 index 0000000..420bbfb --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartAddRequest.java @@ -0,0 +1,39 @@ +package com.yupi.springbootinit.model.dto.chart; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * 创建请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class ChartAddRequest implements Serializable { + + /** + * 名称 + */ + private String name; + + /** + * 分析目标 + */ + private String goal; + + /** + * 图表数据 + */ + private String chartData; + + /** + * 图表类型 + */ + private String chartType; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartEditRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartEditRequest.java new file mode 100644 index 0000000..2f8568c --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartEditRequest.java @@ -0,0 +1,47 @@ +package com.yupi.springbootinit.model.dto.chart; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * 编辑请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class ChartEditRequest implements Serializable { + + /** + * 名称 + */ + private String name; + + /** + * id + */ + private Long id; + + /** + * 分析目标 + */ + private String goal; + + /** + * 图表数据 + */ + private String chartData; + + /** + * 图表类型 + */ + private String chartType; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartQueryRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartQueryRequest.java new file mode 100644 index 0000000..0c36cc6 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartQueryRequest.java @@ -0,0 +1,42 @@ +package com.yupi.springbootinit.model.dto.chart; + +import com.yupi.springbootinit.common.PageRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + +/** + * 查询请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class ChartQueryRequest extends PageRequest implements Serializable { + + private Long id; + + /** + * 名称 + */ + private String name; + + /** + * 分析目标 + */ + private String goal; + + /** + * 图表类型 + */ + private String chartType; + + /** + * 用户id + */ + private Long userId; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartUpdateRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartUpdateRequest.java new file mode 100644 index 0000000..bac3ac7 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/chart/ChartUpdateRequest.java @@ -0,0 +1,78 @@ +package com.yupi.springbootinit.model.dto.chart; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * 更新请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class ChartUpdateRequest implements Serializable { + /** + * id + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 名称 + */ + private String name; + + /** + * 分析目标 + */ + private String goal; + + /** + * 图表数据 + */ + private String chartData; + + /** + * 图表类型 + */ + private String chartType; + + /** + * 生成的图表数据 + */ + private String genChart; + + /** + * 生成的分析结论 + */ + private String genResult; + + /** + * 创建用户 id + */ + private Long userId; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 是否删除 + */ + @TableLogic + private Integer isDelete; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/chart/GenChartByAiRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/chart/GenChartByAiRequest.java new file mode 100644 index 0000000..b9b8cb2 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/chart/GenChartByAiRequest.java @@ -0,0 +1,32 @@ +package com.yupi.springbootinit.model.dto.chart; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 文件上传请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class GenChartByAiRequest implements Serializable { + + /** + * 名称 + */ + private String name; + + /** + * 分析目标 + */ + private String goal; + + /** + * 图标类型 + */ + private String chartType; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/file/UploadFileRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/file/UploadFileRequest.java new file mode 100644 index 0000000..09654ec --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/file/UploadFileRequest.java @@ -0,0 +1,21 @@ +package com.yupi.springbootinit.model.dto.file; + +import java.io.Serializable; +import lombok.Data; + +/** + * 文件上传请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class UploadFileRequest implements Serializable { + + /** + * 业务 + */ + private String biz; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/post/PostAddRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/post/PostAddRequest.java new file mode 100644 index 0000000..45ac49c --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/post/PostAddRequest.java @@ -0,0 +1,32 @@ +package com.yupi.springbootinit.model.dto.post; + +import java.io.Serializable; +import java.util.List; +import lombok.Data; + +/** + * 创建请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class PostAddRequest implements Serializable { + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 + */ + private List tags; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/post/PostEditRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/post/PostEditRequest.java new file mode 100644 index 0000000..40c3888 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/post/PostEditRequest.java @@ -0,0 +1,37 @@ +package com.yupi.springbootinit.model.dto.post; + +import java.io.Serializable; +import java.util.List; +import lombok.Data; + +/** + * 编辑请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class PostEditRequest implements Serializable { + + /** + * id + */ + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 + */ + private List tags; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/post/PostEsDTO.java b/src/main/java/com/yupi/springbootinit/model/dto/post/PostEsDTO.java new file mode 100644 index 0000000..aa7ae46 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/post/PostEsDTO.java @@ -0,0 +1,123 @@ +package com.yupi.springbootinit.model.dto.post; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.json.JSONUtil; +import com.yupi.springbootinit.model.entity.Post; +import lombok.Data; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * 帖子 ES 包装类 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + **/ +// todo 取消注释开启 ES(须先配置 ES) +//@Document(indexName = "post") +@Data +public class PostEsDTO implements Serializable { + + private static final String DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + + /** + * id + */ + @Id + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 + */ + private List tags; + + /** + * 点赞数 + */ + private Integer thumbNum; + + /** + * 收藏数 + */ + private Integer favourNum; + + /** + * 创建用户 id + */ + private Long userId; + + /** + * 创建时间 + */ + @Field(index = false, store = true, type = FieldType.Date, format = {}, pattern = DATE_TIME_PATTERN) + private Date createTime; + + /** + * 更新时间 + */ + @Field(index = false, store = true, type = FieldType.Date, format = {}, pattern = DATE_TIME_PATTERN) + private Date updateTime; + + /** + * 是否删除 + */ + private Integer isDelete; + + private static final long serialVersionUID = 1L; + + /** + * 对象转包装类 + * + * @param post + * @return + */ + public static PostEsDTO objToDto(Post post) { + if (post == null) { + return null; + } + PostEsDTO postEsDTO = new PostEsDTO(); + BeanUtils.copyProperties(post, postEsDTO); + String tagsStr = post.getTags(); + if (StringUtils.isNotBlank(tagsStr)) { + postEsDTO.setTags(JSONUtil.toList(tagsStr, String.class)); + } + return postEsDTO; + } + + /** + * 包装类转对象 + * + * @param postEsDTO + * @return + */ + public static Post dtoToObj(PostEsDTO postEsDTO) { + if (postEsDTO == null) { + return null; + } + Post post = new Post(); + BeanUtils.copyProperties(postEsDTO, post); + List tagList = postEsDTO.getTags(); + if (CollUtil.isNotEmpty(tagList)) { + post.setTags(JSONUtil.toJsonStr(tagList)); + } + return post; + } +} diff --git a/src/main/java/com/yupi/springbootinit/model/dto/post/PostQueryRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/post/PostQueryRequest.java new file mode 100644 index 0000000..c9f160a --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/post/PostQueryRequest.java @@ -0,0 +1,65 @@ +package com.yupi.springbootinit.model.dto.post; + +import com.yupi.springbootinit.common.PageRequest; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 查询请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class PostQueryRequest extends PageRequest implements Serializable { + + /** + * id + */ + private Long id; + + /** + * id + */ + private Long notId; + + /** + * 搜索词 + */ + private String searchText; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 + */ + private List tags; + + /** + * 至少有一个标签 + */ + private List orTags; + + /** + * 创建用户 id + */ + private Long userId; + + /** + * 收藏用户 id + */ + private Long favourUserId; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/post/PostUpdateRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/post/PostUpdateRequest.java new file mode 100644 index 0000000..c90bc70 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/post/PostUpdateRequest.java @@ -0,0 +1,37 @@ +package com.yupi.springbootinit.model.dto.post; + +import java.io.Serializable; +import java.util.List; +import lombok.Data; + +/** + * 更新请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class PostUpdateRequest implements Serializable { + + /** + * id + */ + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 + */ + private List tags; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/postfavour/PostFavourAddRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/postfavour/PostFavourAddRequest.java new file mode 100644 index 0000000..1b8cc27 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/postfavour/PostFavourAddRequest.java @@ -0,0 +1,21 @@ +package com.yupi.springbootinit.model.dto.postfavour; + +import java.io.Serializable; +import lombok.Data; + +/** + * 帖子收藏 / 取消收藏请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class PostFavourAddRequest implements Serializable { + + /** + * 帖子 id + */ + private Long postId; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/postfavour/PostFavourQueryRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/postfavour/PostFavourQueryRequest.java new file mode 100644 index 0000000..e8bf989 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/postfavour/PostFavourQueryRequest.java @@ -0,0 +1,30 @@ +package com.yupi.springbootinit.model.dto.postfavour; + +import com.yupi.springbootinit.common.PageRequest; +import com.yupi.springbootinit.model.dto.post.PostQueryRequest; +import java.io.Serializable; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 帖子收藏查询请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class PostFavourQueryRequest extends PageRequest implements Serializable { + + /** + * 帖子查询请求 + */ + private PostQueryRequest postQueryRequest; + + /** + * 用户 id + */ + private Long userId; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/postthumb/PostThumbAddRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/postthumb/PostThumbAddRequest.java new file mode 100644 index 0000000..e6209e7 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/postthumb/PostThumbAddRequest.java @@ -0,0 +1,21 @@ +package com.yupi.springbootinit.model.dto.postthumb; + +import java.io.Serializable; +import lombok.Data; + +/** + * 帖子点赞请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class PostThumbAddRequest implements Serializable { + + /** + * 帖子 id + */ + private Long postId; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/user/UserAddRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/user/UserAddRequest.java new file mode 100644 index 0000000..7c56337 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/user/UserAddRequest.java @@ -0,0 +1,36 @@ +package com.yupi.springbootinit.model.dto.user; + +import java.io.Serializable; +import lombok.Data; + +/** + * 用户创建请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class UserAddRequest implements Serializable { + + /** + * 用户昵称 + */ + private String userName; + + /** + * 账号 + */ + private String userAccount; + + /** + * 用户头像 + */ + private String userAvatar; + + /** + * 用户角色: user, admin + */ + private String userRole; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/user/UserLoginRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/user/UserLoginRequest.java new file mode 100644 index 0000000..85c241e --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/user/UserLoginRequest.java @@ -0,0 +1,20 @@ +package com.yupi.springbootinit.model.dto.user; + +import java.io.Serializable; +import lombok.Data; + +/** + * 用户登录请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class UserLoginRequest implements Serializable { + + private static final long serialVersionUID = 3191241716373120793L; + + private String userAccount; + + private String userPassword; +} diff --git a/src/main/java/com/yupi/springbootinit/model/dto/user/UserQueryRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/user/UserQueryRequest.java new file mode 100644 index 0000000..7b0e3d4 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/user/UserQueryRequest.java @@ -0,0 +1,48 @@ +package com.yupi.springbootinit.model.dto.user; + +import com.yupi.springbootinit.common.PageRequest; +import java.io.Serializable; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户查询请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class UserQueryRequest extends PageRequest implements Serializable { + /** + * id + */ + private Long id; + + /** + * 开放平台id + */ + private String unionId; + + /** + * 公众号openId + */ + private String mpOpenId; + + /** + * 用户昵称 + */ + private String userName; + + /** + * 简介 + */ + private String userProfile; + + /** + * 用户角色:user/admin/ban + */ + private String userRole; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/user/UserRegisterRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/user/UserRegisterRequest.java new file mode 100644 index 0000000..ded70ed --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/user/UserRegisterRequest.java @@ -0,0 +1,22 @@ +package com.yupi.springbootinit.model.dto.user; + +import java.io.Serializable; +import lombok.Data; + +/** + * 用户注册请求体 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class UserRegisterRequest implements Serializable { + + private static final long serialVersionUID = 3191241716373120793L; + + private String userAccount; + + private String userPassword; + + private String checkPassword; +} diff --git a/src/main/java/com/yupi/springbootinit/model/dto/user/UserUpdateMyRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/user/UserUpdateMyRequest.java new file mode 100644 index 0000000..f07f55a --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/user/UserUpdateMyRequest.java @@ -0,0 +1,31 @@ +package com.yupi.springbootinit.model.dto.user; + +import java.io.Serializable; +import lombok.Data; + +/** + * 用户更新个人信息请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class UserUpdateMyRequest implements Serializable { + + /** + * 用户昵称 + */ + private String userName; + + /** + * 用户头像 + */ + private String userAvatar; + + /** + * 简介 + */ + private String userProfile; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/user/UserUpdateRequest.java b/src/main/java/com/yupi/springbootinit/model/dto/user/UserUpdateRequest.java new file mode 100644 index 0000000..6536de5 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/user/UserUpdateRequest.java @@ -0,0 +1,40 @@ +package com.yupi.springbootinit.model.dto.user; + +import java.io.Serializable; +import lombok.Data; + +/** + * 用户更新请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class UserUpdateRequest implements Serializable { + /** + * id + */ + private Long id; + + /** + * 用户昵称 + */ + private String userName; + + /** + * 用户头像 + */ + private String userAvatar; + + /** + * 简介 + */ + private String userProfile; + + /** + * 用户角色:user/admin/ban + */ + private String userRole; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/entity/Chart.java b/src/main/java/com/yupi/springbootinit/model/entity/Chart.java new file mode 100644 index 0000000..02da097 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/entity/Chart.java @@ -0,0 +1,81 @@ +package com.yupi.springbootinit.model.entity; + +import com.baomidou.mybatisplus.annotation.*; + +import java.util.Date; +import lombok.Data; + +/** + * 图表信息表 + * @TableName chart + */ +@TableName(value ="chart") +@Data +public class Chart { + /** + * id + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 名称 + */ + private String name; + + /** + * 分析目标 + */ + private String goal; + + /** + * 图表数据 + */ + private String chartData; + + /** + * 图表类型 + */ + private String chartType; + + /** + * 生成的图表数据 + */ + private String genChart; + + /** + * 生成的分析结论 + */ + private String genResult; + + /** + * 任务状态 + */ + private String status; + + /** + * 执行信息 + */ + private String execMessage; + + /** + * 创建用户 id + */ + private Long userId; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 是否删除 + */ + @TableLogic + private Integer isDelete; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/entity/Post.java b/src/main/java/com/yupi/springbootinit/model/entity/Post.java new file mode 100644 index 0000000..3c0e301 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/entity/Post.java @@ -0,0 +1,76 @@ +package com.yupi.springbootinit.model.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.util.Date; +import lombok.Data; + +/** + * 帖子 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@TableName(value = "post") +@Data +public class Post implements Serializable { + + /** + * id + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 json + */ + private String tags; + + /** + * 点赞数 + */ + private Integer thumbNum; + + /** + * 收藏数 + */ + private Integer favourNum; + + /** + * 创建用户 id + */ + private Long userId; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 是否删除 + */ + @TableLogic + private Integer isDelete; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/entity/PostFavour.java b/src/main/java/com/yupi/springbootinit/model/entity/PostFavour.java new file mode 100644 index 0000000..7626ec5 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/entity/PostFavour.java @@ -0,0 +1,49 @@ +package com.yupi.springbootinit.model.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.util.Date; +import lombok.Data; + +/** + * 帖子收藏 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + **/ +@TableName(value = "post_favour") +@Data +public class PostFavour implements Serializable { + + /** + * id + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 帖子 id + */ + private Long postId; + + /** + * 创建用户 id + */ + private Long userId; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/entity/PostThumb.java b/src/main/java/com/yupi/springbootinit/model/entity/PostThumb.java new file mode 100644 index 0000000..c22f119 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/entity/PostThumb.java @@ -0,0 +1,49 @@ +package com.yupi.springbootinit.model.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.util.Date; +import lombok.Data; + +/** + * 帖子点赞 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@TableName(value = "post_thumb") +@Data +public class PostThumb implements Serializable { + + /** + * id + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 帖子 id + */ + private Long postId; + + /** + * 创建用户 id + */ + private Long userId; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/entity/User.java b/src/main/java/com/yupi/springbootinit/model/entity/User.java new file mode 100644 index 0000000..95ea135 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/entity/User.java @@ -0,0 +1,61 @@ +package com.yupi.springbootinit.model.entity; + +import com.baomidou.mybatisplus.annotation.*; + +import java.util.Date; +import lombok.Data; + +/** + * 用户 + * @TableName user + */ +@TableName(value ="user") +@Data +public class User { + /** + * id + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 账号 + */ + private String userAccount; + + /** + * 密码 + */ + private String userPassword; + + /** + * 用户昵称 + */ + private String userName; + + /** + * 用户头像 + */ + private String userAvatar; + + /** + * 用户角色:user/admin + */ + private String userRole; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 是否删除 + */ + @TableLogic + private Integer isDelete; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/enums/FileUploadBizEnum.java b/src/main/java/com/yupi/springbootinit/model/enums/FileUploadBizEnum.java new file mode 100644 index 0000000..da246eb --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/enums/FileUploadBizEnum.java @@ -0,0 +1,61 @@ +package com.yupi.springbootinit.model.enums; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.lang3.ObjectUtils; + +/** + * 文件上传业务类型枚举 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public enum FileUploadBizEnum { + + USER_AVATAR("用户头像", "user_avatar"); + + private final String text; + + private final String value; + + FileUploadBizEnum(String text, String value) { + this.text = text; + this.value = value; + } + + /** + * 获取值列表 + * + * @return + */ + public static List getValues() { + return Arrays.stream(values()).map(item -> item.value).collect(Collectors.toList()); + } + + /** + * 根据 value 获取枚举 + * + * @param value + * @return + */ + public static FileUploadBizEnum getEnumByValue(String value) { + if (ObjectUtils.isEmpty(value)) { + return null; + } + for (FileUploadBizEnum anEnum : FileUploadBizEnum.values()) { + if (anEnum.value.equals(value)) { + return anEnum; + } + } + return null; + } + + public String getValue() { + return value; + } + + public String getText() { + return text; + } +} diff --git a/src/main/java/com/yupi/springbootinit/model/enums/UserRoleEnum.java b/src/main/java/com/yupi/springbootinit/model/enums/UserRoleEnum.java new file mode 100644 index 0000000..3aeb3cc --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/enums/UserRoleEnum.java @@ -0,0 +1,63 @@ +package com.yupi.springbootinit.model.enums; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.lang3.ObjectUtils; + +/** + * 用户角色枚举 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public enum UserRoleEnum { + + USER("用户", "user"), + ADMIN("管理员", "admin"), + BAN("被封号", "ban"); + + private final String text; + + private final String value; + + UserRoleEnum(String text, String value) { + this.text = text; + this.value = value; + } + + /** + * 获取值列表 + * + * @return + */ + public static List getValues() { + return Arrays.stream(values()).map(item -> item.value).collect(Collectors.toList()); + } + + /** + * 根据 value 获取枚举 + * + * @param value + * @return + */ + public static UserRoleEnum getEnumByValue(String value) { + if (ObjectUtils.isEmpty(value)) { + return null; + } + for (UserRoleEnum anEnum : UserRoleEnum.values()) { + if (anEnum.value.equals(value)) { + return anEnum; + } + } + return null; + } + + public String getValue() { + return value; + } + + public String getText() { + return text; + } +} diff --git a/src/main/java/com/yupi/springbootinit/model/vo/BiResponse.java b/src/main/java/com/yupi/springbootinit/model/vo/BiResponse.java new file mode 100644 index 0000000..4cdd211 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/vo/BiResponse.java @@ -0,0 +1,17 @@ +package com.yupi.springbootinit.model.vo; + +import lombok.Data; + +/** + * Bi的返回结果 + */ + +@Data +public class BiResponse { + + private String genChart; + + private String genResult; + + private Long chartId; +} diff --git a/src/main/java/com/yupi/springbootinit/model/vo/LoginUserVO.java b/src/main/java/com/yupi/springbootinit/model/vo/LoginUserVO.java new file mode 100644 index 0000000..47ebf8f --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/vo/LoginUserVO.java @@ -0,0 +1,52 @@ +package com.yupi.springbootinit.model.vo; + +import java.io.Serializable; +import java.util.Date; +import lombok.Data; + +/** + * 已登录用户视图(脱敏) + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + **/ +@Data +public class LoginUserVO implements Serializable { + + /** + * 用户 id + */ + private Long id; + + /** + * 用户昵称 + */ + private String userName; + + /** + * 用户头像 + */ + private String userAvatar; + + /** + * 用户简介 + */ + private String userProfile; + + /** + * 用户角色:user/admin/ban + */ + private String userRole; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/vo/PostVO.java b/src/main/java/com/yupi/springbootinit/model/vo/PostVO.java new file mode 100644 index 0000000..92aa7fd --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/vo/PostVO.java @@ -0,0 +1,112 @@ +package com.yupi.springbootinit.model.vo; + +import cn.hutool.json.JSONUtil; +import com.yupi.springbootinit.model.entity.Post; +import java.io.Serializable; +import java.util.Date; +import java.util.List; +import lombok.Data; +import org.springframework.beans.BeanUtils; + +/** + * 帖子视图 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class PostVO implements Serializable { + + /** + * id + */ + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 点赞数 + */ + private Integer thumbNum; + + /** + * 收藏数 + */ + private Integer favourNum; + + /** + * 创建用户 id + */ + private Long userId; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 标签列表 + */ + private List tagList; + + /** + * 创建人信息 + */ + private UserVO user; + + /** + * 是否已点赞 + */ + private Boolean hasThumb; + + /** + * 是否已收藏 + */ + private Boolean hasFavour; + + /** + * 包装类转对象 + * + * @param postVO + * @return + */ + public static Post voToObj(PostVO postVO) { + if (postVO == null) { + return null; + } + Post post = new Post(); + BeanUtils.copyProperties(postVO, post); + List tagList = postVO.getTagList(); + post.setTags(JSONUtil.toJsonStr(tagList)); + return post; + } + + /** + * 对象转包装类 + * + * @param post + * @return + */ + public static PostVO objToVo(Post post) { + if (post == null) { + return null; + } + PostVO postVO = new PostVO(); + BeanUtils.copyProperties(post, postVO); + postVO.setTagList(JSONUtil.toList(post.getTags(), String.class)); + return postVO; + } +} diff --git a/src/main/java/com/yupi/springbootinit/model/vo/UserVO.java b/src/main/java/com/yupi/springbootinit/model/vo/UserVO.java new file mode 100644 index 0000000..536b1f4 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/vo/UserVO.java @@ -0,0 +1,47 @@ +package com.yupi.springbootinit.model.vo; + +import java.io.Serializable; +import java.util.Date; +import lombok.Data; + +/** + * 用户视图(脱敏) + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class UserVO implements Serializable { + + /** + * id + */ + private Long id; + + /** + * 用户昵称 + */ + private String userName; + + /** + * 用户头像 + */ + private String userAvatar; + + /** + * 用户简介 + */ + private String userProfile; + + /** + * 用户角色:user/admin/ban + */ + private String userRole; + + /** + * 创建时间 + */ + private Date createTime; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/service/ChartService.java b/src/main/java/com/yupi/springbootinit/service/ChartService.java new file mode 100644 index 0000000..4531e1d --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/service/ChartService.java @@ -0,0 +1,13 @@ +package com.yupi.springbootinit.service; + +import com.yupi.springbootinit.model.entity.Chart; +import com.baomidou.mybatisplus.extension.service.IService; + +/** +* @author HeSun +* @description 针对表【chart(图表信息表)】的数据库操作Service +* @createDate 2025-02-20 20:52:40 +*/ +public interface ChartService extends IService { + +} diff --git a/src/main/java/com/yupi/springbootinit/service/PostFavourService.java b/src/main/java/com/yupi/springbootinit/service/PostFavourService.java new file mode 100644 index 0000000..1e29151 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/service/PostFavourService.java @@ -0,0 +1,47 @@ +package com.yupi.springbootinit.service; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yupi.springbootinit.model.entity.Post; +import com.yupi.springbootinit.model.entity.PostFavour; +import com.yupi.springbootinit.model.entity.User; + +/** + * 帖子收藏服务 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface PostFavourService extends IService { + + /** + * 帖子收藏 + * + * @param postId + * @param loginUser + * @return + */ + int doPostFavour(long postId, User loginUser); + + /** + * 分页获取用户收藏的帖子列表 + * + * @param page + * @param queryWrapper + * @param favourUserId + * @return + */ + Page listFavourPostByPage(IPage page, Wrapper queryWrapper, + long favourUserId); + + /** + * 帖子收藏(内部服务) + * + * @param userId + * @param postId + * @return + */ + int doPostFavourInner(long userId, long postId); +} diff --git a/src/main/java/com/yupi/springbootinit/service/PostService.java b/src/main/java/com/yupi/springbootinit/service/PostService.java new file mode 100644 index 0000000..d938f0b --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/service/PostService.java @@ -0,0 +1,60 @@ +package com.yupi.springbootinit.service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yupi.springbootinit.model.dto.post.PostQueryRequest; +import com.yupi.springbootinit.model.entity.Post; +import com.yupi.springbootinit.model.vo.PostVO; +import javax.servlet.http.HttpServletRequest; + +/** + * 帖子服务 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface PostService extends IService { + + /** + * 校验 + * + * @param post + * @param add + */ + void validPost(Post post, boolean add); + + /** + * 获取查询条件 + * + * @param postQueryRequest + * @return + */ + QueryWrapper getQueryWrapper(PostQueryRequest postQueryRequest); + + /** + * 从 ES 查询 + * + * @param postQueryRequest + * @return + */ + Page searchFromEs(PostQueryRequest postQueryRequest); + + /** + * 获取帖子封装 + * + * @param post + * @param request + * @return + */ + PostVO getPostVO(Post post, HttpServletRequest request); + + /** + * 分页获取帖子封装 + * + * @param postPage + * @param request + * @return + */ + Page getPostVOPage(Page postPage, HttpServletRequest request); +} diff --git a/src/main/java/com/yupi/springbootinit/service/PostThumbService.java b/src/main/java/com/yupi/springbootinit/service/PostThumbService.java new file mode 100644 index 0000000..db731f9 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/service/PostThumbService.java @@ -0,0 +1,32 @@ +package com.yupi.springbootinit.service; + +import com.yupi.springbootinit.model.entity.PostThumb; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yupi.springbootinit.model.entity.User; + +/** + * 帖子点赞服务 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface PostThumbService extends IService { + + /** + * 点赞 + * + * @param postId + * @param loginUser + * @return + */ + int doPostThumb(long postId, User loginUser); + + /** + * 帖子点赞(内部服务) + * + * @param userId + * @param postId + * @return + */ + int doPostThumbInner(long userId, long postId); +} diff --git a/src/main/java/com/yupi/springbootinit/service/UserService.java b/src/main/java/com/yupi/springbootinit/service/UserService.java new file mode 100644 index 0000000..5035bf6 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/service/UserService.java @@ -0,0 +1,112 @@ +package com.yupi.springbootinit.service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yupi.springbootinit.model.dto.user.UserQueryRequest; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.model.vo.LoginUserVO; +import com.yupi.springbootinit.model.vo.UserVO; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import me.chanjar.weixin.common.bean.WxOAuth2UserInfo; + +/** + * 用户服务 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface UserService extends IService { + + /** + * 用户注册 + * + * @param userAccount 用户账户 + * @param userPassword 用户密码 + * @param checkPassword 校验密码 + * @return 新用户 id + */ + long userRegister(String userAccount, String userPassword, String checkPassword); + + /** + * 用户登录 + * + * @param userAccount 用户账户 + * @param userPassword 用户密码 + * @param request + * @return 脱敏后的用户信息 + */ + LoginUserVO userLogin(String userAccount, String userPassword, HttpServletRequest request); + + /** + * 获取当前登录用户 + * + * @param request + * @return + */ + User getLoginUser(HttpServletRequest request); + + /** + * 获取当前登录用户(允许未登录) + * + * @param request + * @return + */ + User getLoginUserPermitNull(HttpServletRequest request); + + /** + * 是否为管理员 + * + * @param request + * @return + */ + boolean isAdmin(HttpServletRequest request); + + /** + * 是否为管理员 + * + * @param user + * @return + */ + boolean isAdmin(User user); + + /** + * 用户注销 + * + * @param request + * @return + */ + boolean userLogout(HttpServletRequest request); + + /** + * 获取脱敏的已登录用户信息 + * + * @return + */ + LoginUserVO getLoginUserVO(User user); + + /** + * 获取脱敏的用户信息 + * + * @param user + * @return + */ + UserVO getUserVO(User user); + + /** + * 获取脱敏的用户信息 + * + * @param userList + * @return + */ + List getUserVO(List userList); + + /** + * 获取查询条件 + * + * @param userQueryRequest + * @return + */ + QueryWrapper getQueryWrapper(UserQueryRequest userQueryRequest); + +} diff --git a/src/main/java/com/yupi/springbootinit/service/impl/ChartServiceImpl.java b/src/main/java/com/yupi/springbootinit/service/impl/ChartServiceImpl.java new file mode 100644 index 0000000..2cb39d7 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/service/impl/ChartServiceImpl.java @@ -0,0 +1,22 @@ +package com.yupi.springbootinit.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yupi.springbootinit.model.entity.Chart; +import com.yupi.springbootinit.service.ChartService; +import com.yupi.springbootinit.mapper.ChartMapper; +import org.springframework.stereotype.Service; + +/** +* @author HeSun +* @description 针对表【chart(图表信息表)】的数据库操作Service实现 +* @createDate 2025-02-20 20:52:40 +*/ +@Service +public class ChartServiceImpl extends ServiceImpl + implements ChartService{ + +} + + + + diff --git a/src/main/java/com/yupi/springbootinit/service/impl/PostFavourServiceImpl.java b/src/main/java/com/yupi/springbootinit/service/impl/PostFavourServiceImpl.java new file mode 100644 index 0000000..08abe11 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/service/impl/PostFavourServiceImpl.java @@ -0,0 +1,116 @@ +package com.yupi.springbootinit.service.impl; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.mapper.PostFavourMapper; +import com.yupi.springbootinit.model.entity.Post; +import com.yupi.springbootinit.model.entity.PostFavour; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.service.PostFavourService; +import com.yupi.springbootinit.service.PostService; +import javax.annotation.Resource; +import org.springframework.aop.framework.AopContext; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * 帖子收藏服务实现 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Service +public class PostFavourServiceImpl extends ServiceImpl + implements PostFavourService { + + @Resource + private PostService postService; + + /** + * 帖子收藏 + * + * @param postId + * @param loginUser + * @return + */ + @Override + public int doPostFavour(long postId, User loginUser) { + // 判断是否存在 + Post post = postService.getById(postId); + if (post == null) { + throw new BusinessException(ErrorCode.NOT_FOUND_ERROR); + } + // 是否已帖子收藏 + long userId = loginUser.getId(); + // 每个用户串行帖子收藏 + // 锁必须要包裹住事务方法 + PostFavourService postFavourService = (PostFavourService) AopContext.currentProxy(); + synchronized (String.valueOf(userId).intern()) { + return postFavourService.doPostFavourInner(userId, postId); + } + } + + @Override + public Page listFavourPostByPage(IPage page, Wrapper queryWrapper, long favourUserId) { + if (favourUserId <= 0) { + return new Page<>(); + } + return baseMapper.listFavourPostByPage(page, queryWrapper, favourUserId); + } + + /** + * 封装了事务的方法 + * + * @param userId + * @param postId + * @return + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int doPostFavourInner(long userId, long postId) { + PostFavour postFavour = new PostFavour(); + postFavour.setUserId(userId); + postFavour.setPostId(postId); + QueryWrapper postFavourQueryWrapper = new QueryWrapper<>(postFavour); + PostFavour oldPostFavour = this.getOne(postFavourQueryWrapper); + boolean result; + // 已收藏 + if (oldPostFavour != null) { + result = this.remove(postFavourQueryWrapper); + if (result) { + // 帖子收藏数 - 1 + result = postService.update() + .eq("id", postId) + .gt("favourNum", 0) + .setSql("favourNum = favourNum - 1") + .update(); + return result ? -1 : 0; + } else { + throw new BusinessException(ErrorCode.SYSTEM_ERROR); + } + } else { + // 未帖子收藏 + result = this.save(postFavour); + if (result) { + // 帖子收藏数 + 1 + result = postService.update() + .eq("id", postId) + .setSql("favourNum = favourNum + 1") + .update(); + return result ? 1 : 0; + } else { + throw new BusinessException(ErrorCode.SYSTEM_ERROR); + } + } + } + +} + + + + diff --git a/src/main/java/com/yupi/springbootinit/service/impl/PostServiceImpl.java b/src/main/java/com/yupi/springbootinit/service/impl/PostServiceImpl.java new file mode 100644 index 0000000..791c8c4 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/service/impl/PostServiceImpl.java @@ -0,0 +1,312 @@ +package com.yupi.springbootinit.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.constant.CommonConstant; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.exception.ThrowUtils; +import com.yupi.springbootinit.mapper.PostFavourMapper; +import com.yupi.springbootinit.mapper.PostMapper; +import com.yupi.springbootinit.mapper.PostThumbMapper; +import com.yupi.springbootinit.model.dto.post.PostEsDTO; +import com.yupi.springbootinit.model.dto.post.PostQueryRequest; +import com.yupi.springbootinit.model.entity.Post; +import com.yupi.springbootinit.model.entity.PostFavour; +import com.yupi.springbootinit.model.entity.PostThumb; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.model.vo.PostVO; +import com.yupi.springbootinit.model.vo.UserVO; +import com.yupi.springbootinit.service.PostService; +import com.yupi.springbootinit.service.UserService; +import com.yupi.springbootinit.utils.SqlUtils; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import cn.hutool.core.collection.CollUtil; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.search.sort.SortBuilder; +import org.elasticsearch.search.sort.SortBuilders; +import org.elasticsearch.search.sort.SortOrder; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; +import org.springframework.data.elasticsearch.core.SearchHit; +import org.springframework.data.elasticsearch.core.SearchHits; +import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.stereotype.Service; + +/** + * 帖子服务实现 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Service +@Slf4j +public class PostServiceImpl extends ServiceImpl implements PostService { + + @Resource + private UserService userService; + + @Resource + private PostThumbMapper postThumbMapper; + + @Resource + private PostFavourMapper postFavourMapper; + + @Resource + private ElasticsearchRestTemplate elasticsearchRestTemplate; + + @Override + public void validPost(Post post, boolean add) { + if (post == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + String title = post.getTitle(); + String content = post.getContent(); + String tags = post.getTags(); + // 创建时,参数不能为空 + if (add) { + ThrowUtils.throwIf(StringUtils.isAnyBlank(title, content, tags), ErrorCode.PARAMS_ERROR); + } + // 有参数则校验 + if (StringUtils.isNotBlank(title) && title.length() > 80) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "标题过长"); + } + if (StringUtils.isNotBlank(content) && content.length() > 8192) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "内容过长"); + } + } + + /** + * 获取查询包装类 + * + * @param postQueryRequest + * @return + */ + @Override + public QueryWrapper getQueryWrapper(PostQueryRequest postQueryRequest) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (postQueryRequest == null) { + return queryWrapper; + } + String searchText = postQueryRequest.getSearchText(); + String sortField = postQueryRequest.getSortField(); + String sortOrder = postQueryRequest.getSortOrder(); + Long id = postQueryRequest.getId(); + String title = postQueryRequest.getTitle(); + String content = postQueryRequest.getContent(); + List tagList = postQueryRequest.getTags(); + Long userId = postQueryRequest.getUserId(); + Long notId = postQueryRequest.getNotId(); + // 拼接查询条件 + if (StringUtils.isNotBlank(searchText)) { + queryWrapper.and(qw -> qw.like("title", searchText).or().like("content", searchText)); + } + queryWrapper.like(StringUtils.isNotBlank(title), "title", title); + queryWrapper.like(StringUtils.isNotBlank(content), "content", content); + if (CollUtil.isNotEmpty(tagList)) { + for (String tag : tagList) { + queryWrapper.like("tags", "\"" + tag + "\""); + } + } + queryWrapper.ne(ObjectUtils.isNotEmpty(notId), "id", notId); + queryWrapper.eq(ObjectUtils.isNotEmpty(id), "id", id); + queryWrapper.eq(ObjectUtils.isNotEmpty(userId), "userId", userId); + queryWrapper.orderBy(SqlUtils.validSortField(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC), + sortField); + return queryWrapper; + } + + @Override + public Page searchFromEs(PostQueryRequest postQueryRequest) { + Long id = postQueryRequest.getId(); + Long notId = postQueryRequest.getNotId(); + String searchText = postQueryRequest.getSearchText(); + String title = postQueryRequest.getTitle(); + String content = postQueryRequest.getContent(); + List tagList = postQueryRequest.getTags(); + List orTagList = postQueryRequest.getOrTags(); + Long userId = postQueryRequest.getUserId(); + // es 起始页为 0 + long current = postQueryRequest.getCurrent() - 1; + long pageSize = postQueryRequest.getPageSize(); + String sortField = postQueryRequest.getSortField(); + String sortOrder = postQueryRequest.getSortOrder(); + BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); + // 过滤 + boolQueryBuilder.filter(QueryBuilders.termQuery("isDelete", 0)); + if (id != null) { + boolQueryBuilder.filter(QueryBuilders.termQuery("id", id)); + } + if (notId != null) { + boolQueryBuilder.mustNot(QueryBuilders.termQuery("id", notId)); + } + if (userId != null) { + boolQueryBuilder.filter(QueryBuilders.termQuery("userId", userId)); + } + // 必须包含所有标签 + if (CollUtil.isNotEmpty(tagList)) { + for (String tag : tagList) { + boolQueryBuilder.filter(QueryBuilders.termQuery("tags", tag)); + } + } + // 包含任何一个标签即可 + if (CollUtil.isNotEmpty(orTagList)) { + BoolQueryBuilder orTagBoolQueryBuilder = QueryBuilders.boolQuery(); + for (String tag : orTagList) { + orTagBoolQueryBuilder.should(QueryBuilders.termQuery("tags", tag)); + } + orTagBoolQueryBuilder.minimumShouldMatch(1); + boolQueryBuilder.filter(orTagBoolQueryBuilder); + } + // 按关键词检索 + if (StringUtils.isNotBlank(searchText)) { + boolQueryBuilder.should(QueryBuilders.matchQuery("title", searchText)); + boolQueryBuilder.should(QueryBuilders.matchQuery("description", searchText)); + boolQueryBuilder.should(QueryBuilders.matchQuery("content", searchText)); + boolQueryBuilder.minimumShouldMatch(1); + } + // 按标题检索 + if (StringUtils.isNotBlank(title)) { + boolQueryBuilder.should(QueryBuilders.matchQuery("title", title)); + boolQueryBuilder.minimumShouldMatch(1); + } + // 按内容检索 + if (StringUtils.isNotBlank(content)) { + boolQueryBuilder.should(QueryBuilders.matchQuery("content", content)); + boolQueryBuilder.minimumShouldMatch(1); + } + // 排序 + SortBuilder sortBuilder = SortBuilders.scoreSort(); + if (StringUtils.isNotBlank(sortField)) { + sortBuilder = SortBuilders.fieldSort(sortField); + sortBuilder.order(CommonConstant.SORT_ORDER_ASC.equals(sortOrder) ? SortOrder.ASC : SortOrder.DESC); + } + // 分页 + PageRequest pageRequest = PageRequest.of((int) current, (int) pageSize); + // 构造查询 + NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder) + .withPageable(pageRequest).withSorts(sortBuilder).build(); + SearchHits searchHits = elasticsearchRestTemplate.search(searchQuery, PostEsDTO.class); + Page page = new Page<>(); + page.setTotal(searchHits.getTotalHits()); + List resourceList = new ArrayList<>(); + // 查出结果后,从 db 获取最新动态数据(比如点赞数) + if (searchHits.hasSearchHits()) { + List> searchHitList = searchHits.getSearchHits(); + List postIdList = searchHitList.stream().map(searchHit -> searchHit.getContent().getId()) + .collect(Collectors.toList()); + List postList = baseMapper.selectBatchIds(postIdList); + if (postList != null) { + Map> idPostMap = postList.stream().collect(Collectors.groupingBy(Post::getId)); + postIdList.forEach(postId -> { + if (idPostMap.containsKey(postId)) { + resourceList.add(idPostMap.get(postId).get(0)); + } else { + // 从 es 清空 db 已物理删除的数据 + String delete = elasticsearchRestTemplate.delete(String.valueOf(postId), PostEsDTO.class); + log.info("delete post {}", delete); + } + }); + } + } + page.setRecords(resourceList); + return page; + } + + @Override + public PostVO getPostVO(Post post, HttpServletRequest request) { + PostVO postVO = PostVO.objToVo(post); + long postId = post.getId(); + // 1. 关联查询用户信息 + Long userId = post.getUserId(); + User user = null; + if (userId != null && userId > 0) { + user = userService.getById(userId); + } + UserVO userVO = userService.getUserVO(user); + postVO.setUser(userVO); + // 2. 已登录,获取用户点赞、收藏状态 + User loginUser = userService.getLoginUserPermitNull(request); + if (loginUser != null) { + // 获取点赞 + QueryWrapper postThumbQueryWrapper = new QueryWrapper<>(); + postThumbQueryWrapper.in("postId", postId); + postThumbQueryWrapper.eq("userId", loginUser.getId()); + PostThumb postThumb = postThumbMapper.selectOne(postThumbQueryWrapper); + postVO.setHasThumb(postThumb != null); + // 获取收藏 + QueryWrapper postFavourQueryWrapper = new QueryWrapper<>(); + postFavourQueryWrapper.in("postId", postId); + postFavourQueryWrapper.eq("userId", loginUser.getId()); + PostFavour postFavour = postFavourMapper.selectOne(postFavourQueryWrapper); + postVO.setHasFavour(postFavour != null); + } + return postVO; + } + + @Override + public Page getPostVOPage(Page postPage, HttpServletRequest request) { + List postList = postPage.getRecords(); + Page postVOPage = new Page<>(postPage.getCurrent(), postPage.getSize(), postPage.getTotal()); + if (CollUtil.isEmpty(postList)) { + return postVOPage; + } + // 1. 关联查询用户信息 + Set userIdSet = postList.stream().map(Post::getUserId).collect(Collectors.toSet()); + Map> userIdUserListMap = userService.listByIds(userIdSet).stream() + .collect(Collectors.groupingBy(User::getId)); + // 2. 已登录,获取用户点赞、收藏状态 + Map postIdHasThumbMap = new HashMap<>(); + Map postIdHasFavourMap = new HashMap<>(); + User loginUser = userService.getLoginUserPermitNull(request); + if (loginUser != null) { + Set postIdSet = postList.stream().map(Post::getId).collect(Collectors.toSet()); + loginUser = userService.getLoginUser(request); + // 获取点赞 + QueryWrapper postThumbQueryWrapper = new QueryWrapper<>(); + postThumbQueryWrapper.in("postId", postIdSet); + postThumbQueryWrapper.eq("userId", loginUser.getId()); + List postPostThumbList = postThumbMapper.selectList(postThumbQueryWrapper); + postPostThumbList.forEach(postPostThumb -> postIdHasThumbMap.put(postPostThumb.getPostId(), true)); + // 获取收藏 + QueryWrapper postFavourQueryWrapper = new QueryWrapper<>(); + postFavourQueryWrapper.in("postId", postIdSet); + postFavourQueryWrapper.eq("userId", loginUser.getId()); + List postFavourList = postFavourMapper.selectList(postFavourQueryWrapper); + postFavourList.forEach(postFavour -> postIdHasFavourMap.put(postFavour.getPostId(), true)); + } + // 填充信息 + List postVOList = postList.stream().map(post -> { + PostVO postVO = PostVO.objToVo(post); + Long userId = post.getUserId(); + User user = null; + if (userIdUserListMap.containsKey(userId)) { + user = userIdUserListMap.get(userId).get(0); + } + postVO.setUser(userService.getUserVO(user)); + postVO.setHasThumb(postIdHasThumbMap.getOrDefault(post.getId(), false)); + postVO.setHasFavour(postIdHasFavourMap.getOrDefault(post.getId(), false)); + return postVO; + }).collect(Collectors.toList()); + postVOPage.setRecords(postVOList); + return postVOPage; + } + +} + + + + diff --git a/src/main/java/com/yupi/springbootinit/service/impl/PostThumbServiceImpl.java b/src/main/java/com/yupi/springbootinit/service/impl/PostThumbServiceImpl.java new file mode 100644 index 0000000..a0b3c06 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/service/impl/PostThumbServiceImpl.java @@ -0,0 +1,105 @@ +package com.yupi.springbootinit.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.mapper.PostThumbMapper; +import com.yupi.springbootinit.model.entity.Post; +import com.yupi.springbootinit.model.entity.PostThumb; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.service.PostService; +import com.yupi.springbootinit.service.PostThumbService; +import javax.annotation.Resource; +import org.springframework.aop.framework.AopContext; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * 帖子点赞服务实现 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Service +public class PostThumbServiceImpl extends ServiceImpl + implements PostThumbService { + + @Resource + private PostService postService; + + /** + * 点赞 + * + * @param postId + * @param loginUser + * @return + */ + @Override + public int doPostThumb(long postId, User loginUser) { + // 判断实体是否存在,根据类别获取实体 + Post post = postService.getById(postId); + if (post == null) { + throw new BusinessException(ErrorCode.NOT_FOUND_ERROR); + } + // 是否已点赞 + long userId = loginUser.getId(); + // 每个用户串行点赞 + // 锁必须要包裹住事务方法 + PostThumbService postThumbService = (PostThumbService) AopContext.currentProxy(); + synchronized (String.valueOf(userId).intern()) { + return postThumbService.doPostThumbInner(userId, postId); + } + } + + /** + * 封装了事务的方法 + * + * @param userId + * @param postId + * @return + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int doPostThumbInner(long userId, long postId) { + PostThumb postThumb = new PostThumb(); + postThumb.setUserId(userId); + postThumb.setPostId(postId); + QueryWrapper thumbQueryWrapper = new QueryWrapper<>(postThumb); + PostThumb oldPostThumb = this.getOne(thumbQueryWrapper); + boolean result; + // 已点赞 + if (oldPostThumb != null) { + result = this.remove(thumbQueryWrapper); + if (result) { + // 点赞数 - 1 + result = postService.update() + .eq("id", postId) + .gt("thumbNum", 0) + .setSql("thumbNum = thumbNum - 1") + .update(); + return result ? -1 : 0; + } else { + throw new BusinessException(ErrorCode.SYSTEM_ERROR); + } + } else { + // 未点赞 + result = this.save(postThumb); + if (result) { + // 点赞数 + 1 + result = postService.update() + .eq("id", postId) + .setSql("thumbNum = thumbNum + 1") + .update(); + return result ? 1 : 0; + } else { + throw new BusinessException(ErrorCode.SYSTEM_ERROR); + } + } + } + +} + + + + diff --git a/src/main/java/com/yupi/springbootinit/service/impl/UserServiceImpl.java b/src/main/java/com/yupi/springbootinit/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..7bdc599 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/service/impl/UserServiceImpl.java @@ -0,0 +1,240 @@ +package com.yupi.springbootinit.service.impl; + +import static com.yupi.springbootinit.constant.UserConstant.USER_LOGIN_STATE; + +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.constant.CommonConstant; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.mapper.UserMapper; +import com.yupi.springbootinit.model.dto.user.UserQueryRequest; +import com.yupi.springbootinit.model.entity.User; +import com.yupi.springbootinit.model.enums.UserRoleEnum; +import com.yupi.springbootinit.model.vo.LoginUserVO; +import com.yupi.springbootinit.model.vo.UserVO; +import com.yupi.springbootinit.service.UserService; +import com.yupi.springbootinit.utils.SqlUtils; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.bean.WxOAuth2UserInfo; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.util.DigestUtils; + +/** + * 用户服务实现 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Service +@Slf4j +public class UserServiceImpl extends ServiceImpl implements UserService { + + /** + * 盐值,混淆密码 + */ + public static final String SALT = "yupi"; + + @Override + public long userRegister(String userAccount, String userPassword, String checkPassword) { + // 1. 校验 + if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword)) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空"); + } + if (userAccount.length() < 4) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号过短"); + } + if (userPassword.length() < 8 || checkPassword.length() < 8) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过短"); + } + // 密码和校验密码相同 + if (!userPassword.equals(checkPassword)) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "两次输入的密码不一致"); + } + synchronized (userAccount.intern()) { + // 账户不能重复 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("userAccount", userAccount); + long count = this.baseMapper.selectCount(queryWrapper); + if (count > 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复"); + } + // 2. 加密 + String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes()); + // 3. 插入数据 + User user = new User(); + user.setUserAccount(userAccount); + user.setUserPassword(encryptPassword); + boolean saveResult = this.save(user); + if (!saveResult) { + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "注册失败,数据库错误"); + } + return user.getId(); + } + } + + @Override + public LoginUserVO userLogin(String userAccount, String userPassword, HttpServletRequest request) { + // 1. 校验 + if (StringUtils.isAnyBlank(userAccount, userPassword)) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空"); + } + if (userAccount.length() < 4) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号错误"); + } + if (userPassword.length() < 8) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误"); + } + // 2. 加密 + String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes()); + // 查询用户是否存在 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("userAccount", userAccount); + queryWrapper.eq("userPassword", encryptPassword); + User user = this.baseMapper.selectOne(queryWrapper); + // 用户不存在 + if (user == null) { + log.info("user login failed, userAccount cannot match userPassword"); + throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在或密码错误"); + } + // 3. 记录用户的登录态 + request.getSession().setAttribute(USER_LOGIN_STATE, user); + return this.getLoginUserVO(user); + } + + /** + * 获取当前登录用户 + * + * @param request + * @return + */ + @Override + public User getLoginUser(HttpServletRequest request) { + // 先判断是否已登录 + Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE); + User currentUser = (User) userObj; + if (currentUser == null || currentUser.getId() == null) { + throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR); + } + // 从数据库查询(追求性能的话可以注释,直接走缓存) + long userId = currentUser.getId(); + currentUser = this.getById(userId); + if (currentUser == null) { + throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR); + } + return currentUser; + } + + /** + * 获取当前登录用户(允许未登录) + * + * @param request + * @return + */ + @Override + public User getLoginUserPermitNull(HttpServletRequest request) { + // 先判断是否已登录 + Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE); + User currentUser = (User) userObj; + if (currentUser == null || currentUser.getId() == null) { + return null; + } + // 从数据库查询(追求性能的话可以注释,直接走缓存) + long userId = currentUser.getId(); + return this.getById(userId); + } + + /** + * 是否为管理员 + * + * @param request + * @return + */ + @Override + public boolean isAdmin(HttpServletRequest request) { + // 仅管理员可查询 + Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE); + User user = (User) userObj; + return isAdmin(user); + } + + @Override + public boolean isAdmin(User user) { + return user != null && UserRoleEnum.ADMIN.getValue().equals(user.getUserRole()); + } + + /** + * 用户注销 + * + * @param request + */ + @Override + public boolean userLogout(HttpServletRequest request) { + if (request.getSession().getAttribute(USER_LOGIN_STATE) == null) { + throw new BusinessException(ErrorCode.OPERATION_ERROR, "未登录"); + } + // 移除登录态 + request.getSession().removeAttribute(USER_LOGIN_STATE); + return true; + } + + @Override + public LoginUserVO getLoginUserVO(User user) { + if (user == null) { + return null; + } + LoginUserVO loginUserVO = new LoginUserVO(); + BeanUtils.copyProperties(user, loginUserVO); + return loginUserVO; + } + + @Override + public UserVO getUserVO(User user) { + if (user == null) { + return null; + } + UserVO userVO = new UserVO(); + BeanUtils.copyProperties(user, userVO); + return userVO; + } + + @Override + public List getUserVO(List userList) { + if (CollUtil.isEmpty(userList)) { + return new ArrayList<>(); + } + return userList.stream().map(this::getUserVO).collect(Collectors.toList()); + } + + @Override + public QueryWrapper getQueryWrapper(UserQueryRequest userQueryRequest) { + if (userQueryRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空"); + } + Long id = userQueryRequest.getId(); + String unionId = userQueryRequest.getUnionId(); + String mpOpenId = userQueryRequest.getMpOpenId(); + String userName = userQueryRequest.getUserName(); + String userProfile = userQueryRequest.getUserProfile(); + String userRole = userQueryRequest.getUserRole(); + String sortField = userQueryRequest.getSortField(); + String sortOrder = userQueryRequest.getSortOrder(); + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq(id != null, "id", id); + queryWrapper.eq(StringUtils.isNotBlank(unionId), "unionId", unionId); + queryWrapper.eq(StringUtils.isNotBlank(mpOpenId), "mpOpenId", mpOpenId); + queryWrapper.eq(StringUtils.isNotBlank(userRole), "userRole", userRole); + queryWrapper.like(StringUtils.isNotBlank(userProfile), "userProfile", userProfile); + queryWrapper.like(StringUtils.isNotBlank(userName), "userName", userName); + queryWrapper.orderBy(SqlUtils.validSortField(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC), + sortField); + return queryWrapper; + } +} diff --git a/src/main/java/com/yupi/springbootinit/utils/ExcelUtils.java b/src/main/java/com/yupi/springbootinit/utils/ExcelUtils.java new file mode 100644 index 0000000..f2c1ec3 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/utils/ExcelUtils.java @@ -0,0 +1,71 @@ +package com.yupi.springbootinit.utils; +import cn.hutool.core.collection.CollUtil; +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.support.ExcelTypeEnum; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.ResourceUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Excel 相关工具类 + */ +@Slf4j +public class ExcelUtils { + + /** + * excel 转 csv + * + * @param multipartFile + * @return + */ + public static String excelToCsv(MultipartFile multipartFile) { +// File file = null; +// try { +// file = ResourceUtils.getFile("classpath:网站数据.xlsx"); +// } catch (FileNotFoundException e) { +// e.printStackTrace(); +// } + // 读取数据 + List> list = null; + try { + list = EasyExcel.read(multipartFile.getInputStream()) + .excelType(ExcelTypeEnum.XLSX) + .sheet() + .headRowNumber(0) + .doReadSync(); + } catch (IOException e) { + log.error("表格处理错误", e); + } + if (CollUtil.isEmpty(list)) { + return ""; + } + // 转换为 csv + StringBuilder stringBuilder = new StringBuilder(); + // 读取表头 + LinkedHashMap headerMap = (LinkedHashMap) list.get(0); + List headerList = headerMap.values().stream().filter(ObjectUtils::isNotEmpty).collect(Collectors.toList()); + stringBuilder.append(StringUtils.join(headerList, ",")).append("\n"); + // 读取数据 + for (int i = 1; i < list.size(); i++) { + LinkedHashMap dataMap = (LinkedHashMap) list.get(i); + List dataList = dataMap.values().stream().filter(ObjectUtils::isNotEmpty).collect(Collectors.toList()); + stringBuilder.append(StringUtils.join(dataList, ",")).append("\n"); + } + return stringBuilder.toString(); + } + + public static void main(String[] args) { + excelToCsv(null); + } +} + diff --git a/src/main/java/com/yupi/springbootinit/utils/NetUtils.java b/src/main/java/com/yupi/springbootinit/utils/NetUtils.java new file mode 100644 index 0000000..88069fb --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/utils/NetUtils.java @@ -0,0 +1,55 @@ +package com.yupi.springbootinit.utils; + +import java.net.InetAddress; +import javax.servlet.http.HttpServletRequest; + +/** + * 网络工具类 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public class NetUtils { + + /** + * 获取客户端 IP 地址 + * + * @param request + * @return + */ + public static String getIpAddress(HttpServletRequest request) { + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + if (ip.equals("127.0.0.1")) { + // 根据网卡取本机配置的 IP + InetAddress inet = null; + try { + inet = InetAddress.getLocalHost(); + } catch (Exception e) { + e.printStackTrace(); + } + if (inet != null) { + ip = inet.getHostAddress(); + } + } + } + // 多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 + if (ip != null && ip.length() > 15) { + if (ip.indexOf(",") > 0) { + ip = ip.substring(0, ip.indexOf(",")); + } + } + if (ip == null) { + return "127.0.0.1"; + } + return ip; + } + +} diff --git a/src/main/java/com/yupi/springbootinit/utils/SpringContextUtils.java b/src/main/java/com/yupi/springbootinit/utils/SpringContextUtils.java new file mode 100644 index 0000000..60001f6 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/utils/SpringContextUtils.java @@ -0,0 +1,57 @@ +package com.yupi.springbootinit.utils; + +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * Spring 上下文获取工具 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Component +public class SpringContextUtils implements ApplicationContextAware { + + private static ApplicationContext applicationContext; + + @Override + public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException { + SpringContextUtils.applicationContext = applicationContext; + } + + /** + * 通过名称获取 Bean + * + * @param beanName + * @return + */ + public static Object getBean(String beanName) { + return applicationContext.getBean(beanName); + } + + /** + * 通过 class 获取 Bean + * + * @param beanClass + * @param + * @return + */ + public static T getBean(Class beanClass) { + return applicationContext.getBean(beanClass); + } + + /** + * 通过名称和类型获取 Bean + * + * @param beanName + * @param beanClass + * @param + * @return + */ + public static T getBean(String beanName, Class beanClass) { + return applicationContext.getBean(beanName, beanClass); + } +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/utils/SqlUtils.java b/src/main/java/com/yupi/springbootinit/utils/SqlUtils.java new file mode 100644 index 0000000..1efeaa4 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/utils/SqlUtils.java @@ -0,0 +1,25 @@ +package com.yupi.springbootinit.utils; + +import org.apache.commons.lang3.StringUtils; + +/** + * SQL 工具 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public class SqlUtils { + + /** + * 校验排序字段是否合法(防止 SQL 注入) + * + * @param sortField + * @return + */ + public static boolean validSortField(String sortField) { + if (StringUtils.isBlank(sortField)) { + return false; + } + return !StringUtils.containsAny(sortField, "=", "(", ")", " "); + } +} diff --git a/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..670aab3 --- /dev/null +++ b/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,52 @@ +{ + "properties": [ + { + "name": "cos.client.accessKey", + "type": "java.lang.String", + "description": "Description for cos.client.accessKey." + }, + { + "name": "cos.client.secretKey", + "type": "java.lang.String", + "description": "Description for cos.client.secretKey." + }, + { + "name": "cos.client.region", + "type": "java.lang.String", + "description": "Description for cos.client.region." + }, + { + "name": "cos.client.bucket", + "type": "java.lang.String", + "description": "Description for cos.client.bucket." + }, + { + "name": "wx.open.appId", + "type": "java.lang.String", + "description": "Description for wx.open.appId." + }, + { + "name": "wx.open.appSecret", + "type": "java.lang.String", + "description": "Description for wx.open.appSecret." + }, + + + + { + "name": "xun-fei.client.appId", + "type": "java.lang.String" + }, + { + "name": "xun-fei.client.apiSecret", + "type": "java.lang.String" + }, + { + "name": "xun-fei.client.apiKey", + "type": "java.lang.String" + } + + + + ] +} diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 0000000..b085964 --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,37 @@ +# 线上配置文件 +# @author 程序员鱼皮 +# @from 编程导航知识星球 +server: + port: 8101 +spring: + # 数据库配置 + # todo 需替换配置 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/my_db + username: root + password: 123456 + # Redis 配置 + # todo 需替换配置 + redis: + database: 1 + host: localhost + port: 6379 + timeout: 5000 + password: 123456 + # Elasticsearch 配置 + # todo 需替换配置 + elasticsearch: + uris: http://localhost:9200 + username: root + password: 123456 +mybatis-plus: + configuration: + # 生产环境关闭日志 + log-impl: '' +# 接口文档配置 +knife4j: + basic: + enable: true + username: root + password: 123456 diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml new file mode 100644 index 0000000..c0fcf94 --- /dev/null +++ b/src/main/resources/application-test.yml @@ -0,0 +1,27 @@ +# 测试配置文件 +# @author 程序员鱼皮 +# @from 编程导航知识星球 +server: + port: 8101 +spring: + # 数据库配置 + # todo 需替换配置 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/my_db + username: root + password: 123456 + # Redis 配置 + # todo 需替换配置 + redis: + database: 1 + host: localhost + port: 6379 + timeout: 5000 + password: 123456 + # Elasticsearch 配置 + # todo 需替换配置 + elasticsearch: + uris: http://localhost:9200 + username: root + password: 123456 \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..5993780 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,112 @@ +# 公共配置文件 +# @author 程序员鱼皮 +# @from 编程导航知识星球 +spring: + application: + name: hebi-backend + # 默认 dev 环境 + profiles: + active: dev + # 支持 swagger3 + mvc: + pathmatch: + matching-strategy: ant_path_matcher + # session 配置 + session: + # todo 取消注释开启分布式 session(须先配置 Redis) + # store-type: redis + # 30 天过期 + timeout: 2592000 + # 数据库配置 + # todo 需替换配置 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/hebi + username: root + password: 123456 + # Redis 配置 + # todo 需替换配置,然后取消注释 + redis: + database: 1 + host: localhost + port: 6379 + timeout: 5000 +# password: 123456 + + # Elasticsearch 配置 + # todo 需替换配置,然后取消注释 +# elasticsearch: +# uris: http://localhost:9200 +# username: root +# password: 123456 + # 文件上传 + servlet: + multipart: + # 大小限制 + max-file-size: 10MB +server: + address: 0.0.0.0 + port: 8101 + servlet: + context-path: /api + # cookie 30 天过期 + session: + cookie: + max-age: 2592000 +mybatis-plus: + configuration: + map-underscore-to-camel-case: false + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + global-config: + db-config: + logic-delete-field: isDelete # 全局逻辑删除的实体字段名 + logic-delete-value: 1 # 逻辑已删除值(默认为 1) + logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) +# 微信相关 +wx: + # 微信公众平台 + # todo 需替换配置 + mp: + token: xxx + aesKey: xxx + appId: xxx + secret: xxx + config-storage: + http-client-type: HttpClient + key-prefix: wx + redis: + host: 127.0.0.1 + port: 6379 + type: Memory + # 微信开放平台 + # todo 需替换配置 + open: + appId: xxx + appSecret: xxx +# 对象存储 +# todo 需替换配置 +cos: + client: + accessKey: xxx + secretKey: xxx + region: xxx + bucket: xxx +# 接口文档配置 +knife4j: + enable: true + openapi: + title: "接口文档" + version: 1.0 + group: + default: + api-rule: package + api-rule-resources: + - com.yupi.springbootinit.controller + + +## 讯飞 AI 配置 +xun-fei: + client: + appId: 6b30952a + apiSecret: Mzk2YjZhN2ZmODgwNGI2YmUzNDQ0Yjk1 + apiKey: faa9c010048c45e709ebf4585aa03e41 diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt new file mode 100644 index 0000000..1881b7f --- /dev/null +++ b/src/main/resources/banner.txt @@ -0,0 +1,5 @@ + ___ _ _ + / __| _ _ _ _ | || | ___ + \__ \ | +| | | ' \ | __ | / -_) + |___/ \_,_| |_||_| |_||_| \___| +_|"""""|_|"""""|_|"""""|_|"""""|_|"""""| \ No newline at end of file diff --git a/src/main/resources/mapper/ChartMapper.xml b/src/main/resources/mapper/ChartMapper.xml new file mode 100644 index 0000000..228ed32 --- /dev/null +++ b/src/main/resources/mapper/ChartMapper.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + id,`name`,goal,chartData,chartType,genChart,genResult, + userId,createTime,updateTime,isDelete + + diff --git a/src/main/resources/mapper/PostFavourMapper.xml b/src/main/resources/mapper/PostFavourMapper.xml new file mode 100644 index 0000000..b7985c9 --- /dev/null +++ b/src/main/resources/mapper/PostFavourMapper.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + id,postId,userId, + createTime,updateTime + + + + diff --git a/src/main/resources/mapper/PostMapper.xml b/src/main/resources/mapper/PostMapper.xml new file mode 100644 index 0000000..d57b866 --- /dev/null +++ b/src/main/resources/mapper/PostMapper.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + id,title,content,tags, + thumbNum,favourNum,userId, + createTime,updateTime,isDelete + + + + diff --git a/src/main/resources/mapper/PostThumbMapper.xml b/src/main/resources/mapper/PostThumbMapper.xml new file mode 100644 index 0000000..07909ab --- /dev/null +++ b/src/main/resources/mapper/PostThumbMapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + id,postId, + userId,createTime,updateTime + + diff --git a/src/main/resources/mapper/UserMapper.xml b/src/main/resources/mapper/UserMapper.xml new file mode 100644 index 0000000..09df560 --- /dev/null +++ b/src/main/resources/mapper/UserMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + id,userAccount,userPassword,userName,userAvatar,userRole, + createTime,updateTime,isDelete + + diff --git a/src/main/resources/templates/TemplateController.java.ftl b/src/main/resources/templates/TemplateController.java.ftl new file mode 100644 index 0000000..e5714b5 --- /dev/null +++ b/src/main/resources/templates/TemplateController.java.ftl @@ -0,0 +1,239 @@ +package ${packageName}.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import ${packageName}.annotation.AuthCheck; +import ${packageName}.common.BaseResponse; +import ${packageName}.common.DeleteRequest; +import ${packageName}.common.ErrorCode; +import ${packageName}.common.ResultUtils; +import ${packageName}.constant.UserConstant; +import ${packageName}.exception.BusinessException; +import ${packageName}.exception.ThrowUtils; +import ${packageName}.model.dto.${dataKey}.${upperDataKey}AddRequest; +import ${packageName}.model.dto.${dataKey}.${upperDataKey}EditRequest; +import ${packageName}.model.dto.${dataKey}.${upperDataKey}QueryRequest; +import ${packageName}.model.dto.${dataKey}.${upperDataKey}UpdateRequest; +import ${packageName}.model.entity.${upperDataKey}; +import ${packageName}.model.entity.User; +import ${packageName}.model.vo.${upperDataKey}VO; +import ${packageName}.service.${upperDataKey}Service; +import ${packageName}.service.UserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; + +/** + * ${dataName}接口 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +@RestController +@RequestMapping("/${dataKey}") +@Slf4j +public class ${upperDataKey}Controller { + + @Resource + private ${upperDataKey}Service ${dataKey}Service; + + @Resource + private UserService userService; + + // region 增删改查 + + /** + * 创建${dataName} + * + * @param ${dataKey}AddRequest + * @param request + * @return + */ + @PostMapping("/add") + public BaseResponse add${upperDataKey}(@RequestBody ${upperDataKey}AddRequest ${dataKey}AddRequest, HttpServletRequest request) { + ThrowUtils.throwIf(${dataKey}AddRequest == null, ErrorCode.PARAMS_ERROR); + // todo 在此处将实体类和 DTO 进行转换 + ${upperDataKey} ${dataKey} = new ${upperDataKey}(); + BeanUtils.copyProperties(${dataKey}AddRequest, ${dataKey}); + // 数据校验 + ${dataKey}Service.valid${upperDataKey}(${dataKey}, true); + // todo 填充默认值 + User loginUser = userService.getLoginUser(request); + ${dataKey}.setUserId(loginUser.getId()); + // 写入数据库 + boolean result = ${dataKey}Service.save(${dataKey}); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + // 返回新写入的数据 id + long new${upperDataKey}Id = ${dataKey}.getId(); + return ResultUtils.success(new${upperDataKey}Id); + } + + /** + * 删除${dataName} + * + * @param deleteRequest + * @param request + * @return + */ + @PostMapping("/delete") + public BaseResponse delete${upperDataKey}(@RequestBody DeleteRequest deleteRequest, HttpServletRequest request) { + if (deleteRequest == null || deleteRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User user = userService.getLoginUser(request); + long id = deleteRequest.getId(); + // 判断是否存在 + ${upperDataKey} old${upperDataKey} = ${dataKey}Service.getById(id); + ThrowUtils.throwIf(old${upperDataKey} == null, ErrorCode.NOT_FOUND_ERROR); + // 仅本人或管理员可删除 + if (!old${upperDataKey}.getUserId().equals(user.getId()) && !userService.isAdmin(request)) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + // 操作数据库 + boolean result = ${dataKey}Service.removeById(id); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + return ResultUtils.success(true); + } + + /** + * 更新${dataName}(仅管理员可用) + * + * @param ${dataKey}UpdateRequest + * @return + */ + @PostMapping("/update") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public BaseResponse update${upperDataKey}(@RequestBody ${upperDataKey}UpdateRequest ${dataKey}UpdateRequest) { + if (${dataKey}UpdateRequest == null || ${dataKey}UpdateRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + // todo 在此处将实体类和 DTO 进行转换 + ${upperDataKey} ${dataKey} = new ${upperDataKey}(); + BeanUtils.copyProperties(${dataKey}UpdateRequest, ${dataKey}); + // 数据校验 + ${dataKey}Service.valid${upperDataKey}(${dataKey}, false); + // 判断是否存在 + long id = ${dataKey}UpdateRequest.getId(); + ${upperDataKey} old${upperDataKey} = ${dataKey}Service.getById(id); + ThrowUtils.throwIf(old${upperDataKey} == null, ErrorCode.NOT_FOUND_ERROR); + // 操作数据库 + boolean result = ${dataKey}Service.updateById(${dataKey}); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + return ResultUtils.success(true); + } + + /** + * 根据 id 获取${dataName}(封装类) + * + * @param id + * @return + */ + @GetMapping("/get/vo") + public BaseResponse<${upperDataKey}VO> get${upperDataKey}VOById(long id, HttpServletRequest request) { + ThrowUtils.throwIf(id <= 0, ErrorCode.PARAMS_ERROR); + // 查询数据库 + ${upperDataKey} ${dataKey} = ${dataKey}Service.getById(id); + ThrowUtils.throwIf(${dataKey} == null, ErrorCode.NOT_FOUND_ERROR); + // 获取封装类 + return ResultUtils.success(${dataKey}Service.get${upperDataKey}VO(${dataKey}, request)); + } + + /** + * 分页获取${dataName}列表(仅管理员可用) + * + * @param ${dataKey}QueryRequest + * @return + */ + @PostMapping("/list/page") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public BaseResponse> list${upperDataKey}ByPage(@RequestBody ${upperDataKey}QueryRequest ${dataKey}QueryRequest) { + long current = ${dataKey}QueryRequest.getCurrent(); + long size = ${dataKey}QueryRequest.getPageSize(); + // 查询数据库 + Page<${upperDataKey}> ${dataKey}Page = ${dataKey}Service.page(new Page<>(current, size), + ${dataKey}Service.getQueryWrapper(${dataKey}QueryRequest)); + return ResultUtils.success(${dataKey}Page); + } + + /** + * 分页获取${dataName}列表(封装类) + * + * @param ${dataKey}QueryRequest + * @param request + * @return + */ + @PostMapping("/list/page/vo") + public BaseResponse> list${upperDataKey}VOByPage(@RequestBody ${upperDataKey}QueryRequest ${dataKey}QueryRequest, + HttpServletRequest request) { + long current = ${dataKey}QueryRequest.getCurrent(); + long size = ${dataKey}QueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + // 查询数据库 + Page<${upperDataKey}> ${dataKey}Page = ${dataKey}Service.page(new Page<>(current, size), + ${dataKey}Service.getQueryWrapper(${dataKey}QueryRequest)); + // 获取封装类 + return ResultUtils.success(${dataKey}Service.get${upperDataKey}VOPage(${dataKey}Page, request)); + } + + /** + * 分页获取当前登录用户创建的${dataName}列表 + * + * @param ${dataKey}QueryRequest + * @param request + * @return + */ + @PostMapping("/my/list/page/vo") + public BaseResponse> listMy${upperDataKey}VOByPage(@RequestBody ${upperDataKey}QueryRequest ${dataKey}QueryRequest, + HttpServletRequest request) { + ThrowUtils.throwIf(${dataKey}QueryRequest == null, ErrorCode.PARAMS_ERROR); + // 补充查询条件,只查询当前登录用户的数据 + User loginUser = userService.getLoginUser(request); + ${dataKey}QueryRequest.setUserId(loginUser.getId()); + long current = ${dataKey}QueryRequest.getCurrent(); + long size = ${dataKey}QueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + // 查询数据库 + Page<${upperDataKey}> ${dataKey}Page = ${dataKey}Service.page(new Page<>(current, size), + ${dataKey}Service.getQueryWrapper(${dataKey}QueryRequest)); + // 获取封装类 + return ResultUtils.success(${dataKey}Service.get${upperDataKey}VOPage(${dataKey}Page, request)); + } + + /** + * 编辑${dataName}(给用户使用) + * + * @param ${dataKey}EditRequest + * @param request + * @return + */ + @PostMapping("/edit") + public BaseResponse edit${upperDataKey}(@RequestBody ${upperDataKey}EditRequest ${dataKey}EditRequest, HttpServletRequest request) { + if (${dataKey}EditRequest == null || ${dataKey}EditRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + // todo 在此处将实体类和 DTO 进行转换 + ${upperDataKey} ${dataKey} = new ${upperDataKey}(); + BeanUtils.copyProperties(${dataKey}EditRequest, ${dataKey}); + // 数据校验 + ${dataKey}Service.valid${upperDataKey}(${dataKey}, false); + User loginUser = userService.getLoginUser(request); + // 判断是否存在 + long id = ${dataKey}EditRequest.getId(); + ${upperDataKey} old${upperDataKey} = ${dataKey}Service.getById(id); + ThrowUtils.throwIf(old${upperDataKey} == null, ErrorCode.NOT_FOUND_ERROR); + // 仅本人或管理员可编辑 + if (!old${upperDataKey}.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + // 操作数据库 + boolean result = ${dataKey}Service.updateById(${dataKey}); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + return ResultUtils.success(true); + } + + // endregion +} diff --git a/src/main/resources/templates/TemplateService.java.ftl b/src/main/resources/templates/TemplateService.java.ftl new file mode 100644 index 0000000..a688488 --- /dev/null +++ b/src/main/resources/templates/TemplateService.java.ftl @@ -0,0 +1,53 @@ +package ${packageName}.service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import ${packageName}.model.dto.${dataKey}.${upperDataKey}QueryRequest; +import ${packageName}.model.entity.${upperDataKey}; +import ${packageName}.model.vo.${upperDataKey}VO; + +import javax.servlet.http.HttpServletRequest; + +/** + * ${dataName}服务 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +public interface ${upperDataKey}Service extends IService<${upperDataKey}> { + + /** + * 校验数据 + * + * @param ${dataKey} + * @param add 对创建的数据进行校验 + */ + void valid${upperDataKey}(${upperDataKey} ${dataKey}, boolean add); + + /** + * 获取查询条件 + * + * @param ${dataKey}QueryRequest + * @return + */ + QueryWrapper<${upperDataKey}> getQueryWrapper(${upperDataKey}QueryRequest ${dataKey}QueryRequest); + + /** + * 获取${dataName}封装 + * + * @param ${dataKey} + * @param request + * @return + */ + ${upperDataKey}VO get${upperDataKey}VO(${upperDataKey} ${dataKey}, HttpServletRequest request); + + /** + * 分页获取${dataName}封装 + * + * @param ${dataKey}Page + * @param request + * @return + */ + Page<${upperDataKey}VO> get${upperDataKey}VOPage(Page<${upperDataKey}> ${dataKey}Page, HttpServletRequest request); +} diff --git a/src/main/resources/templates/TemplateServiceImpl.java.ftl b/src/main/resources/templates/TemplateServiceImpl.java.ftl new file mode 100644 index 0000000..3921f10 --- /dev/null +++ b/src/main/resources/templates/TemplateServiceImpl.java.ftl @@ -0,0 +1,224 @@ +package ${packageName}.service.impl; + +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import ${packageName}.common.ErrorCode; +import ${packageName}.constant.CommonConstant; +import ${packageName}.exception.ThrowUtils; +import ${packageName}.mapper.${upperDataKey}Mapper; +import ${packageName}.model.dto.${dataKey}.${upperDataKey}QueryRequest; +import ${packageName}.model.entity.${upperDataKey}; +import ${packageName}.model.entity.${upperDataKey}Favour; +import ${packageName}.model.entity.${upperDataKey}Thumb; +import ${packageName}.model.entity.User; +import ${packageName}.model.vo.${upperDataKey}VO; +import ${packageName}.model.vo.UserVO; +import ${packageName}.service.${upperDataKey}Service; +import ${packageName}.service.UserService; +import ${packageName}.utils.SqlUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * ${dataName}服务实现 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +@Service +@Slf4j +public class ${upperDataKey}ServiceImpl extends ServiceImpl<${upperDataKey}Mapper, ${upperDataKey}> implements ${upperDataKey}Service { + + @Resource + private UserService userService; + + /** + * 校验数据 + * + * @param ${dataKey} + * @param add 对创建的数据进行校验 + */ + @Override + public void valid${upperDataKey}(${upperDataKey} ${dataKey}, boolean add) { + ThrowUtils.throwIf(${dataKey} == null, ErrorCode.PARAMS_ERROR); + // todo 从对象中取值 + String title = ${dataKey}.getTitle(); + // 创建数据时,参数不能为空 + if (add) { + // todo 补充校验规则 + ThrowUtils.throwIf(StringUtils.isBlank(title), ErrorCode.PARAMS_ERROR); + } + // 修改数据时,有参数则校验 + // todo 补充校验规则 + if (StringUtils.isNotBlank(title)) { + ThrowUtils.throwIf(title.length() > 80, ErrorCode.PARAMS_ERROR, "标题过长"); + } + } + + /** + * 获取查询条件 + * + * @param ${dataKey}QueryRequest + * @return + */ + @Override + public QueryWrapper<${upperDataKey}> getQueryWrapper(${upperDataKey}QueryRequest ${dataKey}QueryRequest) { + QueryWrapper<${upperDataKey}> queryWrapper = new QueryWrapper<>(); + if (${dataKey}QueryRequest == null) { + return queryWrapper; + } + // todo 从对象中取值 + Long id = ${dataKey}QueryRequest.getId(); + Long notId = ${dataKey}QueryRequest.getNotId(); + String title = ${dataKey}QueryRequest.getTitle(); + String content = ${dataKey}QueryRequest.getContent(); + String searchText = ${dataKey}QueryRequest.getSearchText(); + String sortField = ${dataKey}QueryRequest.getSortField(); + String sortOrder = ${dataKey}QueryRequest.getSortOrder(); + List tagList = ${dataKey}QueryRequest.getTags(); + Long userId = ${dataKey}QueryRequest.getUserId(); + // todo 补充需要的查询条件 + // 从多字段中搜索 + if (StringUtils.isNotBlank(searchText)) { + // 需要拼接查询条件 + queryWrapper.and(qw -> qw.like("title", searchText).or().like("content", searchText)); + } + // 模糊查询 + queryWrapper.like(StringUtils.isNotBlank(title), "title", title); + queryWrapper.like(StringUtils.isNotBlank(content), "content", content); + // JSON 数组查询 + if (CollUtil.isNotEmpty(tagList)) { + for (String tag : tagList) { + queryWrapper.like("tags", "\"" + tag + "\""); + } + } + // 精确查询 + queryWrapper.ne(ObjectUtils.isNotEmpty(notId), "id", notId); + queryWrapper.eq(ObjectUtils.isNotEmpty(id), "id", id); + queryWrapper.eq(ObjectUtils.isNotEmpty(userId), "userId", userId); + // 排序规则 + queryWrapper.orderBy(SqlUtils.validSortField(sortField), + sortOrder.equals(CommonConstant.SORT_ORDER_ASC), + sortField); + return queryWrapper; + } + + /** + * 获取${dataName}封装 + * + * @param ${dataKey} + * @param request + * @return + */ + @Override + public ${upperDataKey}VO get${upperDataKey}VO(${upperDataKey} ${dataKey}, HttpServletRequest request) { + // 对象转封装类 + ${upperDataKey}VO ${dataKey}VO = ${upperDataKey}VO.objToVo(${dataKey}); + + // todo 可以根据需要为封装对象补充值,不需要的内容可以删除 + // region 可选 + // 1. 关联查询用户信息 + Long userId = ${dataKey}.getUserId(); + User user = null; + if (userId != null && userId > 0) { + user = userService.getById(userId); + } + UserVO userVO = userService.getUserVO(user); + ${dataKey}VO.setUser(userVO); + // 2. 已登录,获取用户点赞、收藏状态 + long ${dataKey}Id = ${dataKey}.getId(); + User loginUser = userService.getLoginUserPermitNull(request); + if (loginUser != null) { + // 获取点赞 + QueryWrapper<${upperDataKey}Thumb> ${dataKey}ThumbQueryWrapper = new QueryWrapper<>(); + ${dataKey}ThumbQueryWrapper.in("${dataKey}Id", ${dataKey}Id); + ${dataKey}ThumbQueryWrapper.eq("userId", loginUser.getId()); + ${upperDataKey}Thumb ${dataKey}Thumb = ${dataKey}ThumbMapper.selectOne(${dataKey}ThumbQueryWrapper); + ${dataKey}VO.setHasThumb(${dataKey}Thumb != null); + // 获取收藏 + QueryWrapper<${upperDataKey}Favour> ${dataKey}FavourQueryWrapper = new QueryWrapper<>(); + ${dataKey}FavourQueryWrapper.in("${dataKey}Id", ${dataKey}Id); + ${dataKey}FavourQueryWrapper.eq("userId", loginUser.getId()); + ${upperDataKey}Favour ${dataKey}Favour = ${dataKey}FavourMapper.selectOne(${dataKey}FavourQueryWrapper); + ${dataKey}VO.setHasFavour(${dataKey}Favour != null); + } + // endregion + + return ${dataKey}VO; + } + + /** + * 分页获取${dataName}封装 + * + * @param ${dataKey}Page + * @param request + * @return + */ + @Override + public Page<${upperDataKey}VO> get${upperDataKey}VOPage(Page<${upperDataKey}> ${dataKey}Page, HttpServletRequest request) { + List<${upperDataKey}> ${dataKey}List = ${dataKey}Page.getRecords(); + Page<${upperDataKey}VO> ${dataKey}VOPage = new Page<>(${dataKey}Page.getCurrent(), ${dataKey}Page.getSize(), ${dataKey}Page.getTotal()); + if (CollUtil.isEmpty(${dataKey}List)) { + return ${dataKey}VOPage; + } + // 对象列表 => 封装对象列表 + List<${upperDataKey}VO> ${dataKey}VOList = ${dataKey}List.stream().map(${dataKey} -> { + return ${upperDataKey}VO.objToVo(${dataKey}); + }).collect(Collectors.toList()); + + // todo 可以根据需要为封装对象补充值,不需要的内容可以删除 + // region 可选 + // 1. 关联查询用户信息 + Set userIdSet = ${dataKey}List.stream().map(${upperDataKey}::getUserId).collect(Collectors.toSet()); + Map> userIdUserListMap = userService.listByIds(userIdSet).stream() + .collect(Collectors.groupingBy(User::getId)); + // 2. 已登录,获取用户点赞、收藏状态 + Map ${dataKey}IdHasThumbMap = new HashMap<>(); + Map ${dataKey}IdHasFavourMap = new HashMap<>(); + User loginUser = userService.getLoginUserPermitNull(request); + if (loginUser != null) { + Set ${dataKey}IdSet = ${dataKey}List.stream().map(${upperDataKey}::getId).collect(Collectors.toSet()); + loginUser = userService.getLoginUser(request); + // 获取点赞 + QueryWrapper<${upperDataKey}Thumb> ${dataKey}ThumbQueryWrapper = new QueryWrapper<>(); + ${dataKey}ThumbQueryWrapper.in("${dataKey}Id", ${dataKey}IdSet); + ${dataKey}ThumbQueryWrapper.eq("userId", loginUser.getId()); + List<${upperDataKey}Thumb> ${dataKey}${upperDataKey}ThumbList = ${dataKey}ThumbMapper.selectList(${dataKey}ThumbQueryWrapper); + ${dataKey}${upperDataKey}ThumbList.forEach(${dataKey}${upperDataKey}Thumb -> ${dataKey}IdHasThumbMap.put(${dataKey}${upperDataKey}Thumb.get${upperDataKey}Id(), true)); + // 获取收藏 + QueryWrapper<${upperDataKey}Favour> ${dataKey}FavourQueryWrapper = new QueryWrapper<>(); + ${dataKey}FavourQueryWrapper.in("${dataKey}Id", ${dataKey}IdSet); + ${dataKey}FavourQueryWrapper.eq("userId", loginUser.getId()); + List<${upperDataKey}Favour> ${dataKey}FavourList = ${dataKey}FavourMapper.selectList(${dataKey}FavourQueryWrapper); + ${dataKey}FavourList.forEach(${dataKey}Favour -> ${dataKey}IdHasFavourMap.put(${dataKey}Favour.get${upperDataKey}Id(), true)); + } + // 填充信息 + ${dataKey}VOList.forEach(${dataKey}VO -> { + Long userId = ${dataKey}VO.getUserId(); + User user = null; + if (userIdUserListMap.containsKey(userId)) { + user = userIdUserListMap.get(userId).get(0); + } + ${dataKey}VO.setUser(userService.getUserVO(user)); + ${dataKey}VO.setHasThumb(${dataKey}IdHasThumbMap.getOrDefault(${dataKey}VO.getId(), false)); + ${dataKey}VO.setHasFavour(${dataKey}IdHasFavourMap.getOrDefault(${dataKey}VO.getId(), false)); + }); + // endregion + + ${dataKey}VOPage.setRecords(${dataKey}VOList); + return ${dataKey}VOPage; + } + +} diff --git a/src/main/resources/templates/model/TemplateAddRequest.java.ftl b/src/main/resources/templates/model/TemplateAddRequest.java.ftl new file mode 100644 index 0000000..a527ded --- /dev/null +++ b/src/main/resources/templates/model/TemplateAddRequest.java.ftl @@ -0,0 +1,33 @@ +package ${packageName}.model.dto.${dataKey}; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 创建${dataName}请求 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +@Data +public class ${upperDataKey}AddRequest implements Serializable { + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 + */ + private List tags; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/resources/templates/model/TemplateEditRequest.java.ftl b/src/main/resources/templates/model/TemplateEditRequest.java.ftl new file mode 100644 index 0000000..f955c53 --- /dev/null +++ b/src/main/resources/templates/model/TemplateEditRequest.java.ftl @@ -0,0 +1,38 @@ +package ${packageName}.model.dto.${dataKey}; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 编辑${dataName}请求 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +@Data +public class ${upperDataKey}EditRequest implements Serializable { + + /** + * id + */ + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 + */ + private List tags; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/resources/templates/model/TemplateQueryRequest.java.ftl b/src/main/resources/templates/model/TemplateQueryRequest.java.ftl new file mode 100644 index 0000000..3ff9b8e --- /dev/null +++ b/src/main/resources/templates/model/TemplateQueryRequest.java.ftl @@ -0,0 +1,56 @@ +package ${packageName}.model.dto.${dataKey}; + +import ${packageName}.common.PageRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.List; + +/** + * 查询${dataName}请求 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class ${upperDataKey}QueryRequest extends PageRequest implements Serializable { + + /** + * id + */ + private Long id; + + /** + * id + */ + private Long notId; + + /** + * 搜索词 + */ + private String searchText; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 + */ + private List tags; + + /** + * 创建用户 id + */ + private Long userId; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/resources/templates/model/TemplateUpdateRequest.java.ftl b/src/main/resources/templates/model/TemplateUpdateRequest.java.ftl new file mode 100644 index 0000000..9188baf --- /dev/null +++ b/src/main/resources/templates/model/TemplateUpdateRequest.java.ftl @@ -0,0 +1,38 @@ +package ${packageName}.model.dto.${dataKey}; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 更新${dataName}请求 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +@Data +public class ${upperDataKey}UpdateRequest implements Serializable { + + /** + * id + */ + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 + */ + private List tags; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/resources/templates/model/TemplateVO.java.ftl b/src/main/resources/templates/model/TemplateVO.java.ftl new file mode 100644 index 0000000..9f04289 --- /dev/null +++ b/src/main/resources/templates/model/TemplateVO.java.ftl @@ -0,0 +1,93 @@ +package ${packageName}.model.vo; + +import cn.hutool.json.JSONUtil; +import ${packageName}.model.entity.${upperDataKey}; +import lombok.Data; +import org.springframework.beans.BeanUtils; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * ${dataName}视图 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +@Data +public class ${upperDataKey}VO implements Serializable { + + /** + * id + */ + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 创建用户 id + */ + private Long userId; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 标签列表 + */ + private List tagList; + + /** + * 创建用户信息 + */ + private UserVO user; + + /** + * 封装类转对象 + * + * @param ${dataKey}VO + * @return + */ + public static ${upperDataKey} voToObj(${upperDataKey}VO ${dataKey}VO) { + if (${dataKey}VO == null) { + return null; + } + ${upperDataKey} ${dataKey} = new ${upperDataKey}(); + BeanUtils.copyProperties(${dataKey}VO, ${dataKey}); + List tagList = ${dataKey}VO.getTagList(); + ${dataKey}.setTags(JSONUtil.toJsonStr(tagList)); + return ${dataKey}; + } + + /** + * 对象转封装类 + * + * @param ${dataKey} + * @return + */ + public static ${upperDataKey}VO objToVo(${upperDataKey} ${dataKey}) { + if (${dataKey} == null) { + return null; + } + ${upperDataKey}VO ${dataKey}VO = new ${upperDataKey}VO(); + BeanUtils.copyProperties(${dataKey}, ${dataKey}VO); + ${dataKey}VO.setTagList(JSONUtil.toList(${dataKey}.getTags(), String.class)); + return ${dataKey}VO; + } +} diff --git a/src/main/resources/test_excel.xlsx b/src/main/resources/test_excel.xlsx new file mode 100644 index 0000000..9e4c4c6 Binary files /dev/null and b/src/main/resources/test_excel.xlsx differ diff --git a/src/main/resources/网站数据.xlsx b/src/main/resources/网站数据.xlsx new file mode 100644 index 0000000..e8df245 Binary files /dev/null and b/src/main/resources/网站数据.xlsx differ diff --git a/src/test/.DS_Store b/src/test/.DS_Store new file mode 100644 index 0000000..bfeab2c Binary files /dev/null and b/src/test/.DS_Store differ diff --git a/src/test/java/com/yupi/springbootinit/MainApplicationTests.java b/src/test/java/com/yupi/springbootinit/MainApplicationTests.java new file mode 100644 index 0000000..c1133bf --- /dev/null +++ b/src/test/java/com/yupi/springbootinit/MainApplicationTests.java @@ -0,0 +1,19 @@ +package com.yupi.springbootinit; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * 主类测试 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@SpringBootTest +class MainApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/src/test/java/com/yupi/springbootinit/manager/AiManagerTest.java b/src/test/java/com/yupi/springbootinit/manager/AiManagerTest.java new file mode 100644 index 0000000..0432f1e --- /dev/null +++ b/src/test/java/com/yupi/springbootinit/manager/AiManagerTest.java @@ -0,0 +1,36 @@ +package com.yupi.springbootinit.manager; + +import com.yupi.springbootinit.MainApplication; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import javax.annotation.Resource; + +/** + * @author leikooo + * @date 2024/9/12 + * @description + */ +@SpringBootTest(classes = MainApplication.class) +public class AiManagerTest { + @Resource + private AiManager aiManager; + + + // import org.junit.jupiter.api.Test; + @Test + public void test() { + String c = "分析需求:\n" + + "分析网站用户的增长情况 \n" + + "请使用 柱状图 \n" + + "原始数据:\n" + + "日期,用户数\n" + + "1号,10\n" + + " 2号,20\n" + + " 3号,30"; + String s = aiManager.sendMsgToXingHuo(true, c); + System.out.println("s = " + s); + } + +} + diff --git a/src/test/java/com/yupi/springbootinit/manager/CosManagerTest.java b/src/test/java/com/yupi/springbootinit/manager/CosManagerTest.java new file mode 100644 index 0000000..e803a4b --- /dev/null +++ b/src/test/java/com/yupi/springbootinit/manager/CosManagerTest.java @@ -0,0 +1,23 @@ +package com.yupi.springbootinit.manager; + +import javax.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * Cos 操作测试 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@SpringBootTest +class CosManagerTest { + + @Resource + private CosManager cosManager; + + @Test + void putObject() { + cosManager.putObject("test", "test.json"); + } +} \ No newline at end of file diff --git a/src/test/java/com/yupi/springbootinit/manager/RedisLimiterManagerTest.java b/src/test/java/com/yupi/springbootinit/manager/RedisLimiterManagerTest.java new file mode 100644 index 0000000..82529f9 --- /dev/null +++ b/src/test/java/com/yupi/springbootinit/manager/RedisLimiterManagerTest.java @@ -0,0 +1,32 @@ +package com.yupi.springbootinit.manager; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import javax.annotation.Resource; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +class RedisLimiterManagerTest { + + @Resource + private RedisLimiterManager redisLimiterManager; + + @Test + void doRateLimit() throws InterruptedException { + String userId = "1"; + for (int i = 0; i < 2; i++) { + redisLimiterManager.doRateLimit(userId); + System.out.println("成功"); + } + Thread.sleep(1000); + for (int i = 0; i < 5; i++) { + redisLimiterManager.doRateLimit(userId); + System.out.println("成功"); + } + + + + } +} \ No newline at end of file diff --git a/src/test/java/com/yupi/springbootinit/mapper/PostFavourMapperTest.java b/src/test/java/com/yupi/springbootinit/mapper/PostFavourMapperTest.java new file mode 100644 index 0000000..96bd2f3 --- /dev/null +++ b/src/test/java/com/yupi/springbootinit/mapper/PostFavourMapperTest.java @@ -0,0 +1,33 @@ +package com.yupi.springbootinit.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yupi.springbootinit.model.entity.Post; +import javax.annotation.Resource; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * 帖子收藏数据库操作测试 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@SpringBootTest +class PostFavourMapperTest { + + @Resource + private PostFavourMapper postFavourMapper; + + @Test + void listUserFavourPostByPage() { + IPage page = new Page<>(2, 1); + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("id", 1); + queryWrapper.like("content", "a"); + IPage result = postFavourMapper.listFavourPostByPage(page, queryWrapper, 1); + Assertions.assertNotNull(result); + } +} \ No newline at end of file diff --git a/src/test/java/com/yupi/springbootinit/mapper/PostMapperTest.java b/src/test/java/com/yupi/springbootinit/mapper/PostMapperTest.java new file mode 100644 index 0000000..b0186ba --- /dev/null +++ b/src/test/java/com/yupi/springbootinit/mapper/PostMapperTest.java @@ -0,0 +1,28 @@ +package com.yupi.springbootinit.mapper; + +import com.yupi.springbootinit.model.entity.Post; +import java.util.Date; +import java.util.List; +import javax.annotation.Resource; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * 帖子数据库操作测试 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@SpringBootTest +class PostMapperTest { + + @Resource + private PostMapper postMapper; + + @Test + void listPostWithDelete() { + List postList = postMapper.listPostWithDelete(new Date()); + Assertions.assertNotNull(postList); + } +} \ No newline at end of file diff --git a/src/test/java/com/yupi/springbootinit/service/PostFavourServiceTest.java b/src/test/java/com/yupi/springbootinit/service/PostFavourServiceTest.java new file mode 100644 index 0000000..e5434e2 --- /dev/null +++ b/src/test/java/com/yupi/springbootinit/service/PostFavourServiceTest.java @@ -0,0 +1,46 @@ +package com.yupi.springbootinit.service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yupi.springbootinit.model.entity.Post; + +import javax.annotation.Resource; + +import com.yupi.springbootinit.model.entity.User; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * 帖子收藏服务测试 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@SpringBootTest +class PostFavourServiceTest { + + @Resource + private PostFavourService postFavourService; + + private static final User loginUser = new User(); + + @BeforeAll + static void setUp() { + loginUser.setId(1L); + } + + @Test + void doPostFavour() { + int i = postFavourService.doPostFavour(1L, loginUser); + Assertions.assertTrue(i >= 0); + } + + @Test + void listFavourPostByPage() { + QueryWrapper postQueryWrapper = new QueryWrapper<>(); + postQueryWrapper.eq("id", 1L); + postFavourService.listFavourPostByPage(Page.of(0, 1), postQueryWrapper, loginUser.getId()); + } +} diff --git a/src/test/java/com/yupi/springbootinit/service/PostThumbServiceTest.java b/src/test/java/com/yupi/springbootinit/service/PostThumbServiceTest.java new file mode 100644 index 0000000..acd0774 --- /dev/null +++ b/src/test/java/com/yupi/springbootinit/service/PostThumbServiceTest.java @@ -0,0 +1,35 @@ +package com.yupi.springbootinit.service; + +import javax.annotation.Resource; + +import com.yupi.springbootinit.model.entity.User; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * 帖子点赞服务测试 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@SpringBootTest +class PostThumbServiceTest { + + @Resource + private PostThumbService postThumbService; + + private static final User loginUser = new User(); + + @BeforeAll + static void setUp() { + loginUser.setId(1L); + } + + @Test + void doPostThumb() { + int i = postThumbService.doPostThumb(1L, loginUser); + Assertions.assertTrue(i >= 0); + } +} diff --git a/src/test/java/com/yupi/springbootinit/service/UserServiceTest.java b/src/test/java/com/yupi/springbootinit/service/UserServiceTest.java new file mode 100644 index 0000000..0b152c5 --- /dev/null +++ b/src/test/java/com/yupi/springbootinit/service/UserServiceTest.java @@ -0,0 +1,35 @@ +package com.yupi.springbootinit.service; + +import javax.annotation.Resource; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * 用户服务测试 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@SpringBootTest +public class UserServiceTest { + + @Resource + private UserService userService; + + @Test + void userRegister() { + String userAccount = "yupi"; + String userPassword = ""; + String checkPassword = "123456"; + try { + long result = userService.userRegister(userAccount, userPassword, checkPassword); + Assertions.assertEquals(-1, result); + userAccount = "yu"; + result = userService.userRegister(userAccount, userPassword, checkPassword); + Assertions.assertEquals(-1, result); + } catch (Exception e) { + + } + } +} diff --git a/src/test/java/com/yupi/springbootinit/utils/EasyExcelTest.java b/src/test/java/com/yupi/springbootinit/utils/EasyExcelTest.java new file mode 100644 index 0000000..b74eb9f --- /dev/null +++ b/src/test/java/com/yupi/springbootinit/utils/EasyExcelTest.java @@ -0,0 +1,34 @@ +package com.yupi.springbootinit.utils; + +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.support.ExcelTypeEnum; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.util.ResourceUtils; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.List; +import java.util.Map; + +/** + * EasyExcel 测试 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@SpringBootTest +public class EasyExcelTest { + + @Test + public void doImport() throws FileNotFoundException { + File file = ResourceUtils.getFile("classpath:test_excel.xlsx"); + List> list = EasyExcel.read(file) + .excelType(ExcelTypeEnum.XLSX) + .sheet() + .headRowNumber(0) + .doReadSync(); + System.out.println(list); + } + +} \ No newline at end of file