master
barney 1 year ago
commit 5d485c0edc
  1. BIN
      .DS_Store
  2. 144
      .gitignore
  3. 2
      .mvn/wrapper/maven-wrapper.properties
  4. 13
      Dockerfile
  5. 158
      README.md
  6. BIN
      doc/swagger.png
  7. 316
      mvnw
  8. 188
      mvnw.cmd
  9. 143
      pom.xml
  10. 65
      sql/create_table.sql
  11. 52
      sql/post_es_mapping.json
  12. BIN
      src/.DS_Store
  13. 25
      src/main/java/cc/bnblogs/springbootinit/MainApplication.java
  14. 23
      src/main/java/cc/bnblogs/springbootinit/annotation/AuthCheck.java
  15. 66
      src/main/java/cc/bnblogs/springbootinit/aop/AuthInterceptor.java
  16. 53
      src/main/java/cc/bnblogs/springbootinit/aop/LogInterceptor.java
  17. 31
      src/main/java/cc/bnblogs/springbootinit/common/BaseResponse.java
  18. 18
      src/main/java/cc/bnblogs/springbootinit/common/DeleteRequest.java
  19. 40
      src/main/java/cc/bnblogs/springbootinit/common/ErrorCode.java
  20. 31
      src/main/java/cc/bnblogs/springbootinit/common/PageRequest.java
  21. 49
      src/main/java/cc/bnblogs/springbootinit/common/ResultUtils.java
  22. 25
      src/main/java/cc/bnblogs/springbootinit/config/CorsConfig.java
  23. 50
      src/main/java/cc/bnblogs/springbootinit/config/CosClientConfig.java
  24. 28
      src/main/java/cc/bnblogs/springbootinit/config/JsonConfig.java
  25. 36
      src/main/java/cc/bnblogs/springbootinit/config/Knife4jConfig.java
  26. 31
      src/main/java/cc/bnblogs/springbootinit/config/MyBatisPlusConfig.java
  27. 48
      src/main/java/cc/bnblogs/springbootinit/config/WxOpenConfig.java
  28. 18
      src/main/java/cc/bnblogs/springbootinit/constant/CommonConstant.java
  29. 13
      src/main/java/cc/bnblogs/springbootinit/constant/FileConstant.java
  30. 31
      src/main/java/cc/bnblogs/springbootinit/constant/UserConstant.java
  31. 106
      src/main/java/cc/bnblogs/springbootinit/controller/FileController.java
  32. 247
      src/main/java/cc/bnblogs/springbootinit/controller/PostController.java
  33. 108
      src/main/java/cc/bnblogs/springbootinit/controller/PostFavourController.java
  34. 53
      src/main/java/cc/bnblogs/springbootinit/controller/PostThumbController.java
  35. 311
      src/main/java/cc/bnblogs/springbootinit/controller/UserController.java
  36. 134
      src/main/java/cc/bnblogs/springbootinit/controller/WxMpController.java
  37. 14
      src/main/java/cc/bnblogs/springbootinit/esdao/PostEsDao.java
  38. 33
      src/main/java/cc/bnblogs/springbootinit/exception/BusinessException.java
  39. 28
      src/main/java/cc/bnblogs/springbootinit/exception/GlobalExceptionHandler.java
  40. 42
      src/main/java/cc/bnblogs/springbootinit/exception/ThrowUtils.java
  41. 55
      src/main/java/cc/bnblogs/springbootinit/job/cycle/IncSyncPostToEs.java
  42. 45
      src/main/java/cc/bnblogs/springbootinit/job/once/FullSyncPostToEs.java
  43. 50
      src/main/java/cc/bnblogs/springbootinit/manager/CosManager.java
  44. 32
      src/main/java/cc/bnblogs/springbootinit/mapper/PostFavourMapper.java
  45. 23
      src/main/java/cc/bnblogs/springbootinit/mapper/PostMapper.java
  46. 15
      src/main/java/cc/bnblogs/springbootinit/mapper/PostThumbMapper.java
  47. 15
      src/main/java/cc/bnblogs/springbootinit/mapper/UserMapper.java
  48. 18
      src/main/java/cc/bnblogs/springbootinit/model/dto/file/UploadFileRequest.java
  49. 29
      src/main/java/cc/bnblogs/springbootinit/model/dto/post/PostAddRequest.java
  50. 34
      src/main/java/cc/bnblogs/springbootinit/model/dto/post/PostEditRequest.java
  51. 123
      src/main/java/cc/bnblogs/springbootinit/model/dto/post/PostEsDTO.java
  52. 63
      src/main/java/cc/bnblogs/springbootinit/model/dto/post/PostQueryRequest.java
  53. 34
      src/main/java/cc/bnblogs/springbootinit/model/dto/post/PostUpdateRequest.java
  54. 18
      src/main/java/cc/bnblogs/springbootinit/model/dto/postfavour/PostFavourAddRequest.java
  55. 27
      src/main/java/cc/bnblogs/springbootinit/model/dto/postfavour/PostFavourQueryRequest.java
  56. 18
      src/main/java/cc/bnblogs/springbootinit/model/dto/postthumb/PostThumbAddRequest.java
  57. 33
      src/main/java/cc/bnblogs/springbootinit/model/dto/user/UserAddRequest.java
  58. 18
      src/main/java/cc/bnblogs/springbootinit/model/dto/user/UserLoginRequest.java
  59. 46
      src/main/java/cc/bnblogs/springbootinit/model/dto/user/UserQueryRequest.java
  60. 19
      src/main/java/cc/bnblogs/springbootinit/model/dto/user/UserRegisterRequest.java
  61. 28
      src/main/java/cc/bnblogs/springbootinit/model/dto/user/UserUpdateMyRequest.java
  62. 37
      src/main/java/cc/bnblogs/springbootinit/model/dto/user/UserUpdateRequest.java
  63. 73
      src/main/java/cc/bnblogs/springbootinit/model/entity/Post.java
  64. 46
      src/main/java/cc/bnblogs/springbootinit/model/entity/PostFavour.java
  65. 46
      src/main/java/cc/bnblogs/springbootinit/model/entity/PostThumb.java
  66. 83
      src/main/java/cc/bnblogs/springbootinit/model/entity/User.java
  67. 58
      src/main/java/cc/bnblogs/springbootinit/model/enums/FileUploadBizEnum.java
  68. 60
      src/main/java/cc/bnblogs/springbootinit/model/enums/UserRoleEnum.java
  69. 49
      src/main/java/cc/bnblogs/springbootinit/model/vo/LoginUserVO.java
  70. 116
      src/main/java/cc/bnblogs/springbootinit/model/vo/PostVO.java
  71. 45
      src/main/java/cc/bnblogs/springbootinit/model/vo/UserVO.java
  72. 44
      src/main/java/cc/bnblogs/springbootinit/service/PostFavourService.java
  73. 58
      src/main/java/cc/bnblogs/springbootinit/service/PostService.java
  74. 29
      src/main/java/cc/bnblogs/springbootinit/service/PostThumbService.java
  75. 119
      src/main/java/cc/bnblogs/springbootinit/service/UserService.java
  76. 113
      src/main/java/cc/bnblogs/springbootinit/service/impl/PostFavourServiceImpl.java
  77. 314
      src/main/java/cc/bnblogs/springbootinit/service/impl/PostServiceImpl.java
  78. 102
      src/main/java/cc/bnblogs/springbootinit/service/impl/PostThumbServiceImpl.java
  79. 270
      src/main/java/cc/bnblogs/springbootinit/service/impl/UserServiceImpl.java
  80. 52
      src/main/java/cc/bnblogs/springbootinit/utils/NetUtils.java
  81. 54
      src/main/java/cc/bnblogs/springbootinit/utils/SpringContextUtils.java
  82. 22
      src/main/java/cc/bnblogs/springbootinit/utils/SqlUtils.java
  83. 13
      src/main/java/cc/bnblogs/springbootinit/wxmp/WxMpConstant.java
  84. 58
      src/main/java/cc/bnblogs/springbootinit/wxmp/WxMpMsgRouter.java
  85. 28
      src/main/java/cc/bnblogs/springbootinit/wxmp/handler/EventHandler.java
  86. 27
      src/main/java/cc/bnblogs/springbootinit/wxmp/handler/MessageHandler.java
  87. 28
      src/main/java/cc/bnblogs/springbootinit/wxmp/handler/SubscribeHandler.java
  88. 34
      src/main/resources/META-INF/additional-spring-configuration-metadata.json
  89. 29
      src/main/resources/application-prod.yml
  90. 25
      src/main/resources/application-test.yml
  91. 91
      src/main/resources/application.yml
  92. 1
      src/main/resources/banner.txt
  93. 27
      src/main/resources/mapper/PostFavourMapper.xml
  94. 31
      src/main/resources/mapper/PostMapper.xml
  95. 19
      src/main/resources/mapper/PostThumbMapper.xml
  96. 25
      src/main/resources/mapper/UserMapper.xml
  97. BIN
      src/main/resources/test_excel.xlsx
  98. BIN
      src/test/.DS_Store
  99. 22
      src/test/java/cc/bnblogs/springbootinit/MainApplicationTests.java
  100. 80
      src/test/java/cc/bnblogs/springbootinit/esdao/PostEsDaoTest.java
  101. Some files were not shown because too many files have changed in this diff Show More

BIN
.DS_Store vendored

Binary file not shown.

144
.gitignore vendored

@ -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

@ -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

@ -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"]

@ -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
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 KiB

316
mvnw vendored

@ -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 "$@"

188
mvnw.cmd vendored

@ -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%

@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.yupi</groupId>
<artifactId>springboot-init</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-init</name>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!-- elasticsearch-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.binarywang/wx-java-mp-spring-boot-starter -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-mp-spring-boot-starter</artifactId>
<version>4.4.0</version>
</dependency>
<!-- https://doc.xiaominfo.com/knife4j/documentation/get_start.html-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<!-- https://cloud.tencent.com/document/product/436/10199-->
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.6.89</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.1</version>
</dependency>
<!-- https://github.com/alibaba/easyexcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.1</version>
</dependency>
<!-- https://hutool.cn/docs/index.html#/-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.8</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

@ -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 '帖子收藏';

@ -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"
}
}
}
}

BIN
src/.DS_Store vendored

Binary file not shown.

@ -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);
}
}

@ -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 "";
}

@ -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();
}
}

@ -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;
}
}

@ -0,0 +1,31 @@
package cc.bnblogs.springbootinit.common;
import java.io.Serializable;
import lombok.Data;
/**
* 通用返回类
*/
@Data
public class BaseResponse<T> 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());
}
}

@ -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;
}

@ -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;
}
}

@ -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;
}

@ -0,0 +1,49 @@
package cc.bnblogs.springbootinit.common;
/**
* 返回工具类
*/
public class ResultUtils {
/**
* 成功
*
* @param data
* @param <T>
* @return
*/
public static <T> BaseResponse<T> 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);
}
}

@ -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("*");
}
}

@ -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);
}
}

@ -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;
}
}

@ -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();
}
}

@ -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;
}
}

@ -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;
}
}
}

@ -0,0 +1,18 @@
package cc.bnblogs.springbootinit.constant;
/**
* 通用常量
*/
public interface CommonConstant {
/**
* 升序
*/
String SORT_ORDER_ASC = "ascend";
/**
* 降序
*/
String SORT_ORDER_DESC = " descend";
}

@ -0,0 +1,13 @@
package cc.bnblogs.springbootinit.constant;
/**
* 文件常量
*/
public interface FileConstant {
/**
* COS 访问地址
* todo 需替换配置
*/
String COS_HOST = "https://yupi.icu";
}

@ -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
}

@ -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<String> 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, "文件类型错误");
}
}
}
}

@ -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<Long> addPost(@RequestBody PostAddRequest postAddRequest, HttpServletRequest request) {
if (postAddRequest == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
Post post = new Post();
BeanUtils.copyProperties(postAddRequest, post);
List<String> 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<Boolean> 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<Boolean> 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<String> 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<PostVO> 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<Page<PostVO>> listPostVOByPage(@RequestBody PostQueryRequest postQueryRequest,
HttpServletRequest request) {
long current = postQueryRequest.getCurrent();
long size = postQueryRequest.getPageSize();
// 限制爬虫
ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
Page<Post> 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<Page<PostVO>> 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<Post> 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<Page<PostVO>> searchPostVOByPage(@RequestBody PostQueryRequest postQueryRequest,
HttpServletRequest request) {
long size = postQueryRequest.getPageSize();
// 限制爬虫
ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
Page<Post> postPage = postService.searchFromEs(postQueryRequest);
return ResultUtils.success(postService.getPostVOPage(postPage, request));
}
/**
* 编辑用户
*
* @param postEditRequest
* @param request
* @return
*/
@PostMapping("/edit")
public BaseResponse<Boolean> 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<String> 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);
}
}

@ -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<Integer> 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<Page<PostVO>> 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<Post> 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<Page<PostVO>> 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<Post> postPage = postFavourService.listFavourPostByPage(new Page<>(current, size),
postService.getQueryWrapper(postFavourQueryRequest.getPostQueryRequest()), userId);
return ResultUtils.success(postService.getPostVOPage(postPage, request));
}
}

@ -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<Integer> 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);
}
}

@ -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<Long> 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<LoginUserVO> 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<LoginUserVO> 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<Boolean> 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<LoginUserVO> 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<Long> 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<Boolean> 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<Boolean> 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<User> 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<UserVO> getUserVOById(long id, HttpServletRequest request) {
BaseResponse<User> 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<Page<User>> listUserByPage(@RequestBody UserQueryRequest userQueryRequest,
HttpServletRequest request) {
long current = userQueryRequest.getCurrent();
long size = userQueryRequest.getPageSize();
Page<User> 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<Page<UserVO>> 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<User> userPage = userService.page(new Page<>(current, size),
userService.getQueryWrapper(userQueryRequest));
Page<UserVO> userVOPage = new Page<>(current, size, userPage.getTotal());
List<UserVO> userVO = userService.getUserVO(userPage.getRecords());
userVOPage.setRecords(userVO);
return ResultUtils.success(userVOPage);
}
// endregion
/**
* 更新个人信息
*
* @param userUpdateMyRequest
* @param request
* @return
*/
@PostMapping("/update/my")
public BaseResponse<Boolean> 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);
}
}

@ -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";
}
}

@ -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<PostEsDTO, Long> {
List<PostEsDTO> findByUserId(Long userId);
}

@ -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;
}
}

@ -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, "系统错误");
}
}

@ -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));
}
}

@ -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<Post> postList = postMapper.listPostWithDelete(fiveMinutesAgoDate);
if (CollectionUtils.isEmpty(postList)) {
log.info("no inc post");
return;
}
List<PostEsDTO> 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);
}
}

@ -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<Post> postList = postService.list();
if (CollectionUtils.isEmpty(postList)) {
return;
}
List<PostEsDTO> 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);
}
}

@ -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);
}
}

@ -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<PostFavour> {
/**
* 分页查询收藏帖子列表
*
* @param page
* @param queryWrapper
* @param favourUserId
* @return
*/
Page<Post> listFavourPostByPage(IPage<Post> page, @Param(Constants.WRAPPER) Wrapper<Post> queryWrapper,
long favourUserId);
}

@ -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<Post> {
/**
* 查询帖子列表包括已被删除的数据
*/
List<Post> listPostWithDelete(Date minUpdateTime);
}

@ -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<PostThumb> {
}

@ -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<User> {
}

@ -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;
}

@ -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<String> tags;
private static final long serialVersionUID = 1L;
}

@ -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<String> tags;
private static final long serialVersionUID = 1L;
}

@ -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<String> 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<List<String>>() {
}.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<String> tagList = postEsDTO.getTags();
if (CollectionUtils.isNotEmpty(tagList)) {
post.setTags(GSON.toJson(tagList));
}
return post;
}
}

@ -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<String> tags;
/**
* 至少有一个标签
*/
private List<String> orTags;
/**
* 创建用户 id
*/
private Long userId;
/**
* 收藏用户 id
*/
private Long favourUserId;
private static final long serialVersionUID = 1L;
}

@ -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<String> tags;
private static final long serialVersionUID = 1L;
}

@ -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;
}

@ -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;
}

@ -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;
}

@ -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;
}

@ -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;
}

@ -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;
}

@ -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;
}

@ -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;
}

@ -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;
}

@ -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;
}

@ -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;
}

@ -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;
}

@ -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;
}

@ -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<String> 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;
}
}

@ -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<String> 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;
}
}

@ -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;
}

@ -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<String> 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<String> 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<List<String>>() {
}.getType()));
return postVO;
}
}

@ -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;
}

@ -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<PostFavour> {
/**
* 帖子收藏
*
* @param postId
* @param loginUser
* @return
*/
int doPostFavour(long postId, User loginUser);
/**
* 分页获取用户收藏的帖子列表
*
* @param page
* @param queryWrapper
* @param favourUserId
* @return
*/
Page<Post> listFavourPostByPage(IPage<Post> page, Wrapper<Post> queryWrapper,
long favourUserId);
/**
* 帖子收藏内部服务
*
* @param userId
* @param postId
* @return
*/
int doPostFavourInner(long userId, long postId);
}

@ -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<Post> {
/**
* 校验
*
* @param post
* @param add
*/
void validPost(Post post, boolean add);
/**
* 获取查询条件
*
* @param postQueryRequest
* @return
*/
QueryWrapper<Post> getQueryWrapper(PostQueryRequest postQueryRequest);
/**
* ES 查询
*
* @param postQueryRequest
* @return
*/
Page<Post> searchFromEs(PostQueryRequest postQueryRequest);
/**
* 获取帖子封装
*
* @param post
* @param request
* @return
*/
PostVO getPostVO(Post post, HttpServletRequest request);
/**
* 分页获取帖子封装
*
* @param postPage
* @param request
* @return
*/
Page<PostVO> getPostVOPage(Page<Post> postPage, HttpServletRequest request);
}

@ -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<PostThumb> {
/**
* 点赞
*
* @param postId
* @param loginUser
* @return
*/
int doPostThumb(long postId, User loginUser);
/**
* 帖子点赞内部服务
*
* @param userId
* @param postId
* @return
*/
int doPostThumbInner(long userId, long postId);
}

@ -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<User> {
/**
* 用户注册
*
* @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<UserVO> getUserVO(List<User> userList);
/**
* 获取查询条件
*
* @param userQueryRequest
* @return
*/
QueryWrapper<User> getQueryWrapper(UserQueryRequest userQueryRequest);
}

@ -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<PostFavourMapper, PostFavour>
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<Post> listFavourPostByPage(IPage<Post> page, Wrapper<Post> 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<PostFavour> 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);
}
}
}
}

@ -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<PostMapper, Post> 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<Post> getQueryWrapper(PostQueryRequest postQueryRequest) {
QueryWrapper<Post> 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<String> 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<Post> searchFromEs(PostQueryRequest postQueryRequest) {
Long id = postQueryRequest.getId();
Long notId = postQueryRequest.getNotId();
String searchText = postQueryRequest.getSearchText();
String title = postQueryRequest.getTitle();
String content = postQueryRequest.getContent();
List<String> tagList = postQueryRequest.getTags();
List<String> 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<PostEsDTO> searchHits = elasticsearchRestTemplate.search(searchQuery, PostEsDTO.class);
Page<Post> page = new Page<>();
page.setTotal(searchHits.getTotalHits());
List<Post> resourceList = new ArrayList<>();
// 查出结果后,从 db 获取最新动态数据(比如点赞数)
if (searchHits.hasSearchHits()) {
List<SearchHit<PostEsDTO>> searchHitList = searchHits.getSearchHits();
List<Long> postIdList = searchHitList.stream().map(searchHit -> searchHit.getContent().getId())
.collect(Collectors.toList());
List<Post> postList = baseMapper.selectBatchIds(postIdList);
if (postList != null) {
Map<Long, List<Post>> 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<PostThumb> postThumbQueryWrapper = new QueryWrapper<>();
postThumbQueryWrapper.in("postId", postId);
postThumbQueryWrapper.eq("userId", loginUser.getId());
PostThumb postThumb = postThumbMapper.selectOne(postThumbQueryWrapper);
postVO.setHasThumb(postThumb != null);
// 获取收藏
QueryWrapper<PostFavour> 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<PostVO> getPostVOPage(Page<Post> postPage, HttpServletRequest request) {
List<Post> postList = postPage.getRecords();
Page<PostVO> postVOPage = new Page<>(postPage.getCurrent(), postPage.getSize(), postPage.getTotal());
if (CollectionUtils.isEmpty(postList)) {
return postVOPage;
}
// 1. 关联查询用户信息
Set<Long> userIdSet = postList.stream().map(Post::getUserId).collect(Collectors.toSet());
Map<Long, List<User>> userIdUserListMap = userService.listByIds(userIdSet).stream()
.collect(Collectors.groupingBy(User::getId));
// 2. 已登录,获取用户点赞、收藏状态
Map<Long, Boolean> postIdHasThumbMap = new HashMap<>();
Map<Long, Boolean> postIdHasFavourMap = new HashMap<>();
User loginUser = userService.getLoginUserPermitNull(request);
if (loginUser != null) {
Set<Long> postIdSet = postList.stream().map(Post::getId).collect(Collectors.toSet());
loginUser = userService.getLoginUser(request);
// 获取点赞
QueryWrapper<PostThumb> postThumbQueryWrapper = new QueryWrapper<>();
postThumbQueryWrapper.in("postId", postIdSet);
postThumbQueryWrapper.eq("userId", loginUser.getId());
List<PostThumb> postPostThumbList = postThumbMapper.selectList(postThumbQueryWrapper);
postPostThumbList.forEach(postPostThumb -> postIdHasThumbMap.put(postPostThumb.getPostId(), true));
// 获取收藏
QueryWrapper<PostFavour> postFavourQueryWrapper = new QueryWrapper<>();
postFavourQueryWrapper.in("postId", postIdSet);
postFavourQueryWrapper.eq("userId", loginUser.getId());
List<PostFavour> postFavourList = postFavourMapper.selectList(postFavourQueryWrapper);
postFavourList.forEach(postFavour -> postIdHasFavourMap.put(postFavour.getPostId(), true));
}
// 填充信息
List<PostVO> 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;
}
}

@ -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<PostThumbMapper, PostThumb>
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<PostThumb> 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);
}
}
}
}

@ -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<UserMapper, User> 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<User> 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<User> 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<User> 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<UserVO> getUserVO(List<User> userList) {
if (CollectionUtils.isEmpty(userList)) {
return new ArrayList<>();
}
return userList.stream().map(this::getUserVO).collect(Collectors.toList());
}
@Override
public QueryWrapper<User> 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<User> 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;
}
}

@ -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;
}
}

@ -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 <T>
* @return
*/
public static <T> T getBean(Class<T> beanClass) {
return applicationContext.getBean(beanClass);
}
/**
* 通过名称和类型获取 Bean
*
* @param beanName
* @param beanClass
* @param <T>
* @return
*/
public static <T> T getBean(String beanName, Class<T> beanClass) {
return applicationContext.getBean(beanName, beanClass);
}
}

@ -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, "=", "(", ")", " ");
}
}

@ -0,0 +1,13 @@
package cc.bnblogs.springbootinit.wxmp;
/**
* 微信公众号相关常量
**/
public class WxMpConstant {
/**
* 点击菜单 key
*/
public static final String CLICK_MENU_KEY = "CLICK_MENU_KEY";
}

@ -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;
}
}

@ -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<String, Object> map, WxMpService wxMpService,
WxSessionManager wxSessionManager) throws WxErrorException {
final String content = "您点击了菜单";
// 调用接口,返回验证码
return WxMpXmlOutMessage.TEXT().content(content)
.fromUser(wxMpXmlMessage.getToUser())
.toUser(wxMpXmlMessage.getFromUser())
.build();
}
}

@ -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<String, Object> map,
WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException {
String content = "我是复读机:" + wxMpXmlMessage.getContent();
return WxMpXmlOutMessage.TEXT().content(content)
.fromUser(wxMpXmlMessage.getToUser())
.toUser(wxMpXmlMessage.getFromUser())
.build();
}
}

@ -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<String, Object> map,
WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException {
final String content = "感谢关注";
// 调用接口,返回验证码
return WxMpXmlOutMessage.TEXT().content(content)
.fromUser(wxMpXmlMessage.getToUser())
.toUser(wxMpXmlMessage.getFromUser())
.build();
}
}

@ -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."
}
]
}

@ -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: ''

@ -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

@ -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

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cc.bnblogs.springbootinit.mapper.PostFavourMapper">
<resultMap id="BaseResultMap" type="cc.bnblogs.springbootinit.model.entity.PostFavour">
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="postId" column="postId" jdbcType="BIGINT"/>
<result property="userId" column="userId" jdbcType="BIGINT"/>
<result property="createTime" column="createTime" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="updateTime" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">
id,postId,userId,
createTime,updateTime
</sql>
<select id="listFavourPostByPage"
resultType="cc.bnblogs.springbootinit.model.entity.Post">
select p.*
from post p
join (select postId from post_favour where userId = #{favourUserId}) pf
on p.id = pf.postId ${ew.customSqlSegment}
</select>
</mapper>

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cc.bnblogs.springbootinit.mapper.PostMapper">
<resultMap id="BaseResultMap" type="cc.bnblogs.springbootinit.model.entity.Post">
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="title" column="title" jdbcType="VARCHAR"/>
<result property="content" column="content" jdbcType="VARCHAR"/>
<result property="tags" column="tags" jdbcType="VARCHAR"/>
<result property="thumbNum" column="thumbNum" jdbcType="BIGINT"/>
<result property="favourNum" column="favourNum" jdbcType="BIGINT"/>
<result property="userId" column="userId" jdbcType="BIGINT"/>
<result property="createTime" column="createTime" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="updateTime" jdbcType="TIMESTAMP"/>
<result property="isDelete" column="isDelete" jdbcType="TINYINT"/>
</resultMap>
<sql id="Base_Column_List">
id,title,content,tags,
thumbNum,favourNum,userId,
createTime,updateTime,isDelete
</sql>
<select id="listPostWithDelete" resultType="cc.bnblogs.springbootinit.model.entity.Post">
select *
from post
where updateTime >= #{minUpdateTime}
</select>
</mapper>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cc.bnblogs.springbootinit.mapper.PostThumbMapper">
<resultMap id="BaseResultMap" type="cc.bnblogs.springbootinit.model.entity.PostThumb">
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="postId" column="postId" jdbcType="BIGINT"/>
<result property="userId" column="userId" jdbcType="BIGINT"/>
<result property="createTime" column="createTime" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="updateTime" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">
id,postId,
userId,createTime,updateTime
</sql>
</mapper>

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cc.bnblogs.springbootinit.mapper.UserMapper">
<resultMap id="BaseResultMap" type="cc.bnblogs.springbootinit.model.entity.User">
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="unionId" column="unionId" jdbcType="VARCHAR"/>
<result property="mpOpenId" column="mpOpenId" jdbcType="VARCHAR"/>
<result property="userName" column="userName" jdbcType="VARCHAR"/>
<result property="userAvatar" column="userAvatar" jdbcType="VARCHAR"/>
<result property="userProfile" column="userProfile" jdbcType="VARCHAR"/>
<result property="userRole" column="userRole" jdbcType="VARCHAR"/>
<result property="createTime" column="createTime" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="updateTime" jdbcType="TIMESTAMP"/>
<result property="isDelete" column="isDelete" jdbcType="TINYINT"/>
</resultMap>
<sql id="Base_Column_List">
id,unionId,mpOpenId,
userName,userAvatar,userProfile,
userRole,createTime,updateTime,isDelete
</sql>
</mapper>

BIN
src/test/.DS_Store vendored

Binary file not shown.

@ -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);
}
}

@ -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<Post> page =
postService.searchFromEs(postQueryRequest);
System.out.println(page);
}
@Test
void testSelect() {
System.out.println(postEsDao.count());
Page<PostEsDTO> PostPage = postEsDao.findAll(
PageRequest.of(0, 5, Sort.by("createTime")));
List<PostEsDTO> 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> postEsDTO = postEsDao.findById(1L);
System.out.println(postEsDTO);
}
@Test
void testCount() {
System.out.println(postEsDao.count());
}
@Test
void testFindByCategory() {
List<PostEsDTO> postEsDaoTestList = postEsDao.findByUserId(1L);
System.out.println(postEsDaoTestList);
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save