From aca37b62d4e9337d0f6c0cf0177f326cc6b1bf88 Mon Sep 17 00:00:00 2001 From: Ziin Date: Thu, 12 Jun 2025 19:44:47 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E5=88=9D=E6=AC=A1=E6=8F=90?= =?UTF-8?q?=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 148 ++++++++ .xcodemap/config/xcodemap-class-filter.yaml | 23 ++ Dockerfile | 15 + README.md | 181 ++++++++++ mvnw | 316 ++++++++++++++++++ mvnw.cmd | 188 +++++++++++ pom.xml | 134 ++++++++ .../yupi/springbootinit/MainApplication.java | 27 ++ .../springbootinit/annotation/AuthCheck.java | 26 ++ .../springbootinit/aop/LogInterceptor.java | 56 ++++ .../springbootinit/common/BaseResponse.java | 35 ++ .../springbootinit/common/DeleteRequest.java | 21 ++ .../yupi/springbootinit/common/ErrorCode.java | 47 +++ .../springbootinit/common/PageRequest.java | 34 ++ .../springbootinit/common/ResultUtils.java | 52 +++ .../springbootinit/config/CorsConfig.java | 28 ++ .../springbootinit/config/JsonConfig.java | 31 ++ .../config/MyBatisPlusConfig.java | 31 ++ .../springbootinit/config/RabbitMQConfig.java | 26 ++ .../constant/CommonConstant.java | 21 ++ .../springbootinit/constant/FileConstant.java | 16 + .../springbootinit/constant/UserConstant.java | 34 ++ .../controller/HostInfoController.java | 35 ++ .../exception/BusinessException.java | 36 ++ .../exception/GlobalExceptionHandler.java | 31 ++ .../springbootinit/exception/ThrowUtils.java | 45 +++ .../generate/CodeGenerator.java | 128 +++++++ .../springbootinit/mapper/NewHostsMapper.java | 27 ++ .../model/dto/host/HostInfoDTO.java | 88 +++++ .../model/dto/user/SystemUsersDTO.java | 30 ++ .../springbootinit/model/entity/NewHosts.java | 90 +++++ .../model/entity/SystemUsers.java | 124 +++++++ .../model/enums/FileUploadBizEnum.java | 62 ++++ .../model/enums/UserRoleEnum.java | 64 ++++ .../model/vo/hosts/NewHostsVO.java | 81 +++++ .../model/vo/user/SystemUsersVO.java | 30 ++ .../springbootinit/rabbitMQ/MQReceiver.java | 56 ++++ .../springbootinit/rabbitMQ/MQSender.java | 35 ++ .../service/HostInfoService.java | 23 ++ .../service/impl/HostInfoServiceImpl.java | 87 +++++ .../yupi/springbootinit/utils/NetUtils.java | 55 +++ .../utils/SpringContextUtils.java | 57 ++++ .../yupi/springbootinit/utils/SqlUtils.java | 25 ++ ...itional-spring-configuration-metadata.json | 34 ++ src/main/resources/application-dev.yml | 30 ++ src/main/resources/application-prod.yml | 40 +++ src/main/resources/application.yml | 109 ++++++ src/main/resources/mapper/NewHostsMapper.xml | 225 +++++++++++++ .../templates/TemplateController.java.ftl | 239 +++++++++++++ .../templates/TemplateService.java.ftl | 53 +++ .../templates/TemplateServiceImpl.java.ftl | 224 +++++++++++++ .../model/TemplateAddRequest.java.ftl | 33 ++ .../model/TemplateEditRequest.java.ftl | 38 +++ .../model/TemplateQueryRequest.java.ftl | 56 ++++ .../model/TemplateUpdateRequest.java.ftl | 38 +++ .../templates/model/TemplateVO.java.ftl | 93 ++++++ src/main/resources/test_excel.xlsx | Bin 0 -> 9143 bytes .../springbootinit/MainApplicationTests.java | 19 ++ 58 files changed, 3930 insertions(+) create mode 100644 .gitignore create mode 100644 .xcodemap/config/xcodemap-class-filter.yaml create mode 100644 Dockerfile create mode 100644 README.md create mode 100755 mvnw create mode 100644 mvnw.cmd create mode 100644 pom.xml create mode 100644 src/main/java/com/yupi/springbootinit/MainApplication.java create mode 100644 src/main/java/com/yupi/springbootinit/annotation/AuthCheck.java create mode 100644 src/main/java/com/yupi/springbootinit/aop/LogInterceptor.java create mode 100644 src/main/java/com/yupi/springbootinit/common/BaseResponse.java create mode 100644 src/main/java/com/yupi/springbootinit/common/DeleteRequest.java create mode 100644 src/main/java/com/yupi/springbootinit/common/ErrorCode.java create mode 100644 src/main/java/com/yupi/springbootinit/common/PageRequest.java create mode 100644 src/main/java/com/yupi/springbootinit/common/ResultUtils.java create mode 100644 src/main/java/com/yupi/springbootinit/config/CorsConfig.java create mode 100644 src/main/java/com/yupi/springbootinit/config/JsonConfig.java create mode 100644 src/main/java/com/yupi/springbootinit/config/MyBatisPlusConfig.java create mode 100644 src/main/java/com/yupi/springbootinit/config/RabbitMQConfig.java create mode 100644 src/main/java/com/yupi/springbootinit/constant/CommonConstant.java create mode 100644 src/main/java/com/yupi/springbootinit/constant/FileConstant.java create mode 100644 src/main/java/com/yupi/springbootinit/constant/UserConstant.java create mode 100644 src/main/java/com/yupi/springbootinit/controller/HostInfoController.java create mode 100644 src/main/java/com/yupi/springbootinit/exception/BusinessException.java create mode 100644 src/main/java/com/yupi/springbootinit/exception/GlobalExceptionHandler.java create mode 100644 src/main/java/com/yupi/springbootinit/exception/ThrowUtils.java create mode 100644 src/main/java/com/yupi/springbootinit/generate/CodeGenerator.java create mode 100644 src/main/java/com/yupi/springbootinit/mapper/NewHostsMapper.java create mode 100644 src/main/java/com/yupi/springbootinit/model/dto/host/HostInfoDTO.java create mode 100644 src/main/java/com/yupi/springbootinit/model/dto/user/SystemUsersDTO.java create mode 100644 src/main/java/com/yupi/springbootinit/model/entity/NewHosts.java create mode 100644 src/main/java/com/yupi/springbootinit/model/entity/SystemUsers.java create mode 100644 src/main/java/com/yupi/springbootinit/model/enums/FileUploadBizEnum.java create mode 100644 src/main/java/com/yupi/springbootinit/model/enums/UserRoleEnum.java create mode 100644 src/main/java/com/yupi/springbootinit/model/vo/hosts/NewHostsVO.java create mode 100644 src/main/java/com/yupi/springbootinit/model/vo/user/SystemUsersVO.java create mode 100644 src/main/java/com/yupi/springbootinit/rabbitMQ/MQReceiver.java create mode 100644 src/main/java/com/yupi/springbootinit/rabbitMQ/MQSender.java create mode 100644 src/main/java/com/yupi/springbootinit/service/HostInfoService.java create mode 100644 src/main/java/com/yupi/springbootinit/service/impl/HostInfoServiceImpl.java create mode 100644 src/main/java/com/yupi/springbootinit/utils/NetUtils.java create mode 100644 src/main/java/com/yupi/springbootinit/utils/SpringContextUtils.java create mode 100644 src/main/java/com/yupi/springbootinit/utils/SqlUtils.java create mode 100644 src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 src/main/resources/application-dev.yml create mode 100644 src/main/resources/application-prod.yml create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/mapper/NewHostsMapper.xml create mode 100644 src/main/resources/templates/TemplateController.java.ftl create mode 100644 src/main/resources/templates/TemplateService.java.ftl create mode 100644 src/main/resources/templates/TemplateServiceImpl.java.ftl create mode 100644 src/main/resources/templates/model/TemplateAddRequest.java.ftl create mode 100644 src/main/resources/templates/model/TemplateEditRequest.java.ftl create mode 100644 src/main/resources/templates/model/TemplateQueryRequest.java.ftl create mode 100644 src/main/resources/templates/model/TemplateUpdateRequest.java.ftl create mode 100644 src/main/resources/templates/model/TemplateVO.java.ftl create mode 100644 src/main/resources/test_excel.xlsx create mode 100644 src/test/java/com/yupi/springbootinit/MainApplicationTests.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d454a7f --- /dev/null +++ b/.gitignore @@ -0,0 +1,148 @@ +### @author 程序员鱼皮 ### +### @from 编程导航知识星球 ### + +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +### Java template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Maven template +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +# https://github.com/takari/maven-wrapper#usage-without-binary-jar +.mvn/wrapper/maven-wrapper.jar + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +!/.xcodemap/ diff --git a/.xcodemap/config/xcodemap-class-filter.yaml b/.xcodemap/config/xcodemap-class-filter.yaml new file mode 100644 index 0000000..1559fe0 --- /dev/null +++ b/.xcodemap/config/xcodemap-class-filter.yaml @@ -0,0 +1,23 @@ +autoDetectedPackages: +- com.yupi.springbootinit +enableAutoDetect: true +entryDisplayConfig: + excludedPathPatterns: [] + skipJsCss: true +funcDisplayConfig: + skipConstructors: false + skipFieldAccess: true + skipFieldChange: true + skipGetters: false + skipNonProjectPackages: false + skipPrivateMethods: false + skipSetters: false +ignoreSameClassCall: null +ignoreSamePackageCall: null +includedPackagePrefixes: null +includedParentClasses: null +name: xcodemap-filter +recordMode: all +sourceDisplayConfig: + color: blue +startOnDebug: false diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e7b0e64 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +# Docker 镜像构建 +# @author 程序员鱼皮 +# @from 编程导航知识星球 +FROM maven:3.8.1-jdk-8-slim as builder + +# Copy local code to the container image. +WORKDIR /app +COPY pom.xml . +COPY src ./src + +# Build a release artifact. +RUN mvn package -DskipTests + +# Run the web service on container startup. +CMD ["java","-jar","/app/target/springboot-init-0.0.1-SNAPSHOT.jar","--spring.profiles.active=prod"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..30b6575 --- /dev/null +++ b/README.md @@ -0,0 +1,181 @@ +# SpringBoot 项目初始模板 + +> 作者:[程序员鱼皮](https://github.com/liyupi) +> 仅分享于 [编程导航知识星球](https://yupi.icu) + +基于 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 工具库 +- Apache Commons Lang3 工具类 +- Lombok 注解 + +### 业务特性 + +- 业务代码生成器(支持自动生成 Service、Controller、数据模型代码) +- Spring Session Redis 分布式登录 +- 全局请求响应拦截器(记录日志) +- 全局异常处理器 +- 自定义错误码 +- 封装通用响应类 +- Swagger + Knife4j 接口文档 +- 自定义权限注解 + 全局校验 +- 全局跨域处理 +- 长整数丢失精度解决 +- 多环境配置 + + +## 业务功能 + +- 提供示例 SQL(用户、帖子、帖子点赞、帖子收藏表) +- 用户登录、注册、注销、更新、检索、权限管理 +- 帖子创建、删除、编辑、更新、数据库检索、ES 灵活检索 +- 帖子点赞、取消点赞 +- 帖子收藏、取消收藏、检索已收藏帖子 +- 帖子全量同步 ES、增量同步 ES 定时任务 +- 支持微信开放平台登录 +- 支持微信公众号订阅、收发消息、设置菜单 +- 支持分业务的文件上传 + +### 单元测试 + +- JUnit5 单元测试 +- 示例单元测试类 + +### 架构设计 + +- 合理分层 + + +## 快速上手 + +> 所有需要修改的地方鱼皮都标记了 `todo`,便于大家找到修改的位置~ + +### 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 +``` + +### 业务代码生成器 + +支持自动生成 Service、Controller、数据模型代码,配合 MyBatisX 插件,可以快速开发增删改查等实用基础功能。 + +找到 `generate.CodeGenerator` 类,修改生成参数和生成路径,并且支持注释掉不需要的生成逻辑,然后运行即可。 + +``` +// 指定生成参数 +String packageName = "com.yupi.springbootinit"; +String dataName = "用户评论"; +String dataKey = "userComment"; +String upperDataKey = "UserComment"; +``` + +生成代码后,可以移动到实际项目中,并且按照 `// todo` 注释的提示来针对自己的业务需求进行修改。 diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..8a8fb22 --- /dev/null +++ b/mvnw @@ -0,0 +1,316 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`\\unset -f command; \\command -v java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..1d8ab01 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,188 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..4901caf --- /dev/null +++ b/pom.xml @@ -0,0 +1,134 @@ + + + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.2 + + + com.yupi + springboot-init + 0.0.1-SNAPSHOT + springboot-init + + 11 + + + + org.springframework.boot + spring-boot-starter-freemarker + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-aop + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.2.2 + + + + com.baomidou + mybatis-plus-boot-starter + 3.5.2 + + + + org.springframework.boot + spring-boot-starter-amqp + + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.session + spring-session-data-redis + + + + com.github.xiaoymin + knife4j-openapi2-spring-boot-starter + 4.4.0 + + + + org.apache.commons + commons-lang3 + + + + cn.hutool + hutool-all + 5.8.8 + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + mysql + mysql-connector-java + runtime + + + com.google.guava + guava + r07 + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 9 + 9 + + + + + + diff --git a/src/main/java/com/yupi/springbootinit/MainApplication.java b/src/main/java/com/yupi/springbootinit/MainApplication.java new file mode 100644 index 0000000..712c0b0 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/MainApplication.java @@ -0,0 +1,27 @@ +package com.yupi.springbootinit; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * 主类(项目启动入口) + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +// todo 如需开启 Redis,须移除 exclude 中的内容 +@SpringBootApplication(exclude = {RedisAutoConfiguration.class}) +@MapperScan("com.yupi.springbootinit.mapper") +@EnableScheduling +@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true) +public class MainApplication { + + public static void main(String[] args) { + SpringApplication.run(MainApplication.class, args); + } + +} diff --git a/src/main/java/com/yupi/springbootinit/annotation/AuthCheck.java b/src/main/java/com/yupi/springbootinit/annotation/AuthCheck.java new file mode 100644 index 0000000..b06c232 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/annotation/AuthCheck.java @@ -0,0 +1,26 @@ +package com.yupi.springbootinit.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 权限校验 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface AuthCheck { + + /** + * 必须有某个角色 + * + * @return + */ + String mustRole() default ""; + +} + diff --git a/src/main/java/com/yupi/springbootinit/aop/LogInterceptor.java b/src/main/java/com/yupi/springbootinit/aop/LogInterceptor.java new file mode 100644 index 0000000..5b804b9 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/aop/LogInterceptor.java @@ -0,0 +1,56 @@ +package com.yupi.springbootinit.aop; + +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; +import org.springframework.util.StopWatch; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * 请求响应日志 AOP + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + **/ +@Aspect +@Component +@Slf4j +public class LogInterceptor { + + /** + * 执行拦截 + */ + @Around("execution(* com.yupi.springbootinit.controller.*.*(..))") + public Object doInterceptor(ProceedingJoinPoint point) throws Throwable { + // 计时 + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + // 获取请求路径 + RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); + HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest(); + // 生成请求唯一 id + String requestId = UUID.randomUUID().toString(); + String url = httpServletRequest.getRequestURI(); + // 获取请求参数 + Object[] args = point.getArgs(); + String reqParam = "[" + StringUtils.join(args, ", ") + "]"; + // 输出请求日志 + log.info("request start,id: {}, path: {}, ip: {}, params: {}", requestId, url, + httpServletRequest.getRemoteHost(), reqParam); + // 执行原方法 + Object result = point.proceed(); + // 输出响应日志 + stopWatch.stop(); + long totalTimeMillis = stopWatch.getTotalTimeMillis(); + log.info("request end, id: {}, cost: {}ms", requestId, totalTimeMillis); + return result; + } +} + diff --git a/src/main/java/com/yupi/springbootinit/common/BaseResponse.java b/src/main/java/com/yupi/springbootinit/common/BaseResponse.java new file mode 100644 index 0000000..c1ca661 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/common/BaseResponse.java @@ -0,0 +1,35 @@ +package com.yupi.springbootinit.common; + +import java.io.Serializable; +import lombok.Data; + +/** + * 通用返回类 + * + * @param + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class BaseResponse implements Serializable { + + private int code; + + private T data; + + private String message; + + public BaseResponse(int code, T data, String message) { + this.code = code; + this.data = data; + this.message = message; + } + + public BaseResponse(int code, T data) { + this(code, data, ""); + } + + public BaseResponse(ErrorCode errorCode) { + this(errorCode.getCode(), null, errorCode.getMessage()); + } +} diff --git a/src/main/java/com/yupi/springbootinit/common/DeleteRequest.java b/src/main/java/com/yupi/springbootinit/common/DeleteRequest.java new file mode 100644 index 0000000..0109023 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/common/DeleteRequest.java @@ -0,0 +1,21 @@ +package com.yupi.springbootinit.common; + +import java.io.Serializable; +import lombok.Data; + +/** + * 删除请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class DeleteRequest implements Serializable { + + /** + * id + */ + private Long id; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/common/ErrorCode.java b/src/main/java/com/yupi/springbootinit/common/ErrorCode.java new file mode 100644 index 0000000..e6e00d8 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/common/ErrorCode.java @@ -0,0 +1,47 @@ +package com.yupi.springbootinit.common; + +/** + * 自定义错误码 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public enum ErrorCode { + + SUCCESS(0, "ok"), + PARAMS_ERROR(40000, "请求参数错误"), + NOT_LOGIN_ERROR(40100, "未登录"), + USERNAME_OR_PASSWORD_ERROR(40200, "账号或密码错误"), + USER_DISABLE(40300, "用户被禁用"), + NO_AUTH_ERROR(40101, "无权限"), + NOT_FOUND_ERROR(40400, "请求数据不存在"), + FORBIDDEN_ERROR(40300, "禁止访问"), + SYSTEM_ERROR(50000, "系统内部异常"), + OPERATION_ERROR(50001, "操作失败"), + QUEUE_ERROR(60001, "队列消息添加失败"), + QUEUE_CONSUMPTION_FAILURE(60001, "队列消息消费失败"); + + /** + * 状态码 + */ + private final int code; + + /** + * 信息 + */ + private final String message; + + ErrorCode(int code, String message) { + this.code = code; + this.message = message; + } + + public int getCode() { + return code; + } + + public String getMessage() { + return message; + } + +} diff --git a/src/main/java/com/yupi/springbootinit/common/PageRequest.java b/src/main/java/com/yupi/springbootinit/common/PageRequest.java new file mode 100644 index 0000000..f17de55 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/common/PageRequest.java @@ -0,0 +1,34 @@ +package com.yupi.springbootinit.common; + +import com.yupi.springbootinit.constant.CommonConstant; +import lombok.Data; + +/** + * 分页请求 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Data +public class PageRequest { + + /** + * 当前页号 + */ + private int current = 1; + + /** + * 页面大小 + */ + private int pageSize = 10; + + /** + * 排序字段 + */ + private String sortField; + + /** + * 排序顺序(默认升序) + */ + private String sortOrder = CommonConstant.SORT_ORDER_ASC; +} diff --git a/src/main/java/com/yupi/springbootinit/common/ResultUtils.java b/src/main/java/com/yupi/springbootinit/common/ResultUtils.java new file mode 100644 index 0000000..5544068 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/common/ResultUtils.java @@ -0,0 +1,52 @@ +package com.yupi.springbootinit.common; + +/** + * 返回工具类 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public class ResultUtils { + + /** + * 成功 + * + * @param data + * @param + * @return + */ + public static BaseResponse success(T data) { + return new BaseResponse<>(0, data, "ok"); + } + + /** + * 失败 + * + * @param errorCode + * @return + */ + public static BaseResponse error(ErrorCode errorCode) { + return new BaseResponse<>(errorCode); + } + + /** + * 失败 + * + * @param code + * @param message + * @return + */ + public static BaseResponse error(int code, String message) { + return new BaseResponse(code, null, message); + } + + /** + * 失败 + * + * @param errorCode + * @return + */ + public static BaseResponse error(ErrorCode errorCode, String message) { + return new BaseResponse(errorCode.getCode(), null, message); + } +} diff --git a/src/main/java/com/yupi/springbootinit/config/CorsConfig.java b/src/main/java/com/yupi/springbootinit/config/CorsConfig.java new file mode 100644 index 0000000..23184b5 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/config/CorsConfig.java @@ -0,0 +1,28 @@ +package com.yupi.springbootinit.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 全局跨域配置 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Configuration +public class CorsConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + // 覆盖所有请求 + registry.addMapping("/**") + // 允许发送 Cookie + .allowCredentials(true) + // 放行哪些域名(必须用 patterns,否则 * 会和 allowCredentials 冲突) + .allowedOriginPatterns("*") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .exposedHeaders("*"); + } +} diff --git a/src/main/java/com/yupi/springbootinit/config/JsonConfig.java b/src/main/java/com/yupi/springbootinit/config/JsonConfig.java new file mode 100644 index 0000000..86ad8a8 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/config/JsonConfig.java @@ -0,0 +1,31 @@ +package com.yupi.springbootinit.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import org.springframework.boot.jackson.JsonComponent; +import org.springframework.context.annotation.Bean; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; + +/** + * Spring MVC Json 配置 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@JsonComponent +public class JsonConfig { + + /** + * 添加 Long 转 json 精度丢失的配置 + */ + @Bean + public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { + ObjectMapper objectMapper = builder.createXmlMapper(false).build(); + SimpleModule module = new SimpleModule(); + module.addSerializer(Long.class, ToStringSerializer.instance); + module.addSerializer(Long.TYPE, ToStringSerializer.instance); + objectMapper.registerModule(module); + return objectMapper; + } +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/config/MyBatisPlusConfig.java b/src/main/java/com/yupi/springbootinit/config/MyBatisPlusConfig.java new file mode 100644 index 0000000..a5f3b9b --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/config/MyBatisPlusConfig.java @@ -0,0 +1,31 @@ +package com.yupi.springbootinit.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * MyBatis Plus 配置 + * + * @author https://github.com/liyupi + */ +@Configuration +@MapperScan("com.yupi.springbootinit.mapper") +public class MyBatisPlusConfig { + + /** + * 拦截器配置 + * + * @return + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + // 分页插件 + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/config/RabbitMQConfig.java b/src/main/java/com/yupi/springbootinit/config/RabbitMQConfig.java new file mode 100644 index 0000000..980b298 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/config/RabbitMQConfig.java @@ -0,0 +1,26 @@ +package com.yupi.springbootinit.config; + +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RabbitMQConfig { + private static final String QUEUE = "HOST_INFO_QUEUE"; + + //创建队列 + //true:表示持久化 + //队列在默认情况下放到内存,rabbitmq重启后就丢失了,如果希望重启后,队列 + //数据还能使用,就需要持久化 + @Bean + public Queue hostInfoQueue(){ + return new Queue(QUEUE,true); + } + @Bean + public MessageConverter messageConverter(){ + return new Jackson2JsonMessageConverter(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/constant/CommonConstant.java b/src/main/java/com/yupi/springbootinit/constant/CommonConstant.java new file mode 100644 index 0000000..d7a1a54 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/constant/CommonConstant.java @@ -0,0 +1,21 @@ +package com.yupi.springbootinit.constant; + +/** + * 通用常量 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface CommonConstant { + + /** + * 升序 + */ + String SORT_ORDER_ASC = "ascend"; + + /** + * 降序 + */ + String SORT_ORDER_DESC = " descend"; + +} diff --git a/src/main/java/com/yupi/springbootinit/constant/FileConstant.java b/src/main/java/com/yupi/springbootinit/constant/FileConstant.java new file mode 100644 index 0000000..8781d56 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/constant/FileConstant.java @@ -0,0 +1,16 @@ +package com.yupi.springbootinit.constant; + +/** + * 文件常量 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface FileConstant { + + /** + * COS 访问地址 + * todo 需替换配置 + */ + String COS_HOST = "https://yupi.icu"; +} diff --git a/src/main/java/com/yupi/springbootinit/constant/UserConstant.java b/src/main/java/com/yupi/springbootinit/constant/UserConstant.java new file mode 100644 index 0000000..eeddd9b --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/constant/UserConstant.java @@ -0,0 +1,34 @@ +package com.yupi.springbootinit.constant; + +/** + * 用户常量 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public interface UserConstant { + + /** + * 用户登录态键 + */ + String USER_LOGIN_STATE = "user_login"; + + // region 权限 + + /** + * 默认角色 + */ + String DEFAULT_ROLE = "user"; + + /** + * 管理员角色 + */ + String ADMIN_ROLE = "admin"; + + /** + * 被封号 + */ + String BAN_ROLE = "ban"; + + // endregion +} diff --git a/src/main/java/com/yupi/springbootinit/controller/HostInfoController.java b/src/main/java/com/yupi/springbootinit/controller/HostInfoController.java new file mode 100644 index 0000000..077b541 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/controller/HostInfoController.java @@ -0,0 +1,35 @@ +package com.yupi.springbootinit.controller; + +import com.yupi.springbootinit.common.BaseResponse; +import com.yupi.springbootinit.common.ResultUtils; +import com.yupi.springbootinit.model.dto.host.HostInfoDTO; +import com.yupi.springbootinit.model.entity.NewHosts; +import com.yupi.springbootinit.model.vo.hosts.NewHostsVO; +import com.yupi.springbootinit.rabbitMQ.MQSender; +import com.yupi.springbootinit.service.HostInfoService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/* + * @author: ziin + * @date: 2025/6/10 17:09 + */ +@RestController +@RequestMapping("/save_data") +@Slf4j +@CrossOrigin +public class HostInfoController { + + @Resource + private MQSender mqSender; + + @PostMapping("add_host") + public BaseResponse addHost(@RequestBody List newHosts){ + mqSender.send(newHosts); + return ResultUtils.success(true); + } + +} diff --git a/src/main/java/com/yupi/springbootinit/exception/BusinessException.java b/src/main/java/com/yupi/springbootinit/exception/BusinessException.java new file mode 100644 index 0000000..6e7dd1c --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/exception/BusinessException.java @@ -0,0 +1,36 @@ +package com.yupi.springbootinit.exception; + +import com.yupi.springbootinit.common.ErrorCode; + +/** + * 自定义异常类 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public class BusinessException extends RuntimeException { + + /** + * 错误码 + */ + private final int code; + + public BusinessException(int code, String message) { + super(message); + this.code = code; + } + + public BusinessException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.code = errorCode.getCode(); + } + + public BusinessException(ErrorCode errorCode, String message) { + super(message); + this.code = errorCode.getCode(); + } + + public int getCode() { + return code; + } +} diff --git a/src/main/java/com/yupi/springbootinit/exception/GlobalExceptionHandler.java b/src/main/java/com/yupi/springbootinit/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..610ca49 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/exception/GlobalExceptionHandler.java @@ -0,0 +1,31 @@ +package com.yupi.springbootinit.exception; + +import com.yupi.springbootinit.common.BaseResponse; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.common.ResultUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * 全局异常处理器 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@RestControllerAdvice +@Slf4j +public class GlobalExceptionHandler { + + @ExceptionHandler(BusinessException.class) + public BaseResponse businessExceptionHandler(BusinessException e) { + log.error("BusinessException", e); + return ResultUtils.error(e.getCode(), e.getMessage()); + } + + @ExceptionHandler(RuntimeException.class) + public BaseResponse runtimeExceptionHandler(RuntimeException e) { + log.error("RuntimeException", e); + return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "系统错误"); + } +} diff --git a/src/main/java/com/yupi/springbootinit/exception/ThrowUtils.java b/src/main/java/com/yupi/springbootinit/exception/ThrowUtils.java new file mode 100644 index 0000000..e25468a --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/exception/ThrowUtils.java @@ -0,0 +1,45 @@ +package com.yupi.springbootinit.exception; + +import com.yupi.springbootinit.common.ErrorCode; + +/** + * 抛异常工具类 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public class ThrowUtils { + + /** + * 条件成立则抛异常 + * + * @param condition + * @param runtimeException + */ + public static void throwIf(boolean condition, RuntimeException runtimeException) { + if (condition) { + throw runtimeException; + } + } + + /** + * 条件成立则抛异常 + * + * @param condition + * @param errorCode + */ + public static void throwIf(boolean condition, ErrorCode errorCode) { + throwIf(condition, new BusinessException(errorCode)); + } + + /** + * 条件成立则抛异常 + * + * @param condition + * @param errorCode + * @param message + */ + public static void throwIf(boolean condition, ErrorCode errorCode, String message) { + throwIf(condition, new BusinessException(errorCode, message)); + } +} diff --git a/src/main/java/com/yupi/springbootinit/generate/CodeGenerator.java b/src/main/java/com/yupi/springbootinit/generate/CodeGenerator.java new file mode 100644 index 0000000..c379215 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/generate/CodeGenerator.java @@ -0,0 +1,128 @@ +package com.yupi.springbootinit.generate; + +import cn.hutool.core.io.FileUtil; +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.io.FileWriter; +import java.io.Writer; + +/** + * 代码生成器 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +public class CodeGenerator { + + /** + * 用法:修改生成参数和生成路径,注释掉不需要的生成逻辑,然后运行即可 + * + * @param args + * @throws TemplateException + * @throws IOException + */ + public static void main(String[] args) throws TemplateException, IOException { + // 指定生成参数 + String packageName = "com.yupi.springbootinit"; + String dataName = "用户评论"; + String dataKey = "userComment"; + String upperDataKey = "UserComment"; + + // 封装生成参数 + Map dataModel = new HashMap<>(); + dataModel.put("packageName", packageName); + dataModel.put("dataName", dataName); + dataModel.put("dataKey", dataKey); + dataModel.put("upperDataKey", upperDataKey); + + // 生成路径默认值 + String projectPath = System.getProperty("user.dir"); + // 参考路径,可以自己调整下面的 outputPath + String inputPath = projectPath + File.separator + "src/main/resources/templates/模板名称.java.ftl"; + String outputPath = String.format("%s/generator/包名/%s类后缀.java", projectPath, upperDataKey); + + // 1、生成 Controller + // 指定生成路径 + inputPath = projectPath + File.separator + "src/main/resources/templates/TemplateController.java.ftl"; + outputPath = String.format("%s/generator/controller/%sController.java", projectPath, upperDataKey); + // 生成 + doGenerate(inputPath, outputPath, dataModel); + System.out.println("生成 Controller 成功,文件路径:" + outputPath); + + // 2、生成 Service 接口和实现类 + // 生成 Service 接口 + inputPath = projectPath + File.separator + "src/main/resources/templates/TemplateService.java.ftl"; + outputPath = String.format("%s/generator/service/%sService.java", projectPath, upperDataKey); + doGenerate(inputPath, outputPath, dataModel); + System.out.println("生成 Service 接口成功,文件路径:" + outputPath); + // 生成 Service 实现类 + inputPath = projectPath + File.separator + "src/main/resources/templates/TemplateServiceImpl.java.ftl"; + outputPath = String.format("%s/generator/service/impl/%sServiceImpl.java", projectPath, upperDataKey); + doGenerate(inputPath, outputPath, dataModel); + System.out.println("生成 Service 实现类成功,文件路径:" + outputPath); + + // 3、生成数据模型封装类(包括 DTO 和 VO) + // 生成 DTO + inputPath = projectPath + File.separator + "src/main/resources/templates/model/TemplateAddRequest.java.ftl"; + outputPath = String.format("%s/generator/model/dto/%sAddRequest.java", projectPath, upperDataKey); + doGenerate(inputPath, outputPath, dataModel); + inputPath = projectPath + File.separator + "src/main/resources/templates/model/TemplateQueryRequest.java.ftl"; + outputPath = String.format("%s/generator/model/dto/%sQueryRequest.java", projectPath, upperDataKey); + doGenerate(inputPath, outputPath, dataModel); + inputPath = projectPath + File.separator + "src/main/resources/templates/model/TemplateEditRequest.java.ftl"; + outputPath = String.format("%s/generator/model/dto/%sEditRequest.java", projectPath, upperDataKey); + doGenerate(inputPath, outputPath, dataModel); + inputPath = projectPath + File.separator + "src/main/resources/templates/model/TemplateUpdateRequest.java.ftl"; + outputPath = String.format("%s/generator/model/dto/%sUpdateRequest.java", projectPath, upperDataKey); + doGenerate(inputPath, outputPath, dataModel); + System.out.println("生成 DTO 成功,文件路径:" + outputPath); + // 生成 VO + inputPath = projectPath + File.separator + "src/main/resources/templates/model/TemplateVO.java.ftl"; + outputPath = String.format("%s/generator/model/vo/%sVO.java", projectPath, upperDataKey); + doGenerate(inputPath, outputPath, dataModel); + System.out.println("生成 VO 成功,文件路径:" + outputPath); + } + + /** + * 生成文件 + * + * @param inputPath 模板文件输入路径 + * @param outputPath 输出路径 + * @param model 数据模型 + * @throws IOException + * @throws TemplateException + */ + public static void doGenerate(String inputPath, String outputPath, Object model) throws IOException, TemplateException { + // new 出 Configuration 对象,参数为 FreeMarker 版本号 + Configuration configuration = new Configuration(Configuration.VERSION_2_3_31); + + // 指定模板文件所在的路径 + File templateDir = new File(inputPath).getParentFile(); + configuration.setDirectoryForTemplateLoading(templateDir); + + // 设置模板文件使用的字符集 + configuration.setDefaultEncoding("utf-8"); + + // 创建模板对象,加载指定模板 + String templateName = new File(inputPath).getName(); + Template template = configuration.getTemplate(templateName); + + // 文件不存在则创建文件和父目录 + if (!FileUtil.exist(outputPath)) { + FileUtil.touch(outputPath); + } + + // 生成 + Writer out = new FileWriter(outputPath); + template.process(model, out); + + // 生成文件后别忘了关闭哦 + out.close(); + } +} diff --git a/src/main/java/com/yupi/springbootinit/mapper/NewHostsMapper.java b/src/main/java/com/yupi/springbootinit/mapper/NewHostsMapper.java new file mode 100644 index 0000000..6ce3b6b --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/mapper/NewHostsMapper.java @@ -0,0 +1,27 @@ +package com.yupi.springbootinit.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yupi.springbootinit.model.entity.NewHosts; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/* +* @author: ziin +* @date: 2025/6/10 18:54 +*/ +public interface NewHostsMapper extends BaseMapper { + int deleteByPrimaryKey(Long id); + + int insert(NewHosts record); + + int insertSelective(NewHosts record); + + NewHosts selectByPrimaryKey(Long id); + + int updateByPrimaryKeySelective(NewHosts record); + + int updateByPrimaryKey(NewHosts record); + + int batchInsert(@Param("list") List list); +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/dto/host/HostInfoDTO.java b/src/main/java/com/yupi/springbootinit/model/dto/host/HostInfoDTO.java new file mode 100644 index 0000000..81e554f --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/host/HostInfoDTO.java @@ -0,0 +1,88 @@ +package com.yupi.springbootinit.model.dto.host; + +import lombok.Data; + +import java.util.Date; + +/* + * @author: ziin + * @date: 2025/6/10 18:58 + */ +@Data +public class HostInfoDTO { + + /** + * 主键 + */ + private Long id; + + /** + * 主播id + */ + private String hostsId; + + /** + * 主播等级 + */ + private String hostsLevel; + + /** + * 主播金币 + */ + private Integer hostsCoins; + + /** + * 邀请类型 + */ + private Integer invitationType; + + /** + * 粉丝数量 + */ + private Integer fans; + + /** + * 关注数量 + */ + private Integer fllowernum; + + /** + * 昨日金币 + */ + private Integer yesterdayCoins; + + /** + * 主播国家 + */ + private String country; + + /** + * 直播类型 娱乐,游戏 + */ + private String hostsKind; + + /** + * 租户 Id + */ + private Long tenantId; + + /** + * 入库人 + */ + private Integer creator; + + /** + * 数据插入时间 + */ + private Date createTime; + + /** + * 更新人 + */ + private String updater; + + /** + * 更新时间 + */ + private Date updateTime; +} diff --git a/src/main/java/com/yupi/springbootinit/model/dto/user/SystemUsersDTO.java b/src/main/java/com/yupi/springbootinit/model/dto/user/SystemUsersDTO.java new file mode 100644 index 0000000..1c258cd --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/dto/user/SystemUsersDTO.java @@ -0,0 +1,30 @@ +package com.yupi.springbootinit.model.dto.user; + +import lombok.Data; + +/* +* @author: ziin +* @date: 2025/6/11 20:13 +*/ + +/** + * 用户信息表 + */ +@Data +public class SystemUsersDTO { + + /** + * 用户账号 + */ + private String username; + + /** + * 密码 + */ + private String password; + + /** + * 租户编号 + */ + private Long tenantId; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/entity/NewHosts.java b/src/main/java/com/yupi/springbootinit/model/entity/NewHosts.java new file mode 100644 index 0000000..08f7401 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/entity/NewHosts.java @@ -0,0 +1,90 @@ +package com.yupi.springbootinit.model.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; + +import java.util.Date; + +/* +* @author: ziin +* @date: 2025/6/10 18:54 +*/ +@Data +public class NewHosts { + /** + * 主键 + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 主播id + */ + private String hostsId; + + /** + * 主播等级 + */ + private String hostsLevel; + + /** + * 主播金币 + */ + private Integer hostsCoins; + + /** + * 邀请类型 + */ + private Integer invitationType; + + /** + * 粉丝数量 + */ + private Integer fans; + + /** + * 关注数量 + */ + private Integer fllowernum; + + /** + * 昨日金币 + */ + private Integer yesterdayCoins; + + /** + * 主播国家 + */ + private String country; + + /** + * 直播类型 娱乐,游戏 + */ + private String hostsKind; + + /** + * 租户 Id + */ + private Long tenantId; + + /** + * 入库人 + */ + private Long creator; + + /** + * 数据插入时间 + */ + private Date createTime; + + /** + * 更新人 + */ + private String updater; + + /** + * 更新时间 + */ + private Date updateTime; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/entity/SystemUsers.java b/src/main/java/com/yupi/springbootinit/model/entity/SystemUsers.java new file mode 100644 index 0000000..e880ff5 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/entity/SystemUsers.java @@ -0,0 +1,124 @@ +package com.yupi.springbootinit.model.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import lombok.Data; + +import java.util.Date; + +/* +* @author: ziin +* @date: 2025/6/11 20:13 +*/ + +/** + * 用户信息表 + */ +@Data +public class SystemUsers { + /** + * 用户ID + */ + private Long id; + + /** + * 用户账号 + */ + private String username; + + /** + * 密码 + */ + private String password; + + /** + * 用户昵称 + */ + private String nickname; + + /** + * 备注 + */ + private String remark; + + /** + * 部门ID + */ + @TableField("dept_id") + private Long deptId; + + /** + * 岗位编号数组 + */ + @TableField("post_ids") + private String postIds; + + /** + * 用户邮箱 + */ + private String email; + + /** + * 手机号码 + */ + private String mobile; + + /** + * 用户性别 + */ + private Byte sex; + + /** + * 头像地址 + */ + private String avatar; + + /** + * 帐号状态(0正常 1停用) + */ + private Byte status; + + /** + * 最后登录IP + */ + @TableField("login_ip") + private String loginIp; + + /** + * 最后登录时间 + */ + @TableField("login_date") + private Date loginDate; + + /** + * 创建者 + */ + private String creator; + + /** + * 创建时间 + */ + @TableField("create_time") + private Date createTime; + + /** + * 更新者 + */ + private String updater; + + /** + * 更新时间 + */ + @TableField("update_time") + private Date updateTime; + + /** + * 是否删除 + */ + private Boolean deleted; + + /** + * 租户编号 + */ + @TableField("tenant_id") + private Long tenantId; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/enums/FileUploadBizEnum.java b/src/main/java/com/yupi/springbootinit/model/enums/FileUploadBizEnum.java new file mode 100644 index 0000000..8746a7f --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/enums/FileUploadBizEnum.java @@ -0,0 +1,62 @@ +package com.yupi.springbootinit.model.enums; + +import org.apache.commons.lang3.ObjectUtils; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 文件上传业务类型枚举 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public enum FileUploadBizEnum { + + USER_AVATAR("用户头像", "user_avatar"); + + private final String text; + + private final String value; + + FileUploadBizEnum(String text, String value) { + this.text = text; + this.value = value; + } + + /** + * 获取值列表 + * + * @return + */ + public static List getValues() { + return Arrays.stream(values()).map(item -> item.value).collect(Collectors.toList()); + } + + /** + * 根据 value 获取枚举 + * + * @param value + * @return + */ + public static FileUploadBizEnum getEnumByValue(String value) { + if (ObjectUtils.isEmpty(value)) { + return null; + } + for (FileUploadBizEnum anEnum : FileUploadBizEnum.values()) { + if (anEnum.value.equals(value)) { + return anEnum; + } + } + return null; + } + + public String getValue() { + return value; + } + + public String getText() { + return text; + } +} diff --git a/src/main/java/com/yupi/springbootinit/model/enums/UserRoleEnum.java b/src/main/java/com/yupi/springbootinit/model/enums/UserRoleEnum.java new file mode 100644 index 0000000..94cb857 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/enums/UserRoleEnum.java @@ -0,0 +1,64 @@ +package com.yupi.springbootinit.model.enums; + +import org.apache.commons.lang3.ObjectUtils; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 用户角色枚举 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public enum UserRoleEnum { + + USER("用户", "user"), + ADMIN("管理员", "admin"), + BAN("被封号", "ban"); + + private final String text; + + private final String value; + + UserRoleEnum(String text, String value) { + this.text = text; + this.value = value; + } + + /** + * 获取值列表 + * + * @return + */ + public static List getValues() { + return Arrays.stream(values()).map(item -> item.value).collect(Collectors.toList()); + } + + /** + * 根据 value 获取枚举 + * + * @param value + * @return + */ + public static UserRoleEnum getEnumByValue(String value) { + if (ObjectUtils.isEmpty(value)) { + return null; + } + for (UserRoleEnum anEnum : UserRoleEnum.values()) { + if (anEnum.value.equals(value)) { + return anEnum; + } + } + return null; + } + + public String getValue() { + return value; + } + + public String getText() { + return text; + } +} diff --git a/src/main/java/com/yupi/springbootinit/model/vo/hosts/NewHostsVO.java b/src/main/java/com/yupi/springbootinit/model/vo/hosts/NewHostsVO.java new file mode 100644 index 0000000..8c9f7db --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/vo/hosts/NewHostsVO.java @@ -0,0 +1,81 @@ +package com.yupi.springbootinit.model.vo.hosts; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; + +import java.util.Date; + +/* +* @author: ziin +* @date: 2025/6/10 18:54 +*/ +@Data +public class NewHostsVO { + /** + * 主键 + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 主播id + */ + private String hostsId; + + /** + * 主播等级 + */ + private String hostsLevel; + + /** + * 主播金币 + */ + private Integer hostsCoins; + + /** + * 邀请类型 + */ + private Integer invitationType; + + /** + * 粉丝数量 + */ + private Integer fans; + + /** + * 关注数量 + */ + private Integer fllowernum; + + /** + * 昨日金币 + */ + private Integer yesterdayCoins; + + /** + * 主播国家 + */ + private String country; + + /** + * 直播类型 娱乐,游戏 + */ + private String hostsKind; + + /** + * 租户 Id + */ + private Long tenantId; + + /** + * 入库人 + */ + private Long creator; + + /** + * 数据插入时间 + */ + private Date createTime; + +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/model/vo/user/SystemUsersVO.java b/src/main/java/com/yupi/springbootinit/model/vo/user/SystemUsersVO.java new file mode 100644 index 0000000..4087366 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/model/vo/user/SystemUsersVO.java @@ -0,0 +1,30 @@ +package com.yupi.springbootinit.model.vo.user; + +import lombok.Data; + +/* +* @author: ziin +* @date: 2025/6/11 20:13 +*/ + +/** + * 用户信息表 + */ +@Data +public class SystemUsersVO { + /** + * 用户ID + */ + private Long id; + + /** + * 租户编号 + */ + private Long tenantId; + + private String username; + + private String tokenName; + + private String tokenValue; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/rabbitMQ/MQReceiver.java b/src/main/java/com/yupi/springbootinit/rabbitMQ/MQReceiver.java new file mode 100644 index 0000000..0a34676 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/rabbitMQ/MQReceiver.java @@ -0,0 +1,56 @@ +package com.yupi.springbootinit.rabbitMQ; + + +import cn.hutool.core.date.DateTime; +import com.rabbitmq.client.Channel; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.model.entity.NewHosts; +import com.yupi.springbootinit.service.HostInfoService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.io.IOException; +import java.util.List; + +@Service +@Slf4j +public class MQReceiver { + @Resource + private HostInfoService hostInfoService; + + +// //方法:接收消息 +// @RabbitListener(queues = "HOST_INFO_QUEUE") +// public void receive(List hosts, Channel channel, Message message) throws IOException { +// long deliveryTag = message.getMessageProperties().getDeliveryTag(); +// try { +// hostInfoService.processHosts(hosts); +// channel.basicAck(deliveryTag,false); +// log.info("deliveryTag:{}",deliveryTag); +// log.info("接收到的消息------->" + hosts.size()); +// }catch (Exception e){ +// channel.basicNack(deliveryTag,false,true); +// log.error("消息消费失败------->消息内容大小{}",hosts.size() ); +// } +// } + + @RabbitListener(queues = "HOST_INFO_QUEUE") + public void receive(List hosts, Channel channel, Message message) throws IOException { + long deliveryTag = message.getMessageProperties().getDeliveryTag(); + try { + // 等待所有异步任务完成 + hostInfoService.processHosts(hosts).join(); // 这里会抛出异常 + channel.basicAck(deliveryTag, false); + log.info("deliveryTag:{}", deliveryTag); + log.info("{} 消息消费内容大小-------> {}",DateTime.now(),hosts.size()); + } catch (Exception e) { + channel.basicNack(deliveryTag, false, false); + log.error("消息消费失败------->消息内容大小{}", hosts.size(), e); + throw new BusinessException(ErrorCode.QUEUE_CONSUMPTION_FAILURE); + } + } + } diff --git a/src/main/java/com/yupi/springbootinit/rabbitMQ/MQSender.java b/src/main/java/com/yupi/springbootinit/rabbitMQ/MQSender.java new file mode 100644 index 0000000..7947532 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/rabbitMQ/MQSender.java @@ -0,0 +1,35 @@ +package com.yupi.springbootinit.rabbitMQ; + +import cn.hutool.core.date.DateTime; +import com.yupi.springbootinit.common.ErrorCode; +import com.yupi.springbootinit.exception.BusinessException; +import com.yupi.springbootinit.model.entity.NewHosts; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; + +@Slf4j +@Service +public class MQSender { + + @Resource + private RabbitTemplate rabbitTemplate; + + + //方法:发送消息 + public void send(List list){ + try { + log.info("{} 接收到的消息数量----------->{}", DateTime.now(),list.size()); + this.rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter()); + //指定你队列的名字 + rabbitTemplate.convertAndSend("HOST_INFO_QUEUE",list); + }catch (Exception e){ + throw new BusinessException(ErrorCode.QUEUE_ERROR); + } + + } +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/service/HostInfoService.java b/src/main/java/com/yupi/springbootinit/service/HostInfoService.java new file mode 100644 index 0000000..70a82b8 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/service/HostInfoService.java @@ -0,0 +1,23 @@ +package com.yupi.springbootinit.service; + + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yupi.springbootinit.model.dto.host.HostInfoDTO; +import com.yupi.springbootinit.model.entity.NewHosts; +import com.yupi.springbootinit.model.vo.hosts.NewHostsVO; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/* + * @author: ziin + * @date: 2025/6/10 19:04 + */ +public interface HostInfoService extends IService { + + public CompletableFuture saveHostInfo(List newHosts); + + public CompletableFuture processHosts(List hosts); + + List getConditionHosts(HostInfoDTO hostInfoDTO); +} diff --git a/src/main/java/com/yupi/springbootinit/service/impl/HostInfoServiceImpl.java b/src/main/java/com/yupi/springbootinit/service/impl/HostInfoServiceImpl.java new file mode 100644 index 0000000..9bdc944 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/service/impl/HostInfoServiceImpl.java @@ -0,0 +1,87 @@ +package com.yupi.springbootinit.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.google.common.collect.Lists; +import com.yupi.springbootinit.mapper.NewHostsMapper; +import com.yupi.springbootinit.model.dto.host.HostInfoDTO; +import com.yupi.springbootinit.model.entity.NewHosts; +import com.yupi.springbootinit.model.vo.hosts.NewHostsVO; +import com.yupi.springbootinit.service.HostInfoService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StopWatch; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/* + * @author: ziin + * @date: 2025/6/10 19:04 + */ +@Service +@Slf4j +@Transactional(rollbackFor = Exception.class) +public class HostInfoServiceImpl extends ServiceImpl implements HostInfoService { + + + @Override + @Async("taskExecutor") + public CompletableFuture saveHostInfo(List newHosts) { + try { + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + saveBatch(newHosts); + stopWatch.stop(); + long totalTimeMillis = stopWatch.getTotalTimeMillis(); + log.info("存储花费: {}ms", totalTimeMillis); + return CompletableFuture.completedFuture(null); + } catch (Exception e) { + // 将异常包装到Future,使调用方能处理 + return CompletableFuture.failedFuture(e); + } + } + + // public void processHosts(List hosts) { +// List> futures = new ArrayList<>(); +// // 分片提交(避免单批次过大) +// Lists.partition(hosts, 1500).forEach(batch -> { +// CompletableFuture future = this.saveHostInfo(batch); +// futures.add(future); +// }); +// CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) +// .whenComplete((result, ex) -> { +// if (ex != null) { +// log.error("部分批次处理失败", ex); +// } else { +// log.info("所有批次处理完成"); +// } +// // 这里可以触发其他业务逻辑(如发送通知) +// }); +// } + public CompletableFuture processHosts(List hosts) { + List> futures = new ArrayList<>(); + // 分片提交(避免单批次过大) + Lists.partition(hosts, 1500).forEach(batch -> { + log.info("当前存储数据量大小 {}", batch.size()); + CompletableFuture future = this.saveHostInfo(batch); + futures.add(future); + }); + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .whenComplete((result, ex) -> { + if (ex != null) { + log.error("部分批次处理失败", ex); + } else { + log.info("所有批次处理完成"); + } + }); + } + + @Override + public List getConditionHosts(HostInfoDTO hostInfoDTO) { + + return List.of(); + } +} diff --git a/src/main/java/com/yupi/springbootinit/utils/NetUtils.java b/src/main/java/com/yupi/springbootinit/utils/NetUtils.java new file mode 100644 index 0000000..88069fb --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/utils/NetUtils.java @@ -0,0 +1,55 @@ +package com.yupi.springbootinit.utils; + +import java.net.InetAddress; +import javax.servlet.http.HttpServletRequest; + +/** + * 网络工具类 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public class NetUtils { + + /** + * 获取客户端 IP 地址 + * + * @param request + * @return + */ + public static String getIpAddress(HttpServletRequest request) { + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + if (ip.equals("127.0.0.1")) { + // 根据网卡取本机配置的 IP + InetAddress inet = null; + try { + inet = InetAddress.getLocalHost(); + } catch (Exception e) { + e.printStackTrace(); + } + if (inet != null) { + ip = inet.getHostAddress(); + } + } + } + // 多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 + if (ip != null && ip.length() > 15) { + if (ip.indexOf(",") > 0) { + ip = ip.substring(0, ip.indexOf(",")); + } + } + if (ip == null) { + return "127.0.0.1"; + } + return ip; + } + +} diff --git a/src/main/java/com/yupi/springbootinit/utils/SpringContextUtils.java b/src/main/java/com/yupi/springbootinit/utils/SpringContextUtils.java new file mode 100644 index 0000000..60001f6 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/utils/SpringContextUtils.java @@ -0,0 +1,57 @@ +package com.yupi.springbootinit.utils; + +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * Spring 上下文获取工具 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@Component +public class SpringContextUtils implements ApplicationContextAware { + + private static ApplicationContext applicationContext; + + @Override + public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException { + SpringContextUtils.applicationContext = applicationContext; + } + + /** + * 通过名称获取 Bean + * + * @param beanName + * @return + */ + public static Object getBean(String beanName) { + return applicationContext.getBean(beanName); + } + + /** + * 通过 class 获取 Bean + * + * @param beanClass + * @param + * @return + */ + public static T getBean(Class beanClass) { + return applicationContext.getBean(beanClass); + } + + /** + * 通过名称和类型获取 Bean + * + * @param beanName + * @param beanClass + * @param + * @return + */ + public static T getBean(String beanName, Class beanClass) { + return applicationContext.getBean(beanName, beanClass); + } +} \ No newline at end of file diff --git a/src/main/java/com/yupi/springbootinit/utils/SqlUtils.java b/src/main/java/com/yupi/springbootinit/utils/SqlUtils.java new file mode 100644 index 0000000..1efeaa4 --- /dev/null +++ b/src/main/java/com/yupi/springbootinit/utils/SqlUtils.java @@ -0,0 +1,25 @@ +package com.yupi.springbootinit.utils; + +import org.apache.commons.lang3.StringUtils; + +/** + * SQL 工具 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +public class SqlUtils { + + /** + * 校验排序字段是否合法(防止 SQL 注入) + * + * @param sortField + * @return + */ + public static boolean validSortField(String sortField) { + if (StringUtils.isBlank(sortField)) { + return false; + } + return !StringUtils.containsAny(sortField, "=", "(", ")", " "); + } +} diff --git a/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..46f9d16 --- /dev/null +++ b/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,34 @@ +{ + "properties": [ + { + "name": "cos.client.accessKey", + "type": "java.lang.String", + "description": "Description for cos.client.accessKey." + }, + { + "name": "cos.client.secretKey", + "type": "java.lang.String", + "description": "Description for cos.client.secretKey." + }, + { + "name": "cos.client.region", + "type": "java.lang.String", + "description": "Description for cos.client.region." + }, + { + "name": "cos.client.bucket", + "type": "java.lang.String", + "description": "Description for cos.client.bucket." + }, + { + "name": "wx.open.appId", + "type": "java.lang.String", + "description": "Description for wx.open.appId." + }, + { + "name": "wx.open.appSecret", + "type": "java.lang.String", + "description": "Description for wx.open.appSecret." + } + ] +} \ No newline at end of file diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..2ea8743 --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,30 @@ +# 测试配置文件 +# @author 程序员鱼皮 +# @from 编程导航知识星球 +server: + port: 19665 +spring: + # 数据库配置 + # todo 需替换配置 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/ruoyi-vue-pro?rewriteBatchedStatements=true + username: root + password: 123asd + # Redis 配置 + # todo 需替换配置 + redis: + database: 1 + host: localhost + port: 6379 + timeout: 5000 + password: 123456 + rabbitmq: + host: localhost + port: 5672 + username: guest + password: guest + listener: + simple: + acknowledge-mode: manual + diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 0000000..cd818a7 --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,40 @@ +# 线上配置文件 +# @author 程序员鱼皮 +# @from 编程导航知识星球 +server: + port: 19665 +spring: + # 数据库配置 + # todo 需替换配置 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/ruoyi-vue-pro?rewriteBatchedStatements=true + username: root + password: 123asd + # Redis 配置 + # todo 需替换配置 + redis: + database: 1 + host: localhost + port: 6379 + timeout: 5000 + password: 123456 + rabbitmq: + host: localhost + port: 5672 + username: guest + password: guest + listener: + simple: + acknowledge-mode: manual +mybatis-plus: + configuration: + # 生产环境关闭日志 + log-impl: '' +# 接口文档配置 +knife4j: + basic: + enable: false + username: root + password: 123456 + diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..2f599a2 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,109 @@ +# 公共配置文件 +# @author 程序员鱼皮 +# @from 编程导航知识星球 +spring: + application: + name: springboot-init + # 默认 dev 环境 + profiles: + active: dev + # 支持 swagger3 + mvc: + pathmatch: + matching-strategy: ant_path_matcher + # session 配置 + session: + # todo 取消注释开启分布式 session(须先配置 Redis) + # store-type: redis + # 30 天过期 + timeout: 2592000 + # 数据库配置 + # todo 需替换配置 + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/my_db + username: root + password: 123asd + jackson: + date-format: yyyy-MM-dd HH:mm:ss + servlet: + multipart: + # 大小限制 + max-file-size: 10MB +server: + address: 0.0.0.0 + port: 8101 + servlet: + context-path: /api + # cookie 30 天过期 + session: + cookie: + max-age: 2592000 +mybatis-plus: + configuration: + map-underscore-to-camel-case: false + log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl + log-sql: + default-executor-type: batch + global-config: + db-config: + logic-delete-field: isDelete # 全局逻辑删除的实体字段名 + logic-delete-value: 1 # 逻辑已删除值(默认为 1) + logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) +# 微信相关 +wx: + # 微信公众平台 + # todo 需替换配置 + mp: + token: xxx + aesKey: xxx + appId: xxx + secret: xxx + config-storage: + http-client-type: HttpClient + key-prefix: wx + redis: + host: 127.0.0.1 + port: 6379 + type: Memory + # 微信开放平台 + # todo 需替换配置 + open: + appId: xxx + appSecret: xxx +# 对象存储 +# todo 需替换配置 +cos: + client: + accessKey: xxx + secretKey: xxx + region: xxx + bucket: xxx +# 接口文档配置 +knife4j: + enable: true + openapi: + title: "接口文档" + version: 1.0 + group: + default: + api-rule: package + api-rule-resources: + - com.yupi.springbootinit.controller + +############## Sa-Token 配置 (文档: https://sa-token.cc) ############## +sa-token: + # token 名称(同时也是 cookie 名称) + token-name: vvtoken + # token 有效期(单位:秒) 默认30天,-1 代表永久有效 + timeout: 2592000 + # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结 + active-timeout: -1 + # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录) + is-concurrent: false + # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token) + is-share: false + # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik) + token-style: random-128 + # 是否输出操作日志 + is-log: true diff --git a/src/main/resources/mapper/NewHostsMapper.xml b/src/main/resources/mapper/NewHostsMapper.xml new file mode 100644 index 0000000..5279c82 --- /dev/null +++ b/src/main/resources/mapper/NewHostsMapper.xml @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + id, hosts_id, hosts_level, hosts_coins, Invitation_type, fans, fllowernum, yesterday_coins, + country, hosts_kind, tenant_id, creator, create_time, updater, update_time + + + + + delete from new_hosts + where id = #{id,jdbcType=BIGINT} + + + + insert into new_hosts (hosts_id, hosts_level, hosts_coins, + Invitation_type, fans, fllowernum, + yesterday_coins, country, hosts_kind, + tenant_id, creator + ) + values (#{hostsId,jdbcType=VARCHAR}, #{hostsLevel,jdbcType=VARCHAR}, #{hostsCoins,jdbcType=INTEGER}, + #{invitationType,jdbcType=INTEGER}, #{fans,jdbcType=INTEGER}, #{fllowernum,jdbcType=INTEGER}, + #{yesterdayCoins,jdbcType=INTEGER}, #{country,jdbcType=VARCHAR}, #{hostsKind,jdbcType=VARCHAR}, + #{tenantId,jdbcType=BIGINT}, #{creator,jdbcType=BIGINT}) + + + + insert into new_hosts + + + hosts_id, + + + hosts_level, + + + hosts_coins, + + + Invitation_type, + + + fans, + + + fllowernum, + + + yesterday_coins, + + + country, + + + hosts_kind, + + + tenant_id, + + + creator, + + + create_time, + + + updater, + + + update_time, + + + + + #{hostsId,jdbcType=VARCHAR}, + + + #{hostsLevel,jdbcType=VARCHAR}, + + + #{hostsCoins,jdbcType=INTEGER}, + + + #{invitationType,jdbcType=INTEGER}, + + + #{fans,jdbcType=INTEGER}, + + + #{fllowernum,jdbcType=INTEGER}, + + + #{yesterdayCoins,jdbcType=INTEGER}, + + + #{country,jdbcType=VARCHAR}, + + + #{hostsKind,jdbcType=VARCHAR}, + + + #{tenantId,jdbcType=BIGINT}, + + + #{creator,jdbcType=BIGINT}, + + + #{createTime,jdbcType=TIMESTAMP}, + + + #{updater,jdbcType=VARCHAR}, + + + #{updateTime,jdbcType=TIMESTAMP}, + + + + + + update new_hosts + + + hosts_id = #{hostsId,jdbcType=VARCHAR}, + + + hosts_level = #{hostsLevel,jdbcType=VARCHAR}, + + + hosts_coins = #{hostsCoins,jdbcType=INTEGER}, + + + Invitation_type = #{invitationType,jdbcType=INTEGER}, + + + fans = #{fans,jdbcType=INTEGER}, + + + fllowernum = #{fllowernum,jdbcType=INTEGER}, + + + yesterday_coins = #{yesterdayCoins,jdbcType=INTEGER}, + + + country = #{country,jdbcType=VARCHAR}, + + + hosts_kind = #{hostsKind,jdbcType=VARCHAR}, + + + tenant_id = #{tenantId,jdbcType=BIGINT}, + + + creator = #{creator,jdbcType=INTEGER}, + + + create_time = #{createTime,jdbcType=TIMESTAMP}, + + + updater = #{updater,jdbcType=VARCHAR}, + + + update_time = #{updateTime,jdbcType=TIMESTAMP}, + + + where id = #{id,jdbcType=BIGINT} + + + + update new_hosts + set hosts_id = #{hostsId,jdbcType=VARCHAR}, + hosts_level = #{hostsLevel,jdbcType=VARCHAR}, + hosts_coins = #{hostsCoins,jdbcType=INTEGER}, + Invitation_type = #{invitationType,jdbcType=INTEGER}, + fans = #{fans,jdbcType=INTEGER}, + fllowernum = #{fllowernum,jdbcType=INTEGER}, + yesterday_coins = #{yesterdayCoins,jdbcType=INTEGER}, + country = #{country,jdbcType=VARCHAR}, + hosts_kind = #{hostsKind,jdbcType=VARCHAR}, + tenant_id = #{tenantId,jdbcType=BIGINT}, + creator = #{creator,jdbcType=INTEGER}, + create_time = #{createTime,jdbcType=TIMESTAMP}, + updater = #{updater,jdbcType=VARCHAR}, + update_time = #{updateTime,jdbcType=TIMESTAMP} + where id = #{id,jdbcType=BIGINT} + + + + insert into new_hosts + (hosts_id, hosts_level, hosts_coins, Invitation_type, fans, fllowernum, yesterday_coins, + country, hosts_kind, tenant_id, creator) + values + + (#{item.hostsId,jdbcType=VARCHAR}, #{item.hostsLevel,jdbcType=VARCHAR}, #{item.hostsCoins,jdbcType=INTEGER}, + #{item.invitationType,jdbcType=INTEGER}, #{item.fans,jdbcType=INTEGER}, #{item.fllowernum,jdbcType=INTEGER}, + #{item.yesterdayCoins,jdbcType=INTEGER}, #{item.country,jdbcType=VARCHAR}, #{item.hostsKind,jdbcType=VARCHAR}, + #{item.tenantId,jdbcType=BIGINT}, #{item.creator,jdbcType=INTEGER}) + + + \ No newline at end of file diff --git a/src/main/resources/templates/TemplateController.java.ftl b/src/main/resources/templates/TemplateController.java.ftl new file mode 100644 index 0000000..e5714b5 --- /dev/null +++ b/src/main/resources/templates/TemplateController.java.ftl @@ -0,0 +1,239 @@ +package ${packageName}.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import ${packageName}.annotation.AuthCheck; +import ${packageName}.common.BaseResponse; +import ${packageName}.common.DeleteRequest; +import ${packageName}.common.ErrorCode; +import ${packageName}.common.ResultUtils; +import ${packageName}.constant.UserConstant; +import ${packageName}.exception.BusinessException; +import ${packageName}.exception.ThrowUtils; +import ${packageName}.model.dto.${dataKey}.${upperDataKey}AddRequest; +import ${packageName}.model.dto.${dataKey}.${upperDataKey}EditRequest; +import ${packageName}.model.dto.${dataKey}.${upperDataKey}QueryRequest; +import ${packageName}.model.dto.${dataKey}.${upperDataKey}UpdateRequest; +import ${packageName}.model.entity.${upperDataKey}; +import ${packageName}.model.entity.User; +import ${packageName}.model.vo.${upperDataKey}VO; +import ${packageName}.service.${upperDataKey}Service; +import ${packageName}.service.UserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; + +/** + * ${dataName}接口 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +@RestController +@RequestMapping("/${dataKey}") +@Slf4j +public class ${upperDataKey}Controller { + + @Resource + private ${upperDataKey}Service ${dataKey}Service; + + @Resource + private UserService userService; + + // region 增删改查 + + /** + * 创建${dataName} + * + * @param ${dataKey}AddRequest + * @param request + * @return + */ + @PostMapping("/add") + public BaseResponse add${upperDataKey}(@RequestBody ${upperDataKey}AddRequest ${dataKey}AddRequest, HttpServletRequest request) { + ThrowUtils.throwIf(${dataKey}AddRequest == null, ErrorCode.PARAMS_ERROR); + // todo 在此处将实体类和 DTO 进行转换 + ${upperDataKey} ${dataKey} = new ${upperDataKey}(); + BeanUtils.copyProperties(${dataKey}AddRequest, ${dataKey}); + // 数据校验 + ${dataKey}Service.valid${upperDataKey}(${dataKey}, true); + // todo 填充默认值 + User loginUser = userService.getLoginUser(request); + ${dataKey}.setUserId(loginUser.getId()); + // 写入数据库 + boolean result = ${dataKey}Service.save(${dataKey}); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + // 返回新写入的数据 id + long new${upperDataKey}Id = ${dataKey}.getId(); + return ResultUtils.success(new${upperDataKey}Id); + } + + /** + * 删除${dataName} + * + * @param deleteRequest + * @param request + * @return + */ + @PostMapping("/delete") + public BaseResponse delete${upperDataKey}(@RequestBody DeleteRequest deleteRequest, HttpServletRequest request) { + if (deleteRequest == null || deleteRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User user = userService.getLoginUser(request); + long id = deleteRequest.getId(); + // 判断是否存在 + ${upperDataKey} old${upperDataKey} = ${dataKey}Service.getById(id); + ThrowUtils.throwIf(old${upperDataKey} == null, ErrorCode.NOT_FOUND_ERROR); + // 仅本人或管理员可删除 + if (!old${upperDataKey}.getUserId().equals(user.getId()) && !userService.isAdmin(request)) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + // 操作数据库 + boolean result = ${dataKey}Service.removeById(id); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + return ResultUtils.success(true); + } + + /** + * 更新${dataName}(仅管理员可用) + * + * @param ${dataKey}UpdateRequest + * @return + */ + @PostMapping("/update") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public BaseResponse update${upperDataKey}(@RequestBody ${upperDataKey}UpdateRequest ${dataKey}UpdateRequest) { + if (${dataKey}UpdateRequest == null || ${dataKey}UpdateRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + // todo 在此处将实体类和 DTO 进行转换 + ${upperDataKey} ${dataKey} = new ${upperDataKey}(); + BeanUtils.copyProperties(${dataKey}UpdateRequest, ${dataKey}); + // 数据校验 + ${dataKey}Service.valid${upperDataKey}(${dataKey}, false); + // 判断是否存在 + long id = ${dataKey}UpdateRequest.getId(); + ${upperDataKey} old${upperDataKey} = ${dataKey}Service.getById(id); + ThrowUtils.throwIf(old${upperDataKey} == null, ErrorCode.NOT_FOUND_ERROR); + // 操作数据库 + boolean result = ${dataKey}Service.updateById(${dataKey}); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + return ResultUtils.success(true); + } + + /** + * 根据 id 获取${dataName}(封装类) + * + * @param id + * @return + */ + @GetMapping("/get/vo") + public BaseResponse<${upperDataKey}VO> get${upperDataKey}VOById(long id, HttpServletRequest request) { + ThrowUtils.throwIf(id <= 0, ErrorCode.PARAMS_ERROR); + // 查询数据库 + ${upperDataKey} ${dataKey} = ${dataKey}Service.getById(id); + ThrowUtils.throwIf(${dataKey} == null, ErrorCode.NOT_FOUND_ERROR); + // 获取封装类 + return ResultUtils.success(${dataKey}Service.get${upperDataKey}VO(${dataKey}, request)); + } + + /** + * 分页获取${dataName}列表(仅管理员可用) + * + * @param ${dataKey}QueryRequest + * @return + */ + @PostMapping("/list/page") + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + public BaseResponse> list${upperDataKey}ByPage(@RequestBody ${upperDataKey}QueryRequest ${dataKey}QueryRequest) { + long current = ${dataKey}QueryRequest.getCurrent(); + long size = ${dataKey}QueryRequest.getPageSize(); + // 查询数据库 + Page<${upperDataKey}> ${dataKey}Page = ${dataKey}Service.page(new Page<>(current, size), + ${dataKey}Service.getQueryWrapper(${dataKey}QueryRequest)); + return ResultUtils.success(${dataKey}Page); + } + + /** + * 分页获取${dataName}列表(封装类) + * + * @param ${dataKey}QueryRequest + * @param request + * @return + */ + @PostMapping("/list/page/vo") + public BaseResponse> list${upperDataKey}VOByPage(@RequestBody ${upperDataKey}QueryRequest ${dataKey}QueryRequest, + HttpServletRequest request) { + long current = ${dataKey}QueryRequest.getCurrent(); + long size = ${dataKey}QueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + // 查询数据库 + Page<${upperDataKey}> ${dataKey}Page = ${dataKey}Service.page(new Page<>(current, size), + ${dataKey}Service.getQueryWrapper(${dataKey}QueryRequest)); + // 获取封装类 + return ResultUtils.success(${dataKey}Service.get${upperDataKey}VOPage(${dataKey}Page, request)); + } + + /** + * 分页获取当前登录用户创建的${dataName}列表 + * + * @param ${dataKey}QueryRequest + * @param request + * @return + */ + @PostMapping("/my/list/page/vo") + public BaseResponse> listMy${upperDataKey}VOByPage(@RequestBody ${upperDataKey}QueryRequest ${dataKey}QueryRequest, + HttpServletRequest request) { + ThrowUtils.throwIf(${dataKey}QueryRequest == null, ErrorCode.PARAMS_ERROR); + // 补充查询条件,只查询当前登录用户的数据 + User loginUser = userService.getLoginUser(request); + ${dataKey}QueryRequest.setUserId(loginUser.getId()); + long current = ${dataKey}QueryRequest.getCurrent(); + long size = ${dataKey}QueryRequest.getPageSize(); + // 限制爬虫 + ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR); + // 查询数据库 + Page<${upperDataKey}> ${dataKey}Page = ${dataKey}Service.page(new Page<>(current, size), + ${dataKey}Service.getQueryWrapper(${dataKey}QueryRequest)); + // 获取封装类 + return ResultUtils.success(${dataKey}Service.get${upperDataKey}VOPage(${dataKey}Page, request)); + } + + /** + * 编辑${dataName}(给用户使用) + * + * @param ${dataKey}EditRequest + * @param request + * @return + */ + @PostMapping("/edit") + public BaseResponse edit${upperDataKey}(@RequestBody ${upperDataKey}EditRequest ${dataKey}EditRequest, HttpServletRequest request) { + if (${dataKey}EditRequest == null || ${dataKey}EditRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + // todo 在此处将实体类和 DTO 进行转换 + ${upperDataKey} ${dataKey} = new ${upperDataKey}(); + BeanUtils.copyProperties(${dataKey}EditRequest, ${dataKey}); + // 数据校验 + ${dataKey}Service.valid${upperDataKey}(${dataKey}, false); + User loginUser = userService.getLoginUser(request); + // 判断是否存在 + long id = ${dataKey}EditRequest.getId(); + ${upperDataKey} old${upperDataKey} = ${dataKey}Service.getById(id); + ThrowUtils.throwIf(old${upperDataKey} == null, ErrorCode.NOT_FOUND_ERROR); + // 仅本人或管理员可编辑 + if (!old${upperDataKey}.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + // 操作数据库 + boolean result = ${dataKey}Service.updateById(${dataKey}); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + return ResultUtils.success(true); + } + + // endregion +} diff --git a/src/main/resources/templates/TemplateService.java.ftl b/src/main/resources/templates/TemplateService.java.ftl new file mode 100644 index 0000000..a688488 --- /dev/null +++ b/src/main/resources/templates/TemplateService.java.ftl @@ -0,0 +1,53 @@ +package ${packageName}.service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import ${packageName}.model.dto.${dataKey}.${upperDataKey}QueryRequest; +import ${packageName}.model.entity.${upperDataKey}; +import ${packageName}.model.vo.${upperDataKey}VO; + +import javax.servlet.http.HttpServletRequest; + +/** + * ${dataName}服务 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +public interface ${upperDataKey}Service extends IService<${upperDataKey}> { + + /** + * 校验数据 + * + * @param ${dataKey} + * @param add 对创建的数据进行校验 + */ + void valid${upperDataKey}(${upperDataKey} ${dataKey}, boolean add); + + /** + * 获取查询条件 + * + * @param ${dataKey}QueryRequest + * @return + */ + QueryWrapper<${upperDataKey}> getQueryWrapper(${upperDataKey}QueryRequest ${dataKey}QueryRequest); + + /** + * 获取${dataName}封装 + * + * @param ${dataKey} + * @param request + * @return + */ + ${upperDataKey}VO get${upperDataKey}VO(${upperDataKey} ${dataKey}, HttpServletRequest request); + + /** + * 分页获取${dataName}封装 + * + * @param ${dataKey}Page + * @param request + * @return + */ + Page<${upperDataKey}VO> get${upperDataKey}VOPage(Page<${upperDataKey}> ${dataKey}Page, HttpServletRequest request); +} diff --git a/src/main/resources/templates/TemplateServiceImpl.java.ftl b/src/main/resources/templates/TemplateServiceImpl.java.ftl new file mode 100644 index 0000000..3921f10 --- /dev/null +++ b/src/main/resources/templates/TemplateServiceImpl.java.ftl @@ -0,0 +1,224 @@ +package ${packageName}.service.impl; + +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import ${packageName}.common.ErrorCode; +import ${packageName}.constant.CommonConstant; +import ${packageName}.exception.ThrowUtils; +import ${packageName}.mapper.${upperDataKey}Mapper; +import ${packageName}.model.dto.${dataKey}.${upperDataKey}QueryRequest; +import ${packageName}.model.entity.${upperDataKey}; +import ${packageName}.model.entity.${upperDataKey}Favour; +import ${packageName}.model.entity.${upperDataKey}Thumb; +import ${packageName}.model.entity.User; +import ${packageName}.model.vo.${upperDataKey}VO; +import ${packageName}.model.vo.UserVO; +import ${packageName}.service.${upperDataKey}Service; +import ${packageName}.service.UserService; +import ${packageName}.utils.SqlUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * ${dataName}服务实现 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +@Service +@Slf4j +public class ${upperDataKey}ServiceImpl extends ServiceImpl<${upperDataKey}Mapper, ${upperDataKey}> implements ${upperDataKey}Service { + + @Resource + private UserService userService; + + /** + * 校验数据 + * + * @param ${dataKey} + * @param add 对创建的数据进行校验 + */ + @Override + public void valid${upperDataKey}(${upperDataKey} ${dataKey}, boolean add) { + ThrowUtils.throwIf(${dataKey} == null, ErrorCode.PARAMS_ERROR); + // todo 从对象中取值 + String title = ${dataKey}.getTitle(); + // 创建数据时,参数不能为空 + if (add) { + // todo 补充校验规则 + ThrowUtils.throwIf(StringUtils.isBlank(title), ErrorCode.PARAMS_ERROR); + } + // 修改数据时,有参数则校验 + // todo 补充校验规则 + if (StringUtils.isNotBlank(title)) { + ThrowUtils.throwIf(title.length() > 80, ErrorCode.PARAMS_ERROR, "标题过长"); + } + } + + /** + * 获取查询条件 + * + * @param ${dataKey}QueryRequest + * @return + */ + @Override + public QueryWrapper<${upperDataKey}> getQueryWrapper(${upperDataKey}QueryRequest ${dataKey}QueryRequest) { + QueryWrapper<${upperDataKey}> queryWrapper = new QueryWrapper<>(); + if (${dataKey}QueryRequest == null) { + return queryWrapper; + } + // todo 从对象中取值 + Long id = ${dataKey}QueryRequest.getId(); + Long notId = ${dataKey}QueryRequest.getNotId(); + String title = ${dataKey}QueryRequest.getTitle(); + String content = ${dataKey}QueryRequest.getContent(); + String searchText = ${dataKey}QueryRequest.getSearchText(); + String sortField = ${dataKey}QueryRequest.getSortField(); + String sortOrder = ${dataKey}QueryRequest.getSortOrder(); + List tagList = ${dataKey}QueryRequest.getTags(); + Long userId = ${dataKey}QueryRequest.getUserId(); + // todo 补充需要的查询条件 + // 从多字段中搜索 + if (StringUtils.isNotBlank(searchText)) { + // 需要拼接查询条件 + queryWrapper.and(qw -> qw.like("title", searchText).or().like("content", searchText)); + } + // 模糊查询 + queryWrapper.like(StringUtils.isNotBlank(title), "title", title); + queryWrapper.like(StringUtils.isNotBlank(content), "content", content); + // JSON 数组查询 + if (CollUtil.isNotEmpty(tagList)) { + for (String tag : tagList) { + queryWrapper.like("tags", "\"" + tag + "\""); + } + } + // 精确查询 + queryWrapper.ne(ObjectUtils.isNotEmpty(notId), "id", notId); + queryWrapper.eq(ObjectUtils.isNotEmpty(id), "id", id); + queryWrapper.eq(ObjectUtils.isNotEmpty(userId), "userId", userId); + // 排序规则 + queryWrapper.orderBy(SqlUtils.validSortField(sortField), + sortOrder.equals(CommonConstant.SORT_ORDER_ASC), + sortField); + return queryWrapper; + } + + /** + * 获取${dataName}封装 + * + * @param ${dataKey} + * @param request + * @return + */ + @Override + public ${upperDataKey}VO get${upperDataKey}VO(${upperDataKey} ${dataKey}, HttpServletRequest request) { + // 对象转封装类 + ${upperDataKey}VO ${dataKey}VO = ${upperDataKey}VO.objToVo(${dataKey}); + + // todo 可以根据需要为封装对象补充值,不需要的内容可以删除 + // region 可选 + // 1. 关联查询用户信息 + Long userId = ${dataKey}.getUserId(); + User user = null; + if (userId != null && userId > 0) { + user = userService.getById(userId); + } + UserVO userVO = userService.getUserVO(user); + ${dataKey}VO.setUser(userVO); + // 2. 已登录,获取用户点赞、收藏状态 + long ${dataKey}Id = ${dataKey}.getId(); + User loginUser = userService.getLoginUserPermitNull(request); + if (loginUser != null) { + // 获取点赞 + QueryWrapper<${upperDataKey}Thumb> ${dataKey}ThumbQueryWrapper = new QueryWrapper<>(); + ${dataKey}ThumbQueryWrapper.in("${dataKey}Id", ${dataKey}Id); + ${dataKey}ThumbQueryWrapper.eq("userId", loginUser.getId()); + ${upperDataKey}Thumb ${dataKey}Thumb = ${dataKey}ThumbMapper.selectOne(${dataKey}ThumbQueryWrapper); + ${dataKey}VO.setHasThumb(${dataKey}Thumb != null); + // 获取收藏 + QueryWrapper<${upperDataKey}Favour> ${dataKey}FavourQueryWrapper = new QueryWrapper<>(); + ${dataKey}FavourQueryWrapper.in("${dataKey}Id", ${dataKey}Id); + ${dataKey}FavourQueryWrapper.eq("userId", loginUser.getId()); + ${upperDataKey}Favour ${dataKey}Favour = ${dataKey}FavourMapper.selectOne(${dataKey}FavourQueryWrapper); + ${dataKey}VO.setHasFavour(${dataKey}Favour != null); + } + // endregion + + return ${dataKey}VO; + } + + /** + * 分页获取${dataName}封装 + * + * @param ${dataKey}Page + * @param request + * @return + */ + @Override + public Page<${upperDataKey}VO> get${upperDataKey}VOPage(Page<${upperDataKey}> ${dataKey}Page, HttpServletRequest request) { + List<${upperDataKey}> ${dataKey}List = ${dataKey}Page.getRecords(); + Page<${upperDataKey}VO> ${dataKey}VOPage = new Page<>(${dataKey}Page.getCurrent(), ${dataKey}Page.getSize(), ${dataKey}Page.getTotal()); + if (CollUtil.isEmpty(${dataKey}List)) { + return ${dataKey}VOPage; + } + // 对象列表 => 封装对象列表 + List<${upperDataKey}VO> ${dataKey}VOList = ${dataKey}List.stream().map(${dataKey} -> { + return ${upperDataKey}VO.objToVo(${dataKey}); + }).collect(Collectors.toList()); + + // todo 可以根据需要为封装对象补充值,不需要的内容可以删除 + // region 可选 + // 1. 关联查询用户信息 + Set userIdSet = ${dataKey}List.stream().map(${upperDataKey}::getUserId).collect(Collectors.toSet()); + Map> userIdUserListMap = userService.listByIds(userIdSet).stream() + .collect(Collectors.groupingBy(User::getId)); + // 2. 已登录,获取用户点赞、收藏状态 + Map ${dataKey}IdHasThumbMap = new HashMap<>(); + Map ${dataKey}IdHasFavourMap = new HashMap<>(); + User loginUser = userService.getLoginUserPermitNull(request); + if (loginUser != null) { + Set ${dataKey}IdSet = ${dataKey}List.stream().map(${upperDataKey}::getId).collect(Collectors.toSet()); + loginUser = userService.getLoginUser(request); + // 获取点赞 + QueryWrapper<${upperDataKey}Thumb> ${dataKey}ThumbQueryWrapper = new QueryWrapper<>(); + ${dataKey}ThumbQueryWrapper.in("${dataKey}Id", ${dataKey}IdSet); + ${dataKey}ThumbQueryWrapper.eq("userId", loginUser.getId()); + List<${upperDataKey}Thumb> ${dataKey}${upperDataKey}ThumbList = ${dataKey}ThumbMapper.selectList(${dataKey}ThumbQueryWrapper); + ${dataKey}${upperDataKey}ThumbList.forEach(${dataKey}${upperDataKey}Thumb -> ${dataKey}IdHasThumbMap.put(${dataKey}${upperDataKey}Thumb.get${upperDataKey}Id(), true)); + // 获取收藏 + QueryWrapper<${upperDataKey}Favour> ${dataKey}FavourQueryWrapper = new QueryWrapper<>(); + ${dataKey}FavourQueryWrapper.in("${dataKey}Id", ${dataKey}IdSet); + ${dataKey}FavourQueryWrapper.eq("userId", loginUser.getId()); + List<${upperDataKey}Favour> ${dataKey}FavourList = ${dataKey}FavourMapper.selectList(${dataKey}FavourQueryWrapper); + ${dataKey}FavourList.forEach(${dataKey}Favour -> ${dataKey}IdHasFavourMap.put(${dataKey}Favour.get${upperDataKey}Id(), true)); + } + // 填充信息 + ${dataKey}VOList.forEach(${dataKey}VO -> { + Long userId = ${dataKey}VO.getUserId(); + User user = null; + if (userIdUserListMap.containsKey(userId)) { + user = userIdUserListMap.get(userId).get(0); + } + ${dataKey}VO.setUser(userService.getUserVO(user)); + ${dataKey}VO.setHasThumb(${dataKey}IdHasThumbMap.getOrDefault(${dataKey}VO.getId(), false)); + ${dataKey}VO.setHasFavour(${dataKey}IdHasFavourMap.getOrDefault(${dataKey}VO.getId(), false)); + }); + // endregion + + ${dataKey}VOPage.setRecords(${dataKey}VOList); + return ${dataKey}VOPage; + } + +} diff --git a/src/main/resources/templates/model/TemplateAddRequest.java.ftl b/src/main/resources/templates/model/TemplateAddRequest.java.ftl new file mode 100644 index 0000000..a527ded --- /dev/null +++ b/src/main/resources/templates/model/TemplateAddRequest.java.ftl @@ -0,0 +1,33 @@ +package ${packageName}.model.dto.${dataKey}; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 创建${dataName}请求 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +@Data +public class ${upperDataKey}AddRequest implements Serializable { + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 + */ + private List tags; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/resources/templates/model/TemplateEditRequest.java.ftl b/src/main/resources/templates/model/TemplateEditRequest.java.ftl new file mode 100644 index 0000000..f955c53 --- /dev/null +++ b/src/main/resources/templates/model/TemplateEditRequest.java.ftl @@ -0,0 +1,38 @@ +package ${packageName}.model.dto.${dataKey}; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 编辑${dataName}请求 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +@Data +public class ${upperDataKey}EditRequest implements Serializable { + + /** + * id + */ + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 + */ + private List tags; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/resources/templates/model/TemplateQueryRequest.java.ftl b/src/main/resources/templates/model/TemplateQueryRequest.java.ftl new file mode 100644 index 0000000..3ff9b8e --- /dev/null +++ b/src/main/resources/templates/model/TemplateQueryRequest.java.ftl @@ -0,0 +1,56 @@ +package ${packageName}.model.dto.${dataKey}; + +import ${packageName}.common.PageRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.List; + +/** + * 查询${dataName}请求 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class ${upperDataKey}QueryRequest extends PageRequest implements Serializable { + + /** + * id + */ + private Long id; + + /** + * id + */ + private Long notId; + + /** + * 搜索词 + */ + private String searchText; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 + */ + private List tags; + + /** + * 创建用户 id + */ + private Long userId; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/resources/templates/model/TemplateUpdateRequest.java.ftl b/src/main/resources/templates/model/TemplateUpdateRequest.java.ftl new file mode 100644 index 0000000..9188baf --- /dev/null +++ b/src/main/resources/templates/model/TemplateUpdateRequest.java.ftl @@ -0,0 +1,38 @@ +package ${packageName}.model.dto.${dataKey}; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 更新${dataName}请求 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +@Data +public class ${upperDataKey}UpdateRequest implements Serializable { + + /** + * id + */ + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 标签列表 + */ + private List tags; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/resources/templates/model/TemplateVO.java.ftl b/src/main/resources/templates/model/TemplateVO.java.ftl new file mode 100644 index 0000000..9f04289 --- /dev/null +++ b/src/main/resources/templates/model/TemplateVO.java.ftl @@ -0,0 +1,93 @@ +package ${packageName}.model.vo; + +import cn.hutool.json.JSONUtil; +import ${packageName}.model.entity.${upperDataKey}; +import lombok.Data; +import org.springframework.beans.BeanUtils; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * ${dataName}视图 + * + * @author 程序员鱼皮 + * @from 编程导航学习圈 + */ +@Data +public class ${upperDataKey}VO implements Serializable { + + /** + * id + */ + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 创建用户 id + */ + private Long userId; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 标签列表 + */ + private List tagList; + + /** + * 创建用户信息 + */ + private UserVO user; + + /** + * 封装类转对象 + * + * @param ${dataKey}VO + * @return + */ + public static ${upperDataKey} voToObj(${upperDataKey}VO ${dataKey}VO) { + if (${dataKey}VO == null) { + return null; + } + ${upperDataKey} ${dataKey} = new ${upperDataKey}(); + BeanUtils.copyProperties(${dataKey}VO, ${dataKey}); + List tagList = ${dataKey}VO.getTagList(); + ${dataKey}.setTags(JSONUtil.toJsonStr(tagList)); + return ${dataKey}; + } + + /** + * 对象转封装类 + * + * @param ${dataKey} + * @return + */ + public static ${upperDataKey}VO objToVo(${upperDataKey} ${dataKey}) { + if (${dataKey} == null) { + return null; + } + ${upperDataKey}VO ${dataKey}VO = new ${upperDataKey}VO(); + BeanUtils.copyProperties(${dataKey}, ${dataKey}VO); + ${dataKey}VO.setTagList(JSONUtil.toList(${dataKey}.getTags(), String.class)); + return ${dataKey}VO; + } +} diff --git a/src/main/resources/test_excel.xlsx b/src/main/resources/test_excel.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..9e4c4c6e0f68fce96fcdd5e6457b54e9d6a3cb52 GIT binary patch literal 9143 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 literal 0 HcmV?d00001 diff --git a/src/test/java/com/yupi/springbootinit/MainApplicationTests.java b/src/test/java/com/yupi/springbootinit/MainApplicationTests.java new file mode 100644 index 0000000..c1133bf --- /dev/null +++ b/src/test/java/com/yupi/springbootinit/MainApplicationTests.java @@ -0,0 +1,19 @@ +package com.yupi.springbootinit; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * 主类测试 + * + * @author 程序员鱼皮 + * @from 编程导航知识星球 + */ +@SpringBootTest +class MainApplicationTests { + + @Test + void contextLoads() { + } + +}