From 28086db2196e0bb3a5011e3e620b1db0bba3a9cb Mon Sep 17 00:00:00 2001 From: drhuxq <1695669804@qq.com> Date: Mon, 13 Jan 2025 14:27:12 +0800 Subject: [PATCH] first commit --- Dockerfile | 15 + mvnw | 316 ++++++++++++++ mvnw.cmd | 188 ++++++++ pom.xml | 138 ++++++ sql/.author | 0 sql/create_table.sql | 79 ++++ src/.DS_Store | Bin 0 -> 6148 bytes .../java/com/yupi/yupao/MyApplication.java | 22 + .../com/yupi/yupao/common/BaseResponse.java | 42 ++ .../com/yupi/yupao/common/DeleteRequest.java | 17 + .../java/com/yupi/yupao/common/ErrorCode.java | 48 +++ .../com/yupi/yupao/common/PageRequest.java | 26 ++ .../com/yupi/yupao/common/ResultUtils.java | 62 +++ .../yupi/yupao/config/MybatisPlusConfig.java | 30 ++ .../yupao/config/RedisTemplateConfig.java | 25 ++ .../com/yupi/yupao/config/RedissonConfig.java | 34 ++ .../com/yupi/yupao/config/SwaggerConfig.java | 50 +++ .../com/yupi/yupao/config/WebMvcConfg.java | 27 ++ .../com/yupi/yupao/constant/UserConstant.java | 26 ++ .../yupi/yupao/controller/TeamController.java | 250 +++++++++++ .../yupi/yupao/controller/UserController.java | 184 ++++++++ .../yupao/exception/BusinessException.java | 40 ++ .../exception/GlobalExceptionHandler.java | 29 ++ .../java/com/yupi/yupao/job/PreCacheJob.java | 75 ++++ .../com/yupi/yupao/mapper/TeamMapper.java | 16 + .../com/yupi/yupao/mapper/UserMapper.java | 16 + .../com/yupi/yupao/mapper/UserTeamMapper.java | 15 + .../com/yupi/yupao/model/domain/Team.java | 76 ++++ .../com/yupi/yupao/model/domain/User.java | 95 ++++ .../com/yupi/yupao/model/domain/UserTeam.java | 57 +++ .../com/yupi/yupao/model/dto/TeamQuery.java | 56 +++ .../yupao/model/enums/TeamStatusEnum.java | 52 +++ .../yupao/model/request/TeamAddRequest.java | 52 +++ .../yupao/model/request/TeamJoinRequest.java | 27 ++ .../yupao/model/request/TeamQuitRequest.java | 24 ++ .../model/request/TeamUpdateRequest.java | 48 +++ .../yupao/model/request/UserLoginRequest.java | 20 + .../model/request/UserRegisterRequest.java | 22 + .../com/yupi/yupao/model/vo/TeamUserVO.java | 77 ++++ .../java/com/yupi/yupao/model/vo/UserVO.java | 80 ++++ .../yupao/once/importuser/ImportExcel.java | 47 ++ .../once/importuser/ImportXingQiuUser.java | 35 ++ .../yupao/once/importuser/InsertUsers.java | 47 ++ .../yupao/once/importuser/TableListener.java | 35 ++ .../once/importuser/XingQiuTableUserInfo.java | 26 ++ .../com/yupi/yupao/service/TeamService.java | 73 ++++ .../com/yupi/yupao/service/UserService.java | 97 +++++ .../yupi/yupao/service/UserTeamService.java | 12 + .../yupao/service/impl/TeamServiceImpl.java | 408 ++++++++++++++++++ .../yupao/service/impl/UserServiceImpl.java | 340 +++++++++++++++ .../service/impl/UserTeamServiceImpl.java | 21 + .../com/yupi/yupao/utils/AlgorithmUtils.java | 91 ++++ src/main/resources/application-prod.yml | 15 + src/main/resources/application.yml | 41 ++ src/main/resources/mapper/TeamMapper.xml | 28 ++ src/main/resources/mapper/UserMapper.xml | 32 ++ src/main/resources/mapper/UserTeamMapper.xml | 25 ++ .../com/yupi/yupao/MyApplicationTest.java | 27 ++ .../yupao/service/AlgorithmUtilsTest.java | 42 ++ .../yupi/yupao/service/InsertUsersTest.java | 99 +++++ .../com/yupi/yupao/service/RedisTest.java | 44 ++ .../com/yupi/yupao/service/RedissonTest.java | 76 ++++ .../yupi/yupao/service/UserServiceTest.java | 102 +++++ 63 files changed, 4219 insertions(+) create mode 100644 Dockerfile create mode 100644 mvnw create mode 100644 mvnw.cmd create mode 100644 pom.xml create mode 100644 sql/.author create mode 100644 sql/create_table.sql create mode 100644 src/.DS_Store create mode 100644 src/main/java/com/yupi/yupao/MyApplication.java create mode 100644 src/main/java/com/yupi/yupao/common/BaseResponse.java create mode 100644 src/main/java/com/yupi/yupao/common/DeleteRequest.java create mode 100644 src/main/java/com/yupi/yupao/common/ErrorCode.java create mode 100644 src/main/java/com/yupi/yupao/common/PageRequest.java create mode 100644 src/main/java/com/yupi/yupao/common/ResultUtils.java create mode 100644 src/main/java/com/yupi/yupao/config/MybatisPlusConfig.java create mode 100644 src/main/java/com/yupi/yupao/config/RedisTemplateConfig.java create mode 100644 src/main/java/com/yupi/yupao/config/RedissonConfig.java create mode 100644 src/main/java/com/yupi/yupao/config/SwaggerConfig.java create mode 100644 src/main/java/com/yupi/yupao/config/WebMvcConfg.java create mode 100644 src/main/java/com/yupi/yupao/constant/UserConstant.java create mode 100644 src/main/java/com/yupi/yupao/controller/TeamController.java create mode 100644 src/main/java/com/yupi/yupao/controller/UserController.java create mode 100644 src/main/java/com/yupi/yupao/exception/BusinessException.java create mode 100644 src/main/java/com/yupi/yupao/exception/GlobalExceptionHandler.java create mode 100644 src/main/java/com/yupi/yupao/job/PreCacheJob.java create mode 100644 src/main/java/com/yupi/yupao/mapper/TeamMapper.java create mode 100644 src/main/java/com/yupi/yupao/mapper/UserMapper.java create mode 100644 src/main/java/com/yupi/yupao/mapper/UserTeamMapper.java create mode 100644 src/main/java/com/yupi/yupao/model/domain/Team.java create mode 100644 src/main/java/com/yupi/yupao/model/domain/User.java create mode 100644 src/main/java/com/yupi/yupao/model/domain/UserTeam.java create mode 100644 src/main/java/com/yupi/yupao/model/dto/TeamQuery.java create mode 100644 src/main/java/com/yupi/yupao/model/enums/TeamStatusEnum.java create mode 100644 src/main/java/com/yupi/yupao/model/request/TeamAddRequest.java create mode 100644 src/main/java/com/yupi/yupao/model/request/TeamJoinRequest.java create mode 100644 src/main/java/com/yupi/yupao/model/request/TeamQuitRequest.java create mode 100644 src/main/java/com/yupi/yupao/model/request/TeamUpdateRequest.java create mode 100644 src/main/java/com/yupi/yupao/model/request/UserLoginRequest.java create mode 100644 src/main/java/com/yupi/yupao/model/request/UserRegisterRequest.java create mode 100644 src/main/java/com/yupi/yupao/model/vo/TeamUserVO.java create mode 100644 src/main/java/com/yupi/yupao/model/vo/UserVO.java create mode 100644 src/main/java/com/yupi/yupao/once/importuser/ImportExcel.java create mode 100644 src/main/java/com/yupi/yupao/once/importuser/ImportXingQiuUser.java create mode 100644 src/main/java/com/yupi/yupao/once/importuser/InsertUsers.java create mode 100644 src/main/java/com/yupi/yupao/once/importuser/TableListener.java create mode 100644 src/main/java/com/yupi/yupao/once/importuser/XingQiuTableUserInfo.java create mode 100644 src/main/java/com/yupi/yupao/service/TeamService.java create mode 100644 src/main/java/com/yupi/yupao/service/UserService.java create mode 100644 src/main/java/com/yupi/yupao/service/UserTeamService.java create mode 100644 src/main/java/com/yupi/yupao/service/impl/TeamServiceImpl.java create mode 100644 src/main/java/com/yupi/yupao/service/impl/UserServiceImpl.java create mode 100644 src/main/java/com/yupi/yupao/service/impl/UserTeamServiceImpl.java create mode 100644 src/main/java/com/yupi/yupao/utils/AlgorithmUtils.java create mode 100644 src/main/resources/application-prod.yml create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/mapper/TeamMapper.xml create mode 100644 src/main/resources/mapper/UserMapper.xml create mode 100644 src/main/resources/mapper/UserTeamMapper.xml create mode 100644 src/test/java/com/yupi/yupao/MyApplicationTest.java create mode 100644 src/test/java/com/yupi/yupao/service/AlgorithmUtilsTest.java create mode 100644 src/test/java/com/yupi/yupao/service/InsertUsersTest.java create mode 100644 src/test/java/com/yupi/yupao/service/RedisTest.java create mode 100644 src/test/java/com/yupi/yupao/service/RedissonTest.java create mode 100644 src/test/java/com/yupi/yupao/service/UserServiceTest.java diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..dfc74ca --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +# Docker 镜像构建 + +FROM maven:3.5-jdk-8-alpine 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/yupao-backend-0.0.1-SNAPSHOT.jar","--spring.profiles.active=prod"] + diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..8a8fb22 --- /dev/null +++ b/mvnw @@ -0,0 +1,316 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`\\unset -f command; \\command -v java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..1d8ab01 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,188 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..3bd73e5 --- /dev/null +++ b/pom.xml @@ -0,0 +1,138 @@ + + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.6.4 + + + com.yupi + yupao-backend + 0.0.1-SNAPSHOT + yupao-backend + yupao-backend + + 1.8 + + + + org.springframework.boot + spring-boot-starter-web + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.2.2 + + + com.baomidou + mybatis-plus-boot-starter + 3.5.1 + + + + org.springframework.boot + spring-boot-starter-data-redis + 2.6.4 + + + + org.springframework.session + spring-session-data-redis + 2.6.3 + + + + org.redisson + redisson + 3.17.5 + + + + org.apache.commons + commons-lang3 + 3.12.0 + + + + org.apache.commons + commons-collections4 + 4.4 + + + + com.google.code.gson + gson + 2.8.9 + + + + com.alibaba + easyexcel + 3.1.0 + + + + com.github.xiaoymin + knife4j-spring-boot-starter + 2.0.7 + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + mysql + mysql-connector-java + runtime + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + junit + junit + 4.13.2 + test + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + + diff --git a/sql/.author b/sql/.author new file mode 100644 index 0000000..e69de29 diff --git a/sql/create_table.sql b/sql/create_table.sql new file mode 100644 index 0000000..320a50f --- /dev/null +++ b/sql/create_table.sql @@ -0,0 +1,79 @@ +# 数据库初始化 + +create +database if not exists yupao; + +use +yupao; + +-- 用户表 +create table user +( + username varchar(256) null comment '用户昵称', + id bigint auto_increment comment 'id' + primary key, + userAccount varchar(256) null comment '账号', + avatarUrl varchar(1024) null comment '用户头像', + gender tinyint null comment '性别', + userPassword varchar(512) not null comment '密码', + phone varchar(128) null comment '电话', + email varchar(512) null comment '邮箱', + userStatus int default 0 not null comment '状态 0 - 正常', + createTime datetime default CURRENT_TIMESTAMP null comment '创建时间', + updateTime datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP, + isDelete tinyint default 0 not null comment '是否删除', + userRole int default 0 not null comment '用户角色 0 - 普通用户 1 - 管理员', + planetCode varchar(512) null comment '编号', + tags varchar(1024) null comment '标签 json 列表' +) comment '用户'; + +-- 队伍表 +create table team +( + id bigint auto_increment comment 'id' primary key, + name varchar(256) not null comment '队伍名称', + description varchar(1024) null comment '描述', + maxNum int default 1 not null comment '最大人数', + expireTime datetime null comment '过期时间', + userId bigint comment '用户id(队长 id)', + status int default 0 not null comment '0 - 公开,1 - 私有,2 - 加密', + password varchar(512) null comment '密码', + createTime datetime default CURRENT_TIMESTAMP null comment '创建时间', + updateTime datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP, + isDelete tinyint default 0 not null comment '是否删除' +) comment '队伍'; + +-- 用户队伍关系 +create table user_team +( + id bigint auto_increment comment 'id' + primary key, + userId bigint comment '用户id', + teamId bigint comment '队伍id', + joinTime datetime null comment '加入时间', + createTime datetime default CURRENT_TIMESTAMP null comment '创建时间', + updateTime datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP, + isDelete tinyint default 0 not null comment '是否删除' +) comment '用户队伍关系'; + + +-- 标签表(可以不创建,因为标签字段已经放到了用户表中) +create table tag +( + id bigint auto_increment comment 'id' + primary key, + tagName varchar(256) null comment '标签名称', + userId bigint null comment '用户 id', + parentId bigint null comment '父标签 id', + isParent tinyint null comment '0 - 不是, 1 - 父标签', + createTime datetime default CURRENT_TIMESTAMP null comment '创建时间', + updateTime datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP, + isDelete tinyint default 0 not null comment '是否删除', + constraint uniIdx_tagName + unique (tagName) +) comment '标签'; + +# https://t.zsxq.com/0emozsIJh + +create index idx_userId + on tag (userId); \ No newline at end of file diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..4b6fb16dcc335bfa6304c4f6c3294aa0c29c4815 GIT binary patch literal 6148 zcmeHKOHRWu5S@W6BC*M`OP>JI8-yyHpcg2ih>sdliL$MH8E$|C7vLJ~xCd`M1C2$* z5+OPx+0UMD@}$@qBI4P@a!OPYQHdeQvKSFjkD3n5S&S?>R#(&cMc=JQlKfSbJbOVM zozo57WzWx#;Ady+`le}DeG6UryuE#Tt6ooY`|?No`g*`kae`3f-q0o8(bf`s&VV!E4D2@pxU)sdBSoK`0cXG&_+miLhkzki8%D)+ zbYMy?0I+~M3D(j}NK7!S4Wl9~5Y|wjhO)I7tl`iH^Q#S`qJ|S&^TAg6vw7iE9r;6Y zC$1HJb_Se*oPk5#R&xKJ;gcC{^79biIRnnXKVyK4X5P&3Qg*j~dp)^p1I7YFMEs&G nAXsNV0ocenGD$|Y2U)|fHjIjrMf52g=syC95TBfZUtr(^DceU3 literal 0 HcmV?d00001 diff --git a/src/main/java/com/yupi/yupao/MyApplication.java b/src/main/java/com/yupi/yupao/MyApplication.java new file mode 100644 index 0000000..a997f20 --- /dev/null +++ b/src/main/java/com/yupi/yupao/MyApplication.java @@ -0,0 +1,22 @@ +package com.yupi.yupao; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * 启动类 + * + */ +@SpringBootApplication +@MapperScan("com.yupi.yupao.mapper") +@EnableScheduling +public class MyApplication { + + public static void main(String[] args) { + SpringApplication.run(MyApplication.class, args); + } + +} + diff --git a/src/main/java/com/yupi/yupao/common/BaseResponse.java b/src/main/java/com/yupi/yupao/common/BaseResponse.java new file mode 100644 index 0000000..6be199c --- /dev/null +++ b/src/main/java/com/yupi/yupao/common/BaseResponse.java @@ -0,0 +1,42 @@ +package com.yupi.yupao.common; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 通用返回类 + * + * @param + */ +@Data +public class BaseResponse implements Serializable { + + private int code; + + private T data; + + private String message; + + private String description; + + public BaseResponse(int code, T data, String message, String description) { + this.code = code; + this.data = data; + this.message = message; + this.description = description; + } + + + public BaseResponse(int code, T data, String message) { + this(code, data, message, ""); + } + + public BaseResponse(int code, T data) { + this(code, data, "", ""); + } + + public BaseResponse(ErrorCode errorCode) { + this(errorCode.getCode(), null, errorCode.getMessage(), errorCode.getDescription()); + } +} diff --git a/src/main/java/com/yupi/yupao/common/DeleteRequest.java b/src/main/java/com/yupi/yupao/common/DeleteRequest.java new file mode 100644 index 0000000..8206fb9 --- /dev/null +++ b/src/main/java/com/yupi/yupao/common/DeleteRequest.java @@ -0,0 +1,17 @@ +package com.yupi.yupao.common; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 通用删除请求 + * + */ +@Data +public class DeleteRequest implements Serializable { + + private static final long serialVersionUID = -5860707094194210842L; + + private long id; +} diff --git a/src/main/java/com/yupi/yupao/common/ErrorCode.java b/src/main/java/com/yupi/yupao/common/ErrorCode.java new file mode 100644 index 0000000..869d0ea --- /dev/null +++ b/src/main/java/com/yupi/yupao/common/ErrorCode.java @@ -0,0 +1,48 @@ +package com.yupi.yupao.common; + +/** + * 错误码 + * + */ +public enum ErrorCode { + + // https://yupi.icu/ + + SUCCESS(0, "ok", ""), + PARAMS_ERROR(40000, "请求参数错误", ""), + NULL_ERROR(40001, "请求数据为空", ""), + NOT_LOGIN(40100, "未登录", ""), + NO_AUTH(40101, "无权限", ""), + FORBIDDEN(40301, "禁止操作", ""), + SYSTEM_ERROR(50000, "系统内部异常", ""); + + private final int code; + + /** + * 状态码信息 + */ + private final String message; + + /** + * 状态码描述(详情) + */ + private final String description; + + ErrorCode(int code, String message, String description) { + this.code = code; + this.message = message; + this.description = description; + } + + public int getCode() { + return code; + } + + public String getMessage() { + return message; + } + + public String getDescription() { + return description; + } +} diff --git a/src/main/java/com/yupi/yupao/common/PageRequest.java b/src/main/java/com/yupi/yupao/common/PageRequest.java new file mode 100644 index 0000000..645f48c --- /dev/null +++ b/src/main/java/com/yupi/yupao/common/PageRequest.java @@ -0,0 +1,26 @@ +package com.yupi.yupao.common; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 通用分页请求参数 + * + + */ +@Data +public class PageRequest implements Serializable { + + private static final long serialVersionUID = -5860707094194210842L; + + /** + * 页面大小 + */ + protected int pageSize = 10; + + /** + * 当前是第几页 + */ + protected int pageNum = 1; +} diff --git a/src/main/java/com/yupi/yupao/common/ResultUtils.java b/src/main/java/com/yupi/yupao/common/ResultUtils.java new file mode 100644 index 0000000..9df7d06 --- /dev/null +++ b/src/main/java/com/yupi/yupao/common/ResultUtils.java @@ -0,0 +1,62 @@ +package com.yupi.yupao.common; + +/** + * 返回工具类 + * + */ +public class ResultUtils { + + /** + * 成功 + * + * @param data + * @param + * @return + */ + public static BaseResponse success(T data) { + return new BaseResponse<>(0, data, "ok"); + } + + /** + * 失败 + * + * @param errorCode + * @return + */ + public static BaseResponse error(ErrorCode errorCode) { + return new BaseResponse<>(errorCode); + } + + + /** + * 失败 + * + * @param code + * @param message + * @param description + * @return + */ + public static BaseResponse error(int code, String message, String description) { + return new BaseResponse(code, null, message, description); + } + + /** + * 失败 + * + * @param errorCode + * @return + */ + public static BaseResponse error(ErrorCode errorCode, String message, String description) { + return new BaseResponse(errorCode.getCode(), null, message, description); + } + + /** + * 失败 + * + * @param errorCode + * @return + */ + public static BaseResponse error(ErrorCode errorCode, String description) { + return new BaseResponse(errorCode.getCode(), errorCode.getMessage(), description); + } +} diff --git a/src/main/java/com/yupi/yupao/config/MybatisPlusConfig.java b/src/main/java/com/yupi/yupao/config/MybatisPlusConfig.java new file mode 100644 index 0000000..cbb2478 --- /dev/null +++ b/src/main/java/com/yupi/yupao/config/MybatisPlusConfig.java @@ -0,0 +1,30 @@ +package com.yupi.yupao.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; + +/** + * MyBatisPlus 配置 + * + */ +@Configuration +@MapperScan("com.yupi.yupao.mapper") +public class MybatisPlusConfig { + + /** + * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除) + */ + @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/yupao/config/RedisTemplateConfig.java b/src/main/java/com/yupi/yupao/config/RedisTemplateConfig.java new file mode 100644 index 0000000..604b8c9 --- /dev/null +++ b/src/main/java/com/yupi/yupao/config/RedisTemplateConfig.java @@ -0,0 +1,25 @@ +package com.yupi.yupao.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializer; + +/** + * RedisTemplate 配置 + * + */ +@Configuration +public class RedisTemplateConfig { + + // https://space.bilibili.com/12890453/ + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(connectionFactory); + redisTemplate.setKeySerializer(RedisSerializer.string()); + return redisTemplate; + } +} diff --git a/src/main/java/com/yupi/yupao/config/RedissonConfig.java b/src/main/java/com/yupi/yupao/config/RedissonConfig.java new file mode 100644 index 0000000..705f337 --- /dev/null +++ b/src/main/java/com/yupi/yupao/config/RedissonConfig.java @@ -0,0 +1,34 @@ +package com.yupi.yupao.config; + +import lombok.Data; +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Redisson 配置 + * + */ +@Configuration +@ConfigurationProperties(prefix = "spring.redis") +@Data +public class RedissonConfig { + + private String host; + + private String port; + + @Bean + public RedissonClient redissonClient() { + // 1. 创建配置 + Config config = new Config(); + String redisAddress = String.format("redis://%s:%s", host, port); + config.useSingleServer().setAddress(redisAddress).setDatabase(3); + // 2. 创建实例 + RedissonClient redisson = Redisson.create(config); + return redisson; + } +} diff --git a/src/main/java/com/yupi/yupao/config/SwaggerConfig.java b/src/main/java/com/yupi/yupao/config/SwaggerConfig.java new file mode 100644 index 0000000..3a67c5e --- /dev/null +++ b/src/main/java/com/yupi/yupao/config/SwaggerConfig.java @@ -0,0 +1,50 @@ +package com.yupi.yupao.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.Contact; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc; + +/** + * 自定义 Swagger 接口文档的配置 + * + */ +@Configuration +@EnableSwagger2WebMvc +@Profile({"dev", "test"}) +public class SwaggerConfig { + + @Bean(value = "defaultApi2") + public Docket defaultApi2() { + return new Docket(DocumentationType.SWAGGER_2) + .apiInfo(apiInfo()) + .select() + // 这里一定要标注你控制器的位置 + .apis(RequestHandlerSelectors.basePackage("com.yupi.yupao.controller")) + .paths(PathSelectors.any()) + .build(); + } + + + + /** + * api 信息 + * @return + */ + private ApiInfo apiInfo() { + return new ApiInfoBuilder() + .title("用户中心") + .description("用户中心接口文档") + .termsOfServiceUrl("https://github.com/liyupi") + .contact(new Contact("yupi","https://github.com/liyupi","xxx@qq.com")) + .version("1.0") + .build(); + } +} diff --git a/src/main/java/com/yupi/yupao/config/WebMvcConfg.java b/src/main/java/com/yupi/yupao/config/WebMvcConfg.java new file mode 100644 index 0000000..da3fc11 --- /dev/null +++ b/src/main/java/com/yupi/yupao/config/WebMvcConfg.java @@ -0,0 +1,27 @@ +package com.yupi.yupao.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 跨域配置 + */ +@Configuration +public class WebMvcConfg implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + //设置允许跨域的路径 + registry.addMapping("/**") + //设置允许跨域请求的域名 + //当**Credentials为true时,**Origin不能为星号,需为具体的ip地址【如果接口不带cookie,ip无需设成具体ip】 + .allowedOrigins("http://localhost:9527", "http://127.0.0.1:9527", "http://127.0.0.1:8082", "http://127.0.0.1:8083") + //是否允许证书 不再默认开启 + .allowCredentials(true) + //设置允许的方法 + .allowedMethods("*") + //跨域允许时间 + .maxAge(3600); + } +} \ No newline at end of file diff --git a/src/main/java/com/yupi/yupao/constant/UserConstant.java b/src/main/java/com/yupi/yupao/constant/UserConstant.java new file mode 100644 index 0000000..76f75b8 --- /dev/null +++ b/src/main/java/com/yupi/yupao/constant/UserConstant.java @@ -0,0 +1,26 @@ +package com.yupi.yupao.constant; + +/** + * 用户常量 + * + */ +public interface UserConstant { + + /** + * 用户登录态键 + */ + String USER_LOGIN_STATE = "userLoginState"; + + // ------- 权限 -------- + + /** + * 默认权限 + */ + int DEFAULT_ROLE = 0; + + /** + * 管理员权限 + */ + int ADMIN_ROLE = 1; + +} diff --git a/src/main/java/com/yupi/yupao/controller/TeamController.java b/src/main/java/com/yupi/yupao/controller/TeamController.java new file mode 100644 index 0000000..67c4495 --- /dev/null +++ b/src/main/java/com/yupi/yupao/controller/TeamController.java @@ -0,0 +1,250 @@ +package com.yupi.yupao.controller; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yupi.yupao.common.BaseResponse; +import com.yupi.yupao.common.DeleteRequest; +import com.yupi.yupao.common.ErrorCode; +import com.yupi.yupao.common.ResultUtils; +import com.yupi.yupao.exception.BusinessException; +import com.yupi.yupao.model.domain.Team; +import com.yupi.yupao.model.domain.User; +import com.yupi.yupao.model.domain.UserTeam; +import com.yupi.yupao.model.dto.TeamQuery; +import com.yupi.yupao.model.request.TeamAddRequest; +import com.yupi.yupao.model.request.TeamJoinRequest; +import com.yupi.yupao.model.request.TeamQuitRequest; +import com.yupi.yupao.model.request.TeamUpdateRequest; +import com.yupi.yupao.model.vo.TeamUserVO; +import com.yupi.yupao.service.TeamService; +import com.yupi.yupao.service.UserService; +import com.yupi.yupao.service.UserTeamService; +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; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 队伍接口 + * + */ +@RestController +@RequestMapping("/team") +@CrossOrigin(origins = {"http://localhost:3000"}) +@Slf4j +public class TeamController { + + @Resource + private UserService userService; + + @Resource + private TeamService teamService; + + @Resource + private UserTeamService userTeamService; + + @PostMapping("/add") + public BaseResponse addTeam(@RequestBody TeamAddRequest teamAddRequest, HttpServletRequest request) { + if (teamAddRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User loginUser = userService.getLoginUser(request); + Team team = new Team(); + BeanUtils.copyProperties(teamAddRequest, team); + long teamId = teamService.addTeam(team, loginUser); + return ResultUtils.success(teamId); + } + + @PostMapping("/update") + public BaseResponse updateTeam(@RequestBody TeamUpdateRequest teamUpdateRequest, HttpServletRequest request) { + if (teamUpdateRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User loginUser = userService.getLoginUser(request); + boolean result = teamService.updateTeam(teamUpdateRequest, loginUser); + if (!result) { + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "更新失败"); + } + return ResultUtils.success(true); + } + + @GetMapping("/get") + public BaseResponse getTeamById(long id) { + if (id <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Team team = teamService.getById(id); + if (team == null) { + throw new BusinessException(ErrorCode.NULL_ERROR); + } + return ResultUtils.success(team); + } + + @GetMapping("/list") + public BaseResponse> listTeams(TeamQuery teamQuery, HttpServletRequest request) { + if (teamQuery == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + boolean isAdmin = userService.isAdmin(request); + // 1、查询队伍列表 + List teamList = teamService.listTeams(teamQuery, isAdmin); + final List teamIdList = teamList.stream().map(TeamUserVO::getId).collect(Collectors.toList()); + // 2、判断当前用户是否已加入队伍 + QueryWrapper userTeamQueryWrapper = new QueryWrapper<>(); + try { + User loginUser = userService.getLoginUser(request); + userTeamQueryWrapper.eq("userId", loginUser.getId()); + userTeamQueryWrapper.in("teamId", teamIdList); + List userTeamList = userTeamService.list(userTeamQueryWrapper); + // 已加入的队伍 id 集合 + Set hasJoinTeamIdSet = userTeamList.stream().map(UserTeam::getTeamId).collect(Collectors.toSet()); + teamList.forEach(team -> { + boolean hasJoin = hasJoinTeamIdSet.contains(team.getId()); + team.setHasJoin(hasJoin); + }); + } catch (Exception e) { + } + // 3、查询已加入队伍的人数 + QueryWrapper userTeamJoinQueryWrapper = new QueryWrapper<>(); + userTeamJoinQueryWrapper.in("teamId", teamIdList); + List userTeamList = userTeamService.list(userTeamJoinQueryWrapper); + // 队伍 id => 加入这个队伍的用户列表 + Map> teamIdUserTeamList = userTeamList.stream().collect(Collectors.groupingBy(UserTeam::getTeamId)); + teamList.forEach(team -> team.setHasJoinNum(teamIdUserTeamList.getOrDefault(team.getId(), new ArrayList<>()).size())); + return ResultUtils.success(teamList); + } + + // todo 查询分页 + @GetMapping("/list/page") + public BaseResponse> listTeamsByPage(TeamQuery teamQuery) { + if (teamQuery == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Team team = new Team(); + BeanUtils.copyProperties(teamQuery, team); + Page page = new Page<>(teamQuery.getPageNum(), teamQuery.getPageSize()); + QueryWrapper queryWrapper = new QueryWrapper<>(team); + Page resultPage = teamService.page(page, queryWrapper); + return ResultUtils.success(resultPage); + } + + @PostMapping("/join") + public BaseResponse joinTeam(@RequestBody TeamJoinRequest teamJoinRequest, HttpServletRequest request) { + if (teamJoinRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User loginUser = userService.getLoginUser(request); + boolean result = teamService.joinTeam(teamJoinRequest, loginUser); + return ResultUtils.success(result); + } + + @PostMapping("/quit") + public BaseResponse quitTeam(@RequestBody TeamQuitRequest teamQuitRequest, HttpServletRequest request) { + if (teamQuitRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User loginUser = userService.getLoginUser(request); + boolean result = teamService.quitTeam(teamQuitRequest, loginUser); + return ResultUtils.success(result); + } + + @PostMapping("/delete") + public BaseResponse deleteTeam(@RequestBody DeleteRequest deleteRequest, HttpServletRequest request) { + if (deleteRequest == null || deleteRequest.getId() <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + long id = deleteRequest.getId(); + User loginUser = userService.getLoginUser(request); + boolean result = teamService.deleteTeam(id, loginUser); + if (!result) { + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "删除失败"); + } + return ResultUtils.success(true); + } + + + /** + * 获取我创建的队伍 + * + * @param teamQuery + * @param request + * @return + */ + @GetMapping("/list/my/create") + public BaseResponse> listMyCreateTeams(TeamQuery teamQuery, HttpServletRequest request) { + if (teamQuery == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User loginUser = userService.getLoginUser(request); + teamQuery.setUserId(loginUser.getId()); + List teamList = teamService.listTeams(teamQuery, true); + return ResultUtils.success(teamList); + } + + + /** + * 获取我加入的队伍 + * + * @param teamQuery + * @param request + * @return + */ + @GetMapping("/list/my/join") + public BaseResponse> listMyJoinTeams(TeamQuery teamQuery, HttpServletRequest request) { + if (teamQuery == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User loginUser = userService.getLoginUser(request); + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("userId", loginUser.getId()); + List userTeamList = userTeamService.list(queryWrapper); + // 取出不重复的队伍 id + // teamId userId + // 1, 2 + // 1, 3 + // 2, 3 + // result + // 1 => 2, 3 + // 2 => 3 + Map> listMap = userTeamList.stream() + .collect(Collectors.groupingBy(UserTeam::getTeamId)); + List idList = new ArrayList<>(listMap.keySet()); + teamQuery.setIdList(idList); + List teamList = teamService.listTeams(teamQuery, true); + return ResultUtils.success(teamList); + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/yupi/yupao/controller/UserController.java b/src/main/java/com/yupi/yupao/controller/UserController.java new file mode 100644 index 0000000..27571a1 --- /dev/null +++ b/src/main/java/com/yupi/yupao/controller/UserController.java @@ -0,0 +1,184 @@ +package com.yupi.yupao.controller; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yupi.yupao.common.BaseResponse; +import com.yupi.yupao.common.ErrorCode; +import com.yupi.yupao.common.ResultUtils; +import com.yupi.yupao.exception.BusinessException; +import com.yupi.yupao.model.domain.User; +import com.yupi.yupao.model.request.UserLoginRequest; +import com.yupi.yupao.model.request.UserRegisterRequest; +import com.yupi.yupao.model.vo.UserVO; +import com.yupi.yupao.service.UserService; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static com.yupi.yupao.constant.UserConstant.USER_LOGIN_STATE; + +/** + * 用户接口 + * + */ +@RestController +@RequestMapping("/user") +@CrossOrigin(origins = {"http://localhost:3000"}) +@Slf4j +public class UserController { + + @Resource + private UserService userService; + + @Resource + private RedisTemplate redisTemplate; + + @PostMapping("/register") + public BaseResponse userRegister(@RequestBody UserRegisterRequest userRegisterRequest) { + if (userRegisterRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + String userAccount = userRegisterRequest.getUserAccount(); + String userPassword = userRegisterRequest.getUserPassword(); + String checkPassword = userRegisterRequest.getCheckPassword(); + String planetCode = userRegisterRequest.getPlanetCode(); + if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword, planetCode)) { + return null; + } + long result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode); + return ResultUtils.success(result); + } + + @PostMapping("/login") + public BaseResponse userLogin(@RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request) { + if (userLoginRequest == null) { + return ResultUtils.error(ErrorCode.PARAMS_ERROR); + } + String userAccount = userLoginRequest.getUserAccount(); + String userPassword = userLoginRequest.getUserPassword(); + if (StringUtils.isAnyBlank(userAccount, userPassword)) { + return ResultUtils.error(ErrorCode.PARAMS_ERROR); + } + User user = userService.userLogin(userAccount, userPassword, request); + return ResultUtils.success(user); + } + + @PostMapping("/logout") + public BaseResponse userLogout(HttpServletRequest request) { + if (request == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + int result = userService.userLogout(request); + return ResultUtils.success(result); + } + + @GetMapping("/current") + public BaseResponse getCurrentUser(HttpServletRequest request) { + Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE); + User currentUser = (User) userObj; + if (currentUser == null) { + throw new BusinessException(ErrorCode.NOT_LOGIN); + } + long userId = currentUser.getId(); + // TODO 校验用户是否合法 + User user = userService.getById(userId); + User safetyUser = userService.getSafetyUser(user); + return ResultUtils.success(safetyUser); + } + + @GetMapping("/search") + public BaseResponse> searchUsers(String username, HttpServletRequest request) { + if (!userService.isAdmin(request)) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (StringUtils.isNotBlank(username)) { + queryWrapper.like("username", username); + } + List userList = userService.list(queryWrapper); + List list = userList.stream().map(user -> userService.getSafetyUser(user)).collect(Collectors.toList()); + return ResultUtils.success(list); + } + + @GetMapping("/search/tags") + public BaseResponse> searchUsersByTags(@RequestParam(required = false) List tagNameList) { + if (CollectionUtils.isEmpty(tagNameList)) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + List userList = userService.searchUsersByTags(tagNameList); + return ResultUtils.success(userList); + } + + // todo 推荐多个,未实现 + @GetMapping("/recommend") + public BaseResponse> recommendUsers(long pageSize, long pageNum, HttpServletRequest request) { + User loginUser = userService.getLoginUser(request); + String redisKey = String.format("yupao:user:recommend:%s", loginUser.getId()); + ValueOperations valueOperations = redisTemplate.opsForValue(); + // 如果有缓存,直接读缓存 + Page userPage = (Page) valueOperations.get(redisKey); + if (userPage != null) { + return ResultUtils.success(userPage); + } + // 无缓存,查数据库 + QueryWrapper queryWrapper = new QueryWrapper<>(); + userPage = userService.page(new Page<>(pageNum, pageSize), queryWrapper); + // 写缓存 + try { + valueOperations.set(redisKey, userPage, 30000, TimeUnit.MILLISECONDS); + } catch (Exception e) { + log.error("redis set key error", e); + } + return ResultUtils.success(userPage); + } + + + @PostMapping("/update") + public BaseResponse updateUser(@RequestBody User user, HttpServletRequest request) { + // 校验参数是否为空 + if (user == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User loginUser = userService.getLoginUser(request); + int result = userService.updateUser(user, loginUser); + return ResultUtils.success(result); + } + + @PostMapping("/delete") + public BaseResponse deleteUser(@RequestBody long id, HttpServletRequest request) { + if (!userService.isAdmin(request)) { + throw new BusinessException(ErrorCode.NO_AUTH); + } + if (id <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + boolean b = userService.removeById(id); + return ResultUtils.success(b); + } + + /** + * 获取最匹配的用户 + * + * @param num + * @param request + * @return + */ + @GetMapping("/match") + public BaseResponse> matchUsers(long num, HttpServletRequest request) { + if (num <= 0 || num > 20) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + User user = userService.getLoginUser(request); + return ResultUtils.success(userService.matchUsers(num, user)); + } + +} diff --git a/src/main/java/com/yupi/yupao/exception/BusinessException.java b/src/main/java/com/yupi/yupao/exception/BusinessException.java new file mode 100644 index 0000000..4838778 --- /dev/null +++ b/src/main/java/com/yupi/yupao/exception/BusinessException.java @@ -0,0 +1,40 @@ +package com.yupi.yupao.exception; + +import com.yupi.yupao.common.ErrorCode; + +/** + * 自定义异常类 + * + */ +public class BusinessException extends RuntimeException { + + private final int code; + + private final String description; + + public BusinessException(String message, int code, String description) { + super(message); + this.code = code; + this.description = description; + } + + public BusinessException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.code = errorCode.getCode(); + this.description = errorCode.getDescription(); + } + + public BusinessException(ErrorCode errorCode, String description) { + super(errorCode.getMessage()); + this.code = errorCode.getCode(); + this.description = description; + } + + public int getCode() { + return code; + } + + public String getDescription() { + return description; + } +} diff --git a/src/main/java/com/yupi/yupao/exception/GlobalExceptionHandler.java b/src/main/java/com/yupi/yupao/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..7bf1beb --- /dev/null +++ b/src/main/java/com/yupi/yupao/exception/GlobalExceptionHandler.java @@ -0,0 +1,29 @@ +package com.yupi.yupao.exception; + +import com.yupi.yupao.common.BaseResponse; +import com.yupi.yupao.common.ErrorCode; +import com.yupi.yupao.common.ResultUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * 全局异常处理器 + * + */ +@RestControllerAdvice +@Slf4j +public class GlobalExceptionHandler { + + @ExceptionHandler(BusinessException.class) + public BaseResponse businessExceptionHandler(BusinessException e) { + log.error("businessException: " + e.getMessage(), e); + return ResultUtils.error(e.getCode(), e.getMessage(), e.getDescription()); + } + + @ExceptionHandler(RuntimeException.class) + public BaseResponse runtimeExceptionHandler(RuntimeException e) { + log.error("runtimeException", e); + return ResultUtils.error(ErrorCode.SYSTEM_ERROR, e.getMessage(), ""); + } +} diff --git a/src/main/java/com/yupi/yupao/job/PreCacheJob.java b/src/main/java/com/yupi/yupao/job/PreCacheJob.java new file mode 100644 index 0000000..cd4dd4b --- /dev/null +++ b/src/main/java/com/yupi/yupao/job/PreCacheJob.java @@ -0,0 +1,75 @@ +package com.yupi.yupao.job; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yupi.yupao.mapper.UserMapper; +import com.yupi.yupao.model.domain.User; +import com.yupi.yupao.service.UserService; +import lombok.extern.slf4j.Slf4j; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * 缓存预热任务 + * + + */ +@Component +@Slf4j +public class PreCacheJob { + + @Resource + private UserService userService; + + @Resource + private RedisTemplate redisTemplate; + + @Resource + private RedissonClient redissonClient; + + // 重点用户 + private List mainUserList = Arrays.asList(1L); + + // 每天执行,预热推荐用户 + @Scheduled(cron = "0 31 0 * * *") + public void doCacheRecommendUser() { + RLock lock = redissonClient.getLock("yupao:precachejob:docache:lock"); + try { + // 只有一个线程能获取到锁 + if (lock.tryLock(0, -1, TimeUnit.MILLISECONDS)) { + System.out.println("getLock: " + Thread.currentThread().getId()); + for (Long userId : mainUserList) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + Page userPage = userService.page(new Page<>(1, 20), queryWrapper); + String redisKey = String.format("yupao:user:recommend:%s", userId); + ValueOperations valueOperations = redisTemplate.opsForValue(); + // 写缓存 + try { + valueOperations.set(redisKey, userPage, 30000, TimeUnit.MILLISECONDS); + } catch (Exception e) { + log.error("redis set key error", e); + } + } + } + } catch (InterruptedException e) { + log.error("doCacheRecommendUser error", e); + } finally { + // 只能释放自己的锁 + if (lock.isHeldByCurrentThread()) { + System.out.println("unLock: " + Thread.currentThread().getId()); + lock.unlock(); + } + } + } + +} diff --git a/src/main/java/com/yupi/yupao/mapper/TeamMapper.java b/src/main/java/com/yupi/yupao/mapper/TeamMapper.java new file mode 100644 index 0000000..bd8c8d8 --- /dev/null +++ b/src/main/java/com/yupi/yupao/mapper/TeamMapper.java @@ -0,0 +1,16 @@ +package com.yupi.yupao.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yupi.yupao.model.domain.Team; + +/** + * 队伍 Mapper + * + */ +public interface TeamMapper extends BaseMapper { + +} + + + + diff --git a/src/main/java/com/yupi/yupao/mapper/UserMapper.java b/src/main/java/com/yupi/yupao/mapper/UserMapper.java new file mode 100644 index 0000000..90a37a9 --- /dev/null +++ b/src/main/java/com/yupi/yupao/mapper/UserMapper.java @@ -0,0 +1,16 @@ +package com.yupi.yupao.mapper; + +import com.yupi.yupao.model.domain.User; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 用户 Mapper + * + */ +public interface UserMapper extends BaseMapper { + +} + + + + diff --git a/src/main/java/com/yupi/yupao/mapper/UserTeamMapper.java b/src/main/java/com/yupi/yupao/mapper/UserTeamMapper.java new file mode 100644 index 0000000..e7bc8fa --- /dev/null +++ b/src/main/java/com/yupi/yupao/mapper/UserTeamMapper.java @@ -0,0 +1,15 @@ +package com.yupi.yupao.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yupi.yupao.model.domain.UserTeam; + +/** + * 用户队伍 Mapper + */ +public interface UserTeamMapper extends BaseMapper { + +} + + + + diff --git a/src/main/java/com/yupi/yupao/model/domain/Team.java b/src/main/java/com/yupi/yupao/model/domain/Team.java new file mode 100644 index 0000000..d3087ca --- /dev/null +++ b/src/main/java/com/yupi/yupao/model/domain/Team.java @@ -0,0 +1,76 @@ +package com.yupi.yupao.model.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 队伍实体 + * + + */ +@TableName(value = "team") +@Data +public class Team implements Serializable { + /** + * id + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 队伍名称 + */ + private String name; + + /** + * 描述 + */ + private String description; + + /** + * 最大人数 + */ + private Integer maxNum; + + /** + * 过期时间 + */ + private Date expireTime; + + /** + * 用户id + */ + private Long userId; + + /** + * 0 - 公开,1 - 私有,2 - 加密 + */ + private Integer status; + + /** + * 密码 + */ + private String password; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * + */ + private Date updateTime; + + /** + * 是否删除 + */ + @TableLogic + private Integer isDelete; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/yupao/model/domain/User.java b/src/main/java/com/yupi/yupao/model/domain/User.java new file mode 100644 index 0000000..39b2018 --- /dev/null +++ b/src/main/java/com/yupi/yupao/model/domain/User.java @@ -0,0 +1,95 @@ +package com.yupi.yupao.model.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 用户实体 + * + */ +@TableName(value = "user") +@Data +public class User implements Serializable { + /** + * id + */ + @TableId(type = IdType.AUTO) + private long id; + + /** + * 用户昵称 + */ + private String username; + + /** + * 账号 + */ + private String userAccount; + + /** + * 用户头像 + */ + private String avatarUrl; + + /** + * 性别 + */ + private Integer gender; + + /** + * 密码 + */ + private String userPassword; + + /** + * 电话 + */ + private String phone; + + /** + * 邮箱 + */ + private String email; + + /** + * 标签列表 json + */ + private String tags; + + /** + * 状态 0 - 正常 + */ + private Integer userStatus; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * + */ + private Date updateTime; + + /** + * 是否删除 + */ + @TableLogic + private Integer isDelete; + + /** + * 用户角色 0 - 普通用户 1 - 管理员 + */ + private Integer userRole; + + /** + * 编号 + */ + private String planetCode; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} diff --git a/src/main/java/com/yupi/yupao/model/domain/UserTeam.java b/src/main/java/com/yupi/yupao/model/domain/UserTeam.java new file mode 100644 index 0000000..a4c2f68 --- /dev/null +++ b/src/main/java/com/yupi/yupao/model/domain/UserTeam.java @@ -0,0 +1,57 @@ +package com.yupi.yupao.model.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 用户队伍关系实体 + * + */ +@TableName(value = "user_team") +@Data +public class UserTeam implements Serializable { + /** + * id + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 用户id + */ + private Long userId; + + /** + * 队伍id + */ + private Long teamId; + + /** + * 加入时间 + */ + private Date joinTime; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * + */ + private Date updateTime; + + + + /** + * 是否删除 + */ + @TableLogic + private Integer isDelete; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/yupao/model/dto/TeamQuery.java b/src/main/java/com/yupi/yupao/model/dto/TeamQuery.java new file mode 100644 index 0000000..8b80d68 --- /dev/null +++ b/src/main/java/com/yupi/yupao/model/dto/TeamQuery.java @@ -0,0 +1,56 @@ +package com.yupi.yupao.model.dto; + +import com.yupi.yupao.common.PageRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + + +/** + * 队伍查询封装类 + * + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class TeamQuery extends PageRequest { + /** + * id + */ + private Long id; + + /** + * id 列表 + */ + private List idList; + + /** + * 搜索关键词(同时对队伍名称和描述搜索) + */ + private String searchText; + + /** + * 队伍名称 + */ + private String name; + + /** + * 描述 + */ + private String description; + + /** + * 最大人数 + */ + private Integer maxNum; + + /** + * 用户id + */ + private Long userId; + + /** + * 0 - 公开,1 - 私有,2 - 加密 + */ + private Integer status; +} diff --git a/src/main/java/com/yupi/yupao/model/enums/TeamStatusEnum.java b/src/main/java/com/yupi/yupao/model/enums/TeamStatusEnum.java new file mode 100644 index 0000000..ee4f657 --- /dev/null +++ b/src/main/java/com/yupi/yupao/model/enums/TeamStatusEnum.java @@ -0,0 +1,52 @@ +package com.yupi.yupao.model.enums; + +/** + * 队伍状态枚举 + * + + */ +public enum TeamStatusEnum { + + PUBLIC(0, "公开"), + PRIVATE(1, "私有"), + SECRET(2, "加密"); + + private int value; + + private String text; + + + public static TeamStatusEnum getEnumByValue(Integer value) { + if (value == null) { + return null; + } + TeamStatusEnum[] values = TeamStatusEnum.values(); + for (TeamStatusEnum teamStatusEnum : values) { + if (teamStatusEnum.getValue() == value) { + return teamStatusEnum; + } + } + return null; + } + + TeamStatusEnum(int value, String text) { + this.value = value; + this.text = text; + } + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } +} diff --git a/src/main/java/com/yupi/yupao/model/request/TeamAddRequest.java b/src/main/java/com/yupi/yupao/model/request/TeamAddRequest.java new file mode 100644 index 0000000..7464e03 --- /dev/null +++ b/src/main/java/com/yupi/yupao/model/request/TeamAddRequest.java @@ -0,0 +1,52 @@ +package com.yupi.yupao.model.request; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 创建队伍请求体 + * + */ +@Data +public class TeamAddRequest implements Serializable { + + private static final long serialVersionUID = 3191241716373120793L; + + /** + * 队伍名称 + */ + private String name; + + /** + * 描述 + */ + private String description; + + /** + * 最大人数 + */ + private Integer maxNum; + + /** + * 过期时间 + */ + private Date expireTime; + + /** + * 用户id + */ + private Long userId; + + /** + * 0 - 公开,1 - 私有,2 - 加密 + */ + private Integer status; + + + /** + * 密码 + */ + private String password; +} diff --git a/src/main/java/com/yupi/yupao/model/request/TeamJoinRequest.java b/src/main/java/com/yupi/yupao/model/request/TeamJoinRequest.java new file mode 100644 index 0000000..ccf02e7 --- /dev/null +++ b/src/main/java/com/yupi/yupao/model/request/TeamJoinRequest.java @@ -0,0 +1,27 @@ +package com.yupi.yupao.model.request; + + + +import lombok.Data; + +import java.io.Serializable; + +/** + * 用户加入队伍请求体 + + */ +@Data +public class TeamJoinRequest implements Serializable { + + private static final long serialVersionUID = 3191241716373120793L; + + /** + * id + */ + private Long teamId; + + /** + * 密码 + */ + private String password; +} diff --git a/src/main/java/com/yupi/yupao/model/request/TeamQuitRequest.java b/src/main/java/com/yupi/yupao/model/request/TeamQuitRequest.java new file mode 100644 index 0000000..2291a38 --- /dev/null +++ b/src/main/java/com/yupi/yupao/model/request/TeamQuitRequest.java @@ -0,0 +1,24 @@ +package com.yupi.yupao.model.request; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 用户退出队伍请求体 + * + + */ +@Data +public class TeamQuitRequest implements Serializable { + + // 开发者 [coder_yupi](https://space.bilibili.com/12890453/) + + private static final long serialVersionUID = 3191241716373120793L; + + /** + * id + */ + private Long teamId; + +} diff --git a/src/main/java/com/yupi/yupao/model/request/TeamUpdateRequest.java b/src/main/java/com/yupi/yupao/model/request/TeamUpdateRequest.java new file mode 100644 index 0000000..cfeba91 --- /dev/null +++ b/src/main/java/com/yupi/yupao/model/request/TeamUpdateRequest.java @@ -0,0 +1,48 @@ +package com.yupi.yupao.model.request; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 队伍更新请求体 + * + */ +@Data +public class TeamUpdateRequest implements Serializable { + + private static final long serialVersionUID = 3191241716373120793L; + + /** + * id + */ + private Long id; + + /** + * 队伍名称 + */ + private String name; + + /** + * 描述 + */ + private String description; + + /** + * 过期时间 + */ + private Date expireTime; + + /** + * 0 - 公开,1 - 私有,2 - 加密 + */ + private Integer status; + + /** + * 密码 + */ + private String password; +} + +// 负责人【yupi】 https://space.bilibili.com/12890453/ diff --git a/src/main/java/com/yupi/yupao/model/request/UserLoginRequest.java b/src/main/java/com/yupi/yupao/model/request/UserLoginRequest.java new file mode 100644 index 0000000..c83a080 --- /dev/null +++ b/src/main/java/com/yupi/yupao/model/request/UserLoginRequest.java @@ -0,0 +1,20 @@ +package com.yupi.yupao.model.request; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 用户登录请求体 + * + */ +@Data +public class UserLoginRequest implements Serializable { + + private static final long serialVersionUID = 3191241716373120793L; + + private String userAccount; + + + private String userPassword; +} diff --git a/src/main/java/com/yupi/yupao/model/request/UserRegisterRequest.java b/src/main/java/com/yupi/yupao/model/request/UserRegisterRequest.java new file mode 100644 index 0000000..d70bdda --- /dev/null +++ b/src/main/java/com/yupi/yupao/model/request/UserRegisterRequest.java @@ -0,0 +1,22 @@ +package com.yupi.yupao.model.request; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 用户注册请求体 + */ +@Data +public class UserRegisterRequest implements Serializable { + + private static final long serialVersionUID = 3191241716373120793L; + + private String userAccount; + + private String userPassword; + + private String checkPassword; + + private String planetCode; +} diff --git a/src/main/java/com/yupi/yupao/model/vo/TeamUserVO.java b/src/main/java/com/yupi/yupao/model/vo/TeamUserVO.java new file mode 100644 index 0000000..e11c52c --- /dev/null +++ b/src/main/java/com/yupi/yupao/model/vo/TeamUserVO.java @@ -0,0 +1,77 @@ +package com.yupi.yupao.model.vo; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 队伍和用户信息封装类(脱敏) + */ +@Data +public class TeamUserVO implements Serializable { + + private static final long serialVersionUID = 1899063007109226944L; + + /** + * id + */ + private Long id; + + /** + * 队伍名称 + */ + private String name; + + + + /** + * 描述 + */ + private String description; + + /** + * 最大人数 + */ + private Integer maxNum; + + /** + * 过期时间 + */ + private Date expireTime; + + /** + * 用户id + */ + private Long userId; + + /** + * 0 - 公开,1 - 私有,2 - 加密 + */ + private Integer status; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 创建人用户信息 + */ + private UserVO createUser; + + /** + * 已加入的用户数 + */ + private Integer hasJoinNum; + + /** + * 是否已加入队伍 + */ + private boolean hasJoin = false; +} diff --git a/src/main/java/com/yupi/yupao/model/vo/UserVO.java b/src/main/java/com/yupi/yupao/model/vo/UserVO.java new file mode 100644 index 0000000..e51b837 --- /dev/null +++ b/src/main/java/com/yupi/yupao/model/vo/UserVO.java @@ -0,0 +1,80 @@ +package com.yupi.yupao.model.vo; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 用户包装类(脱敏) + * + */ +@Data +public class UserVO implements Serializable { + /** + * id + */ + private long id; + + /** + * 用户昵称 + */ + private String username; + + /** + * 账号 + */ + private String userAccount; + + /** + * 用户头像 + */ + private String avatarUrl; + + /** + * 性别 + */ + private Integer gender; + + /** + * 电话 + */ + private String phone; + + /** + * 邮箱 + */ + private String email; + + /** + * 标签列表 json + */ + private String tags; + + /** + * 状态 0 - 正常 + */ + private Integer userStatus; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * + */ + private Date updateTime; + + /** + * 用户角色 0 - 普通用户 1 - 管理员 + */ + private Integer userRole; + + /** + * 编号 + */ + private String planetCode; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/yupi/yupao/once/importuser/ImportExcel.java b/src/main/java/com/yupi/yupao/once/importuser/ImportExcel.java new file mode 100644 index 0000000..1f53206 --- /dev/null +++ b/src/main/java/com/yupi/yupao/once/importuser/ImportExcel.java @@ -0,0 +1,47 @@ +package com.yupi.yupao.once.importuser; + +import com.alibaba.excel.EasyExcel; + +import java.util.List; + +/** + * 导入 Excel + * + */ +public class ImportExcel { + + /** + * 读取数据 + */ + public static void main(String[] args) { + // todo 记得改为自己的测试文件 + String fileName = "E:\\项目\\yupao-backend\\src\\main\\resources\\testExcel.xlsx"; +// readByListener(fileName); + synchronousRead(fileName); + } + + /** + * 监听器读取 + * + * @param fileName + */ + public static void readByListener(String fileName) { + EasyExcel.read(fileName, XingQiuTableUserInfo.class, new TableListener()).sheet().doRead(); + } + + + /** + * 同步读 + * + * @param fileName + */ + public static void synchronousRead(String fileName) { + // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish + List totalDataList = + EasyExcel.read(fileName).head(XingQiuTableUserInfo.class).sheet().doReadSync(); + for (XingQiuTableUserInfo xingQiuTableUserInfo : totalDataList) { + System.out.println(xingQiuTableUserInfo); + } + } + +} diff --git a/src/main/java/com/yupi/yupao/once/importuser/ImportXingQiuUser.java b/src/main/java/com/yupi/yupao/once/importuser/ImportXingQiuUser.java new file mode 100644 index 0000000..0eaba30 --- /dev/null +++ b/src/main/java/com/yupi/yupao/once/importuser/ImportXingQiuUser.java @@ -0,0 +1,35 @@ +package com.yupi.yupao.once.importuser; + +import com.alibaba.excel.EasyExcel; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 导入用户到数据库 + * + */ +public class ImportXingQiuUser { + + public static void main(String[] args) { + // todo 记得改为自己的测试文件 + String fileName = "E:\\项目\\yupao-backend\\src\\main\\resources\\prodExcel.xlsx"; + // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish + List userInfoList = + EasyExcel.read(fileName).head(XingQiuTableUserInfo.class).sheet().doReadSync(); + System.out.println("总数 = " + userInfoList.size()); + Map> listMap = + userInfoList.stream() + .filter(userInfo -> StringUtils.isNotEmpty(userInfo.getUsername())) + .collect(Collectors.groupingBy(XingQiuTableUserInfo::getUsername)); + for (Map.Entry> stringListEntry : listMap.entrySet()) { + if (stringListEntry.getValue().size() > 1) { + System.out.println("username = " + stringListEntry.getKey()); + System.out.println("1"); + } + } + System.out.println("不重复昵称数 = " + listMap.keySet().size()); + } +} diff --git a/src/main/java/com/yupi/yupao/once/importuser/InsertUsers.java b/src/main/java/com/yupi/yupao/once/importuser/InsertUsers.java new file mode 100644 index 0000000..9d944a7 --- /dev/null +++ b/src/main/java/com/yupi/yupao/once/importuser/InsertUsers.java @@ -0,0 +1,47 @@ +package com.yupi.yupao.once.importuser; + +import com.yupi.yupao.mapper.UserMapper; +import com.yupi.yupao.model.domain.User; +import org.springframework.stereotype.Component; +import org.springframework.util.StopWatch; + +import javax.annotation.Resource; + +/** + * 导入用户任务 + + */ +@Component +public class InsertUsers { + + @Resource + private UserMapper userMapper; + + /** + * 批量插入用户 + */ +// @Scheduled(initialDelay = 5000, fixedRate = Long.MAX_VALUE) + public void doInsertUsers() { + StopWatch stopWatch = new StopWatch(); + System.out.println("goodgoodgood"); + stopWatch.start(); + final int INSERT_NUM = 1000; + for (int i = 0; i < INSERT_NUM; i++) { + User user = new User(); + user.setUsername("假鱼皮"); + user.setUserAccount("fakeyupi"); + user.setAvatarUrl("https://636f-codenav-8grj8px727565176-1256524210.tcb.qcloud.la/img/logo.png"); + user.setGender(0); + user.setUserPassword("12345678"); + user.setPhone("123"); + user.setEmail("123@qq.com"); + user.setTags("[]"); + user.setUserStatus(0); + user.setUserRole(0); + user.setPlanetCode("11111111"); + userMapper.insert(user); + } + stopWatch.stop(); + System.out.println(stopWatch.getTotalTimeMillis()); + } +} diff --git a/src/main/java/com/yupi/yupao/once/importuser/TableListener.java b/src/main/java/com/yupi/yupao/once/importuser/TableListener.java new file mode 100644 index 0000000..ab5699b --- /dev/null +++ b/src/main/java/com/yupi/yupao/once/importuser/TableListener.java @@ -0,0 +1,35 @@ +package com.yupi.yupao.once.importuser; + +import com.alibaba.excel.context.AnalysisContext; +import com.alibaba.excel.read.listener.ReadListener; +import lombok.extern.slf4j.Slf4j; + +/** + * Excel 读取监听 + * + + */ +@Slf4j +public class TableListener implements ReadListener { + + /** + * 这个每一条数据解析都会来调用 + * + * @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()} + * @param context + */ + @Override + public void invoke(XingQiuTableUserInfo data, AnalysisContext context) { + System.out.println(data); + } + + /** + * 所有数据解析完成了 都会来调用 + * + * @param context + */ + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + System.out.println("已解析完成"); + } +} \ No newline at end of file diff --git a/src/main/java/com/yupi/yupao/once/importuser/XingQiuTableUserInfo.java b/src/main/java/com/yupi/yupao/once/importuser/XingQiuTableUserInfo.java new file mode 100644 index 0000000..15e82e6 --- /dev/null +++ b/src/main/java/com/yupi/yupao/once/importuser/XingQiuTableUserInfo.java @@ -0,0 +1,26 @@ +package com.yupi.yupao.once.importuser; + +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +/** + * 表格用户信息 + */ +@Data +public class XingQiuTableUserInfo { + + /** + * id + */ + @ExcelProperty("成员编号") + private String planetCode; + + /** + * 用户昵称 + */ + @ExcelProperty("成员昵称") + private String username; + + + +} \ No newline at end of file diff --git a/src/main/java/com/yupi/yupao/service/TeamService.java b/src/main/java/com/yupi/yupao/service/TeamService.java new file mode 100644 index 0000000..fb8c16f --- /dev/null +++ b/src/main/java/com/yupi/yupao/service/TeamService.java @@ -0,0 +1,73 @@ +package com.yupi.yupao.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yupi.yupao.model.domain.Team; +import com.yupi.yupao.model.domain.User; +import com.yupi.yupao.model.dto.TeamQuery; +import com.yupi.yupao.model.request.TeamJoinRequest; +import com.yupi.yupao.model.request.TeamQuitRequest; +import com.yupi.yupao.model.request.TeamUpdateRequest; +import com.yupi.yupao.model.vo.TeamUserVO; + +import java.util.List; + +/** + * 队伍服务 + * + */ +public interface TeamService extends IService { + + /** + * 创建队伍 + * + * @param team + * @param loginUser + * @return + */ + long addTeam(Team team, User loginUser); + + /** + * 搜索队伍 + * + * @param teamQuery + * @param isAdmin + * @return + */ + List listTeams(TeamQuery teamQuery, boolean isAdmin); + + /** + * 更新队伍 + * + * @param teamUpdateRequest + * @param loginUser + * @return + */ + boolean updateTeam(TeamUpdateRequest teamUpdateRequest, User loginUser); + + /** + * 加入队伍 + * + * @param teamJoinRequest + * @return + */ + boolean joinTeam(TeamJoinRequest teamJoinRequest, User loginUser); + + /** + * 退出队伍 + * + * @param teamQuitRequest + * @param loginUser + * @return + */ + boolean quitTeam(TeamQuitRequest teamQuitRequest, User loginUser); + + + /** + * 删除(解散)队伍 + * + * @param id + * @param loginUser + * @return + */ + boolean deleteTeam(long id, User loginUser); +} diff --git a/src/main/java/com/yupi/yupao/service/UserService.java b/src/main/java/com/yupi/yupao/service/UserService.java new file mode 100644 index 0000000..0da29cb --- /dev/null +++ b/src/main/java/com/yupi/yupao/service/UserService.java @@ -0,0 +1,97 @@ +package com.yupi.yupao.service; + +import com.yupi.yupao.common.BaseResponse; +import com.yupi.yupao.model.domain.User; +import com.baomidou.mybatisplus.extension.service.IService; +import com.yupi.yupao.model.vo.UserVO; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * 用户服务 + */ +public interface UserService extends IService { + + /** + * 用户注册 + * + * @param userAccount 用户账户 + * @param userPassword 用户密码 + * @param checkPassword 校验密码 + * @param planetCode 编号 + * @return 新用户 id + */ + long userRegister(String userAccount, String userPassword, String checkPassword, String planetCode); + + /** + * 用户登录 + * + * @param userAccount 用户账户 + * @param userPassword 用户密码 + * @param request + * @return 脱敏后的用户信息 + */ + User userLogin(String userAccount, String userPassword, HttpServletRequest request); + + /** + * 用户脱敏 + * + * @param originUser + * @return + */ + User getSafetyUser(User originUser); + + /** + * 用户注销 + * + * @param request + * @return + */ + int userLogout(HttpServletRequest request); + + /** + * 根据标签搜索用户 + * + * @param tagNameList + * @return + */ + List searchUsersByTags(List tagNameList); + + /** + * 更新用户信息 + * @param user + * @return + */ + int updateUser(User user, User loginUser); + + /** + * 获取当前登录用户信息 + * @return + */ + User getLoginUser(HttpServletRequest request); + + /** + * 是否为管理员 + * + * @param request + * @return + */ + boolean isAdmin(HttpServletRequest request); + + /** + * 是否为管理员 + * + * @param loginUser + * @return + */ + boolean isAdmin(User loginUser); + + /** + * 匹配用户 + * @param num + * @param loginUser + * @return + */ + List matchUsers(long num, User loginUser); +} diff --git a/src/main/java/com/yupi/yupao/service/UserTeamService.java b/src/main/java/com/yupi/yupao/service/UserTeamService.java new file mode 100644 index 0000000..8d9d84a --- /dev/null +++ b/src/main/java/com/yupi/yupao/service/UserTeamService.java @@ -0,0 +1,12 @@ +package com.yupi.yupao.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.yupi.yupao.model.domain.UserTeam; + +/** + * 用户队伍服务 + * + */ +public interface UserTeamService extends IService { + +} diff --git a/src/main/java/com/yupi/yupao/service/impl/TeamServiceImpl.java b/src/main/java/com/yupi/yupao/service/impl/TeamServiceImpl.java new file mode 100644 index 0000000..e10493f --- /dev/null +++ b/src/main/java/com/yupi/yupao/service/impl/TeamServiceImpl.java @@ -0,0 +1,408 @@ +package com.yupi.yupao.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yupi.yupao.common.ErrorCode; +import com.yupi.yupao.exception.BusinessException; +import com.yupi.yupao.model.domain.User; +import com.yupi.yupao.model.domain.UserTeam; +import com.yupi.yupao.model.dto.TeamQuery; +import com.yupi.yupao.model.enums.TeamStatusEnum; +import com.yupi.yupao.model.request.TeamJoinRequest; +import com.yupi.yupao.model.request.TeamQuitRequest; +import com.yupi.yupao.model.request.TeamUpdateRequest; +import com.yupi.yupao.model.vo.TeamUserVO; +import com.yupi.yupao.model.vo.UserVO; +import com.yupi.yupao.service.TeamService; +import com.yupi.yupao.model.domain.Team; +import com.yupi.yupao.mapper.TeamMapper; +import com.yupi.yupao.service.UserService; +import com.yupi.yupao.service.UserTeamService; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.CalendarUtils; +import org.apache.commons.lang3.time.DateUtils; +import org.apache.poi.ss.formula.functions.T; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.beans.BeanUtils; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +/** + * 队伍服务实现类 + * + */ +@Service +public class TeamServiceImpl extends ServiceImpl + implements TeamService { + + @Resource + private UserTeamService userTeamService; + + @Resource + private UserService userService; + + @Resource + private RedissonClient redissonClient; + + @Override + @Transactional(rollbackFor = Exception.class) + public long addTeam(Team team, User loginUser) { + // 1. 请求参数是否为空? + if (team == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + // 2. 是否登录,未登录不允许创建 + if (loginUser == null) { + throw new BusinessException(ErrorCode.NOT_LOGIN); + } + final long userId = loginUser.getId(); + // 3. 校验信息 + // 1. 队伍人数 > 1 且 <= 20 + int maxNum = Optional.ofNullable(team.getMaxNum()).orElse(0); + if (maxNum < 1 || maxNum > 20) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍人数不满足要求"); + } + // 2. 队伍标题 <= 20 + String name = team.getName(); + if (StringUtils.isBlank(name) || name.length() > 20) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍标题不满足要求"); + } + // 3. 描述 <= 512 + String description = team.getDescription(); + if (StringUtils.isNotBlank(description) && description.length() > 512) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍描述过长"); + } + // 4. status 是否公开(int)不传默认为 0(公开) + int status = Optional.ofNullable(team.getStatus()).orElse(0); + TeamStatusEnum statusEnum = TeamStatusEnum.getEnumByValue(status); + if (statusEnum == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍状态不满足要求"); + } + // 5. 如果 status 是加密状态,一定要有密码,且密码 <= 32 + String password = team.getPassword(); + if (TeamStatusEnum.SECRET.equals(statusEnum)) { + if (StringUtils.isBlank(password) || password.length() > 32) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码设置不正确"); + } + } + // 6. 超时时间 > 当前时间 + Date expireTime = team.getExpireTime(); + if (new Date().after(expireTime)) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "超时时间 > 当前时间"); + } + // 7. 校验用户最多创建 5 个队伍 + // todo 有 bug,可能同时创建 100 个队伍 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("userId", userId); + long hasTeamNum = this.count(queryWrapper); + if (hasTeamNum >= 5) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户最多创建 5 个队伍"); + } + // 8. 插入队伍信息到队伍表 + team.setId(null); + team.setUserId(userId); + boolean result = this.save(team); + Long teamId = team.getId(); + if (!result || teamId == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "创建队伍失败"); + } + // 9. 插入用户 => 队伍关系到关系表 + UserTeam userTeam = new UserTeam(); + userTeam.setUserId(userId); + userTeam.setTeamId(teamId); + userTeam.setJoinTime(new Date()); + result = userTeamService.save(userTeam); + if (!result) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "创建队伍失败"); + } + return teamId; + } + + @Override + public List listTeams(TeamQuery teamQuery, boolean isAdmin) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + // 组合查询条件 + if (teamQuery != null) { + Long id = teamQuery.getId(); + if (id != null && id > 0) { + queryWrapper.eq("id", id); + } + List idList = teamQuery.getIdList(); + if (CollectionUtils.isNotEmpty(idList)) { + queryWrapper.in("id", idList); + } + String searchText = teamQuery.getSearchText(); + if (StringUtils.isNotBlank(searchText)) { + queryWrapper.and(qw -> qw.like("name", searchText).or().like("description", searchText)); + } + String name = teamQuery.getName(); + if (StringUtils.isNotBlank(name)) { + queryWrapper.like("name", name); + } + String description = teamQuery.getDescription(); + if (StringUtils.isNotBlank(description)) { + queryWrapper.like("description", description); + } + Integer maxNum = teamQuery.getMaxNum(); + // 查询最大人数相等的 + if (maxNum != null && maxNum > 0) { + queryWrapper.eq("maxNum", maxNum); + } + Long userId = teamQuery.getUserId(); + // 根据创建人来查询 + if (userId != null && userId > 0) { + queryWrapper.eq("userId", userId); + } + // 根据状态来查询 + Integer status = teamQuery.getStatus(); + TeamStatusEnum statusEnum = TeamStatusEnum.getEnumByValue(status); + if (statusEnum == null) { + statusEnum = TeamStatusEnum.PUBLIC; + } + if (!isAdmin && statusEnum.equals(TeamStatusEnum.PRIVATE)) { + throw new BusinessException(ErrorCode.NO_AUTH); + } + queryWrapper.eq("status", statusEnum.getValue()); + } + // 不展示已过期的队伍 + // expireTime is null or expireTime > now() + queryWrapper.and(qw -> qw.gt("expireTime", new Date()).or().isNull("expireTime")); + List teamList = this.list(queryWrapper); + if (CollectionUtils.isEmpty(teamList)) { + return new ArrayList<>(); + } + List teamUserVOList = new ArrayList<>(); + // 关联查询创建人的用户信息 + for (Team team : teamList) { + Long userId = team.getUserId(); + if (userId == null) { + continue; + } + User user = userService.getById(userId); + TeamUserVO teamUserVO = new TeamUserVO(); + BeanUtils.copyProperties(team, teamUserVO); + // 脱敏用户信息 + if (user != null) { + UserVO userVO = new UserVO(); + BeanUtils.copyProperties(user, userVO); + teamUserVO.setCreateUser(userVO); + } + teamUserVOList.add(teamUserVO); + } + return teamUserVOList; + } + + @Override + public boolean updateTeam(TeamUpdateRequest teamUpdateRequest, User loginUser) { + if (teamUpdateRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Long id = teamUpdateRequest.getId(); + if (id == null || id <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Team oldTeam = this.getById(id); + if (oldTeam == null) { + throw new BusinessException(ErrorCode.NULL_ERROR, "队伍不存在"); + } + // 只有管理员或者队伍的创建者可以修改 + if (oldTeam.getUserId() != loginUser.getId() && !userService.isAdmin(loginUser)) { + throw new BusinessException(ErrorCode.NO_AUTH); + } + TeamStatusEnum statusEnum = TeamStatusEnum.getEnumByValue(teamUpdateRequest.getStatus()); + if (statusEnum.equals(TeamStatusEnum.SECRET)) { + if (StringUtils.isBlank(teamUpdateRequest.getPassword())) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "加密房间必须要设置密码"); + } + } + Team updateTeam = new Team(); + BeanUtils.copyProperties(teamUpdateRequest, updateTeam); + return this.updateById(updateTeam); + } + + @Override + public boolean joinTeam(TeamJoinRequest teamJoinRequest, User loginUser) { + if (teamJoinRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Long teamId = teamJoinRequest.getTeamId(); + Team team = getTeamById(teamId); + Date expireTime = team.getExpireTime(); + if (expireTime != null && expireTime.before(new Date())) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍已过期"); + } + Integer status = team.getStatus(); + TeamStatusEnum teamStatusEnum = TeamStatusEnum.getEnumByValue(status); + if (TeamStatusEnum.PRIVATE.equals(teamStatusEnum)) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "禁止加入私有队伍"); + } + String password = teamJoinRequest.getPassword(); + if (TeamStatusEnum.SECRET.equals(teamStatusEnum)) { + if (StringUtils.isBlank(password) || !password.equals(team.getPassword())) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误"); + } + } + // 该用户已加入的队伍数量 + long userId = loginUser.getId(); + // 只有一个线程能获取到锁 + RLock lock = redissonClient.getLock("yupao:join_team"); + try { + // 抢到锁并执行 + while (true) { + if (lock.tryLock(0, -1, TimeUnit.MILLISECONDS)) { + System.out.println("getLock: " + Thread.currentThread().getId()); + QueryWrapper userTeamQueryWrapper = new QueryWrapper<>(); + userTeamQueryWrapper.eq("userId", userId); + long hasJoinNum = userTeamService.count(userTeamQueryWrapper); + if (hasJoinNum > 5) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "最多创建和加入 5 个队伍"); + } + // 不能重复加入已加入的队伍 + userTeamQueryWrapper = new QueryWrapper<>(); + userTeamQueryWrapper.eq("userId", userId); + userTeamQueryWrapper.eq("teamId", teamId); + long hasUserJoinTeam = userTeamService.count(userTeamQueryWrapper); + if (hasUserJoinTeam > 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户已加入该队伍"); + } + // 已加入队伍的人数 + long teamHasJoinNum = this.countTeamUserByTeamId(teamId); + if (teamHasJoinNum >= team.getMaxNum()) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍已满"); + } + // 修改队伍信息 + UserTeam userTeam = new UserTeam(); + userTeam.setUserId(userId); + userTeam.setTeamId(teamId); + userTeam.setJoinTime(new Date()); + return userTeamService.save(userTeam); + } + } + } catch (InterruptedException e) { + log.error("doCacheRecommendUser error", e); + return false; + } finally { + // 只能释放自己的锁 + if (lock.isHeldByCurrentThread()) { + System.out.println("unLock: " + Thread.currentThread().getId()); + lock.unlock(); + } + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean quitTeam(TeamQuitRequest teamQuitRequest, User loginUser) { + if (teamQuitRequest == null) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Long teamId = teamQuitRequest.getTeamId(); + Team team = getTeamById(teamId); + long userId = loginUser.getId(); + UserTeam queryUserTeam = new UserTeam(); + queryUserTeam.setTeamId(teamId); + queryUserTeam.setUserId(userId); + QueryWrapper queryWrapper = new QueryWrapper<>(queryUserTeam); + long count = userTeamService.count(queryWrapper); + if (count == 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "未加入队伍"); + } + long teamHasJoinNum = this.countTeamUserByTeamId(teamId); + // 队伍只剩一人,解散 + if (teamHasJoinNum == 1) { + // 删除队伍 + this.removeById(teamId); + } else { + // 队伍还剩至少两人 + // 是队长 + if (team.getUserId() == userId) { + // 把队伍转移给最早加入的用户 + // 1. 查询已加入队伍的所有用户和加入时间 + QueryWrapper userTeamQueryWrapper = new QueryWrapper<>(); + userTeamQueryWrapper.eq("teamId", teamId); + userTeamQueryWrapper.last("order by id asc limit 2"); + List userTeamList = userTeamService.list(userTeamQueryWrapper); + if (CollectionUtils.isEmpty(userTeamList) || userTeamList.size() <= 1) { + throw new BusinessException(ErrorCode.SYSTEM_ERROR); + } + UserTeam nextUserTeam = userTeamList.get(1); + Long nextTeamLeaderId = nextUserTeam.getUserId(); + // 更新当前队伍的队长 + Team updateTeam = new Team(); + updateTeam.setId(teamId); + updateTeam.setUserId(nextTeamLeaderId); + boolean result = this.updateById(updateTeam); + if (!result) { + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "更新队伍队长失败"); + } + } + } + // 移除关系 + return userTeamService.remove(queryWrapper); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean deleteTeam(long id, User loginUser) { + // 校验队伍是否存在 + Team team = getTeamById(id); + long teamId = team.getId(); + // 校验你是不是队伍的队长 + if (team.getUserId() != loginUser.getId()) { + throw new BusinessException(ErrorCode.NO_AUTH, "无访问权限"); + } + // 移除所有加入队伍的关联信息 + QueryWrapper userTeamQueryWrapper = new QueryWrapper<>(); + userTeamQueryWrapper.eq("teamId", teamId); + boolean result = userTeamService.remove(userTeamQueryWrapper); + if (!result) { + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "删除队伍关联信息失败"); + } + // 删除队伍 + return this.removeById(teamId); + } + + /** + * 根据 id 获取队伍信息 + * + * @param teamId + * @return + */ + private Team getTeamById(Long teamId) { + if (teamId == null || teamId <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + Team team = this.getById(teamId); + if (team == null) { + throw new BusinessException(ErrorCode.NULL_ERROR, "队伍不存在"); + } + return team; + } + + /** + * 获取某队伍当前人数 + * + * @param teamId + * @return + */ + private long countTeamUserByTeamId(long teamId) { + QueryWrapper userTeamQueryWrapper = new QueryWrapper<>(); + userTeamQueryWrapper.eq("teamId", teamId); + return userTeamService.count(userTeamQueryWrapper); + } +} + + + + diff --git a/src/main/java/com/yupi/yupao/service/impl/UserServiceImpl.java b/src/main/java/com/yupi/yupao/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..82cf00b --- /dev/null +++ b/src/main/java/com/yupi/yupao/service/impl/UserServiceImpl.java @@ -0,0 +1,340 @@ +package com.yupi.yupao.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.reflect.TypeToken; +import com.yupi.yupao.common.ErrorCode; +import com.yupi.yupao.constant.UserConstant; +import com.yupi.yupao.exception.BusinessException; +import com.yupi.yupao.model.domain.User; +import com.yupi.yupao.model.vo.UserVO; +import com.yupi.yupao.service.UserService; +import com.yupi.yupao.mapper.UserMapper; +import com.yupi.yupao.utils.AlgorithmUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.math3.util.Pair; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import org.springframework.util.DigestUtils; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.yupi.yupao.constant.UserConstant.USER_LOGIN_STATE; + +/** + * 用户服务实现类 + * + */ +@Service +@Slf4j +public class UserServiceImpl extends ServiceImpl + implements UserService { + + @Resource + private UserMapper userMapper; + + /** + * 盐值,混淆密码 + */ + private static final String SALT = "yupi"; + + @Override + public long userRegister(String userAccount, String userPassword, String checkPassword, String planetCode) { + // 1. 校验 + if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword, planetCode)) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空"); + } + if (userAccount.length() < 4) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号过短"); + } + if (userPassword.length() < 8 || checkPassword.length() < 8) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过短"); + } + if (planetCode.length() > 5) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "编号过长"); + } + // 账户不能包含特殊字符 + String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]"; + Matcher matcher = Pattern.compile(validPattern).matcher(userAccount); + if (matcher.find()) { + return -1; + } + // 密码和校验密码相同 + if (!userPassword.equals(checkPassword)) { + return -1; + } + // 账户不能重复 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("userAccount", userAccount); + long count = userMapper.selectCount(queryWrapper); + if (count > 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复"); + } + // 编号不能重复 + queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("planetCode", planetCode); + count = userMapper.selectCount(queryWrapper); + if (count > 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "编号重复"); + } + // 2. 加密 + String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes()); + // 3. 插入数据 + User user = new User(); + user.setUserAccount(userAccount); + user.setUserPassword(encryptPassword); + user.setPlanetCode(planetCode); + boolean saveResult = this.save(user); + if (!saveResult) { + return -1; + } + return user.getId(); + } + + + @Override + public User userLogin(String userAccount, String userPassword, HttpServletRequest request) { + // 1. 校验 + if (StringUtils.isAnyBlank(userAccount, userPassword)) { + return null; + } + if (userAccount.length() < 4) { + return null; + } + if (userPassword.length() < 8) { + return null; + } + // 账户不能包含特殊字符 + String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]"; + Matcher matcher = Pattern.compile(validPattern).matcher(userAccount); + if (matcher.find()) { + return null; + } + // 2. 加密 + String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes()); + // 查询用户是否存在 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("userAccount", userAccount); + queryWrapper.eq("userPassword", encryptPassword); + User user = userMapper.selectOne(queryWrapper); + // 用户不存在 + if (user == null) { + log.info("user login failed, userAccount cannot match userPassword"); + return null; + } + // 3. 用户脱敏 + User safetyUser = getSafetyUser(user); + // 4. 记录用户的登录态 + request.getSession().setAttribute(USER_LOGIN_STATE, safetyUser); + return safetyUser; + } + + /** + * 用户脱敏 + * + * @param originUser + * @return + */ + @Override + public User getSafetyUser(User originUser) { + if (originUser == null) { + return null; + } + User safetyUser = new User(); + safetyUser.setId(originUser.getId()); + safetyUser.setUsername(originUser.getUsername()); + safetyUser.setUserAccount(originUser.getUserAccount()); + safetyUser.setAvatarUrl(originUser.getAvatarUrl()); + safetyUser.setGender(originUser.getGender()); + safetyUser.setPhone(originUser.getPhone()); + safetyUser.setEmail(originUser.getEmail()); + safetyUser.setPlanetCode(originUser.getPlanetCode()); + safetyUser.setUserRole(originUser.getUserRole()); + safetyUser.setUserStatus(originUser.getUserStatus()); + safetyUser.setCreateTime(originUser.getCreateTime()); + safetyUser.setTags(originUser.getTags()); + return safetyUser; + } + + /** + * 用户注销 + * + * @param request + */ + @Override + public int userLogout(HttpServletRequest request) { + // 移除登录态 + request.getSession().removeAttribute(USER_LOGIN_STATE); + return 1; + } + + /** + * 根据标签搜索用户(内存过滤) + * + * @param tagNameList 用户要拥有的标签 + * @return + */ + @Override + public List searchUsersByTags(List tagNameList) { + if (CollectionUtils.isEmpty(tagNameList)) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + // 1. 先查询所有用户 + QueryWrapper queryWrapper = new QueryWrapper<>(); + List userList = userMapper.selectList(queryWrapper); + Gson gson = new Gson(); + // 2. 在内存中判断是否包含要求的标签 + return userList.stream().filter(user -> { + String tagsStr = user.getTags(); + Set tempTagNameSet = gson.fromJson(tagsStr, new TypeToken>() { + }.getType()); + tempTagNameSet = Optional.ofNullable(tempTagNameSet).orElse(new HashSet<>()); + for (String tagName : tagNameList) { + if (!tempTagNameSet.contains(tagName)) { + return false; + } + } + return true; + }).map(this::getSafetyUser).collect(Collectors.toList()); + } + + @Override + public int updateUser(User user, User loginUser) { + long userId = user.getId(); + if (userId <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + // todo 补充校验,如果用户没有传任何要更新的值,就直接报错,不用执行 update 语句 + // 如果是管理员,允许更新任意用户 + // 如果不是管理员,只允许更新当前(自己的)信息 + if (!isAdmin(loginUser) && userId != loginUser.getId()) { + throw new BusinessException(ErrorCode.NO_AUTH); + } + User oldUser = userMapper.selectById(userId); + if (oldUser == null) { + throw new BusinessException(ErrorCode.NULL_ERROR); + } + return userMapper.updateById(user); + } + + @Override + public User getLoginUser(HttpServletRequest request) { + if (request == null) { + return null; + } + Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE); + if (userObj == null) { + throw new BusinessException(ErrorCode.NO_AUTH); + } + return (User) userObj; + } + + /** + * 是否为管理员 + * + * @param request + * @return + */ + @Override + public boolean isAdmin(HttpServletRequest request) { + // 仅管理员可查询 + Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE); + User user = (User) userObj; + return user != null && user.getUserRole() == UserConstant.ADMIN_ROLE; + } + + /** + * 是否为管理员 + * + * @param loginUser + * @return + */ + @Override + public boolean isAdmin(User loginUser) { + return loginUser != null && loginUser.getUserRole() == UserConstant.ADMIN_ROLE; + } + + @Override + public List matchUsers(long num, User loginUser) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.select("id", "tags"); + queryWrapper.isNotNull("tags"); + List userList = this.list(queryWrapper); + String tags = loginUser.getTags(); + Gson gson = new Gson(); + List tagList = gson.fromJson(tags, new TypeToken>() { + }.getType()); + // 用户列表的下标 => 相似度 + List> list = new ArrayList<>(); + // 依次计算所有用户和当前用户的相似度 + for (int i = 0; i < userList.size(); i++) { + User user = userList.get(i); + String userTags = user.getTags(); + // 无标签或者为当前用户自己 + if (StringUtils.isBlank(userTags) || user.getId() == loginUser.getId()) { + continue; + } + List userTagList = gson.fromJson(userTags, new TypeToken>() { + }.getType()); + // 计算分数 + long distance = AlgorithmUtils.minDistance(tagList, userTagList); + list.add(new Pair<>(user, distance)); + } + // 按编辑距离由小到大排序 + List> topUserPairList = list.stream() + .sorted((a, b) -> (int) (a.getValue() - b.getValue())) + .limit(num) + .collect(Collectors.toList()); + // 原本顺序的 userId 列表 + List userIdList = topUserPairList.stream().map(pair -> pair.getKey().getId()).collect(Collectors.toList()); + QueryWrapper userQueryWrapper = new QueryWrapper<>(); + userQueryWrapper.in("id", userIdList); + // 1, 3, 2 + // User1、User2、User3 + // 1 => User1, 2 => User2, 3 => User3 + Map> userIdUserListMap = this.list(userQueryWrapper) + .stream() + .map(user -> getSafetyUser(user)) + .collect(Collectors.groupingBy(User::getId)); + List finalUserList = new ArrayList<>(); + for (Long userId : userIdList) { + finalUserList.add(userIdUserListMap.get(userId).get(0)); + } + return finalUserList; + } + + /** + * 根据标签搜索用户(SQL 查询版) + * + * @param tagNameList 用户要拥有的标签 + * @return + */ + @Deprecated + private List searchUsersByTagsBySQL(List tagNameList) { + if (CollectionUtils.isEmpty(tagNameList)) { + throw new BusinessException(ErrorCode.PARAMS_ERROR); + } + QueryWrapper queryWrapper = new QueryWrapper<>(); + // 拼接 and 查询 + // like '%Java%' and like '%Python%' + for (String tagName : tagNameList) { + queryWrapper = queryWrapper.like("tags", tagName); + } + List userList = userMapper.selectList(queryWrapper); + return userList.stream().map(this::getSafetyUser).collect(Collectors.toList()); + } + +} + + + + diff --git a/src/main/java/com/yupi/yupao/service/impl/UserTeamServiceImpl.java b/src/main/java/com/yupi/yupao/service/impl/UserTeamServiceImpl.java new file mode 100644 index 0000000..3513ee0 --- /dev/null +++ b/src/main/java/com/yupi/yupao/service/impl/UserTeamServiceImpl.java @@ -0,0 +1,21 @@ +package com.yupi.yupao.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.yupi.yupao.service.UserTeamService; +import com.yupi.yupao.model.domain.UserTeam; +import com.yupi.yupao.mapper.UserTeamMapper; +import org.springframework.stereotype.Service; + +/** + * 用户队伍服务实现类 + * + */ +@Service +public class UserTeamServiceImpl extends ServiceImpl + implements UserTeamService { + +} + + + + diff --git a/src/main/java/com/yupi/yupao/utils/AlgorithmUtils.java b/src/main/java/com/yupi/yupao/utils/AlgorithmUtils.java new file mode 100644 index 0000000..cd3a2a3 --- /dev/null +++ b/src/main/java/com/yupi/yupao/utils/AlgorithmUtils.java @@ -0,0 +1,91 @@ +package com.yupi.yupao.utils; + +import java.util.List; +import java.util.Objects; + +/** + * 算法工具类 + * + */ +public class AlgorithmUtils { + + /** + * 编辑距离算法(用于计算最相似的两组标签) + * 原理:https://blog.csdn.net/DBC_121/article/details/104198838 + * + * @param tagList1 + * @param tagList2 + * @return + */ + public static int minDistance(List tagList1, List tagList2) { + int n = tagList1.size(); + int m = tagList2.size(); + + if (n * m == 0) { + return n + m; + } + + int[][] d = new int[n + 1][m + 1]; + for (int i = 0; i < n + 1; i++) { + d[i][0] = i; + } + + for (int j = 0; j < m + 1; j++) { + d[0][j] = j; + } + + for (int i = 1; i < n + 1; i++) { + for (int j = 1; j < m + 1; j++) { + int left = d[i - 1][j] + 1; + int down = d[i][j - 1] + 1; + int left_down = d[i - 1][j - 1]; + if (!Objects.equals(tagList1.get(i - 1), tagList2.get(j - 1))) { + left_down += 1; + } + d[i][j] = Math.min(left, Math.min(down, left_down)); + } + } + return d[n][m]; + } + + + + /** + * 编辑距离算法(用于计算最相似的两个字符串) + * 原理:https://blog.csdn.net/DBC_121/article/details/104198838 + * + * @param word1 + * @param word2 + * @return + */ + public static int minDistance(String word1, String word2) { + int n = word1.length(); + int m = word2.length(); + + if (n * m == 0) { + return n + m; + } + + int[][] d = new int[n + 1][m + 1]; + for (int i = 0; i < n + 1; i++) { + d[i][0] = i; + } + + for (int j = 0; j < m + 1; j++) { + d[0][j] = j; + } + + for (int i = 1; i < n + 1; i++) { + for (int j = 1; j < m + 1; j++) { + int left = d[i - 1][j] + 1; + int down = d[i][j - 1] + 1; + int left_down = d[i - 1][j - 1]; + if (word1.charAt(i - 1) != word2.charAt(j - 1)) { + left_down += 1; + } + d[i][j] = Math.min(left, Math.min(down, left_down)); + } + } + return d[n][m]; + } +} diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 0000000..28afa3f --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,15 @@ +# 线上配置文件 + +spring: + # DataSource Config + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/yupao + username: root + password: 123456 + # session 失效时间 + session: + timeout: 86400 +server: + address: 0.0.0.0 + diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..6a29f3f --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,41 @@ +# 公共配置文件 + +spring: + profiles: + active: dev + application: + name: yupao-backend + # DataSource Config + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/yupao?serverTimezone=Asia/Shanghai + username: root + password: 123456 + # session 失效时间(分钟) + session: + timeout: 86400 + store-type: redis + mvc: + pathmatch: + matching-strategy: ANT_PATH_MATCHER + # redis 配置 + redis: + port: 6379 + host: localhost + database: 1 +server: + port: 8080 + servlet: + context-path: /api + session: + cookie: + domain: localhost +mybatis-plus: + configuration: + map-underscore-to-camel-case: false +# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + global-config: + db-config: + logic-delete-field: isDelete # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2) + logic-delete-value: 1 # 逻辑已删除值(默认为 1) + logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) diff --git a/src/main/resources/mapper/TeamMapper.xml b/src/main/resources/mapper/TeamMapper.xml new file mode 100644 index 0000000..5173b12 --- /dev/null +++ b/src/main/resources/mapper/TeamMapper.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + id,name,description, + maxNum,expireTime,userId, + status,password,createTime, + updateTime,isDelete + + diff --git a/src/main/resources/mapper/UserMapper.xml b/src/main/resources/mapper/UserMapper.xml new file mode 100644 index 0000000..24f54e7 --- /dev/null +++ b/src/main/resources/mapper/UserMapper.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + id,username,userAccount, + avatarUrl,gender,userPassword, + phone,email,userStatus, + createTime,updateTime,isDelete, + userRole,planetCode + + diff --git a/src/main/resources/mapper/UserTeamMapper.xml b/src/main/resources/mapper/UserTeamMapper.xml new file mode 100644 index 0000000..273f0e5 --- /dev/null +++ b/src/main/resources/mapper/UserTeamMapper.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + id,userId,teamId, + joinTime,createTime,updateTime, + isDelete + + diff --git a/src/test/java/com/yupi/yupao/MyApplicationTest.java b/src/test/java/com/yupi/yupao/MyApplicationTest.java new file mode 100644 index 0000000..2c20b16 --- /dev/null +++ b/src/test/java/com/yupi/yupao/MyApplicationTest.java @@ -0,0 +1,27 @@ +package com.yupi.yupao; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.util.DigestUtils; + +import java.security.NoSuchAlgorithmException; + +/** + * 测试类 + * + */ +@SpringBootTest +class MyApplicationTest { + + @Test + void testDigest() throws NoSuchAlgorithmException { + String newPassword = DigestUtils.md5DigestAsHex(("abcd" + "mypassword").getBytes()); + System.out.println(newPassword); + } + + @Test + void contextLoads() { + + } + +} diff --git a/src/test/java/com/yupi/yupao/service/AlgorithmUtilsTest.java b/src/test/java/com/yupi/yupao/service/AlgorithmUtilsTest.java new file mode 100644 index 0000000..89ab244 --- /dev/null +++ b/src/test/java/com/yupi/yupao/service/AlgorithmUtilsTest.java @@ -0,0 +1,42 @@ +package com.yupi.yupao.service; + +import com.yupi.yupao.utils.AlgorithmUtils; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +/** + * 算法工具类测试 + * + */ +public class AlgorithmUtilsTest { + + + @Test + void test() { + String str1 = "皮是狗"; + String str2 = "皮不是狗"; + String str3 = "负责人"; + // 1 + int score1 = AlgorithmUtils.minDistance(str1, str2); + // 3 + int score2 = AlgorithmUtils.minDistance(str1, str3); + System.out.println(score1); + System.out.println(score2); + } + + @Test + void testCompareTags() { + List tagList1 = Arrays.asList("Java", "大一", "男"); + List tagList2 = Arrays.asList("Java", "大一", "女"); + List tagList3 = Arrays.asList("Python", "大二", "女"); + // 1 + int score1 = AlgorithmUtils.minDistance(tagList1, tagList2); + // 3 + int score2 = AlgorithmUtils.minDistance(tagList1, tagList3); + System.out.println(score1); + System.out.println(score2); + } + +} diff --git a/src/test/java/com/yupi/yupao/service/InsertUsersTest.java b/src/test/java/com/yupi/yupao/service/InsertUsersTest.java new file mode 100644 index 0000000..aff4c70 --- /dev/null +++ b/src/test/java/com/yupi/yupao/service/InsertUsersTest.java @@ -0,0 +1,99 @@ +package com.yupi.yupao.service; + +import com.yupi.yupao.model.domain.User; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.util.StopWatch; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.*; + +/** + * 导入用户测试 + * + */ +@SpringBootTest +public class InsertUsersTest { + + @Resource + private UserService userService; + + private ExecutorService executorService = new ThreadPoolExecutor(40, 1000, 10000, TimeUnit.MINUTES, new ArrayBlockingQueue<>(10000)); + + /** + * 批量插入用户 + */ + @Test + public void doInsertUsers() { + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + final int INSERT_NUM = 100000; + List userList = new ArrayList<>(); + for (int i = 0; i < INSERT_NUM; i++) { + User user = new User(); + user.setUsername("原_创"); + user.setUserAccount("fakeyupi"); + user.setAvatarUrl("https://636f-codenav-8grj8px727565176-1256524210.tcb.qcloud.la/img/logo.png"); + user.setGender(0); + user.setUserPassword("12345678"); + user.setPhone("123"); + user.setEmail("123@qq.com"); + user.setTags("[]"); + user.setUserStatus(0); + user.setUserRole(0); + user.setPlanetCode("11111111"); + userList.add(user); + } + // 20 秒 10 万条 + userService.saveBatch(userList, 10000); + stopWatch.stop(); + System.out.println(stopWatch.getTotalTimeMillis()); + } + + /** + * 并发批量插入用户 + */ + @Test + public void doConcurrencyInsertUsers() { + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + // 分十组 + int batchSize = 5000; + int j = 0; + List> futureList = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + List userList = new ArrayList<>(); + while (true) { + j++; + User user = new User(); + user.setUsername("假鱼皮"); + user.setUserAccount("fakeyupi"); + user.setAvatarUrl("https://636f-codenav-8grj8px727565176-1256524210.tcb.qcloud.la/img/logo.png"); + user.setGender(0); + user.setUserPassword("12345678"); + user.setPhone("123"); + user.setEmail("123@qq.com"); + user.setTags("[]"); + user.setUserStatus(0); + user.setUserRole(0); + user.setPlanetCode("11111111"); + userList.add(user); + if (j % batchSize == 0) { + break; + } + } + // 异步执行 + CompletableFuture future = CompletableFuture.runAsync(() -> { + System.out.println("threadName: " + Thread.currentThread().getName()); + userService.saveBatch(userList, batchSize); + }, executorService); + futureList.add(future); + } + CompletableFuture.allOf(futureList.toArray(new CompletableFuture[]{})).join(); + // 20 秒 10 万条 + stopWatch.stop(); + System.out.println(stopWatch.getTotalTimeMillis()); + } +} diff --git a/src/test/java/com/yupi/yupao/service/RedisTest.java b/src/test/java/com/yupi/yupao/service/RedisTest.java new file mode 100644 index 0000000..7691bbc --- /dev/null +++ b/src/test/java/com/yupi/yupao/service/RedisTest.java @@ -0,0 +1,44 @@ +package com.yupi.yupao.service; + +import com.yupi.yupao.model.domain.User; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; + +import javax.annotation.Resource; + +/** + * Redis 测试 + * + */ +@SpringBootTest +public class RedisTest { + + @Resource + private RedisTemplate redisTemplate; + + @Test + void test() { + ValueOperations valueOperations = redisTemplate.opsForValue(); + // 增 + valueOperations.set("yupiString", "dog"); + valueOperations.set("yupiInt", 1); + valueOperations.set("yupiDouble", 2.0); + User user = new User(); + user.setId(1L); + user.setUsername("yupi"); + valueOperations.set("yupiUser", user); + // 查 + Object yupi = valueOperations.get("yupiString"); + Assertions.assertTrue("dog".equals((String) yupi)); + yupi = valueOperations.get("yupiInt"); + Assertions.assertTrue(1 == (Integer) yupi); + yupi = valueOperations.get("yupiDouble"); + Assertions.assertTrue(2.0 == (Double) yupi); + System.out.println(valueOperations.get("yupiUser")); + valueOperations.set("yupiString", "dog"); + redisTemplate.delete("yupiString"); + } +} diff --git a/src/test/java/com/yupi/yupao/service/RedissonTest.java b/src/test/java/com/yupi/yupao/service/RedissonTest.java new file mode 100644 index 0000000..c26d1b2 --- /dev/null +++ b/src/test/java/com/yupi/yupao/service/RedissonTest.java @@ -0,0 +1,76 @@ +package com.yupi.yupao.service; + +import org.junit.jupiter.api.Test; +import org.redisson.api.RList; +import org.redisson.api.RLock; +import org.redisson.api.RMap; +import org.redisson.api.RedissonClient; +import org.springframework.boot.test.context.SpringBootTest; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * Redisson 测试 + * + */ +@SpringBootTest +public class RedissonTest { + + @Resource + private RedissonClient redissonClient; + + @Test + void test() { + // list,数据存在本地 JVM 内存中 + List list = new ArrayList<>(); + list.add("yupi"); + System.out.println("list:" + list.get(0)); + + list.remove(0); + + // 数据存在 redis 的内存中 + RList rList = redissonClient.getList("test-list"); + rList.add("yupi"); + System.out.println("rlist:" + rList.get(0)); + rList.remove(0); + + // map + Map map = new HashMap<>(); + map.put("yupi", 10); + map.get("yupi"); + + RMap map1 = redissonClient.getMap("test-map"); + + // set + + // stack + + + } + + + @Test + void testWatchDog() { + RLock lock = redissonClient.getLock("yupao:precachejob:docache:lock"); + try { + // 只有一个线程能获取到锁 + if (lock.tryLock(0, -1, TimeUnit.MILLISECONDS)) { + Thread.sleep(300000); + System.out.println("getLock: " + Thread.currentThread().getId()); + } + } catch (InterruptedException e) { + System.out.println(e.getMessage()); + } finally { + // 只能释放自己的锁 + if (lock.isHeldByCurrentThread()) { + System.out.println("unLock: " + Thread.currentThread().getId()); + lock.unlock(); + } + } + } +} diff --git a/src/test/java/com/yupi/yupao/service/UserServiceTest.java b/src/test/java/com/yupi/yupao/service/UserServiceTest.java new file mode 100644 index 0000000..b04f348 --- /dev/null +++ b/src/test/java/com/yupi/yupao/service/UserServiceTest.java @@ -0,0 +1,102 @@ +package com.yupi.yupao.service; + +import com.yupi.yupao.model.domain.User; +import org.junit.Assert; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import javax.annotation.Resource; +import java.util.Arrays; +import java.util.List; + +/** + * 用户服务测试 + * + */ +@SpringBootTest +public class UserServiceTest { + + @Resource + private UserService userService; + + @Test + public void testAddUser() { + User user = new User(); + user.setUsername("本项目_所属 \n"); + user.setUserAccount("123"); + user.setAvatarUrl("https://636f-codenav-8grj8px727565176-1256524210.tcb.qcloud.la/img/logo.png"); + user.setGender(0); + user.setUserPassword("xxx"); + user.setPhone("123"); + user.setEmail("456"); + boolean result = userService.save(user); + System.out.println(user.getId()); + Assertions.assertTrue(result); + } + + @Test + public void testUpdateUser() { + User user = new User(); + user.setId(1L); + user.setUsername("dogYupi"); + user.setUserAccount("123"); + user.setAvatarUrl("https://636f-codenav-8grj8px727565176-1256524210.tcb.qcloud.la/img/logo.png"); + user.setGender(0); + user.setUserPassword("xxx"); + user.setPhone("123"); + user.setEmail("456"); + boolean result = userService.updateById(user); + Assertions.assertTrue(result); + } + + @Test + public void testDeleteUser() { + boolean result = userService.removeById(1L); + Assertions.assertTrue(result); + } + + @Test + public void testGetUser() { + User user = userService.getById(1L); + Assertions.assertNotNull(user); + } + + @Test + void userRegister() { + String userAccount = "yupi"; + String userPassword = "12345678"; + String checkPassword = "12345678"; + String planetCode = "1"; + long result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode); + Assertions.assertEquals(-1, result); + userAccount = "yu"; + result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode); + Assertions.assertEquals(-1, result); + userAccount = "yupi"; + userPassword = "123456"; + result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode); + Assertions.assertEquals(-1, result); + userAccount = "yu pi"; + userPassword = "12345678"; + result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode); + Assertions.assertEquals(-1, result); + checkPassword = "123456789"; + result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode); + Assertions.assertEquals(-1, result); + userAccount = "dogYupi"; + checkPassword = "12345678"; + result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode); + Assertions.assertEquals(-1, result); + userAccount = "yupi"; + result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode); + Assertions.assertEquals(-1, result); + } + + @Test + public void testSearchUsersByTags() { + List tagNameList = Arrays.asList("java", "python"); + List userList = userService.searchUsersByTags(tagNameList); + Assert.assertNotNull(userList); + } +} \ No newline at end of file