commit 5d485c0edc8c14afea611b8e5d5fe70c26e9fbb4
Author: barney <15270405776@163.com>
Date: Sat Sep 2 22:35:48 2023 +0800
init
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..ae748dd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,144 @@
+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..748395d
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,13 @@
+# Docker 镜像构建
+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/springboot-init-0.0.1-SNAPSHOT.jar","--spring.profiles.active=prod"]
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..567cd06
--- /dev/null
+++ b/README.md
@@ -0,0 +1,158 @@
+# SpringBoot 项目初始模板
+
+基于 Java SpringBoot 的项目初始模板,整合了常用框架和主流业务的示例代码。
+
+只需 1 分钟即可完成内容网站的后端!!!大家还可以在此基础上快速开发自己的项目。
+
+[toc]
+
+## 模板特点
+
+### 主流框架 & 特性
+
+- Spring Boot 2.7.x(贼新)
+- Spring MVC
+- MyBatis + MyBatis Plus 数据访问(开启分页)
+- Spring Boot 调试工具和项目处理器
+- Spring AOP 切面编程
+- Spring Scheduler 定时任务
+- Spring 事务注解
+
+### 数据存储
+
+- MySQL 数据库
+- Redis 内存数据库
+- Elasticsearch 搜索引擎
+- 腾讯云 COS 对象存储
+
+### 工具类
+
+- Easy Excel 表格处理
+- Hutool 工具库
+- Gson 解析库
+- Apache Commons Lang3 工具类
+- Lombok 注解
+
+### 业务特性
+
+- Spring Session Redis 分布式登录
+- 全局请求响应拦截器(记录日志)
+- 全局异常处理器
+- 自定义错误码
+- 封装通用响应类
+- Swagger + Knife4j 接口文档
+- 自定义权限注解 + 全局校验
+- 全局跨域处理
+- 长整数丢失精度解决
+- 多环境配置
+
+
+## 业务功能
+
+- 提供示例 SQL(用户、帖子、帖子点赞、帖子收藏表)
+- 用户登录、注册、注销、更新、检索、权限管理
+- 帖子创建、删除、编辑、更新、数据库检索、ES 灵活检索
+- 帖子点赞、取消点赞
+- 帖子收藏、取消收藏、检索已收藏帖子
+- 帖子全量同步 ES、增量同步 ES 定时任务
+- 支持微信开放平台登录
+- 支持微信公众号订阅、收发消息、设置菜单
+- 支持分业务的文件上传
+
+### 单元测试
+
+- JUnit5 单元测试
+- 示例单元测试类
+
+### 架构设计
+
+- 合理分层
+
+
+### MySQL 数据库
+
+1)修改 `application.yml` 的数据库配置为你自己的:
+
+```yml
+spring:
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/my_db
+ username: root
+ password: 123456
+```
+
+2)执行 `sql/create_table.sql` 中的数据库语句,自动创建库表
+
+3)启动项目,访问 `http://localhost:8101/api/doc.html` 即可打开接口文档,不需要写前端就能在线调试接口了~
+
+![](doc/swagger.png)
+
+### Redis 分布式登录
+
+1)修改 `application.yml` 的 Redis 配置为你自己的:
+
+```yml
+spring:
+ redis:
+ database: 1
+ host: localhost
+ port: 6379
+ timeout: 5000
+ password: 123456
+```
+
+2)修改 `application.yml` 中的 session 存储方式:
+
+```yml
+spring:
+ session:
+ store-type: redis
+```
+
+3)移除 `MainApplication` 类开头 `@SpringBootApplication` 注解内的 exclude 参数:
+
+修改前:
+
+```java
+@SpringBootApplication(exclude = {RedisAutoConfiguration.class})
+```
+
+修改后:
+
+
+```java
+@SpringBootApplication
+```
+
+### Elasticsearch 搜索引擎
+
+1)修改 `application.yml` 的 Elasticsearch 配置为你自己的:
+
+```yml
+spring:
+ elasticsearch:
+ uris: http://localhost:9200
+ username: root
+ password: 123456
+```
+
+2)复制 `sql/post_es_mapping.json` 文件中的内容,通过调用 Elasticsearch 的接口或者 Kibana Dev Tools 来创建索引(相当于数据库建表)
+
+```
+PUT post_v1
+{
+ 参数见 sql/post_es_mapping.json 文件
+}
+```
+
+这步不会操作的话需要补充下 Elasticsearch 的知识,或者自行百度一下~
+
+3)开启同步任务,将数据库的帖子同步到 Elasticsearch
+
+找到 job 目录下的 `FullSyncPostToEs` 和 `IncSyncPostToEs` 文件,取消掉 `@Component` 注解的注释,再次执行程序即可触发同步:
+
+```java
+// todo 取消注释开启任务
+//@Component
+```
\ 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..9eaa422
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,143 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.2
+
+
+ com.yupi
+ springboot-init
+ 0.0.1-SNAPSHOT
+ springboot-init
+
+ 1.8
+
+
+
+ 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-spring-boot-starter
+ 3.0.3
+
+
+
+ com.qcloud
+ cos_api
+ 5.6.89
+
+
+
+ org.apache.commons
+ commons-lang3
+
+
+
+ com.google.code.gson
+ gson
+ 2.9.1
+
+
+
+ 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..f88556f
--- /dev/null
+++ b/sql/create_table.sql
@@ -0,0 +1,65 @@
+# 数据库初始化
+
+-- 创建库
+create database if not exists my_db;
+
+-- 切换库
+use my_db;
+
+-- 用户表
+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 '密码',
+ unionId varchar(256) null comment '微信开放平台id',
+ mpOpenId varchar(256) null comment '公众号openId',
+ userName varchar(256) null comment '用户昵称',
+ userAvatar varchar(1024) null comment '用户头像',
+ userProfile varchar(512) null comment '用户简介',
+ userRole varchar(256) default 'user' not null comment '用户角色:user/admin/ban',
+ 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_unionId (unionId)
+) comment '用户' collate = utf8mb4_unicode_ci;
+
+-- 帖子表
+create table if not exists post
+(
+ id bigint auto_increment comment 'id' primary key,
+ title varchar(512) null comment '标题',
+ content text null comment '内容',
+ tags varchar(1024) null comment '标签列表(json 数组)',
+ thumbNum int default 0 not null comment '点赞数',
+ favourNum int default 0 not null comment '收藏数',
+ userId bigint not 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 '是否删除',
+ index idx_userId (userId)
+) comment '帖子' collate = utf8mb4_unicode_ci;
+
+-- 帖子点赞表(硬删除)
+create table if not exists post_thumb
+(
+ id bigint auto_increment comment 'id' primary key,
+ postId bigint not null comment '帖子 id',
+ userId bigint not null comment '创建用户 id',
+ createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间',
+ updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
+ index idx_postId (postId),
+ index idx_userId (userId)
+) comment '帖子点赞';
+
+-- 帖子收藏表(硬删除)
+create table if not exists post_favour
+(
+ id bigint auto_increment comment 'id' primary key,
+ postId bigint not null comment '帖子 id',
+ userId bigint not null comment '创建用户 id',
+ createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间',
+ updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
+ index idx_postId (postId),
+ index idx_userId (userId)
+) comment '帖子收藏';
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/cc/bnblogs/springbootinit/MainApplication.java b/src/main/java/cc/bnblogs/springbootinit/MainApplication.java
new file mode 100644
index 0000000..6076255
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/MainApplication.java
@@ -0,0 +1,25 @@
+package cc.bnblogs.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;
+
+/**
+ * 主类(项目启动入口)
+ */
+// todo 如需开启 Redis,须移除 exclude 中的内容
+//@SpringBootApplication(exclude = {RedisAutoConfiguration.class})
+@SpringBootApplication
+@MapperScan("cc.bnblogs.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/cc/bnblogs/springbootinit/annotation/AuthCheck.java b/src/main/java/cc/bnblogs/springbootinit/annotation/AuthCheck.java
new file mode 100644
index 0000000..6177e4c
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/annotation/AuthCheck.java
@@ -0,0 +1,23 @@
+package cc.bnblogs.springbootinit.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 权限校验
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface AuthCheck {
+
+ /**
+ * 必须有某个角色
+ *
+ * @return
+ */
+ String mustRole() default "";
+
+}
+
diff --git a/src/main/java/cc/bnblogs/springbootinit/aop/AuthInterceptor.java b/src/main/java/cc/bnblogs/springbootinit/aop/AuthInterceptor.java
new file mode 100644
index 0000000..4a68fec
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/aop/AuthInterceptor.java
@@ -0,0 +1,66 @@
+package cc.bnblogs.springbootinit.aop;
+
+import cc.bnblogs.springbootinit.common.ErrorCode;
+import cc.bnblogs.springbootinit.model.entity.User;
+import cc.bnblogs.springbootinit.model.enums.UserRoleEnum;
+import cc.bnblogs.springbootinit.annotation.AuthCheck;
+import cc.bnblogs.springbootinit.exception.BusinessException;
+import cc.bnblogs.springbootinit.service.UserService;
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+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.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+/**
+ * 权限校验 AOP
+ */
+@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);
+ // 必须有该权限才通过
+ if (StringUtils.isNotBlank(mustRole)) {
+ UserRoleEnum mustUserRoleEnum = UserRoleEnum.getEnumByValue(mustRole);
+ if (mustUserRoleEnum == null) {
+ throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
+ }
+ String userRole = loginUser.getUserRole();
+ // 如果被封号,直接拒绝
+ if (UserRoleEnum.BAN.equals(mustUserRoleEnum)) {
+ throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
+ }
+ // 必须有管理员权限
+ if (UserRoleEnum.ADMIN.equals(mustUserRoleEnum)) {
+ if (!mustRole.equals(userRole)) {
+ throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
+ }
+ }
+ }
+ // 通过权限校验,放行
+ return joinPoint.proceed();
+ }
+}
+
diff --git a/src/main/java/cc/bnblogs/springbootinit/aop/LogInterceptor.java b/src/main/java/cc/bnblogs/springbootinit/aop/LogInterceptor.java
new file mode 100644
index 0000000..bbac138
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/aop/LogInterceptor.java
@@ -0,0 +1,53 @@
+package cc.bnblogs.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
+ **/
+@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/cc/bnblogs/springbootinit/common/BaseResponse.java b/src/main/java/cc/bnblogs/springbootinit/common/BaseResponse.java
new file mode 100644
index 0000000..48109c5
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/common/BaseResponse.java
@@ -0,0 +1,31 @@
+package cc.bnblogs.springbootinit.common;
+
+import java.io.Serializable;
+import lombok.Data;
+
+/**
+ * 通用返回类
+ */
+@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/cc/bnblogs/springbootinit/common/DeleteRequest.java b/src/main/java/cc/bnblogs/springbootinit/common/DeleteRequest.java
new file mode 100644
index 0000000..e6970b1
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/common/DeleteRequest.java
@@ -0,0 +1,18 @@
+package cc.bnblogs.springbootinit.common;
+
+import java.io.Serializable;
+import lombok.Data;
+
+/**
+ * 删除请求
+ */
+@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/cc/bnblogs/springbootinit/common/ErrorCode.java b/src/main/java/cc/bnblogs/springbootinit/common/ErrorCode.java
new file mode 100644
index 0000000..83b96fe
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/common/ErrorCode.java
@@ -0,0 +1,40 @@
+package cc.bnblogs.springbootinit.common;
+
+/**
+ * 自定义错误码
+ */
+public enum ErrorCode {
+
+ SUCCESS(0, "ok"),
+ PARAMS_ERROR(40000, "请求参数错误"),
+ NOT_LOGIN_ERROR(40100, "未登录"),
+ NO_AUTH_ERROR(40101, "无权限"),
+ NOT_FOUND_ERROR(40400, "请求数据不存在"),
+ 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/cc/bnblogs/springbootinit/common/PageRequest.java b/src/main/java/cc/bnblogs/springbootinit/common/PageRequest.java
new file mode 100644
index 0000000..cc0be7d
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/common/PageRequest.java
@@ -0,0 +1,31 @@
+package cc.bnblogs.springbootinit.common;
+
+import cc.bnblogs.springbootinit.constant.CommonConstant;
+import lombok.Data;
+
+/**
+ * 分页请求
+ */
+@Data
+public class PageRequest {
+
+ /**
+ * 当前页号
+ */
+ private long current = 1;
+
+ /**
+ * 页面大小
+ */
+ private long pageSize = 10;
+
+ /**
+ * 排序字段
+ */
+ private String sortField;
+
+ /**
+ * 排序顺序(默认升序)
+ */
+ private String sortOrder = CommonConstant.SORT_ORDER_ASC;
+}
diff --git a/src/main/java/cc/bnblogs/springbootinit/common/ResultUtils.java b/src/main/java/cc/bnblogs/springbootinit/common/ResultUtils.java
new file mode 100644
index 0000000..ef851be
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/common/ResultUtils.java
@@ -0,0 +1,49 @@
+package cc.bnblogs.springbootinit.common;
+
+/**
+ * 返回工具类
+ */
+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/cc/bnblogs/springbootinit/config/CorsConfig.java b/src/main/java/cc/bnblogs/springbootinit/config/CorsConfig.java
new file mode 100644
index 0000000..2f7e567
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/config/CorsConfig.java
@@ -0,0 +1,25 @@
+package cc.bnblogs.springbootinit.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * 全局跨域配置
+ */
+@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/cc/bnblogs/springbootinit/config/CosClientConfig.java b/src/main/java/cc/bnblogs/springbootinit/config/CosClientConfig.java
new file mode 100644
index 0000000..4afa6eb
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/config/CosClientConfig.java
@@ -0,0 +1,50 @@
+package cc.bnblogs.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;
+
+/**
+ * 腾讯云对象存储客户端
+ */
+@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/cc/bnblogs/springbootinit/config/JsonConfig.java b/src/main/java/cc/bnblogs/springbootinit/config/JsonConfig.java
new file mode 100644
index 0000000..2859701
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/config/JsonConfig.java
@@ -0,0 +1,28 @@
+package cc.bnblogs.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 配置
+ */
+@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/cc/bnblogs/springbootinit/config/Knife4jConfig.java b/src/main/java/cc/bnblogs/springbootinit/config/Knife4jConfig.java
new file mode 100644
index 0000000..26aed35
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/config/Knife4jConfig.java
@@ -0,0 +1,36 @@
+package cc.bnblogs.springbootinit.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+/**
+ * Knife4j 接口文档配置
+ * https://doc.xiaominfo.com/knife4j/documentation/get_start.html
+ */
+@Configuration
+@EnableSwagger2
+@Profile({"dev", "test"})
+public class Knife4jConfig {
+
+ @Bean
+ public Docket defaultApi2() {
+ return new Docket(DocumentationType.SWAGGER_2)
+ .apiInfo(new ApiInfoBuilder()
+ .title("接口文档")
+ .description("springboot-init")
+ .version("1.0")
+ .build())
+ .select()
+ // 指定 Controller 扫描包路径
+ .apis(RequestHandlerSelectors.basePackage("cc.bnblogs.springbootinit.controller"))
+ .paths(PathSelectors.any())
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/cc/bnblogs/springbootinit/config/MyBatisPlusConfig.java b/src/main/java/cc/bnblogs/springbootinit/config/MyBatisPlusConfig.java
new file mode 100644
index 0000000..d93e7fd
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/config/MyBatisPlusConfig.java
@@ -0,0 +1,31 @@
+package cc.bnblogs.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/cc/bnblogs/springbootinit/config/WxOpenConfig.java b/src/main/java/cc/bnblogs/springbootinit/config/WxOpenConfig.java
new file mode 100644
index 0000000..667b487
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/config/WxOpenConfig.java
@@ -0,0 +1,48 @@
+package cc.bnblogs.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;
+
+/**
+ * 微信开放平台配置
+ */
+@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/cc/bnblogs/springbootinit/constant/CommonConstant.java b/src/main/java/cc/bnblogs/springbootinit/constant/CommonConstant.java
new file mode 100644
index 0000000..813f54b
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/constant/CommonConstant.java
@@ -0,0 +1,18 @@
+package cc.bnblogs.springbootinit.constant;
+
+/**
+ * 通用常量
+ */
+public interface CommonConstant {
+
+ /**
+ * 升序
+ */
+ String SORT_ORDER_ASC = "ascend";
+
+ /**
+ * 降序
+ */
+ String SORT_ORDER_DESC = " descend";
+
+}
diff --git a/src/main/java/cc/bnblogs/springbootinit/constant/FileConstant.java b/src/main/java/cc/bnblogs/springbootinit/constant/FileConstant.java
new file mode 100644
index 0000000..3f97231
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/constant/FileConstant.java
@@ -0,0 +1,13 @@
+package cc.bnblogs.springbootinit.constant;
+
+/**
+ * 文件常量
+ */
+public interface FileConstant {
+
+ /**
+ * COS 访问地址
+ * todo 需替换配置
+ */
+ String COS_HOST = "https://yupi.icu";
+}
diff --git a/src/main/java/cc/bnblogs/springbootinit/constant/UserConstant.java b/src/main/java/cc/bnblogs/springbootinit/constant/UserConstant.java
new file mode 100644
index 0000000..0a9578c
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/constant/UserConstant.java
@@ -0,0 +1,31 @@
+package cc.bnblogs.springbootinit.constant;
+
+/**
+ * 用户常量
+ */
+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/cc/bnblogs/springbootinit/controller/FileController.java b/src/main/java/cc/bnblogs/springbootinit/controller/FileController.java
new file mode 100644
index 0000000..b059776
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/controller/FileController.java
@@ -0,0 +1,106 @@
+package cc.bnblogs.springbootinit.controller;
+
+import cc.bnblogs.springbootinit.common.ErrorCode;
+import cc.bnblogs.springbootinit.common.ResultUtils;
+import cc.bnblogs.springbootinit.constant.FileConstant;
+import cc.bnblogs.springbootinit.exception.BusinessException;
+import cc.bnblogs.springbootinit.model.dto.file.UploadFileRequest;
+import cc.bnblogs.springbootinit.model.entity.User;
+import cc.bnblogs.springbootinit.model.enums.FileUploadBizEnum;
+import cc.bnblogs.springbootinit.service.UserService;
+import cn.hutool.core.io.FileUtil;
+import cc.bnblogs.springbootinit.common.BaseResponse;
+import cc.bnblogs.springbootinit.manager.CosManager;
+
+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;
+
+/**
+ * 文件接口
+ */
+@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/cc/bnblogs/springbootinit/controller/PostController.java b/src/main/java/cc/bnblogs/springbootinit/controller/PostController.java
new file mode 100644
index 0000000..aff32c8
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/controller/PostController.java
@@ -0,0 +1,247 @@
+package cc.bnblogs.springbootinit.controller;
+
+import cc.bnblogs.springbootinit.annotation.AuthCheck;
+import cc.bnblogs.springbootinit.common.DeleteRequest;
+import cc.bnblogs.springbootinit.common.ErrorCode;
+import cc.bnblogs.springbootinit.common.ResultUtils;
+import cc.bnblogs.springbootinit.constant.UserConstant;
+import cc.bnblogs.springbootinit.exception.BusinessException;
+import cc.bnblogs.springbootinit.exception.ThrowUtils;
+import cc.bnblogs.springbootinit.model.dto.post.PostAddRequest;
+import cc.bnblogs.springbootinit.model.dto.post.PostEditRequest;
+import cc.bnblogs.springbootinit.model.dto.post.PostQueryRequest;
+import cc.bnblogs.springbootinit.model.dto.post.PostUpdateRequest;
+import cc.bnblogs.springbootinit.model.entity.Post;
+import cc.bnblogs.springbootinit.model.entity.User;
+import cc.bnblogs.springbootinit.model.vo.PostVO;
+import cc.bnblogs.springbootinit.service.UserService;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.google.gson.Gson;
+import cc.bnblogs.springbootinit.common.BaseResponse;
+import cc.bnblogs.springbootinit.service.PostService;
+
+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;
+
+/**
+ * 帖子接口
+ */
+@RestController
+@RequestMapping("/post")
+@Slf4j
+public class PostController {
+
+ @Resource
+ private PostService postService;
+
+ @Resource
+ private UserService userService;
+
+ private final static Gson GSON = new Gson();
+
+ // 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(GSON.toJson(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(GSON.toJson(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
+ * @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(GSON.toJson(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/cc/bnblogs/springbootinit/controller/PostFavourController.java b/src/main/java/cc/bnblogs/springbootinit/controller/PostFavourController.java
new file mode 100644
index 0000000..7cd9041
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/controller/PostFavourController.java
@@ -0,0 +1,108 @@
+package cc.bnblogs.springbootinit.controller;
+
+import cc.bnblogs.springbootinit.common.ErrorCode;
+import cc.bnblogs.springbootinit.common.ResultUtils;
+import cc.bnblogs.springbootinit.exception.BusinessException;
+import cc.bnblogs.springbootinit.exception.ThrowUtils;
+import cc.bnblogs.springbootinit.model.dto.post.PostQueryRequest;
+import cc.bnblogs.springbootinit.model.dto.postfavour.PostFavourAddRequest;
+import cc.bnblogs.springbootinit.model.dto.postfavour.PostFavourQueryRequest;
+import cc.bnblogs.springbootinit.model.entity.Post;
+import cc.bnblogs.springbootinit.model.entity.User;
+import cc.bnblogs.springbootinit.model.vo.PostVO;
+import cc.bnblogs.springbootinit.service.UserService;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import cc.bnblogs.springbootinit.common.BaseResponse;
+import cc.bnblogs.springbootinit.service.PostFavourService;
+import cc.bnblogs.springbootinit.service.PostService;
+
+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;
+
+/**
+ * 帖子收藏接口
+*>
+ */
+@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/cc/bnblogs/springbootinit/controller/PostThumbController.java b/src/main/java/cc/bnblogs/springbootinit/controller/PostThumbController.java
new file mode 100644
index 0000000..1ead38d
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/controller/PostThumbController.java
@@ -0,0 +1,53 @@
+package cc.bnblogs.springbootinit.controller;
+
+import cc.bnblogs.springbootinit.common.ErrorCode;
+import cc.bnblogs.springbootinit.common.ResultUtils;
+import cc.bnblogs.springbootinit.model.dto.postthumb.PostThumbAddRequest;
+import cc.bnblogs.springbootinit.model.entity.User;
+import cc.bnblogs.springbootinit.common.BaseResponse;
+import cc.bnblogs.springbootinit.exception.BusinessException;
+import cc.bnblogs.springbootinit.service.PostThumbService;
+import cc.bnblogs.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;
+
+/**
+ * 帖子点赞接口>
+ */
+@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/cc/bnblogs/springbootinit/controller/UserController.java b/src/main/java/cc/bnblogs/springbootinit/controller/UserController.java
new file mode 100644
index 0000000..f3a9fc6
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/controller/UserController.java
@@ -0,0 +1,311 @@
+package cc.bnblogs.springbootinit.controller;
+
+import cc.bnblogs.springbootinit.common.DeleteRequest;
+import cc.bnblogs.springbootinit.common.ErrorCode;
+import cc.bnblogs.springbootinit.common.ResultUtils;
+import cc.bnblogs.springbootinit.model.entity.User;
+import cc.bnblogs.springbootinit.model.vo.LoginUserVO;
+import cc.bnblogs.springbootinit.model.vo.UserVO;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import cc.bnblogs.springbootinit.annotation.AuthCheck;
+import cc.bnblogs.springbootinit.common.BaseResponse;
+import cc.bnblogs.springbootinit.config.WxOpenConfig;
+import cc.bnblogs.springbootinit.constant.UserConstant;
+import cc.bnblogs.springbootinit.exception.BusinessException;
+import cc.bnblogs.springbootinit.exception.ThrowUtils;
+import cc.bnblogs.springbootinit.model.dto.user.UserAddRequest;
+import cc.bnblogs.springbootinit.model.dto.user.UserLoginRequest;
+import cc.bnblogs.springbootinit.model.dto.user.UserQueryRequest;
+import cc.bnblogs.springbootinit.model.dto.user.UserRegisterRequest;
+import cc.bnblogs.springbootinit.model.dto.user.UserUpdateMyRequest;
+import cc.bnblogs.springbootinit.model.dto.user.UserUpdateRequest;
+import cc.bnblogs.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 me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
+import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
+import me.chanjar.weixin.mp.api.WxMpService;
+import org.apache.commons.lang3.StringUtils;
+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.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 用户接口
+*>
+ */
+@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);
+ }
+
+ /**
+ * 用户登录(微信开放平台)
+ */
+ @GetMapping("/login/wx_open")
+ public BaseResponse userLoginByWxOpen(HttpServletRequest request, HttpServletResponse response,
+ @RequestParam("code") String code) {
+ WxOAuth2AccessToken accessToken;
+ try {
+ WxMpService wxService = wxOpenConfig.getWxMpService();
+ accessToken = wxService.getOAuth2Service().getAccessToken(code);
+ WxOAuth2UserInfo userInfo = wxService.getOAuth2Service().getUserInfo(accessToken, code);
+ String unionId = userInfo.getUnionId();
+ String mpOpenId = userInfo.getOpenid();
+ if (StringUtils.isAnyBlank(unionId, mpOpenId)) {
+ throw new BusinessException(ErrorCode.SYSTEM_ERROR, "登录失败,系统错误");
+ }
+ return ResultUtils.success(userService.userLoginByMpOpen(userInfo, request));
+ } catch (Exception e) {
+ log.error("userLoginByWxOpen error", e);
+ throw new BusinessException(ErrorCode.SYSTEM_ERROR, "登录失败,系统错误");
+ }
+ }
+
+ /**
+ * 用户注销
+ *
+ * @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);
+ 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/cc/bnblogs/springbootinit/controller/WxMpController.java b/src/main/java/cc/bnblogs/springbootinit/controller/WxMpController.java
new file mode 100644
index 0000000..97af9aa
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/controller/WxMpController.java
@@ -0,0 +1,134 @@
+package cc.bnblogs.springbootinit.controller;
+
+import cc.bnblogs.springbootinit.wxmp.WxMpConstant;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.api.WxConsts.MenuButtonType;
+import me.chanjar.weixin.common.bean.menu.WxMenu;
+import me.chanjar.weixin.common.bean.menu.WxMenuButton;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.mp.api.WxMpMessageRouter;
+import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
+import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 微信公众号相关接口
+*>
+ **/
+@RestController
+@RequestMapping("/")
+@Slf4j
+public class WxMpController {
+
+ @Resource
+ private WxMpService wxMpService;
+
+ @Resource
+ private WxMpMessageRouter router;
+
+ @PostMapping("/")
+ public void receiveMessage(HttpServletRequest request, HttpServletResponse response)
+ throws IOException {
+ response.setContentType("text/html;charset=utf-8");
+ response.setStatus(HttpServletResponse.SC_OK);
+ // 校验消息签名,判断是否为公众平台发的消息
+ String signature = request.getParameter("signature");
+ String nonce = request.getParameter("nonce");
+ String timestamp = request.getParameter("timestamp");
+ if (!wxMpService.checkSignature(timestamp, nonce, signature)) {
+ response.getWriter().println("非法请求");
+ }
+ // 加密类型
+ String encryptType = StringUtils.isBlank(request.getParameter("encrypt_type")) ? "raw"
+ : request.getParameter("encrypt_type");
+ // 明文消息
+ if ("raw".equals(encryptType)) {
+ return;
+ }
+ // aes 加密消息
+ if ("aes".equals(encryptType)) {
+ // 解密消息
+ String msgSignature = request.getParameter("msg_signature");
+ WxMpXmlMessage inMessage = WxMpXmlMessage
+ .fromEncryptedXml(request.getInputStream(), wxMpService.getWxMpConfigStorage(), timestamp,
+ nonce,
+ msgSignature);
+ log.info("message content = {}", inMessage.getContent());
+ // 路由消息并处理
+ WxMpXmlOutMessage outMessage = router.route(inMessage);
+ if (outMessage == null) {
+ response.getWriter().write("");
+ } else {
+ response.getWriter().write(outMessage.toEncryptedXml(wxMpService.getWxMpConfigStorage()));
+ }
+ return;
+ }
+ response.getWriter().println("不可识别的加密类型");
+ }
+
+ @GetMapping("/")
+ public String check(String timestamp, String nonce, String signature, String echostr) {
+ log.info("check");
+ if (wxMpService.checkSignature(timestamp, nonce, signature)) {
+ return echostr;
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * 设置公众号菜单
+ *
+ * @return
+ * @throws WxErrorException
+ */
+ @GetMapping("/setMenu")
+ public String setMenu() throws WxErrorException {
+ log.info("setMenu");
+ WxMenu wxMenu = new WxMenu();
+ // 菜单一
+ WxMenuButton wxMenuButton1 = new WxMenuButton();
+ wxMenuButton1.setType(MenuButtonType.VIEW);
+ wxMenuButton1.setName("主菜单一");
+ // 子菜单
+ WxMenuButton wxMenuButton1SubButton1 = new WxMenuButton();
+ wxMenuButton1SubButton1.setType(MenuButtonType.VIEW);
+ wxMenuButton1SubButton1.setName("跳转页面");
+ wxMenuButton1SubButton1.setUrl(
+ "https://yupi.icu");
+ wxMenuButton1.setSubButtons(Collections.singletonList(wxMenuButton1SubButton1));
+
+ // 菜单二
+ WxMenuButton wxMenuButton2 = new WxMenuButton();
+ wxMenuButton2.setType(MenuButtonType.CLICK);
+ wxMenuButton2.setName("点击事件");
+ wxMenuButton2.setKey(WxMpConstant.CLICK_MENU_KEY);
+
+ // 菜单三
+ WxMenuButton wxMenuButton3 = new WxMenuButton();
+ wxMenuButton3.setType(MenuButtonType.VIEW);
+ wxMenuButton3.setName("主菜单三");
+ WxMenuButton wxMenuButton3SubButton1 = new WxMenuButton();
+ wxMenuButton3SubButton1.setType(MenuButtonType.VIEW);
+ wxMenuButton3SubButton1.setName("编程学习");
+ wxMenuButton3SubButton1.setUrl("https://yupi.icu");
+ wxMenuButton3.setSubButtons(Collections.singletonList(wxMenuButton3SubButton1));
+
+ // 设置主菜单
+ wxMenu.setButtons(Arrays.asList(wxMenuButton1, wxMenuButton2, wxMenuButton3));
+ wxMpService.getMenuService().menuCreate(wxMenu);
+ return "ok";
+ }
+}
diff --git a/src/main/java/cc/bnblogs/springbootinit/esdao/PostEsDao.java b/src/main/java/cc/bnblogs/springbootinit/esdao/PostEsDao.java
new file mode 100644
index 0000000..a34bc6f
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/esdao/PostEsDao.java
@@ -0,0 +1,14 @@
+package cc.bnblogs.springbootinit.esdao;
+
+import cc.bnblogs.springbootinit.model.dto.post.PostEsDTO;
+
+import java.util.List;
+import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
+
+/**
+ * 帖子 ES 操作
+ */
+public interface PostEsDao extends ElasticsearchRepository {
+
+ List findByUserId(Long userId);
+}
\ No newline at end of file
diff --git a/src/main/java/cc/bnblogs/springbootinit/exception/BusinessException.java b/src/main/java/cc/bnblogs/springbootinit/exception/BusinessException.java
new file mode 100644
index 0000000..fe27872
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/exception/BusinessException.java
@@ -0,0 +1,33 @@
+package cc.bnblogs.springbootinit.exception;
+
+import cc.bnblogs.springbootinit.common.ErrorCode;
+
+/**
+ * 自定义异常类
+ */
+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/cc/bnblogs/springbootinit/exception/GlobalExceptionHandler.java b/src/main/java/cc/bnblogs/springbootinit/exception/GlobalExceptionHandler.java
new file mode 100644
index 0000000..920ec40
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/exception/GlobalExceptionHandler.java
@@ -0,0 +1,28 @@
+package cc.bnblogs.springbootinit.exception;
+
+import cc.bnblogs.springbootinit.common.ErrorCode;
+import cc.bnblogs.springbootinit.common.ResultUtils;
+import cc.bnblogs.springbootinit.common.BaseResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+/**
+ * 全局异常处理器
+ */
+@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/cc/bnblogs/springbootinit/exception/ThrowUtils.java b/src/main/java/cc/bnblogs/springbootinit/exception/ThrowUtils.java
new file mode 100644
index 0000000..7387985
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/exception/ThrowUtils.java
@@ -0,0 +1,42 @@
+package cc.bnblogs.springbootinit.exception;
+
+import cc.bnblogs.springbootinit.common.ErrorCode;
+
+/**
+ * 抛异常工具类
+ */
+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/cc/bnblogs/springbootinit/job/cycle/IncSyncPostToEs.java b/src/main/java/cc/bnblogs/springbootinit/job/cycle/IncSyncPostToEs.java
new file mode 100644
index 0000000..a5ba0dd
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/job/cycle/IncSyncPostToEs.java
@@ -0,0 +1,55 @@
+package cc.bnblogs.springbootinit.job.cycle;
+
+import cc.bnblogs.springbootinit.model.dto.post.PostEsDTO;
+import cc.bnblogs.springbootinit.model.entity.Post;
+import cc.bnblogs.springbootinit.esdao.PostEsDao;
+import cc.bnblogs.springbootinit.mapper.PostMapper;
+
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.scheduling.annotation.Scheduled;
+
+/**
+ * 增量同步帖子到 es
+ */
+// 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 (CollectionUtils.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/cc/bnblogs/springbootinit/job/once/FullSyncPostToEs.java b/src/main/java/cc/bnblogs/springbootinit/job/once/FullSyncPostToEs.java
new file mode 100644
index 0000000..d92bb2f
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/job/once/FullSyncPostToEs.java
@@ -0,0 +1,45 @@
+package cc.bnblogs.springbootinit.job.once;
+
+import cc.bnblogs.springbootinit.esdao.PostEsDao;
+import cc.bnblogs.springbootinit.model.dto.post.PostEsDTO;
+import cc.bnblogs.springbootinit.model.entity.Post;
+import cc.bnblogs.springbootinit.service.PostService;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.boot.CommandLineRunner;
+
+/**
+ * 全量同步帖子到 es
+ */
+// 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 (CollectionUtils.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/cc/bnblogs/springbootinit/manager/CosManager.java b/src/main/java/cc/bnblogs/springbootinit/manager/CosManager.java
new file mode 100644
index 0000000..ca763f9
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/manager/CosManager.java
@@ -0,0 +1,50 @@
+package cc.bnblogs.springbootinit.manager;
+
+import cc.bnblogs.springbootinit.config.CosClientConfig;
+import com.qcloud.cos.COSClient;
+import com.qcloud.cos.model.PutObjectRequest;
+import com.qcloud.cos.model.PutObjectResult;
+
+import java.io.File;
+import javax.annotation.Resource;
+import org.springframework.stereotype.Component;
+
+/**
+ * Cos 对象存储操作
+ *
+ */
+@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/cc/bnblogs/springbootinit/mapper/PostFavourMapper.java b/src/main/java/cc/bnblogs/springbootinit/mapper/PostFavourMapper.java
new file mode 100644
index 0000000..ca052a4
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/mapper/PostFavourMapper.java
@@ -0,0 +1,32 @@
+package cc.bnblogs.springbootinit.mapper;
+
+import cc.bnblogs.springbootinit.model.entity.Post;
+import cc.bnblogs.springbootinit.model.entity.PostFavour;
+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 org.apache.ibatis.annotations.Param;
+
+/**
+ * 帖子收藏数据库操作
+ */
+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/cc/bnblogs/springbootinit/mapper/PostMapper.java b/src/main/java/cc/bnblogs/springbootinit/mapper/PostMapper.java
new file mode 100644
index 0000000..29966a7
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/mapper/PostMapper.java
@@ -0,0 +1,23 @@
+package cc.bnblogs.springbootinit.mapper;
+
+import cc.bnblogs.springbootinit.model.entity.Post;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 帖子数据库操作
+ */
+public interface PostMapper extends BaseMapper {
+
+ /**
+ * 查询帖子列表(包括已被删除的数据)
+ */
+ List listPostWithDelete(Date minUpdateTime);
+
+}
+
+
+
+
diff --git a/src/main/java/cc/bnblogs/springbootinit/mapper/PostThumbMapper.java b/src/main/java/cc/bnblogs/springbootinit/mapper/PostThumbMapper.java
new file mode 100644
index 0000000..27429b4
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/mapper/PostThumbMapper.java
@@ -0,0 +1,15 @@
+package cc.bnblogs.springbootinit.mapper;
+
+import cc.bnblogs.springbootinit.model.entity.PostThumb;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * 帖子点赞数据库操作
+ */
+public interface PostThumbMapper extends BaseMapper {
+
+}
+
+
+
+
diff --git a/src/main/java/cc/bnblogs/springbootinit/mapper/UserMapper.java b/src/main/java/cc/bnblogs/springbootinit/mapper/UserMapper.java
new file mode 100644
index 0000000..a7cdc1c
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/mapper/UserMapper.java
@@ -0,0 +1,15 @@
+package cc.bnblogs.springbootinit.mapper;
+
+import cc.bnblogs.springbootinit.model.entity.User;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * 用户数据库操作
+ */
+public interface UserMapper extends BaseMapper {
+
+}
+
+
+
+
diff --git a/src/main/java/cc/bnblogs/springbootinit/model/dto/file/UploadFileRequest.java b/src/main/java/cc/bnblogs/springbootinit/model/dto/file/UploadFileRequest.java
new file mode 100644
index 0000000..fa72a2a
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/dto/file/UploadFileRequest.java
@@ -0,0 +1,18 @@
+package cc.bnblogs.springbootinit.model.dto.file;
+
+import java.io.Serializable;
+import lombok.Data;
+
+/**
+ * 文件上传请求
+ */
+@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/cc/bnblogs/springbootinit/model/dto/post/PostAddRequest.java b/src/main/java/cc/bnblogs/springbootinit/model/dto/post/PostAddRequest.java
new file mode 100644
index 0000000..c360d5c
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/dto/post/PostAddRequest.java
@@ -0,0 +1,29 @@
+package cc.bnblogs.springbootinit.model.dto.post;
+
+import java.io.Serializable;
+import java.util.List;
+import lombok.Data;
+
+/**
+ * 创建请求
+ */
+@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/cc/bnblogs/springbootinit/model/dto/post/PostEditRequest.java b/src/main/java/cc/bnblogs/springbootinit/model/dto/post/PostEditRequest.java
new file mode 100644
index 0000000..3d46e47
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/dto/post/PostEditRequest.java
@@ -0,0 +1,34 @@
+package cc.bnblogs.springbootinit.model.dto.post;
+
+import java.io.Serializable;
+import java.util.List;
+import lombok.Data;
+
+/**
+ * 编辑请求
+ */
+@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/cc/bnblogs/springbootinit/model/dto/post/PostEsDTO.java b/src/main/java/cc/bnblogs/springbootinit/model/dto/post/PostEsDTO.java
new file mode 100644
index 0000000..ce2f434
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/dto/post/PostEsDTO.java
@@ -0,0 +1,123 @@
+package cc.bnblogs.springbootinit.model.dto.post;
+
+import cc.bnblogs.springbootinit.model.entity.Post;
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+import lombok.Data;
+import org.apache.commons.collections4.CollectionUtils;
+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;
+
+/**
+ * 帖子 ES 包装类
+ **/
+// 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;
+
+ private static final Gson GSON = new Gson();
+
+ /**
+ * 对象转包装类
+ *
+ * @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(GSON.fromJson(tagsStr, new TypeToken>() {
+ }.getType()));
+ }
+ 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 (CollectionUtils.isNotEmpty(tagList)) {
+ post.setTags(GSON.toJson(tagList));
+ }
+ return post;
+ }
+}
diff --git a/src/main/java/cc/bnblogs/springbootinit/model/dto/post/PostQueryRequest.java b/src/main/java/cc/bnblogs/springbootinit/model/dto/post/PostQueryRequest.java
new file mode 100644
index 0000000..b4ca395
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/dto/post/PostQueryRequest.java
@@ -0,0 +1,63 @@
+package cc.bnblogs.springbootinit.model.dto.post;
+
+import cc.bnblogs.springbootinit.common.PageRequest;
+
+import java.io.Serializable;
+import java.util.List;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 查询请求
+ */
+@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/cc/bnblogs/springbootinit/model/dto/post/PostUpdateRequest.java b/src/main/java/cc/bnblogs/springbootinit/model/dto/post/PostUpdateRequest.java
new file mode 100644
index 0000000..20c7444
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/dto/post/PostUpdateRequest.java
@@ -0,0 +1,34 @@
+package cc.bnblogs.springbootinit.model.dto.post;
+
+import java.io.Serializable;
+import java.util.List;
+import lombok.Data;
+
+/**
+ * 更新请求
+ */
+@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/cc/bnblogs/springbootinit/model/dto/postfavour/PostFavourAddRequest.java b/src/main/java/cc/bnblogs/springbootinit/model/dto/postfavour/PostFavourAddRequest.java
new file mode 100644
index 0000000..6c095b5
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/dto/postfavour/PostFavourAddRequest.java
@@ -0,0 +1,18 @@
+package cc.bnblogs.springbootinit.model.dto.postfavour;
+
+import java.io.Serializable;
+import lombok.Data;
+
+/**
+ * 帖子收藏 / 取消收藏请求
+ */
+@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/cc/bnblogs/springbootinit/model/dto/postfavour/PostFavourQueryRequest.java b/src/main/java/cc/bnblogs/springbootinit/model/dto/postfavour/PostFavourQueryRequest.java
new file mode 100644
index 0000000..421ff71
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/dto/postfavour/PostFavourQueryRequest.java
@@ -0,0 +1,27 @@
+package cc.bnblogs.springbootinit.model.dto.postfavour;
+
+import cc.bnblogs.springbootinit.common.PageRequest;
+import cc.bnblogs.springbootinit.model.dto.post.PostQueryRequest;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 帖子收藏查询请求
+ */
+@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/cc/bnblogs/springbootinit/model/dto/postthumb/PostThumbAddRequest.java b/src/main/java/cc/bnblogs/springbootinit/model/dto/postthumb/PostThumbAddRequest.java
new file mode 100644
index 0000000..ef9193f
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/dto/postthumb/PostThumbAddRequest.java
@@ -0,0 +1,18 @@
+package cc.bnblogs.springbootinit.model.dto.postthumb;
+
+import java.io.Serializable;
+import lombok.Data;
+
+/**
+ * 帖子点赞请求
+ */
+@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/cc/bnblogs/springbootinit/model/dto/user/UserAddRequest.java b/src/main/java/cc/bnblogs/springbootinit/model/dto/user/UserAddRequest.java
new file mode 100644
index 0000000..d8a75be
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/dto/user/UserAddRequest.java
@@ -0,0 +1,33 @@
+package cc.bnblogs.springbootinit.model.dto.user;
+
+import java.io.Serializable;
+import lombok.Data;
+
+/**
+ * 用户创建请求
+ */
+@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/cc/bnblogs/springbootinit/model/dto/user/UserLoginRequest.java b/src/main/java/cc/bnblogs/springbootinit/model/dto/user/UserLoginRequest.java
new file mode 100644
index 0000000..5d8a095
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/dto/user/UserLoginRequest.java
@@ -0,0 +1,18 @@
+package cc.bnblogs.springbootinit.model.dto.user;
+
+import java.io.Serializable;
+import lombok.Data;
+
+/**
+ * 用户登录请求
+>
+ */
+@Data
+public class UserLoginRequest implements Serializable {
+
+ private static final long serialVersionUID = 3191241716373120793L;
+
+ private String userAccount;
+
+ private String userPassword;
+}
diff --git a/src/main/java/cc/bnblogs/springbootinit/model/dto/user/UserQueryRequest.java b/src/main/java/cc/bnblogs/springbootinit/model/dto/user/UserQueryRequest.java
new file mode 100644
index 0000000..e7ed378
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/dto/user/UserQueryRequest.java
@@ -0,0 +1,46 @@
+package cc.bnblogs.springbootinit.model.dto.user;
+
+import cc.bnblogs.springbootinit.common.PageRequest;
+
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 用户查询请求
+ */
+@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/cc/bnblogs/springbootinit/model/dto/user/UserRegisterRequest.java b/src/main/java/cc/bnblogs/springbootinit/model/dto/user/UserRegisterRequest.java
new file mode 100644
index 0000000..fb1d54b
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/dto/user/UserRegisterRequest.java
@@ -0,0 +1,19 @@
+package cc.bnblogs.springbootinit.model.dto.user;
+
+import java.io.Serializable;
+import lombok.Data;
+
+/**
+ * 用户注册请求体
+ */
+@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/cc/bnblogs/springbootinit/model/dto/user/UserUpdateMyRequest.java b/src/main/java/cc/bnblogs/springbootinit/model/dto/user/UserUpdateMyRequest.java
new file mode 100644
index 0000000..a7e5b46
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/dto/user/UserUpdateMyRequest.java
@@ -0,0 +1,28 @@
+package cc.bnblogs.springbootinit.model.dto.user;
+
+import java.io.Serializable;
+import lombok.Data;
+
+/**
+ * 用户更新个人信息请求
+ */
+@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/cc/bnblogs/springbootinit/model/dto/user/UserUpdateRequest.java b/src/main/java/cc/bnblogs/springbootinit/model/dto/user/UserUpdateRequest.java
new file mode 100644
index 0000000..dff4a84
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/dto/user/UserUpdateRequest.java
@@ -0,0 +1,37 @@
+package cc.bnblogs.springbootinit.model.dto.user;
+
+import java.io.Serializable;
+import lombok.Data;
+
+/**
+ * 用户更新请求
+ */
+@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/cc/bnblogs/springbootinit/model/entity/Post.java b/src/main/java/cc/bnblogs/springbootinit/model/entity/Post.java
new file mode 100644
index 0000000..d1ce6d7
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/entity/Post.java
@@ -0,0 +1,73 @@
+package cc.bnblogs.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;
+
+/**
+ * 帖子
+ */
+@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/cc/bnblogs/springbootinit/model/entity/PostFavour.java b/src/main/java/cc/bnblogs/springbootinit/model/entity/PostFavour.java
new file mode 100644
index 0000000..f4cfd2c
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/entity/PostFavour.java
@@ -0,0 +1,46 @@
+package cc.bnblogs.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;
+
+/**
+ * 帖子收藏
+ **/
+@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/cc/bnblogs/springbootinit/model/entity/PostThumb.java b/src/main/java/cc/bnblogs/springbootinit/model/entity/PostThumb.java
new file mode 100644
index 0000000..0edf8cf
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/entity/PostThumb.java
@@ -0,0 +1,46 @@
+package cc.bnblogs.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;
+
+/**
+ * 帖子点赞
+ */
+@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/cc/bnblogs/springbootinit/model/entity/User.java b/src/main/java/cc/bnblogs/springbootinit/model/entity/User.java
new file mode 100644
index 0000000..49f4268
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/entity/User.java
@@ -0,0 +1,83 @@
+package cc.bnblogs.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;
+
+/**
+ * 用户
+ */
+@TableName(value = "user")
+@Data
+public class User implements Serializable {
+
+ /**
+ * id
+ */
+ @TableId(type = IdType.ASSIGN_ID)
+ private Long id;
+
+ /**
+ * 用户账号
+ */
+ private String userAccount;
+
+ /**
+ * 用户密码
+ */
+ private String userPassword;
+
+ /**
+ * 开放平台id
+ */
+ private String unionId;
+
+ /**
+ * 公众号openId
+ */
+ private String mpOpenId;
+
+ /**
+ * 用户昵称
+ */
+ private String userName;
+
+ /**
+ * 用户头像
+ */
+ private String userAvatar;
+
+ /**
+ * 用户简介
+ */
+ private String userProfile;
+
+ /**
+ * 用户角色:user/admin/ban
+ */
+ private String userRole;
+
+ /**
+ * 创建时间
+ */
+ 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/cc/bnblogs/springbootinit/model/enums/FileUploadBizEnum.java b/src/main/java/cc/bnblogs/springbootinit/model/enums/FileUploadBizEnum.java
new file mode 100644
index 0000000..406ccfc
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/enums/FileUploadBizEnum.java
@@ -0,0 +1,58 @@
+package cc.bnblogs.springbootinit.model.enums;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.ObjectUtils;
+
+/**
+ * 文件上传业务类型枚举
+ */
+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/cc/bnblogs/springbootinit/model/enums/UserRoleEnum.java b/src/main/java/cc/bnblogs/springbootinit/model/enums/UserRoleEnum.java
new file mode 100644
index 0000000..8706c3b
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/enums/UserRoleEnum.java
@@ -0,0 +1,60 @@
+package cc.bnblogs.springbootinit.model.enums;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.ObjectUtils;
+
+/**
+ * 用户角色枚举
+ */
+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/cc/bnblogs/springbootinit/model/vo/LoginUserVO.java b/src/main/java/cc/bnblogs/springbootinit/model/vo/LoginUserVO.java
new file mode 100644
index 0000000..88d463e
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/vo/LoginUserVO.java
@@ -0,0 +1,49 @@
+package cc.bnblogs.springbootinit.model.vo;
+
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * 已登录用户视图(脱敏)
+ **/
+@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/cc/bnblogs/springbootinit/model/vo/PostVO.java b/src/main/java/cc/bnblogs/springbootinit/model/vo/PostVO.java
new file mode 100644
index 0000000..b81ddbd
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/vo/PostVO.java
@@ -0,0 +1,116 @@
+package cc.bnblogs.springbootinit.model.vo;
+
+import cc.bnblogs.springbootinit.model.entity.Post;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+import lombok.Data;
+import org.springframework.beans.BeanUtils;
+
+/**
+ * 帖子视图
+ */
+@Data
+public class PostVO implements Serializable {
+
+ private final static Gson GSON = new Gson();
+
+ /**
+ * 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();
+ if (tagList != null) {
+ post.setTags(GSON.toJson(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(GSON.fromJson(post.getTags(), new TypeToken>() {
+ }.getType()));
+ return postVO;
+ }
+}
diff --git a/src/main/java/cc/bnblogs/springbootinit/model/vo/UserVO.java b/src/main/java/cc/bnblogs/springbootinit/model/vo/UserVO.java
new file mode 100644
index 0000000..4a5d4bd
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/model/vo/UserVO.java
@@ -0,0 +1,45 @@
+package cc.bnblogs.springbootinit.model.vo;
+
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * 用户视图(脱敏)
+ * 页面显示所需要的数据
+ */
+@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/cc/bnblogs/springbootinit/service/PostFavourService.java b/src/main/java/cc/bnblogs/springbootinit/service/PostFavourService.java
new file mode 100644
index 0000000..a58972a
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/service/PostFavourService.java
@@ -0,0 +1,44 @@
+package cc.bnblogs.springbootinit.service;
+
+import cc.bnblogs.springbootinit.model.entity.Post;
+import cc.bnblogs.springbootinit.model.entity.PostFavour;
+import cc.bnblogs.springbootinit.model.entity.User;
+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;
+
+/**
+ * 帖子收藏服务
+ */
+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/cc/bnblogs/springbootinit/service/PostService.java b/src/main/java/cc/bnblogs/springbootinit/service/PostService.java
new file mode 100644
index 0000000..1d9bca2
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/service/PostService.java
@@ -0,0 +1,58 @@
+package cc.bnblogs.springbootinit.service;
+
+import cc.bnblogs.springbootinit.model.dto.post.PostQueryRequest;
+import cc.bnblogs.springbootinit.model.entity.Post;
+import cc.bnblogs.springbootinit.model.vo.PostVO;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 帖子服务
+ */
+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/cc/bnblogs/springbootinit/service/PostThumbService.java b/src/main/java/cc/bnblogs/springbootinit/service/PostThumbService.java
new file mode 100644
index 0000000..ba9a27f
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/service/PostThumbService.java
@@ -0,0 +1,29 @@
+package cc.bnblogs.springbootinit.service;
+
+import cc.bnblogs.springbootinit.model.entity.PostThumb;
+import cc.bnblogs.springbootinit.model.entity.User;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * 帖子点赞服务
+ */
+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/cc/bnblogs/springbootinit/service/UserService.java b/src/main/java/cc/bnblogs/springbootinit/service/UserService.java
new file mode 100644
index 0000000..e4f5b29
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/service/UserService.java
@@ -0,0 +1,119 @@
+package cc.bnblogs.springbootinit.service;
+
+import cc.bnblogs.springbootinit.model.dto.user.UserQueryRequest;
+import cc.bnblogs.springbootinit.model.entity.User;
+import cc.bnblogs.springbootinit.model.vo.LoginUserVO;
+import cc.bnblogs.springbootinit.model.vo.UserVO;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
+
+/**
+ * 用户服务
+ */
+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 wxOAuth2UserInfo 从微信获取的用户信息
+ * @param request
+ * @return 脱敏后的用户信息
+ */
+ LoginUserVO userLoginByMpOpen(WxOAuth2UserInfo wxOAuth2UserInfo, 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/cc/bnblogs/springbootinit/service/impl/PostFavourServiceImpl.java b/src/main/java/cc/bnblogs/springbootinit/service/impl/PostFavourServiceImpl.java
new file mode 100644
index 0000000..1f37644
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/service/impl/PostFavourServiceImpl.java
@@ -0,0 +1,113 @@
+package cc.bnblogs.springbootinit.service.impl;
+
+import cc.bnblogs.springbootinit.common.ErrorCode;
+import cc.bnblogs.springbootinit.model.entity.Post;
+import cc.bnblogs.springbootinit.model.entity.PostFavour;
+import cc.bnblogs.springbootinit.model.entity.User;
+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 cc.bnblogs.springbootinit.exception.BusinessException;
+import cc.bnblogs.springbootinit.mapper.PostFavourMapper;
+import cc.bnblogs.springbootinit.service.PostFavourService;
+import cc.bnblogs.springbootinit.service.PostService;
+import javax.annotation.Resource;
+import org.springframework.aop.framework.AopContext;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * 帖子收藏服务实现
+ */
+@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/cc/bnblogs/springbootinit/service/impl/PostServiceImpl.java b/src/main/java/cc/bnblogs/springbootinit/service/impl/PostServiceImpl.java
new file mode 100644
index 0000000..c49971c
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/service/impl/PostServiceImpl.java
@@ -0,0 +1,314 @@
+package cc.bnblogs.springbootinit.service.impl;
+
+import cc.bnblogs.springbootinit.common.ErrorCode;
+import cc.bnblogs.springbootinit.model.dto.post.PostEsDTO;
+import cc.bnblogs.springbootinit.model.dto.post.PostQueryRequest;
+import cc.bnblogs.springbootinit.model.entity.Post;
+import cc.bnblogs.springbootinit.model.entity.PostFavour;
+import cc.bnblogs.springbootinit.model.entity.PostThumb;
+import cc.bnblogs.springbootinit.model.entity.User;
+import cc.bnblogs.springbootinit.model.vo.PostVO;
+import cc.bnblogs.springbootinit.model.vo.UserVO;
+import cc.bnblogs.springbootinit.utils.SqlUtils;
+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.google.gson.Gson;
+import cc.bnblogs.springbootinit.constant.CommonConstant;
+import cc.bnblogs.springbootinit.exception.BusinessException;
+import cc.bnblogs.springbootinit.exception.ThrowUtils;
+import cc.bnblogs.springbootinit.mapper.PostFavourMapper;
+import cc.bnblogs.springbootinit.mapper.PostMapper;
+import cc.bnblogs.springbootinit.mapper.PostThumbMapper;
+import cc.bnblogs.springbootinit.service.PostService;
+import cc.bnblogs.springbootinit.service.UserService;
+
+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 org.apache.commons.collections4.CollectionUtils;
+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;
+
+/**
+ * 帖子服务实现
+ */
+@Service
+@Slf4j
+public class PostServiceImpl extends ServiceImpl implements PostService {
+
+ private final static Gson GSON = new Gson();
+
+ @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.like("title", searchText).or().like("content", searchText);
+ }
+ queryWrapper.like(StringUtils.isNotBlank(title), "title", title);
+ queryWrapper.like(StringUtils.isNotBlank(content), "content", content);
+ if (CollectionUtils.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.eq("isDelete", false);
+ 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 (CollectionUtils.isNotEmpty(tagList)) {
+ for (String tag : tagList) {
+ boolQueryBuilder.filter(QueryBuilders.termQuery("tags", tag));
+ }
+ }
+ // 包含任何一个标签即可
+ if (CollectionUtils.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 (CollectionUtils.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/cc/bnblogs/springbootinit/service/impl/PostThumbServiceImpl.java b/src/main/java/cc/bnblogs/springbootinit/service/impl/PostThumbServiceImpl.java
new file mode 100644
index 0000000..dd11ea7
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/service/impl/PostThumbServiceImpl.java
@@ -0,0 +1,102 @@
+package cc.bnblogs.springbootinit.service.impl;
+
+import cc.bnblogs.springbootinit.common.ErrorCode;
+import cc.bnblogs.springbootinit.model.entity.Post;
+import cc.bnblogs.springbootinit.model.entity.PostThumb;
+import cc.bnblogs.springbootinit.model.entity.User;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import cc.bnblogs.springbootinit.exception.BusinessException;
+import cc.bnblogs.springbootinit.mapper.PostThumbMapper;
+import cc.bnblogs.springbootinit.service.PostService;
+import cc.bnblogs.springbootinit.service.PostThumbService;
+import javax.annotation.Resource;
+import org.springframework.aop.framework.AopContext;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * 帖子点赞服务实现
+ */
+@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/cc/bnblogs/springbootinit/service/impl/UserServiceImpl.java b/src/main/java/cc/bnblogs/springbootinit/service/impl/UserServiceImpl.java
new file mode 100644
index 0000000..1789d54
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/service/impl/UserServiceImpl.java
@@ -0,0 +1,270 @@
+package cc.bnblogs.springbootinit.service.impl;
+
+import static cc.bnblogs.springbootinit.constant.UserConstant.USER_LOGIN_STATE;
+
+import cc.bnblogs.springbootinit.common.ErrorCode;
+import cc.bnblogs.springbootinit.model.dto.user.UserQueryRequest;
+import cc.bnblogs.springbootinit.model.entity.User;
+import cc.bnblogs.springbootinit.model.enums.UserRoleEnum;
+import cc.bnblogs.springbootinit.model.vo.LoginUserVO;
+import cc.bnblogs.springbootinit.model.vo.UserVO;
+import cc.bnblogs.springbootinit.utils.SqlUtils;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import cc.bnblogs.springbootinit.constant.CommonConstant;
+import cc.bnblogs.springbootinit.exception.BusinessException;
+import cc.bnblogs.springbootinit.mapper.UserMapper;
+import cc.bnblogs.springbootinit.service.UserService;
+
+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;
+
+/**
+ * 用户服务实现
+ */
+@Service
+@Slf4j
+public class UserServiceImpl extends ServiceImpl implements UserService {
+
+ /**
+ * 盐值,混淆密码
+ */
+ private static final String SALT = "demo";
+
+ @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);
+ }
+
+ @Override
+ public LoginUserVO userLoginByMpOpen(WxOAuth2UserInfo wxOAuth2UserInfo, HttpServletRequest request) {
+ String unionId = wxOAuth2UserInfo.getUnionId();
+ String mpOpenId = wxOAuth2UserInfo.getOpenid();
+ // 单机锁
+ synchronized (unionId.intern()) {
+ // 查询用户是否已存在
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.eq("unionId", unionId);
+ User user = this.getOne(queryWrapper);
+ // 被封号,禁止登录
+ if (user != null && UserRoleEnum.BAN.getValue().equals(user.getUserRole())) {
+ throw new BusinessException(ErrorCode.FORBIDDEN_ERROR, "该用户已被封,禁止登录");
+ }
+ // 用户不存在则创建
+ if (user == null) {
+ user = new User();
+ user.setUnionId(unionId);
+ user.setMpOpenId(mpOpenId);
+ user.setUserAvatar(wxOAuth2UserInfo.getHeadImgUrl());
+ user.setUserName(wxOAuth2UserInfo.getNickname());
+ boolean result = this.save(user);
+ if (!result) {
+ throw new BusinessException(ErrorCode.SYSTEM_ERROR, "登录失败");
+ }
+ }
+ // 记录用户的登录态
+ request.getSession().setAttribute(USER_LOGIN_STATE, user);
+ return 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 (CollectionUtils.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/cc/bnblogs/springbootinit/utils/NetUtils.java b/src/main/java/cc/bnblogs/springbootinit/utils/NetUtils.java
new file mode 100644
index 0000000..ffef227
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/utils/NetUtils.java
@@ -0,0 +1,52 @@
+package cc.bnblogs.springbootinit.utils;
+
+import java.net.InetAddress;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 网络工具类
+ */
+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/cc/bnblogs/springbootinit/utils/SpringContextUtils.java b/src/main/java/cc/bnblogs/springbootinit/utils/SpringContextUtils.java
new file mode 100644
index 0000000..8952d02
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/utils/SpringContextUtils.java
@@ -0,0 +1,54 @@
+package cc.bnblogs.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 上下文获取工具
+ */
+@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/cc/bnblogs/springbootinit/utils/SqlUtils.java b/src/main/java/cc/bnblogs/springbootinit/utils/SqlUtils.java
new file mode 100644
index 0000000..cf67432
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/utils/SqlUtils.java
@@ -0,0 +1,22 @@
+package cc.bnblogs.springbootinit.utils;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * SQL 工具
+ */
+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/java/cc/bnblogs/springbootinit/wxmp/WxMpConstant.java b/src/main/java/cc/bnblogs/springbootinit/wxmp/WxMpConstant.java
new file mode 100644
index 0000000..16e221f
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/wxmp/WxMpConstant.java
@@ -0,0 +1,13 @@
+package cc.bnblogs.springbootinit.wxmp;
+
+/**
+ * 微信公众号相关常量
+ **/
+public class WxMpConstant {
+
+ /**
+ * 点击菜单 key
+ */
+ public static final String CLICK_MENU_KEY = "CLICK_MENU_KEY";
+
+}
diff --git a/src/main/java/cc/bnblogs/springbootinit/wxmp/WxMpMsgRouter.java b/src/main/java/cc/bnblogs/springbootinit/wxmp/WxMpMsgRouter.java
new file mode 100644
index 0000000..ef01422
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/wxmp/WxMpMsgRouter.java
@@ -0,0 +1,58 @@
+package cc.bnblogs.springbootinit.wxmp;
+
+import cc.bnblogs.springbootinit.wxmp.handler.EventHandler;
+import cc.bnblogs.springbootinit.wxmp.handler.MessageHandler;
+import cc.bnblogs.springbootinit.wxmp.handler.SubscribeHandler;
+import javax.annotation.Resource;
+import me.chanjar.weixin.common.api.WxConsts.EventType;
+import me.chanjar.weixin.common.api.WxConsts.XmlMsgType;
+import me.chanjar.weixin.mp.api.WxMpMessageRouter;
+import me.chanjar.weixin.mp.api.WxMpService;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 微信公众号路由
+ */
+@Configuration
+public class WxMpMsgRouter {
+
+ @Resource
+ private WxMpService wxMpService;
+
+ @Resource
+ private EventHandler eventHandler;
+
+ @Resource
+ private MessageHandler messageHandler;
+
+ @Resource
+ private SubscribeHandler subscribeHandler;
+
+ @Bean
+ public WxMpMessageRouter getWxMsgRouter() {
+ WxMpMessageRouter router = new WxMpMessageRouter(wxMpService);
+ // 消息
+ router.rule()
+ .async(false)
+ .msgType(XmlMsgType.TEXT)
+ .handler(messageHandler)
+ .end();
+ // 关注
+ router.rule()
+ .async(false)
+ .msgType(XmlMsgType.EVENT)
+ .event(EventType.SUBSCRIBE)
+ .handler(subscribeHandler)
+ .end();
+ // 点击按钮
+ router.rule()
+ .async(false)
+ .msgType(XmlMsgType.EVENT)
+ .event(EventType.CLICK)
+ .eventKey(WxMpConstant.CLICK_MENU_KEY)
+ .handler(eventHandler)
+ .end();
+ return router;
+ }
+}
diff --git a/src/main/java/cc/bnblogs/springbootinit/wxmp/handler/EventHandler.java b/src/main/java/cc/bnblogs/springbootinit/wxmp/handler/EventHandler.java
new file mode 100644
index 0000000..ad7be15
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/wxmp/handler/EventHandler.java
@@ -0,0 +1,28 @@
+package cc.bnblogs.springbootinit.wxmp.handler;
+
+import java.util.Map;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.session.WxSessionManager;
+import me.chanjar.weixin.mp.api.WxMpMessageHandler;
+import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
+import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
+import org.springframework.stereotype.Component;
+
+/**
+ * 事件处理器
+ **/
+@Component
+public class EventHandler implements WxMpMessageHandler {
+
+ @Override
+ public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map map, WxMpService wxMpService,
+ WxSessionManager wxSessionManager) throws WxErrorException {
+ final String content = "您点击了菜单";
+ // 调用接口,返回验证码
+ return WxMpXmlOutMessage.TEXT().content(content)
+ .fromUser(wxMpXmlMessage.getToUser())
+ .toUser(wxMpXmlMessage.getFromUser())
+ .build();
+ }
+}
diff --git a/src/main/java/cc/bnblogs/springbootinit/wxmp/handler/MessageHandler.java b/src/main/java/cc/bnblogs/springbootinit/wxmp/handler/MessageHandler.java
new file mode 100644
index 0000000..213595d
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/wxmp/handler/MessageHandler.java
@@ -0,0 +1,27 @@
+package cc.bnblogs.springbootinit.wxmp.handler;
+
+import java.util.Map;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.session.WxSessionManager;
+import me.chanjar.weixin.mp.api.WxMpMessageHandler;
+import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
+import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
+import org.springframework.stereotype.Component;
+
+/**
+ * 消息处理器
+ **/
+@Component
+public class MessageHandler implements WxMpMessageHandler {
+
+ @Override
+ public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map map,
+ WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException {
+ String content = "我是复读机:" + wxMpXmlMessage.getContent();
+ return WxMpXmlOutMessage.TEXT().content(content)
+ .fromUser(wxMpXmlMessage.getToUser())
+ .toUser(wxMpXmlMessage.getFromUser())
+ .build();
+ }
+}
diff --git a/src/main/java/cc/bnblogs/springbootinit/wxmp/handler/SubscribeHandler.java b/src/main/java/cc/bnblogs/springbootinit/wxmp/handler/SubscribeHandler.java
new file mode 100644
index 0000000..c7ed015
--- /dev/null
+++ b/src/main/java/cc/bnblogs/springbootinit/wxmp/handler/SubscribeHandler.java
@@ -0,0 +1,28 @@
+package cc.bnblogs.springbootinit.wxmp.handler;
+
+import java.util.Map;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.session.WxSessionManager;
+import me.chanjar.weixin.mp.api.WxMpMessageHandler;
+import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
+import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
+import org.springframework.stereotype.Component;
+
+/**
+ * 关注处理器
+ **/
+@Component
+public class SubscribeHandler implements WxMpMessageHandler {
+
+ @Override
+ public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map map,
+ WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException {
+ final String content = "感谢关注";
+ // 调用接口,返回验证码
+ return WxMpXmlOutMessage.TEXT().content(content)
+ .fromUser(wxMpXmlMessage.getToUser())
+ .toUser(wxMpXmlMessage.getFromUser())
+ .build();
+ }
+}
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..46f9d16
--- /dev/null
+++ b/src/main/resources/META-INF/additional-spring-configuration-metadata.json
@@ -0,0 +1,34 @@
+{
+ "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."
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml
new file mode 100644
index 0000000..4433f0b
--- /dev/null
+++ b/src/main/resources/application-prod.yml
@@ -0,0 +1,29 @@
+# 线上配置文件
+server:
+ port: 8101
+spring:
+ # 数据库配置
+ # todo 需替换配置
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://127.0.0.1:3306/my_db
+ username: root
+ password: 123456
+ # Redis 配置
+ # todo 需替换配置
+ redis:
+ database: 1
+ host: 127.0.0.1
+ port: 6379
+ timeout: 5000
+ password: 123456
+ # Elasticsearch 配置
+ # todo 需替换配置
+ elasticsearch:
+ uris: http://127.0.0.1:9200
+ username: elastic
+ password: 123456
+mybatis-plus:
+ configuration:
+ # 生产环境关闭日志
+ log-impl: ''
\ No newline at end of file
diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml
new file mode 100644
index 0000000..749ef6d
--- /dev/null
+++ b/src/main/resources/application-test.yml
@@ -0,0 +1,25 @@
+# 测试配置文件
+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..33b40fc
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,91 @@
+# 公共配置文件
+spring:
+ application:
+ name: springboot-init
+ # 默认 dev 环境
+ profiles:
+ active: dev
+ # 支持 swagger3
+ # http://localhost:8101/api/doc.html
+ 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://127.0.0.1:3306/my_db
+ username: root
+ password: 123456
+ # Redis 配置
+ # todo 需替换配置,然后取消注释
+ redis:
+ database: 1
+ host: 192.168.153.131
+ port: 6379
+ timeout: 5000
+ password: admin
+ # Elasticsearch 配置
+ # todo 需替换配置,然后取消注释
+ elasticsearch:
+ uris: http://127.0.0.1:9200
+ username: root
+ password: 123456
+ # 文件上传
+ servlet:
+ multipart:
+ # 大小限制
+ max-file-size: 10MB
+server:
+ address: 127.0.0.1
+ 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
\ No newline at end of file
diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt
new file mode 100644
index 0000000..6b56014
--- /dev/null
+++ b/src/main/resources/banner.txt
@@ -0,0 +1 @@
+Spring-init
diff --git a/src/main/resources/mapper/PostFavourMapper.xml b/src/main/resources/mapper/PostFavourMapper.xml
new file mode 100644
index 0000000..3c02754
--- /dev/null
+++ b/src/main/resources/mapper/PostFavourMapper.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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..c7c6eec
--- /dev/null
+++ b/src/main/resources/mapper/PostMapper.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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..2a9f54d
--- /dev/null
+++ b/src/main/resources/mapper/PostThumbMapper.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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..df0e843
--- /dev/null
+++ b/src/main/resources/mapper/UserMapper.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id,unionId,mpOpenId,
+ userName,userAvatar,userProfile,
+ userRole,createTime,updateTime,isDelete
+
+
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/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/cc/bnblogs/springbootinit/MainApplicationTests.java b/src/test/java/cc/bnblogs/springbootinit/MainApplicationTests.java
new file mode 100644
index 0000000..9f25819
--- /dev/null
+++ b/src/test/java/cc/bnblogs/springbootinit/MainApplicationTests.java
@@ -0,0 +1,22 @@
+package cc.bnblogs.springbootinit;
+
+import cc.bnblogs.springbootinit.config.WxOpenConfig;
+import javax.annotation.Resource;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+/**
+ * 主类测试
+ */
+@SpringBootTest
+class MainApplicationTests {
+
+ @Resource
+ private WxOpenConfig wxOpenConfig;
+
+ @Test
+ void contextLoads() {
+ System.out.println(wxOpenConfig);
+ }
+
+}
diff --git a/src/test/java/cc/bnblogs/springbootinit/esdao/PostEsDaoTest.java b/src/test/java/cc/bnblogs/springbootinit/esdao/PostEsDaoTest.java
new file mode 100644
index 0000000..8ea99a6
--- /dev/null
+++ b/src/test/java/cc/bnblogs/springbootinit/esdao/PostEsDaoTest.java
@@ -0,0 +1,80 @@
+package cc.bnblogs.springbootinit.esdao;
+
+import cc.bnblogs.springbootinit.model.dto.post.PostEsDTO;
+import cc.bnblogs.springbootinit.model.dto.post.PostQueryRequest;
+import cc.bnblogs.springbootinit.model.entity.Post;
+import cc.bnblogs.springbootinit.service.PostService;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+import javax.annotation.Resource;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+
+/**
+ * 帖子 ES 操作测试
+ */
+@SpringBootTest
+public class PostEsDaoTest {
+
+ @Resource
+ private PostEsDao postEsDao;
+
+ @Resource
+ private PostService postService;
+
+ @Test
+ void test() {
+ PostQueryRequest postQueryRequest = new PostQueryRequest();
+ com.baomidou.mybatisplus.extension.plugins.pagination.Page page =
+ postService.searchFromEs(postQueryRequest);
+ System.out.println(page);
+ }
+
+ @Test
+ void testSelect() {
+ System.out.println(postEsDao.count());
+ Page PostPage = postEsDao.findAll(
+ PageRequest.of(0, 5, Sort.by("createTime")));
+ List postList = PostPage.getContent();
+ System.out.println(postList);
+ }
+
+ @Test
+ void testAdd() {
+ PostEsDTO postEsDTO = new PostEsDTO();
+ postEsDTO.setId(1L);
+ postEsDTO.setTitle("test");
+ postEsDTO.setContent("test");
+ postEsDTO.setTags(Arrays.asList("java", "python"));
+ postEsDTO.setThumbNum(1);
+ postEsDTO.setFavourNum(1);
+ postEsDTO.setUserId(1L);
+ postEsDTO.setCreateTime(new Date());
+ postEsDTO.setUpdateTime(new Date());
+ postEsDTO.setIsDelete(0);
+ postEsDao.save(postEsDTO);
+ System.out.println(postEsDTO.getId());
+ }
+
+ @Test
+ void testFindById() {
+ Optional postEsDTO = postEsDao.findById(1L);
+ System.out.println(postEsDTO);
+ }
+
+ @Test
+ void testCount() {
+ System.out.println(postEsDao.count());
+ }
+
+ @Test
+ void testFindByCategory() {
+ List postEsDaoTestList = postEsDao.findByUserId(1L);
+ System.out.println(postEsDaoTestList);
+ }
+}
diff --git a/src/test/java/cc/bnblogs/springbootinit/manager/CosManagerTest.java b/src/test/java/cc/bnblogs/springbootinit/manager/CosManagerTest.java
new file mode 100644
index 0000000..b29393b
--- /dev/null
+++ b/src/test/java/cc/bnblogs/springbootinit/manager/CosManagerTest.java
@@ -0,0 +1,20 @@
+package cc.bnblogs.springbootinit.manager;
+
+import javax.annotation.Resource;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+/**
+ * Cos 操作测试
+ */
+@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/cc/bnblogs/springbootinit/mapper/PostFavourMapperTest.java b/src/test/java/cc/bnblogs/springbootinit/mapper/PostFavourMapperTest.java
new file mode 100644
index 0000000..ec5a8dd
--- /dev/null
+++ b/src/test/java/cc/bnblogs/springbootinit/mapper/PostFavourMapperTest.java
@@ -0,0 +1,31 @@
+package cc.bnblogs.springbootinit.mapper;
+
+import cc.bnblogs.springbootinit.model.entity.Post;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+import javax.annotation.Resource;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+/**
+ * 帖子收藏数据库操作测试
+ */
+@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/cc/bnblogs/springbootinit/mapper/PostMapperTest.java b/src/test/java/cc/bnblogs/springbootinit/mapper/PostMapperTest.java
new file mode 100644
index 0000000..b6ce6cf
--- /dev/null
+++ b/src/test/java/cc/bnblogs/springbootinit/mapper/PostMapperTest.java
@@ -0,0 +1,26 @@
+package cc.bnblogs.springbootinit.mapper;
+
+import cc.bnblogs.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;
+
+/**
+ * 帖子数据库操作测试
+ */
+@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/cc/bnblogs/springbootinit/service/PostFavourServiceTest.java b/src/test/java/cc/bnblogs/springbootinit/service/PostFavourServiceTest.java
new file mode 100644
index 0000000..4742f4b
--- /dev/null
+++ b/src/test/java/cc/bnblogs/springbootinit/service/PostFavourServiceTest.java
@@ -0,0 +1,42 @@
+package cc.bnblogs.springbootinit.service;
+
+import cc.bnblogs.springbootinit.model.entity.Post;
+import cc.bnblogs.springbootinit.model.entity.User;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+import javax.annotation.Resource;
+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;
+
+/**
+ * 帖子收藏服务测试
+ */
+@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/cc/bnblogs/springbootinit/service/PostServiceTest.java b/src/test/java/cc/bnblogs/springbootinit/service/PostServiceTest.java
new file mode 100644
index 0000000..8696be5
--- /dev/null
+++ b/src/test/java/cc/bnblogs/springbootinit/service/PostServiceTest.java
@@ -0,0 +1,29 @@
+package cc.bnblogs.springbootinit.service;
+
+import cc.bnblogs.springbootinit.model.dto.post.PostQueryRequest;
+import cc.bnblogs.springbootinit.model.entity.Post;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+import javax.annotation.Resource;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+/**
+ * 帖子服务测试
+ */
+@SpringBootTest
+class PostServiceTest {
+
+ @Resource
+ private PostService postService;
+
+ @Test
+ void searchFromEs() {
+ PostQueryRequest postQueryRequest = new PostQueryRequest();
+ postQueryRequest.setUserId(1L);
+ Page postPage = postService.searchFromEs(postQueryRequest);
+ Assertions.assertNotNull(postPage);
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/cc/bnblogs/springbootinit/service/PostThumbServiceTest.java b/src/test/java/cc/bnblogs/springbootinit/service/PostThumbServiceTest.java
new file mode 100644
index 0000000..4c5b4c8
--- /dev/null
+++ b/src/test/java/cc/bnblogs/springbootinit/service/PostThumbServiceTest.java
@@ -0,0 +1,32 @@
+package cc.bnblogs.springbootinit.service;
+
+import cc.bnblogs.springbootinit.model.entity.User;
+
+import javax.annotation.Resource;
+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;
+
+/**
+ * 帖子点赞服务测试
+ */
+@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/cc/bnblogs/springbootinit/service/UserServiceTest.java b/src/test/java/cc/bnblogs/springbootinit/service/UserServiceTest.java
new file mode 100644
index 0000000..700094e
--- /dev/null
+++ b/src/test/java/cc/bnblogs/springbootinit/service/UserServiceTest.java
@@ -0,0 +1,32 @@
+package cc.bnblogs.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;
+
+/**
+ * 用户服务测试
+ */
+@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/cc/bnblogs/springbootinit/utils/EasyExcelTest.java b/src/test/java/cc/bnblogs/springbootinit/utils/EasyExcelTest.java
new file mode 100644
index 0000000..61e797b
--- /dev/null
+++ b/src/test/java/cc/bnblogs/springbootinit/utils/EasyExcelTest.java
@@ -0,0 +1,31 @@
+package cc.bnblogs.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 测试
+ */
+@SpringBootTest
+public class EasyExcelTest {
+
+ @Test
+ public void doImport() throws FileNotFoundException {
+ File file = ResourceUtils.getFile("classpath:test_excel.xlsx");
+ List