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> list = EasyExcel.read(file) + .excelType(ExcelTypeEnum.XLSX) + .sheet() + .headRowNumber(0) + .doReadSync(); + System.out.println(list); + } + +} \ No newline at end of file