├── .DS_Store ├── .gitignore ├── Dockerfile ├── README.md ├── doc ├── .yuanchuang ├── img.png └── 加入编程导航.jpeg ├── mvnw ├── mvnw.cmd ├── pom.xml ├── sql ├── .author └── create_table.sql ├── src ├── .DS_Store ├── main │ ├── java │ │ └── com │ │ │ └── yupi │ │ │ └── yupao │ │ │ ├── MyApplication.java │ │ │ ├── common │ │ │ ├── BaseResponse.java │ │ │ ├── DeleteRequest.java │ │ │ ├── ErrorCode.java │ │ │ ├── PageRequest.java │ │ │ ├── ResultUtils.java │ │ │ └── 请尊重原创 │ │ │ ├── config │ │ │ ├── MybatisPlusConfig.java │ │ │ ├── RedisTemplateConfig.java │ │ │ ├── RedissonConfig.java │ │ │ ├── SwaggerConfig.java │ │ │ └── WebMvcConfg.java │ │ │ ├── constant │ │ │ └── UserConstant.java │ │ │ ├── controller │ │ │ ├── TeamController.java │ │ │ └── UserController.java │ │ │ ├── exception │ │ │ ├── BusinessException.java │ │ │ └── GlobalExceptionHandler.java │ │ │ ├── job │ │ │ └── PreCacheJob.java │ │ │ ├── mapper │ │ │ ├── TeamMapper.java │ │ │ ├── UserMapper.java │ │ │ └── UserTeamMapper.java │ │ │ ├── model │ │ │ ├── domain │ │ │ │ ├── Team.java │ │ │ │ ├── User.java │ │ │ │ └── UserTeam.java │ │ │ ├── dto │ │ │ │ └── TeamQuery.java │ │ │ ├── enums │ │ │ │ └── TeamStatusEnum.java │ │ │ ├── request │ │ │ │ ├── TeamAddRequest.java │ │ │ │ ├── TeamJoinRequest.java │ │ │ │ ├── TeamQuitRequest.java │ │ │ │ ├── TeamUpdateRequest.java │ │ │ │ ├── UserLoginRequest.java │ │ │ │ └── UserRegisterRequest.java │ │ │ └── vo │ │ │ │ ├── TeamUserVO.java │ │ │ │ └── UserVO.java │ │ │ ├── once │ │ │ └── importuser │ │ │ │ ├── ImportExcel.java │ │ │ │ ├── ImportXingQiuUser.java │ │ │ │ ├── InsertUsers.java │ │ │ │ ├── TableListener.java │ │ │ │ └── XingQiuTableUserInfo.java │ │ │ ├── service │ │ │ ├── TeamService.java │ │ │ ├── UserService.java │ │ │ ├── UserTeamService.java │ │ │ └── impl │ │ │ │ ├── TeamServiceImpl.java │ │ │ │ ├── UserServiceImpl.java │ │ │ │ └── UserTeamServiceImpl.java │ │ │ └── utils │ │ │ └── AlgorithmUtils.java │ └── resources │ │ ├── application-prod.yml │ │ ├── application.yml │ │ ├── banner.txt │ │ ├── mapper │ │ ├── TeamMapper.xml │ │ ├── UserMapper.xml │ │ └── UserTeamMapper.xml │ │ ├── prodExcel.xlsx │ │ ├── testExcel.xlsx │ │ └── 请尊重原创 └── test │ ├── .copyright │ └── java │ └── com │ └── yupi │ └── yupao │ ├── MyApplicationTest.java │ └── service │ ├── AlgorithmUtilsTest.java │ ├── InsertUsersTest.java │ ├── RedisTest.java │ ├── RedissonTest.java │ └── UserServiceTest.java └── 保护知识,尊重原创 /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liyupi/yupao-backend-public/80c3cf6485543aee4876c61806ff2a917b6f9e62/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### @author 程序员鱼皮 ### 2 | ### @from 编程导航知识星球 ### 3 | 4 | HELP.md 5 | target/ 6 | !.mvn/wrapper/maven-wrapper.jar 7 | !**/src/main/**/target/ 8 | !**/src/test/**/target/ 9 | 10 | ### STS ### 11 | .apt_generated 12 | .classpath 13 | .factorypath 14 | .project 15 | .settings 16 | .springBeans 17 | .sts4-cache 18 | 19 | ### IntelliJ IDEA ### 20 | .idea 21 | *.iws 22 | *.iml 23 | *.ipr 24 | 25 | ### NetBeans ### 26 | /nbproject/private/ 27 | /nbbuild/ 28 | /dist/ 29 | /nbdist/ 30 | /.nb-gradle/ 31 | build/ 32 | !**/src/main/**/build/ 33 | !**/src/test/**/build/ 34 | 35 | ### VS Code ### 36 | .vscode/ 37 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Docker 镜像构建 2 | # @author 程序员鱼皮 3 | # @from 编程导航知识星球 4 | FROM maven:3.5-jdk-8-alpine as builder 5 | 6 | # Copy local code to the container image. 7 | WORKDIR /app 8 | COPY pom.xml . 9 | COPY src ./src 10 | 11 | # Build a release artifact. 12 | RUN mvn package -DskipTests 13 | 14 | # Run the web service on container startup. 15 | CMD ["java","-jar","/app/target/yupao-backend-0.0.1-SNAPSHOT.jar","--spring.profiles.active=prod"] 16 | 17 | # [编程学习交流圈](https://www.code-nav.cn/) 快速入门编程不走弯路!30+ 原创学习路线和专栏、500+ 编程学习指南、1000+ 编程精华文章、20T+ 编程资源汇总 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 鱼皮 - 伙伴匹配系统项目 2 | 3 | > 作者:[程序员鱼皮](https://github.com/liyupi) 4 | 5 | 本项目为 [编程导航知识星球](https://yuyuanweb.feishu.cn/wiki/VC1qwmX9diCBK3kidyec74vFnde) 的原创全栈项目,后端代码开源。 6 | 7 | [加入星球](https://yuyuanweb.feishu.cn/wiki/VC1qwmX9diCBK3kidyec74vFnde) 可获得该项目从 0 到 1 的完整视频教程 + 源码 + 笔记 + 答疑 + 简历写法 + 面试题解。 8 | 9 | ![加入编程导航](./doc/加入编程导航.jpeg) 10 | 11 | 12 | 13 | ## 项目简介 14 | 15 | [编程导航知识星球](https://yuyuanweb.feishu.cn/wiki/VC1qwmX9diCBK3kidyec74vFnde) 原创项目,一个帮助大家找到志同道合的伙伴的移动端网站(APP 风格)。包括用户登录注册、更新个人信息、按标签搜索用户、推荐相似用户、组队等功能。 16 | 17 | 主页: 18 | 19 | ![](https://yupi-picture-1256524210.cos.ap-shanghai.myqcloud.com/1/image-20221023120231097.png) 20 | 21 | 22 | 23 | 找伙伴: 24 | 25 | ![](https://yupi-picture-1256524210.cos.ap-shanghai.myqcloud.com/1/image-20221023120338802.png) 26 | 27 | 28 | 29 | 组队功能: 30 | 31 | ![](https://yupi-picture-1256524210.cos.ap-shanghai.myqcloud.com/1/image-20221023120253418.png) 32 | 33 | 34 | 35 | 创建队伍: 36 | 37 | ![](https://yupi-picture-1256524210.cos.ap-shanghai.myqcloud.com/1/image-20221023120311527.png) 38 | 39 | 40 | 41 | 个人信息及修改: 42 | 43 | ![img](https://yupi-picture-1256524210.cos.ap-shanghai.myqcloud.com/1/image-20221023120321632.png) 44 | 45 | 46 | 47 | 这个该项目基本覆盖了企业开发中常见的需求以及对应的解决方案,比如登录注册、批量数据导入、信息检索展示、定时任务、资源抢占等。并且涵盖了分布式、并发编程、锁、事务、缓存、性能优化、幂等性、数据一致性、大数据、算法等后端程序员必须了解的知识与实践。 48 | 49 | 50 | 51 | 从需求分析、技术选型、系统设计、前后端开发再到最后上线,整个项目的制作过程为 **全程直播** !除了学做项目之外,还能学会很多思考问题、对比方案的方式方法,并提升排查问题、解决 Bug 的能力。此外,还能学习到最最最方便的项目上线方式,几分钟上线一个项目真的轻轻松松! 52 | 53 | 54 | 55 | ## 本项目适合的同学 56 | 57 | 以下两个条件满足一个即可: 58 | 59 | 1. 已经学过基本的前端(HTML + CSS + JS 三件套),想学、在学或已学 Vue 移动端开发 60 | 2. 学习过后端开发技术(比如 Java Web) 61 | 62 | 63 | 64 | ## 技术选型 65 | 66 | ### 前端 67 | 68 | - Vue 3 69 | - Vant UI 组件库 70 | - TypeScript 71 | - Vite 脚手架 72 | - Axios 请求库 73 | 74 | 75 | 76 | ### 后端 77 | 78 | - Java SpringBoot 2.7.x 框架 79 | - MySQL 数据库 80 | - MyBatis-Plus 81 | - MyBatis X 自动生成 82 | - Redis 缓存(Spring Data Redis 等多种实现方式) 83 | - Redisson 分布式锁 84 | - Easy Excel 数据导入 85 | - Spring Scheduler 定时任务 86 | - Swagger + Knife4j 接口文档 87 | - Gson:JSON 序列化库 88 | - 相似度匹配算法 89 | 90 | 91 | 92 | ### 部署 93 | 94 | - Serverless 服务 95 | - 云原生容器平台 96 | 97 | 98 | 99 | ## 项目收获 100 | 101 | 1. 全程直播开发,带你了解并巩固做项目的完整流程,能够独立开发及上线项目 102 | 2. 学会前后端企业主流开发技术(如 Vue 3、Spring Boot 等)的应用,提升开发经验 103 | 3. 学习 Java 8 特性、接口文档、网页内容抓取、分布式登录、大数据量导入、并发编程、Redis、缓存及预热、定时任务、分布式锁、幂等性、算法、免备案上线项目等重要知识 104 | 4. 通过多次带大家思考和对比实现方案,帮你开拓思路,学习系统设计的方法和经验 105 | 5. 学到项目开发、调试和优化技巧,比如开发工具使用技巧、组件抽象封装、问题定位、性能优化、内存优化等 106 | 6. 所有 Bug 和问题均为直播解决,带你提升自主解决问题的能力 107 | 7. 学习一些思考底层原理的方式、以及源码阅读技巧 108 | 109 | 110 | ## 学习者的反馈 111 | 112 | ![img.png](doc/img.png) 113 | 114 | 115 | ## 项目大纲 116 | 117 | 1. 项目简介和计划 118 | 2. 需求分析 119 | 3. 技术选型(各技术作用讲解) 120 | 4. 前端项目初始化 121 | 1. 脚手架 122 | 2. 组件 / 类库引入 123 | 5. 前端页面设计及通用布局开发 124 | 6. 后端数据库表设计 125 | 7. 按标签搜索用户功能 126 | 1. 前端开发 127 | 2. 后端开发 128 | 3. 性能分析 129 | 4. 接口调试 130 | 8. Swagger + Knife4j 接口文档整合 131 | 9. 后端分布式登录改造(Session 共享) 132 | 10. 用户登录功能开发 133 | 11. 修改个人信息功能开发 134 | 12. 主页开发(抽象通用列表组件) 135 | 13. 批量导入数据功能 136 | 1. 几种方案介绍及对比 137 | 2. 测试及性能优化(并发编程) 138 | 14. 主页性能优化 139 | 1. 缓存和分布式缓存讲解 140 | 2. Redis 讲解 141 | 3. 缓存开发和注意事项 142 | 4. 缓存预热设计与实现 143 | 5. 定时任务介绍和实现 144 | 6. 锁 / 分布式锁介绍 145 | 7. 分布式锁注意事项讲解 146 | 8. Redisson 分布式锁实战 147 | 9. 控制定时任务执行的几种方案介绍及对比 148 | 15. 组队功能 149 | 1. 需求分析 150 | 2. 系统设计 151 | 3. 多个接口开发及测试 152 | 4. 前端多页面开发 153 | 5. 权限控制 154 | 16. 随机匹配功能 155 | 1. 匹配算法介绍及实现 156 | 2. 性能优化及测试 157 | 17. 项目优化及完善 158 | 18. 免备案方式上线前后端 159 | 160 | 161 | ## 项目资料 162 | 163 | [加入星球](https://yupi.icu) 可获得: 164 | 165 | 1. 完整视频教程 166 | 2. 视频教程大纲 167 | 3. 完整项目源码 168 | 4. 项目学习笔记 169 | 5. 本项目交流答疑 170 | 6. 本项目简历写法 171 | 7. 更多原创项目教程和学习专栏 172 | 173 | ![加入编程导航](./doc/加入编程导航.jpeg) 174 | 175 | 176 | ## 版权声明 177 | 178 | 请尊重原创!与其泄露资料、二次售卖,不如邀请他人加入星球得大额赏金:https://t.zsxq.com/0eP82UuaG 179 | -------------------------------------------------------------------------------- /doc/.yuanchuang: -------------------------------------------------------------------------------- 1 | 作_者 【程序员_鱼皮】 https://github.com/liyupi -------------------------------------------------------------------------------- /doc/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liyupi/yupao-backend-public/80c3cf6485543aee4876c61806ff2a917b6f9e62/doc/img.png -------------------------------------------------------------------------------- /doc/加入编程导航.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liyupi/yupao-backend-public/80c3cf6485543aee4876c61806ff2a917b6f9e62/doc/加入编程导航.jpeg -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /usr/local/etc/mavenrc ] ; then 40 | . /usr/local/etc/mavenrc 41 | fi 42 | 43 | if [ -f /etc/mavenrc ] ; then 44 | . /etc/mavenrc 45 | fi 46 | 47 | if [ -f "$HOME/.mavenrc" ] ; then 48 | . "$HOME/.mavenrc" 49 | fi 50 | 51 | fi 52 | 53 | # OS specific support. $var _must_ be set to either true or false. 54 | cygwin=false; 55 | darwin=false; 56 | mingw=false 57 | case "`uname`" in 58 | CYGWIN*) cygwin=true ;; 59 | MINGW*) mingw=true;; 60 | Darwin*) darwin=true 61 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 62 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 63 | if [ -z "$JAVA_HOME" ]; then 64 | if [ -x "/usr/libexec/java_home" ]; then 65 | export JAVA_HOME="`/usr/libexec/java_home`" 66 | else 67 | export JAVA_HOME="/Library/Java/Home" 68 | fi 69 | fi 70 | ;; 71 | esac 72 | 73 | if [ -z "$JAVA_HOME" ] ; then 74 | if [ -r /etc/gentoo-release ] ; then 75 | JAVA_HOME=`java-config --jre-home` 76 | fi 77 | fi 78 | 79 | if [ -z "$M2_HOME" ] ; then 80 | ## resolve links - $0 may be a link to maven's home 81 | PRG="$0" 82 | 83 | # need this for relative symlinks 84 | while [ -h "$PRG" ] ; do 85 | ls=`ls -ld "$PRG"` 86 | link=`expr "$ls" : '.*-> \(.*\)$'` 87 | if expr "$link" : '/.*' > /dev/null; then 88 | PRG="$link" 89 | else 90 | PRG="`dirname "$PRG"`/$link" 91 | fi 92 | done 93 | 94 | saveddir=`pwd` 95 | 96 | M2_HOME=`dirname "$PRG"`/.. 97 | 98 | # make it fully qualified 99 | M2_HOME=`cd "$M2_HOME" && pwd` 100 | 101 | cd "$saveddir" 102 | # echo Using m2 at $M2_HOME 103 | fi 104 | 105 | # For Cygwin, ensure paths are in UNIX format before anything is touched 106 | if $cygwin ; then 107 | [ -n "$M2_HOME" ] && 108 | M2_HOME=`cygpath --unix "$M2_HOME"` 109 | [ -n "$JAVA_HOME" ] && 110 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 111 | [ -n "$CLASSPATH" ] && 112 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 113 | fi 114 | 115 | # For Mingw, ensure paths are in UNIX format before anything is touched 116 | if $mingw ; then 117 | [ -n "$M2_HOME" ] && 118 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 119 | [ -n "$JAVA_HOME" ] && 120 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 121 | fi 122 | 123 | if [ -z "$JAVA_HOME" ]; then 124 | javaExecutable="`which javac`" 125 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 126 | # readlink(1) is not available as standard on Solaris 10. 127 | readLink=`which readlink` 128 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 129 | if $darwin ; then 130 | javaHome="`dirname \"$javaExecutable\"`" 131 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 132 | else 133 | javaExecutable="`readlink -f \"$javaExecutable\"`" 134 | fi 135 | javaHome="`dirname \"$javaExecutable\"`" 136 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 137 | JAVA_HOME="$javaHome" 138 | export JAVA_HOME 139 | fi 140 | fi 141 | fi 142 | 143 | if [ -z "$JAVACMD" ] ; then 144 | if [ -n "$JAVA_HOME" ] ; then 145 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 146 | # IBM's JDK on AIX uses strange locations for the executables 147 | JAVACMD="$JAVA_HOME/jre/sh/java" 148 | else 149 | JAVACMD="$JAVA_HOME/bin/java" 150 | fi 151 | else 152 | JAVACMD="`\\unset -f command; \\command -v java`" 153 | fi 154 | fi 155 | 156 | if [ ! -x "$JAVACMD" ] ; then 157 | echo "Error: JAVA_HOME is not defined correctly." >&2 158 | echo " We cannot execute $JAVACMD" >&2 159 | exit 1 160 | fi 161 | 162 | if [ -z "$JAVA_HOME" ] ; then 163 | echo "Warning: JAVA_HOME environment variable is not set." 164 | fi 165 | 166 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 167 | 168 | # traverses directory structure from process work directory to filesystem root 169 | # first directory with .mvn subdirectory is considered project base directory 170 | find_maven_basedir() { 171 | 172 | if [ -z "$1" ] 173 | then 174 | echo "Path not specified to find_maven_basedir" 175 | return 1 176 | fi 177 | 178 | basedir="$1" 179 | wdir="$1" 180 | while [ "$wdir" != '/' ] ; do 181 | if [ -d "$wdir"/.mvn ] ; then 182 | basedir=$wdir 183 | break 184 | fi 185 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 186 | if [ -d "${wdir}" ]; then 187 | wdir=`cd "$wdir/.."; pwd` 188 | fi 189 | # end of workaround 190 | done 191 | echo "${basedir}" 192 | } 193 | 194 | # concatenates all lines of a file 195 | concat_lines() { 196 | if [ -f "$1" ]; then 197 | echo "$(tr -s '\n' ' ' < "$1")" 198 | fi 199 | } 200 | 201 | BASE_DIR=`find_maven_basedir "$(pwd)"` 202 | if [ -z "$BASE_DIR" ]; then 203 | exit 1; 204 | fi 205 | 206 | ########################################################################################## 207 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 208 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 209 | ########################################################################################## 210 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Found .mvn/wrapper/maven-wrapper.jar" 213 | fi 214 | else 215 | if [ "$MVNW_VERBOSE" = true ]; then 216 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 217 | fi 218 | if [ -n "$MVNW_REPOURL" ]; then 219 | jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 220 | else 221 | jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 222 | fi 223 | while IFS="=" read key value; do 224 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 225 | esac 226 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 227 | if [ "$MVNW_VERBOSE" = true ]; then 228 | echo "Downloading from: $jarUrl" 229 | fi 230 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 231 | if $cygwin; then 232 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 233 | fi 234 | 235 | if command -v wget > /dev/null; then 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Found wget ... using wget" 238 | fi 239 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 240 | wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 241 | else 242 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 243 | fi 244 | elif command -v curl > /dev/null; then 245 | if [ "$MVNW_VERBOSE" = true ]; then 246 | echo "Found curl ... using curl" 247 | fi 248 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 249 | curl -o "$wrapperJarPath" "$jarUrl" -f 250 | else 251 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 252 | fi 253 | 254 | else 255 | if [ "$MVNW_VERBOSE" = true ]; then 256 | echo "Falling back to using Java to download" 257 | fi 258 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 259 | # For Cygwin, switch paths to Windows format before running javac 260 | if $cygwin; then 261 | javaClass=`cygpath --path --windows "$javaClass"` 262 | fi 263 | if [ -e "$javaClass" ]; then 264 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 265 | if [ "$MVNW_VERBOSE" = true ]; then 266 | echo " - Compiling MavenWrapperDownloader.java ..." 267 | fi 268 | # Compiling the Java class 269 | ("$JAVA_HOME/bin/javac" "$javaClass") 270 | fi 271 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 272 | # Running the downloader 273 | if [ "$MVNW_VERBOSE" = true ]; then 274 | echo " - Running MavenWrapperDownloader.java ..." 275 | fi 276 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 277 | fi 278 | fi 279 | fi 280 | fi 281 | ########################################################################################## 282 | # End of extension 283 | ########################################################################################## 284 | 285 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 286 | if [ "$MVNW_VERBOSE" = true ]; then 287 | echo $MAVEN_PROJECTBASEDIR 288 | fi 289 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 290 | 291 | # For Cygwin, switch paths to Windows format before running java 292 | if $cygwin; then 293 | [ -n "$M2_HOME" ] && 294 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 295 | [ -n "$JAVA_HOME" ] && 296 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 297 | [ -n "$CLASSPATH" ] && 298 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 299 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 300 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 301 | fi 302 | 303 | # Provide a "standardized" way to retrieve the CLI args that will 304 | # work with both Windows and non-Windows executions. 305 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 306 | export MAVEN_CMD_LINE_ARGS 307 | 308 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 309 | 310 | exec "$JAVACMD" \ 311 | $MAVEN_OPTS \ 312 | $MAVEN_DEBUG_OPTS \ 313 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 314 | "-Dmaven.home=${M2_HOME}" \ 315 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 316 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 317 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 4.0.0 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 2.6.4 11 | 12 | 13 | com.yupi 14 | yupao-backend 15 | 0.0.1-SNAPSHOT 16 | yupao-backend 17 | yupao-backend 18 | 19 | 1.8 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | org.mybatis.spring.boot 28 | mybatis-spring-boot-starter 29 | 2.2.2 30 | 31 | 32 | com.baomidou 33 | mybatis-plus-boot-starter 34 | 3.5.1 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-data-redis 40 | 2.6.4 41 | 42 | 43 | 44 | org.springframework.session 45 | spring-session-data-redis 46 | 2.6.3 47 | 48 | 49 | 50 | org.redisson 51 | redisson 52 | 3.17.5 53 | 54 | 55 | 56 | org.apache.commons 57 | commons-lang3 58 | 3.12.0 59 | 60 | 61 | 62 | org.apache.commons 63 | commons-collections4 64 | 4.4 65 | 66 | 67 | 68 | com.google.code.gson 69 | gson 70 | 2.8.9 71 | 72 | 73 | 74 | com.alibaba 75 | easyexcel 76 | 3.1.0 77 | 78 | 79 | 80 | com.github.xiaoymin 81 | knife4j-spring-boot-starter 82 | 2.0.7 83 | 84 | 85 | org.springframework.boot 86 | spring-boot-devtools 87 | runtime 88 | true 89 | 90 | 91 | mysql 92 | mysql-connector-java 93 | runtime 94 | 95 | 96 | org.springframework.boot 97 | spring-boot-configuration-processor 98 | true 99 | 100 | 101 | org.projectlombok 102 | lombok 103 | true 104 | 105 | 106 | org.springframework.boot 107 | spring-boot-starter-test 108 | test 109 | 110 | 111 | 112 | junit 113 | junit 114 | 4.13.2 115 | test 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | org.springframework.boot 125 | spring-boot-maven-plugin 126 | 127 | 128 | 129 | org.projectlombok 130 | lombok 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /sql/.author: -------------------------------------------------------------------------------- 1 | 原创_项目 [鱼皮](https://github.com/liyupi) 2 | -------------------------------------------------------------------------------- /sql/create_table.sql: -------------------------------------------------------------------------------- 1 | # 数据库初始化 2 | # @author 程序员鱼皮 3 | # @from 编程导航知识星球 4 | create 5 | database if not exists yupao; 6 | 7 | use 8 | yupao; 9 | 10 | -- 用户表 11 | create table user 12 | ( 13 | username varchar(256) null comment '用户昵称', 14 | id bigint auto_increment comment 'id' 15 | primary key, 16 | userAccount varchar(256) null comment '账号', 17 | avatarUrl varchar(1024) null comment '用户头像', 18 | gender tinyint null comment '性别', 19 | userPassword varchar(512) not null comment '密码', 20 | phone varchar(128) null comment '电话', 21 | email varchar(512) null comment '邮箱', 22 | userStatus int default 0 not null comment '状态 0 - 正常', 23 | createTime datetime default CURRENT_TIMESTAMP null comment '创建时间', 24 | updateTime datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP, 25 | isDelete tinyint default 0 not null comment '是否删除', 26 | userRole int default 0 not null comment '用户角色 0 - 普通用户 1 - 管理员', 27 | planetCode varchar(512) null comment '星球编号', 28 | tags varchar(1024) null comment '标签 json 列表' 29 | ) comment '用户'; 30 | 31 | -- 队伍表 32 | create table team 33 | ( 34 | id bigint auto_increment comment 'id' primary key, 35 | name varchar(256) not null comment '队伍名称', 36 | description varchar(1024) null comment '描述', 37 | maxNum int default 1 not null comment '最大人数', 38 | expireTime datetime null comment '过期时间', 39 | userId bigint comment '用户id(队长 id)',, 40 | status int default 0 not null comment '0 - 公开,1 - 私有,2 - 加密', 41 | password varchar(512) null comment '密码', 42 | createTime datetime default CURRENT_TIMESTAMP null comment '创建时间', 43 | updateTime datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP, 44 | isDelete tinyint default 0 not null comment '是否删除' 45 | ) comment '队伍'; 46 | 47 | -- 用户队伍关系 48 | create table user_team 49 | ( 50 | id bigint auto_increment comment 'id' 51 | primary key, 52 | userId bigint comment '用户id', 53 | teamId bigint comment '队伍id', 54 | joinTime datetime null comment '加入时间', 55 | createTime datetime default CURRENT_TIMESTAMP null comment '创建时间', 56 | updateTime datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP, 57 | isDelete tinyint default 0 not null comment '是否删除' 58 | ) comment '用户队伍关系'; 59 | 60 | 61 | -- 标签表(可以不创建,因为标签字段已经放到了用户表中) 62 | create table tag 63 | ( 64 | id bigint auto_increment comment 'id' 65 | primary key, 66 | tagName varchar(256) null comment '标签名称', 67 | userId bigint null comment '用户 id', 68 | parentId bigint null comment '父标签 id', 69 | isParent tinyint null comment '0 - 不是, 1 - 父标签', 70 | createTime datetime default CURRENT_TIMESTAMP null comment '创建时间', 71 | updateTime datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP, 72 | isDelete tinyint default 0 not null comment '是否删除', 73 | constraint uniIdx_tagName 74 | unique (tagName) 75 | ) comment '标签'; 76 | 77 | # https://t.zsxq.com/0emozsIJh 78 | 79 | create index idx_userId 80 | on tag (userId); -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liyupi/yupao-backend-public/80c3cf6485543aee4876c61806ff2a917b6f9e62/src/.DS_Store -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.scheduling.annotation.EnableScheduling; 7 | 8 | /** 9 | * 启动类 10 | * 11 | * @author 程序员鱼皮 12 | * @from 编程导航知识星球 13 | */ 14 | @SpringBootApplication 15 | @MapperScan("com.yupi.yupao.mapper") 16 | @EnableScheduling 17 | public class MyApplication { 18 | 19 | public static void main(String[] args) { 20 | SpringApplication.run(MyApplication.class, args); 21 | } 22 | 23 | } 24 | 25 | // 作_者 [程序员_鱼皮](https://yupi.icu/) -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/common/BaseResponse.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.common; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * 通用返回类 9 | * 10 | * @param 11 | * @author 程序员鱼皮 12 | * @from 编程导航知识星球 13 | */ 14 | @Data 15 | public class BaseResponse implements Serializable { 16 | 17 | private int code; 18 | 19 | private T data; 20 | 21 | private String message; 22 | 23 | private String description; 24 | 25 | public BaseResponse(int code, T data, String message, String description) { 26 | this.code = code; 27 | this.data = data; 28 | this.message = message; 29 | this.description = description; 30 | } 31 | 32 | // [鱼皮的学习圈](https://yupi.icu) 从 0 到 1 求职指导,斩获 offer!1 对 1 简历优化服务、2000+ 求职面试经验分享、200+ 真实简历和建议参考、25w 字前后端精选面试题 33 | 34 | public BaseResponse(int code, T data, String message) { 35 | this(code, data, message, ""); 36 | } 37 | 38 | public BaseResponse(int code, T data) { 39 | this(code, data, "", ""); 40 | } 41 | 42 | public BaseResponse(ErrorCode errorCode) { 43 | this(errorCode.getCode(), null, errorCode.getMessage(), errorCode.getDescription()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/common/DeleteRequest.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.common; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * 通用删除请求 9 | * 10 | * @author 程序员鱼皮 11 | * @from 编程导航知识星球 12 | */ 13 | @Data 14 | public class DeleteRequest implements Serializable { 15 | 16 | private static final long serialVersionUID = -5860707094194210842L; 17 | 18 | private long id; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/common/ErrorCode.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.common; 2 | 3 | /** 4 | * 错误码 5 | * 6 | * @author 程序员鱼皮 7 | * @from 编程导航知识星球 8 | */ 9 | public enum ErrorCode { 10 | 11 | // https://yupi.icu/ 12 | 13 | SUCCESS(0, "ok", ""), 14 | PARAMS_ERROR(40000, "请求参数错误", ""), 15 | NULL_ERROR(40001, "请求数据为空", ""), 16 | NOT_LOGIN(40100, "未登录", ""), 17 | NO_AUTH(40101, "无权限", ""), 18 | FORBIDDEN(40301, "禁止操作", ""), 19 | SYSTEM_ERROR(50000, "系统内部异常", ""); 20 | 21 | private final int code; 22 | 23 | /** 24 | * 状态码信息 25 | */ 26 | private final String message; 27 | 28 | /** 29 | * 状态码描述(详情) 30 | */ 31 | private final String description; 32 | 33 | ErrorCode(int code, String message, String description) { 34 | this.code = code; 35 | this.message = message; 36 | this.description = description; 37 | } 38 | 39 | public int getCode() { 40 | return code; 41 | } 42 | 43 | public String getMessage() { 44 | return message; 45 | } 46 | 47 | public String getDescription() { 48 | return description; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/common/PageRequest.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.common; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * 通用分页请求参数 9 | * 10 | * @author 程序员鱼皮 11 | * @from 编程导航知识星球 12 | */ 13 | @Data 14 | public class PageRequest implements Serializable { 15 | 16 | private static final long serialVersionUID = -5860707094194210842L; 17 | 18 | /** 19 | * 页面大小 20 | */ 21 | protected int pageSize = 10; 22 | 23 | /** 24 | * 当前是第几页 25 | */ 26 | protected int pageNum = 1; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/common/ResultUtils.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.common; 2 | 3 | /** 4 | * 返回工具类 5 | * 6 | * @author 程序员鱼皮 7 | * @from 编程导航知识星球 8 | */ 9 | public class ResultUtils { 10 | 11 | /** 12 | * 成功 13 | * 14 | * @param data 15 | * @param 16 | * @return 17 | */ 18 | public static BaseResponse success(T data) { 19 | return new BaseResponse<>(0, data, "ok"); 20 | } 21 | 22 | /** 23 | * 失败 24 | * 25 | * @param errorCode 26 | * @return 27 | */ 28 | public static BaseResponse error(ErrorCode errorCode) { 29 | return new BaseResponse<>(errorCode); 30 | } 31 | 32 | // 作者 [程序员鱼皮](https://yupi.icu/) 33 | 34 | /** 35 | * 失败 36 | * 37 | * @param code 38 | * @param message 39 | * @param description 40 | * @return 41 | */ 42 | public static BaseResponse error(int code, String message, String description) { 43 | return new BaseResponse(code, null, message, description); 44 | } 45 | 46 | /** 47 | * 失败 48 | * 49 | * @param errorCode 50 | * @return 51 | */ 52 | public static BaseResponse error(ErrorCode errorCode, String message, String description) { 53 | return new BaseResponse(errorCode.getCode(), null, message, description); 54 | } 55 | 56 | /** 57 | * 失败 58 | * 59 | * @param errorCode 60 | * @return 61 | */ 62 | public static BaseResponse error(ErrorCode errorCode, String description) { 63 | return new BaseResponse(errorCode.getCode(), errorCode.getMessage(), description); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/common/请尊重原创: -------------------------------------------------------------------------------- 1 | [编程导航学习圈](https://t.zsxq.com/0emozsIJh) 零基础快速入门编程,不走弯路!30+ 原创学习路线和专栏、1000+ 编程精华文章、500+ 编程学习指南、20T+ 编程资源汇总 -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/config/MybatisPlusConfig.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.config; 2 | 3 | import com.baomidou.mybatisplus.annotation.DbType; 4 | import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; 5 | import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; 6 | import org.mybatis.spring.annotation.MapperScan; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | /** 11 | * MyBatisPlus 配置 12 | * 13 | * @author 程序员鱼皮 14 | * @from 编程导航知识星球 15 | */ 16 | @Configuration 17 | @MapperScan("com.yupi.yupao.mapper") 18 | public class MybatisPlusConfig { 19 | 20 | /** 21 | * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除) 22 | */ 23 | @Bean 24 | public MybatisPlusInterceptor mybatisPlusInterceptor() { 25 | MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); 26 | interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); 27 | return interceptor; 28 | } 29 | 30 | // [加入编程导航](https://yupi.icu) 深耕编程提升【两年半】、国内净值【最高】的编程社群、用心服务【20000+】求学者、帮你自学编程【不走弯路】 31 | 32 | } -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/config/RedisTemplateConfig.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.data.redis.connection.RedisConnectionFactory; 6 | import org.springframework.data.redis.core.RedisTemplate; 7 | import org.springframework.data.redis.serializer.RedisSerializer; 8 | 9 | /** 10 | * RedisTemplate 配置 11 | * 12 | * @author 程序员鱼皮 13 | * @from 编程导航知识星球 14 | */ 15 | @Configuration 16 | public class RedisTemplateConfig { 17 | 18 | // https://space.bilibili.com/12890453/ 19 | 20 | @Bean 21 | public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { 22 | RedisTemplate redisTemplate = new RedisTemplate<>(); 23 | redisTemplate.setConnectionFactory(connectionFactory); 24 | redisTemplate.setKeySerializer(RedisSerializer.string()); 25 | return redisTemplate; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/config/RedissonConfig.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.config; 2 | 3 | import lombok.Data; 4 | import org.redisson.Redisson; 5 | import org.redisson.api.RedissonClient; 6 | import org.redisson.config.Config; 7 | import org.springframework.boot.context.properties.ConfigurationProperties; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | /** 12 | * Redisson 配置 13 | * 14 | * @author 程序员鱼皮 15 | * @from 编程导航知识星球 16 | */ 17 | @Configuration 18 | @ConfigurationProperties(prefix = "spring.redis") 19 | @Data 20 | public class RedissonConfig { 21 | 22 | private String host; 23 | 24 | private String port; 25 | 26 | @Bean 27 | public RedissonClient redissonClient() { 28 | // 1. 创建配置 29 | Config config = new Config(); 30 | String redisAddress = String.format("redis://%s:%s", host, port); 31 | config.useSingleServer().setAddress(redisAddress).setDatabase(3); 32 | // 2. 创建实例 33 | RedissonClient redisson = Redisson.create(config); 34 | return redisson; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.Profile; 6 | import springfox.documentation.builders.ApiInfoBuilder; 7 | import springfox.documentation.builders.PathSelectors; 8 | import springfox.documentation.builders.RequestHandlerSelectors; 9 | import springfox.documentation.service.ApiInfo; 10 | import springfox.documentation.service.Contact; 11 | import springfox.documentation.spi.DocumentationType; 12 | import springfox.documentation.spring.web.plugins.Docket; 13 | import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc; 14 | 15 | /** 16 | * 自定义 Swagger 接口文档的配置 17 | * 18 | * @author 程序员鱼皮 19 | * @from 编程导航知识星球 20 | */ 21 | @Configuration 22 | @EnableSwagger2WebMvc 23 | @Profile({"dev", "test"}) 24 | public class SwaggerConfig { 25 | 26 | @Bean(value = "defaultApi2") 27 | public Docket defaultApi2() { 28 | return new Docket(DocumentationType.SWAGGER_2) 29 | .apiInfo(apiInfo()) 30 | .select() 31 | // 这里一定要标注你控制器的位置 32 | .apis(RequestHandlerSelectors.basePackage("com.yupi.yupao.controller")) 33 | .paths(PathSelectors.any()) 34 | .build(); 35 | } 36 | 37 | // [加入编程导航](https://t.zsxq.com/0emozsIJh) 深耕编程提升【两年半】、国内净值【最高】的编程社群、用心服务【20000+】求学者、帮你自学编程【不走弯路】 38 | 39 | /** 40 | * api 信息 41 | * @return 42 | */ 43 | private ApiInfo apiInfo() { 44 | return new ApiInfoBuilder() 45 | .title("鱼皮用户中心") 46 | .description("鱼皮用户中心接口文档") 47 | .termsOfServiceUrl("https://github.com/liyupi") 48 | .contact(new Contact("yupi","https://github.com/liyupi","xxx@qq.com")) 49 | .version("1.0") 50 | .build(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/config/WebMvcConfg.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 6 | 7 | /** 8 | * 跨域配置 9 | * 10 | * @author 程序员鱼皮 11 | * @from 编程导航知识星球 12 | */ 13 | @Configuration 14 | public class WebMvcConfg implements WebMvcConfigurer { 15 | 16 | @Override 17 | public void addCorsMappings(CorsRegistry registry) { 18 | //设置允许跨域的路径 19 | registry.addMapping("/**") 20 | //设置允许跨域请求的域名 21 | //当**Credentials为true时,**Origin不能为星号,需为具体的ip地址【如果接口不带cookie,ip无需设成具体ip】 22 | .allowedOrigins("http://localhost:9527", "http://127.0.0.1:9527", "http://127.0.0.1:8082", "http://127.0.0.1:8083") 23 | //是否允许证书 不再默认开启 24 | .allowCredentials(true) 25 | //设置允许的方法 26 | .allowedMethods("*") 27 | //跨域允许时间 28 | .maxAge(3600); 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/constant/UserConstant.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.constant; 2 | 3 | /** 4 | * 用户常量 5 | * 6 | * @author 程序员鱼皮 7 | * @from 编程导航知识星球 8 | */ 9 | public interface UserConstant { 10 | 11 | /** 12 | * 用户登录态键 13 | */ 14 | String USER_LOGIN_STATE = "userLoginState"; 15 | 16 | // ------- 权限 -------- 17 | 18 | /** 19 | * 默认权限 20 | */ 21 | int DEFAULT_ROLE = 0; 22 | 23 | /** 24 | * 管理员权限 25 | */ 26 | int ADMIN_ROLE = 1; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/controller/TeamController.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.controller; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 5 | import com.yupi.yupao.common.BaseResponse; 6 | import com.yupi.yupao.common.DeleteRequest; 7 | import com.yupi.yupao.common.ErrorCode; 8 | import com.yupi.yupao.common.ResultUtils; 9 | import com.yupi.yupao.exception.BusinessException; 10 | import com.yupi.yupao.model.domain.Team; 11 | import com.yupi.yupao.model.domain.User; 12 | import com.yupi.yupao.model.domain.UserTeam; 13 | import com.yupi.yupao.model.dto.TeamQuery; 14 | import com.yupi.yupao.model.request.TeamAddRequest; 15 | import com.yupi.yupao.model.request.TeamJoinRequest; 16 | import com.yupi.yupao.model.request.TeamQuitRequest; 17 | import com.yupi.yupao.model.request.TeamUpdateRequest; 18 | import com.yupi.yupao.model.vo.TeamUserVO; 19 | import com.yupi.yupao.service.TeamService; 20 | import com.yupi.yupao.service.UserService; 21 | import com.yupi.yupao.service.UserTeamService; 22 | import lombok.extern.slf4j.Slf4j; 23 | import org.springframework.beans.BeanUtils; 24 | import org.springframework.web.bind.annotation.*; 25 | 26 | import javax.annotation.Resource; 27 | import javax.servlet.http.HttpServletRequest; 28 | import java.util.ArrayList; 29 | import java.util.List; 30 | import java.util.Map; 31 | import java.util.Set; 32 | import java.util.stream.Collectors; 33 | 34 | /** 35 | * 队伍接口 36 | * 37 | * @author 程序员鱼皮 38 | * @from 编程导航知识星球 39 | */ 40 | @RestController 41 | @RequestMapping("/team") 42 | @CrossOrigin(origins = {"http://localhost:3000"}) 43 | @Slf4j 44 | public class TeamController { 45 | 46 | @Resource 47 | private UserService userService; 48 | 49 | @Resource 50 | private TeamService teamService; 51 | 52 | @Resource 53 | private UserTeamService userTeamService; 54 | 55 | @PostMapping("/add") 56 | public BaseResponse addTeam(@RequestBody TeamAddRequest teamAddRequest, HttpServletRequest request) { 57 | if (teamAddRequest == null) { 58 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 59 | } 60 | User loginUser = userService.getLoginUser(request); 61 | Team team = new Team(); 62 | BeanUtils.copyProperties(teamAddRequest, team); 63 | long teamId = teamService.addTeam(team, loginUser); 64 | return ResultUtils.success(teamId); 65 | } 66 | 67 | @PostMapping("/update") 68 | public BaseResponse updateTeam(@RequestBody TeamUpdateRequest teamUpdateRequest, HttpServletRequest request) { 69 | if (teamUpdateRequest == null) { 70 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 71 | } 72 | User loginUser = userService.getLoginUser(request); 73 | boolean result = teamService.updateTeam(teamUpdateRequest, loginUser); 74 | if (!result) { 75 | throw new BusinessException(ErrorCode.SYSTEM_ERROR, "更新失败"); 76 | } 77 | return ResultUtils.success(true); 78 | } 79 | 80 | @GetMapping("/get") 81 | public BaseResponse getTeamById(long id) { 82 | if (id <= 0) { 83 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 84 | } 85 | Team team = teamService.getById(id); 86 | if (team == null) { 87 | throw new BusinessException(ErrorCode.NULL_ERROR); 88 | } 89 | return ResultUtils.success(team); 90 | } 91 | 92 | @GetMapping("/list") 93 | public BaseResponse> listTeams(TeamQuery teamQuery, HttpServletRequest request) { 94 | if (teamQuery == null) { 95 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 96 | } 97 | boolean isAdmin = userService.isAdmin(request); 98 | // 1、查询队伍列表 99 | List teamList = teamService.listTeams(teamQuery, isAdmin); 100 | final List teamIdList = teamList.stream().map(TeamUserVO::getId).collect(Collectors.toList()); 101 | // 2、判断当前用户是否已加入队伍 102 | QueryWrapper userTeamQueryWrapper = new QueryWrapper<>(); 103 | try { 104 | User loginUser = userService.getLoginUser(request); 105 | userTeamQueryWrapper.eq("userId", loginUser.getId()); 106 | userTeamQueryWrapper.in("teamId", teamIdList); 107 | List userTeamList = userTeamService.list(userTeamQueryWrapper); 108 | // 已加入的队伍 id 集合 109 | Set hasJoinTeamIdSet = userTeamList.stream().map(UserTeam::getTeamId).collect(Collectors.toSet()); 110 | teamList.forEach(team -> { 111 | boolean hasJoin = hasJoinTeamIdSet.contains(team.getId()); 112 | team.setHasJoin(hasJoin); 113 | }); 114 | } catch (Exception e) { 115 | } 116 | // 3、查询已加入队伍的人数 117 | QueryWrapper userTeamJoinQueryWrapper = new QueryWrapper<>(); 118 | userTeamJoinQueryWrapper.in("teamId", teamIdList); 119 | List userTeamList = userTeamService.list(userTeamJoinQueryWrapper); 120 | // 队伍 id => 加入这个队伍的用户列表 121 | Map> teamIdUserTeamList = userTeamList.stream().collect(Collectors.groupingBy(UserTeam::getTeamId)); 122 | teamList.forEach(team -> team.setHasJoinNum(teamIdUserTeamList.getOrDefault(team.getId(), new ArrayList<>()).size())); 123 | return ResultUtils.success(teamList); 124 | } 125 | 126 | // todo 查询分页 127 | @GetMapping("/list/page") 128 | public BaseResponse> listTeamsByPage(TeamQuery teamQuery) { 129 | if (teamQuery == null) { 130 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 131 | } 132 | Team team = new Team(); 133 | BeanUtils.copyProperties(teamQuery, team); 134 | Page page = new Page<>(teamQuery.getPageNum(), teamQuery.getPageSize()); 135 | QueryWrapper queryWrapper = new QueryWrapper<>(team); 136 | Page resultPage = teamService.page(page, queryWrapper); 137 | return ResultUtils.success(resultPage); 138 | } 139 | 140 | @PostMapping("/join") 141 | public BaseResponse joinTeam(@RequestBody TeamJoinRequest teamJoinRequest, HttpServletRequest request) { 142 | if (teamJoinRequest == null) { 143 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 144 | } 145 | User loginUser = userService.getLoginUser(request); 146 | boolean result = teamService.joinTeam(teamJoinRequest, loginUser); 147 | return ResultUtils.success(result); 148 | } 149 | 150 | @PostMapping("/quit") 151 | public BaseResponse quitTeam(@RequestBody TeamQuitRequest teamQuitRequest, HttpServletRequest request) { 152 | if (teamQuitRequest == null) { 153 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 154 | } 155 | User loginUser = userService.getLoginUser(request); 156 | boolean result = teamService.quitTeam(teamQuitRequest, loginUser); 157 | return ResultUtils.success(result); 158 | } 159 | 160 | @PostMapping("/delete") 161 | public BaseResponse deleteTeam(@RequestBody DeleteRequest deleteRequest, HttpServletRequest request) { 162 | if (deleteRequest == null || deleteRequest.getId() <= 0) { 163 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 164 | } 165 | long id = deleteRequest.getId(); 166 | User loginUser = userService.getLoginUser(request); 167 | boolean result = teamService.deleteTeam(id, loginUser); 168 | if (!result) { 169 | throw new BusinessException(ErrorCode.SYSTEM_ERROR, "删除失败"); 170 | } 171 | return ResultUtils.success(true); 172 | } 173 | 174 | 175 | /** 176 | * 获取我创建的队伍 177 | * 178 | * @param teamQuery 179 | * @param request 180 | * @return 181 | */ 182 | @GetMapping("/list/my/create") 183 | public BaseResponse> listMyCreateTeams(TeamQuery teamQuery, HttpServletRequest request) { 184 | if (teamQuery == null) { 185 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 186 | } 187 | User loginUser = userService.getLoginUser(request); 188 | teamQuery.setUserId(loginUser.getId()); 189 | List teamList = teamService.listTeams(teamQuery, true); 190 | return ResultUtils.success(teamList); 191 | } 192 | 193 | 194 | /** 195 | * 获取我加入的队伍 196 | * 197 | * @param teamQuery 198 | * @param request 199 | * @return 200 | */ 201 | @GetMapping("/list/my/join") 202 | public BaseResponse> listMyJoinTeams(TeamQuery teamQuery, HttpServletRequest request) { 203 | if (teamQuery == null) { 204 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 205 | } 206 | User loginUser = userService.getLoginUser(request); 207 | QueryWrapper queryWrapper = new QueryWrapper<>(); 208 | queryWrapper.eq("userId", loginUser.getId()); 209 | List userTeamList = userTeamService.list(queryWrapper); 210 | // 取出不重复的队伍 id 211 | // teamId userId 212 | // 1, 2 213 | // 1, 3 214 | // 2, 3 215 | // result 216 | // 1 => 2, 3 217 | // 2 => 3 218 | Map> listMap = userTeamList.stream() 219 | .collect(Collectors.groupingBy(UserTeam::getTeamId)); 220 | List idList = new ArrayList<>(listMap.keySet()); 221 | teamQuery.setIdList(idList); 222 | List teamList = teamService.listTeams(teamQuery, true); 223 | return ResultUtils.success(teamList); 224 | } 225 | } 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.controller; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 5 | import com.yupi.yupao.common.BaseResponse; 6 | import com.yupi.yupao.common.ErrorCode; 7 | import com.yupi.yupao.common.ResultUtils; 8 | import com.yupi.yupao.exception.BusinessException; 9 | import com.yupi.yupao.model.domain.User; 10 | import com.yupi.yupao.model.request.UserLoginRequest; 11 | import com.yupi.yupao.model.request.UserRegisterRequest; 12 | import com.yupi.yupao.model.vo.UserVO; 13 | import com.yupi.yupao.service.UserService; 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.apache.commons.lang3.StringUtils; 16 | import org.springframework.data.redis.core.RedisTemplate; 17 | import org.springframework.data.redis.core.ValueOperations; 18 | import org.springframework.util.CollectionUtils; 19 | import org.springframework.web.bind.annotation.*; 20 | 21 | import javax.annotation.Resource; 22 | import javax.servlet.http.HttpServletRequest; 23 | import java.util.List; 24 | import java.util.concurrent.TimeUnit; 25 | import java.util.stream.Collectors; 26 | 27 | import static com.yupi.yupao.constant.UserConstant.USER_LOGIN_STATE; 28 | 29 | /** 30 | * 用户接口 31 | * 32 | * @author 程序员鱼皮 33 | * @from 编程导航知识星球 34 | */ 35 | @RestController 36 | @RequestMapping("/user") 37 | @CrossOrigin(origins = {"http://localhost:3000"}) 38 | @Slf4j 39 | public class UserController { 40 | 41 | @Resource 42 | private UserService userService; 43 | 44 | @Resource 45 | private RedisTemplate redisTemplate; 46 | 47 | @PostMapping("/register") 48 | public BaseResponse userRegister(@RequestBody UserRegisterRequest userRegisterRequest) { 49 | if (userRegisterRequest == null) { 50 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 51 | } 52 | String userAccount = userRegisterRequest.getUserAccount(); 53 | String userPassword = userRegisterRequest.getUserPassword(); 54 | String checkPassword = userRegisterRequest.getCheckPassword(); 55 | String planetCode = userRegisterRequest.getPlanetCode(); 56 | if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword, planetCode)) { 57 | return null; 58 | } 59 | long result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode); 60 | return ResultUtils.success(result); 61 | } 62 | 63 | @PostMapping("/login") 64 | public BaseResponse userLogin(@RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request) { 65 | if (userLoginRequest == null) { 66 | return ResultUtils.error(ErrorCode.PARAMS_ERROR); 67 | } 68 | String userAccount = userLoginRequest.getUserAccount(); 69 | String userPassword = userLoginRequest.getUserPassword(); 70 | if (StringUtils.isAnyBlank(userAccount, userPassword)) { 71 | return ResultUtils.error(ErrorCode.PARAMS_ERROR); 72 | } 73 | User user = userService.userLogin(userAccount, userPassword, request); 74 | return ResultUtils.success(user); 75 | } 76 | 77 | @PostMapping("/logout") 78 | public BaseResponse userLogout(HttpServletRequest request) { 79 | if (request == null) { 80 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 81 | } 82 | int result = userService.userLogout(request); 83 | return ResultUtils.success(result); 84 | } 85 | 86 | @GetMapping("/current") 87 | public BaseResponse getCurrentUser(HttpServletRequest request) { 88 | Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE); 89 | User currentUser = (User) userObj; 90 | if (currentUser == null) { 91 | throw new BusinessException(ErrorCode.NOT_LOGIN); 92 | } 93 | long userId = currentUser.getId(); 94 | // TODO 校验用户是否合法 95 | User user = userService.getById(userId); 96 | User safetyUser = userService.getSafetyUser(user); 97 | return ResultUtils.success(safetyUser); 98 | } 99 | 100 | @GetMapping("/search") 101 | public BaseResponse> searchUsers(String username, HttpServletRequest request) { 102 | if (!userService.isAdmin(request)) { 103 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 104 | } 105 | QueryWrapper queryWrapper = new QueryWrapper<>(); 106 | if (StringUtils.isNotBlank(username)) { 107 | queryWrapper.like("username", username); 108 | } 109 | List userList = userService.list(queryWrapper); 110 | List list = userList.stream().map(user -> userService.getSafetyUser(user)).collect(Collectors.toList()); 111 | return ResultUtils.success(list); 112 | } 113 | 114 | @GetMapping("/search/tags") 115 | public BaseResponse> searchUsersByTags(@RequestParam(required = false) List tagNameList) { 116 | if (CollectionUtils.isEmpty(tagNameList)) { 117 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 118 | } 119 | List userList = userService.searchUsersByTags(tagNameList); 120 | return ResultUtils.success(userList); 121 | } 122 | 123 | // todo 推荐多个,未实现 124 | @GetMapping("/recommend") 125 | public BaseResponse> recommendUsers(long pageSize, long pageNum, HttpServletRequest request) { 126 | User loginUser = userService.getLoginUser(request); 127 | String redisKey = String.format("yupao:user:recommend:%s", loginUser.getId()); 128 | ValueOperations valueOperations = redisTemplate.opsForValue(); 129 | // 如果有缓存,直接读缓存 130 | Page userPage = (Page) valueOperations.get(redisKey); 131 | if (userPage != null) { 132 | return ResultUtils.success(userPage); 133 | } 134 | // 无缓存,查数据库 135 | QueryWrapper queryWrapper = new QueryWrapper<>(); 136 | userPage = userService.page(new Page<>(pageNum, pageSize), queryWrapper); 137 | // 写缓存 138 | try { 139 | valueOperations.set(redisKey, userPage, 30000, TimeUnit.MILLISECONDS); 140 | } catch (Exception e) { 141 | log.error("redis set key error", e); 142 | } 143 | return ResultUtils.success(userPage); 144 | } 145 | 146 | 147 | @PostMapping("/update") 148 | public BaseResponse updateUser(@RequestBody User user, HttpServletRequest request) { 149 | // 校验参数是否为空 150 | if (user == null) { 151 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 152 | } 153 | User loginUser = userService.getLoginUser(request); 154 | int result = userService.updateUser(user, loginUser); 155 | return ResultUtils.success(result); 156 | } 157 | 158 | @PostMapping("/delete") 159 | public BaseResponse deleteUser(@RequestBody long id, HttpServletRequest request) { 160 | if (!userService.isAdmin(request)) { 161 | throw new BusinessException(ErrorCode.NO_AUTH); 162 | } 163 | if (id <= 0) { 164 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 165 | } 166 | boolean b = userService.removeById(id); 167 | return ResultUtils.success(b); 168 | } 169 | 170 | /** 171 | * 获取最匹配的用户 172 | * 173 | * @param num 174 | * @param request 175 | * @return 176 | */ 177 | @GetMapping("/match") 178 | public BaseResponse> matchUsers(long num, HttpServletRequest request) { 179 | if (num <= 0 || num > 20) { 180 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 181 | } 182 | User user = userService.getLoginUser(request); 183 | return ResultUtils.success(userService.matchUsers(num, user)); 184 | } 185 | 186 | } 187 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/exception/BusinessException.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.exception; 2 | 3 | import com.yupi.yupao.common.ErrorCode; 4 | 5 | /** 6 | * 自定义异常类 7 | * 8 | * @author 程序员鱼皮 9 | * @from 编程导航知识星球 10 | */ 11 | public class BusinessException extends RuntimeException { 12 | 13 | private final int code; 14 | 15 | private final String description; 16 | 17 | public BusinessException(String message, int code, String description) { 18 | super(message); 19 | this.code = code; 20 | this.description = description; 21 | } 22 | 23 | public BusinessException(ErrorCode errorCode) { 24 | super(errorCode.getMessage()); 25 | this.code = errorCode.getCode(); 26 | this.description = errorCode.getDescription(); 27 | } 28 | 29 | public BusinessException(ErrorCode errorCode, String description) { 30 | super(errorCode.getMessage()); 31 | this.code = errorCode.getCode(); 32 | this.description = description; 33 | } 34 | 35 | public int getCode() { 36 | return code; 37 | } 38 | 39 | public String getDescription() { 40 | return description; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/exception/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.exception; 2 | 3 | import com.yupi.yupao.common.BaseResponse; 4 | import com.yupi.yupao.common.ErrorCode; 5 | import com.yupi.yupao.common.ResultUtils; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.web.bind.annotation.ExceptionHandler; 8 | import org.springframework.web.bind.annotation.RestControllerAdvice; 9 | 10 | /** 11 | * 全局异常处理器 12 | * 13 | * @author 程序员鱼皮 14 | * @from 编程导航知识星球 15 | */ 16 | @RestControllerAdvice 17 | @Slf4j 18 | public class GlobalExceptionHandler { 19 | 20 | @ExceptionHandler(BusinessException.class) 21 | public BaseResponse businessExceptionHandler(BusinessException e) { 22 | log.error("businessException: " + e.getMessage(), e); 23 | return ResultUtils.error(e.getCode(), e.getMessage(), e.getDescription()); 24 | } 25 | 26 | @ExceptionHandler(RuntimeException.class) 27 | public BaseResponse runtimeExceptionHandler(RuntimeException e) { 28 | log.error("runtimeException", e); 29 | return ResultUtils.error(ErrorCode.SYSTEM_ERROR, e.getMessage(), ""); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/job/PreCacheJob.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.job; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 5 | import com.yupi.yupao.mapper.UserMapper; 6 | import com.yupi.yupao.model.domain.User; 7 | import com.yupi.yupao.service.UserService; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.redisson.api.RLock; 10 | import org.redisson.api.RedissonClient; 11 | import org.springframework.data.redis.core.RedisTemplate; 12 | import org.springframework.data.redis.core.ValueOperations; 13 | import org.springframework.scheduling.annotation.Scheduled; 14 | import org.springframework.stereotype.Component; 15 | 16 | import javax.annotation.Resource; 17 | import java.util.ArrayList; 18 | import java.util.Arrays; 19 | import java.util.List; 20 | import java.util.concurrent.TimeUnit; 21 | 22 | /** 23 | * 缓存预热任务 24 | * 25 | * @author 程序员鱼皮 26 | * @from 编程导航知识星球 27 | */ 28 | @Component 29 | @Slf4j 30 | public class PreCacheJob { 31 | 32 | @Resource 33 | private UserService userService; 34 | 35 | @Resource 36 | private RedisTemplate redisTemplate; 37 | 38 | @Resource 39 | private RedissonClient redissonClient; 40 | 41 | // 重点用户 42 | private List mainUserList = Arrays.asList(1L); 43 | 44 | // 每天执行,预热推荐用户 45 | @Scheduled(cron = "0 31 0 * * *") 46 | public void doCacheRecommendUser() { 47 | RLock lock = redissonClient.getLock("yupao:precachejob:docache:lock"); 48 | try { 49 | // 只有一个线程能获取到锁 50 | if (lock.tryLock(0, -1, TimeUnit.MILLISECONDS)) { 51 | System.out.println("getLock: " + Thread.currentThread().getId()); 52 | for (Long userId : mainUserList) { 53 | QueryWrapper queryWrapper = new QueryWrapper<>(); 54 | Page userPage = userService.page(new Page<>(1, 20), queryWrapper); 55 | String redisKey = String.format("yupao:user:recommend:%s", userId); 56 | ValueOperations valueOperations = redisTemplate.opsForValue(); 57 | // 写缓存 58 | try { 59 | valueOperations.set(redisKey, userPage, 30000, TimeUnit.MILLISECONDS); 60 | } catch (Exception e) { 61 | log.error("redis set key error", e); 62 | } 63 | } 64 | } 65 | } catch (InterruptedException e) { 66 | log.error("doCacheRecommendUser error", e); 67 | } finally { 68 | // 只能释放自己的锁 69 | if (lock.isHeldByCurrentThread()) { 70 | System.out.println("unLock: " + Thread.currentThread().getId()); 71 | lock.unlock(); 72 | } 73 | } 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/mapper/TeamMapper.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.yupi.yupao.model.domain.Team; 5 | 6 | /** 7 | * 队伍 Mapper 8 | * 9 | * @author 程序员鱼皮 10 | * @from 编程导航知识星球 11 | */ 12 | public interface TeamMapper extends BaseMapper { 13 | 14 | } 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.mapper; 2 | 3 | // [鱼皮的知识星球](https://t.zsxq.com/0emozsIJh) 从 0 到 1 求职指导,斩获 offer!1 对 1 简历优化服务、200+ 真实简历和建议参考、2000+ 求职面试经验分享、25w 字前后端精选面试题 4 | 5 | import com.yupi.yupao.model.domain.User; 6 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 7 | 8 | /** 9 | * 用户 Mapper 10 | * 11 | * @author 程序员鱼皮 12 | * @from 编程导航知识星球 13 | */ 14 | public interface UserMapper extends BaseMapper { 15 | 16 | } 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/mapper/UserTeamMapper.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.yupi.yupao.model.domain.UserTeam; 5 | 6 | /** 7 | * 用户队伍 Mapper 8 | * 9 | * @author 程序员鱼皮 10 | * @from 编程导航知识星球 11 | */ 12 | public interface UserTeamMapper extends BaseMapper { 13 | 14 | } 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/model/domain/Team.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.model.domain; 2 | 3 | import com.baomidou.mybatisplus.annotation.*; 4 | import lombok.Data; 5 | 6 | import java.io.Serializable; 7 | import java.util.Date; 8 | 9 | /** 10 | * 队伍实体 11 | * 12 | * @author 程序员鱼皮 13 | * @from 编程导航知识星球 14 | */ 15 | @TableName(value = "team") 16 | @Data 17 | public class Team implements Serializable { 18 | /** 19 | * id 20 | */ 21 | @TableId(type = IdType.AUTO) 22 | private Long id; 23 | 24 | /** 25 | * 队伍名称 26 | */ 27 | private String name; 28 | 29 | /** 30 | * 描述 31 | */ 32 | private String description; 33 | 34 | /** 35 | * 最大人数 36 | */ 37 | private Integer maxNum; 38 | 39 | /** 40 | * 过期时间 41 | */ 42 | private Date expireTime; 43 | 44 | /** 45 | * 用户id 46 | */ 47 | private Long userId; 48 | 49 | /** 50 | * 0 - 公开,1 - 私有,2 - 加密 51 | */ 52 | private Integer status; 53 | 54 | /** 55 | * 密码 56 | */ 57 | private String password; 58 | 59 | /** 60 | * 创建时间 61 | */ 62 | private Date createTime; 63 | 64 | /** 65 | * 66 | */ 67 | private Date updateTime; 68 | 69 | /** 70 | * 是否删除 71 | */ 72 | @TableLogic 73 | private Integer isDelete; 74 | 75 | @TableField(exist = false) 76 | private static final long serialVersionUID = 1L; 77 | } -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/model/domain/User.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.model.domain; 2 | 3 | import com.baomidou.mybatisplus.annotation.*; 4 | import lombok.Data; 5 | 6 | import java.io.Serializable; 7 | import java.util.Date; 8 | 9 | /** 10 | * 用户实体 11 | * 12 | * @author 程序员鱼皮 13 | * @from 编程导航知识星球 14 | */ 15 | @TableName(value = "user") 16 | @Data 17 | public class User implements Serializable { 18 | /** 19 | * id 20 | */ 21 | @TableId(type = IdType.AUTO) 22 | private long id; 23 | 24 | /** 25 | * 用户昵称 26 | */ 27 | private String username; 28 | 29 | /** 30 | * 账号 31 | */ 32 | private String userAccount; 33 | 34 | /** 35 | * 用户头像 36 | */ 37 | private String avatarUrl; 38 | 39 | /** 40 | * 性别 41 | */ 42 | private Integer gender; 43 | 44 | /** 45 | * 密码 46 | */ 47 | private String userPassword; 48 | 49 | /** 50 | * 电话 51 | */ 52 | private String phone; 53 | 54 | /** 55 | * 邮箱 56 | */ 57 | private String email; 58 | 59 | /** 60 | * 标签列表 json 61 | */ 62 | private String tags; 63 | 64 | /** 65 | * 状态 0 - 正常 66 | */ 67 | private Integer userStatus; 68 | 69 | /** 70 | * 创建时间 71 | */ 72 | private Date createTime; 73 | 74 | /** 75 | * 76 | */ 77 | private Date updateTime; 78 | 79 | /** 80 | * 是否删除 81 | */ 82 | @TableLogic 83 | private Integer isDelete; 84 | 85 | /** 86 | * 用户角色 0 - 普通用户 1 - 管理员 87 | */ 88 | private Integer userRole; 89 | 90 | /** 91 | * 星球编号 92 | */ 93 | private String planetCode; 94 | 95 | @TableField(exist = false) 96 | private static final long serialVersionUID = 1L; 97 | } 98 | 99 | // [程序员交流园地](https://www.code-nav.cn/) 从 0 到 1 求职指导,斩获 offer!1 对 1 简历优化服务、200+ 真实简历和建议参考、25w 字前后端精选面试题、2000+ 求职面试经验分享 -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/model/domain/UserTeam.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.model.domain; 2 | 3 | import com.baomidou.mybatisplus.annotation.*; 4 | import lombok.Data; 5 | 6 | import java.io.Serializable; 7 | import java.util.Date; 8 | 9 | /** 10 | * 用户队伍关系实体 11 | * 12 | * @author 程序员鱼皮 13 | * @from 编程导航知识星球 14 | */ 15 | @TableName(value = "user_team") 16 | @Data 17 | public class UserTeam implements Serializable { 18 | /** 19 | * id 20 | */ 21 | @TableId(type = IdType.AUTO) 22 | private Long id; 23 | 24 | /** 25 | * 用户id 26 | */ 27 | private Long userId; 28 | 29 | /** 30 | * 队伍id 31 | */ 32 | private Long teamId; 33 | 34 | /** 35 | * 加入时间 36 | */ 37 | private Date joinTime; 38 | 39 | /** 40 | * 创建时间 41 | */ 42 | private Date createTime; 43 | 44 | /** 45 | * 46 | */ 47 | private Date updateTime; 48 | 49 | // [加入编程导航](https://www.code-nav.cn/) 深耕编程提升【两年半】、国内净值【最高】的编程社群、用心服务【20000+】求学者、帮你自学编程【不走弯路】 50 | 51 | /** 52 | * 是否删除 53 | */ 54 | @TableLogic 55 | private Integer isDelete; 56 | 57 | @TableField(exist = false) 58 | private static final long serialVersionUID = 1L; 59 | } -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/model/dto/TeamQuery.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.model.dto; 2 | 3 | import com.yupi.yupao.common.PageRequest; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | import java.util.List; 8 | 9 | 10 | /** 11 | * 队伍查询封装类 12 | * 13 | * @author 程序员鱼皮 14 | * @from 编程导航知识星球 15 | */ 16 | @EqualsAndHashCode(callSuper = true) 17 | @Data 18 | public class TeamQuery extends PageRequest { 19 | /** 20 | * id 21 | */ 22 | private Long id; 23 | 24 | /** 25 | * id 列表 26 | */ 27 | private List idList; 28 | 29 | /** 30 | * 搜索关键词(同时对队伍名称和描述搜索) 31 | */ 32 | private String searchText; 33 | 34 | /** 35 | * 队伍名称 36 | */ 37 | private String name; 38 | 39 | /** 40 | * 描述 41 | */ 42 | private String description; 43 | 44 | /** 45 | * 最大人数 46 | */ 47 | private Integer maxNum; 48 | 49 | /** 50 | * 用户id 51 | */ 52 | private Long userId; 53 | 54 | /** 55 | * 0 - 公开,1 - 私有,2 - 加密 56 | */ 57 | private Integer status; 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/model/enums/TeamStatusEnum.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.model.enums; 2 | 3 | /** 4 | * 队伍状态枚举 5 | * 6 | * @author 程序员鱼皮 7 | * @from 编程导航知识星球 8 | */ 9 | public enum TeamStatusEnum { 10 | 11 | PUBLIC(0, "公开"), 12 | PRIVATE(1, "私有"), 13 | SECRET(2, "加密"); 14 | 15 | private int value; 16 | 17 | private String text; 18 | 19 | // 原_创 [鱼_皮](https://github.com/liyupi) 20 | 21 | public static TeamStatusEnum getEnumByValue(Integer value) { 22 | if (value == null) { 23 | return null; 24 | } 25 | TeamStatusEnum[] values = TeamStatusEnum.values(); 26 | for (TeamStatusEnum teamStatusEnum : values) { 27 | if (teamStatusEnum.getValue() == value) { 28 | return teamStatusEnum; 29 | } 30 | } 31 | return null; 32 | } 33 | 34 | TeamStatusEnum(int value, String text) { 35 | this.value = value; 36 | this.text = text; 37 | } 38 | 39 | public int getValue() { 40 | return value; 41 | } 42 | 43 | public void setValue(int value) { 44 | this.value = value; 45 | } 46 | 47 | public String getText() { 48 | return text; 49 | } 50 | 51 | public void setText(String text) { 52 | this.text = text; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/model/request/TeamAddRequest.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.model.request; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | import java.util.Date; 7 | 8 | /** 9 | * 创建队伍请求体 10 | * 11 | * @author 程序员鱼皮 12 | * @from 编程导航知识星球 13 | */ 14 | @Data 15 | public class TeamAddRequest implements Serializable { 16 | 17 | private static final long serialVersionUID = 3191241716373120793L; 18 | 19 | /** 20 | * 队伍名称 21 | */ 22 | private String name; 23 | 24 | /** 25 | * 描述 26 | */ 27 | private String description; 28 | 29 | /** 30 | * 最大人数 31 | */ 32 | private Integer maxNum; 33 | 34 | /** 35 | * 过期时间 36 | */ 37 | private Date expireTime; 38 | 39 | /** 40 | * 用户id 41 | */ 42 | private Long userId; 43 | 44 | /** 45 | * 0 - 公开,1 - 私有,2 - 加密 46 | */ 47 | private Integer status; 48 | 49 | // [加入星球](https://www.code-nav.cn/) 从 0 到 1 项目实战,经验拉满!10+ 原创项目手把手教程、7 日项目提升训练营、60+ 编程经验分享直播、1000+ 项目经验笔记 50 | 51 | /** 52 | * 密码 53 | */ 54 | private String password; 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/model/request/TeamJoinRequest.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.model.request; 2 | 3 | // 本项目_所属 [程序员鱼皮](https://github.com/liyupi) 4 | 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * 用户加入队伍请求体 11 | * 12 | * @author 程序员鱼皮 13 | * @from 编程导航知识星球 14 | */ 15 | @Data 16 | public class TeamJoinRequest implements Serializable { 17 | 18 | private static final long serialVersionUID = 3191241716373120793L; 19 | 20 | /** 21 | * id 22 | */ 23 | private Long teamId; 24 | 25 | /** 26 | * 密码 27 | */ 28 | private String password; 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/model/request/TeamQuitRequest.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.model.request; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * 用户退出队伍请求体 9 | * 10 | * @author 程序员鱼皮 11 | * @from 编程导航知识星球 12 | */ 13 | @Data 14 | public class TeamQuitRequest implements Serializable { 15 | 16 | // 开发者 [coder_yupi](https://space.bilibili.com/12890453/) 17 | 18 | private static final long serialVersionUID = 3191241716373120793L; 19 | 20 | /** 21 | * id 22 | */ 23 | private Long teamId; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/model/request/TeamUpdateRequest.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.model.request; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | import java.util.Date; 7 | 8 | /** 9 | * 队伍更新请求体 10 | * 11 | * @author 程序员鱼皮 12 | * @from 编程导航知识星球 13 | */ 14 | @Data 15 | public class TeamUpdateRequest implements Serializable { 16 | 17 | private static final long serialVersionUID = 3191241716373120793L; 18 | 19 | /** 20 | * id 21 | */ 22 | private Long id; 23 | 24 | /** 25 | * 队伍名称 26 | */ 27 | private String name; 28 | 29 | /** 30 | * 描述 31 | */ 32 | private String description; 33 | 34 | /** 35 | * 过期时间 36 | */ 37 | private Date expireTime; 38 | 39 | /** 40 | * 0 - 公开,1 - 私有,2 - 加密 41 | */ 42 | private Integer status; 43 | 44 | /** 45 | * 密码 46 | */ 47 | private String password; 48 | } 49 | 50 | // 负责人【yupi】 https://space.bilibili.com/12890453/ 51 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/model/request/UserLoginRequest.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.model.request; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * 用户登录请求体 9 | * 10 | * @author 程序员鱼皮 11 | * @from 编程导航知识星球 12 | */ 13 | @Data 14 | public class UserLoginRequest implements Serializable { 15 | 16 | private static final long serialVersionUID = 3191241716373120793L; 17 | 18 | private String userAccount; 19 | 20 | // [加入编程导航](https://yupi.icu) 入门捷径+交流答疑+项目实战+求职指导,帮你自学编程不走弯路 21 | 22 | private String userPassword; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/model/request/UserRegisterRequest.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.model.request; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * 用户注册请求体 9 | * 10 | * @author 程序员鱼皮 11 | * @from 编程导航知识星球 12 | */ 13 | @Data 14 | public class UserRegisterRequest implements Serializable { 15 | 16 | private static final long serialVersionUID = 3191241716373120793L; 17 | 18 | private String userAccount; 19 | 20 | private String userPassword; 21 | 22 | private String checkPassword; 23 | 24 | private String planetCode; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/model/vo/TeamUserVO.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.model.vo; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | import java.util.Date; 7 | 8 | /** 9 | * 队伍和用户信息封装类(脱敏) 10 | * 11 | * @author 程序员鱼皮 12 | * @from 编程导航知识星球 13 | */ 14 | @Data 15 | public class TeamUserVO implements Serializable { 16 | 17 | private static final long serialVersionUID = 1899063007109226944L; 18 | 19 | /** 20 | * id 21 | */ 22 | private Long id; 23 | 24 | /** 25 | * 队伍名称 26 | */ 27 | private String name; 28 | 29 | // 作_者 【程序员_鱼皮】 https://space.bilibili.com/12890453/ 30 | 31 | /** 32 | * 描述 33 | */ 34 | private String description; 35 | 36 | /** 37 | * 最大人数 38 | */ 39 | private Integer maxNum; 40 | 41 | /** 42 | * 过期时间 43 | */ 44 | private Date expireTime; 45 | 46 | /** 47 | * 用户id 48 | */ 49 | private Long userId; 50 | 51 | /** 52 | * 0 - 公开,1 - 私有,2 - 加密 53 | */ 54 | private Integer status; 55 | 56 | /** 57 | * 创建时间 58 | */ 59 | private Date createTime; 60 | 61 | /** 62 | * 更新时间 63 | */ 64 | private Date updateTime; 65 | 66 | /** 67 | * 创建人用户信息 68 | */ 69 | private UserVO createUser; 70 | 71 | /** 72 | * 已加入的用户数 73 | */ 74 | private Integer hasJoinNum; 75 | 76 | /** 77 | * 是否已加入队伍 78 | */ 79 | private boolean hasJoin = false; 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/model/vo/UserVO.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.model.vo; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | import java.util.Date; 7 | 8 | /** 9 | * 用户包装类(脱敏) 10 | * 11 | * @author 程序员鱼皮 12 | * @from 编程导航知识星球 13 | */ 14 | @Data 15 | public class UserVO implements Serializable { 16 | /** 17 | * id 18 | */ 19 | private long id; 20 | 21 | /** 22 | * 用户昵称 23 | */ 24 | private String username; 25 | 26 | /** 27 | * 账号 28 | */ 29 | private String userAccount; 30 | 31 | /** 32 | * 用户头像 33 | */ 34 | private String avatarUrl; 35 | 36 | /** 37 | * 性别 38 | */ 39 | private Integer gender; 40 | 41 | /** 42 | * 电话 43 | */ 44 | private String phone; 45 | 46 | /** 47 | * 邮箱 48 | */ 49 | private String email; 50 | 51 | /** 52 | * 标签列表 json 53 | */ 54 | private String tags; 55 | 56 | /** 57 | * 状态 0 - 正常 58 | */ 59 | private Integer userStatus; 60 | 61 | /** 62 | * 创建时间 63 | */ 64 | private Date createTime; 65 | 66 | /** 67 | * 68 | */ 69 | private Date updateTime; 70 | 71 | /** 72 | * 用户角色 0 - 普通用户 1 - 管理员 73 | */ 74 | private Integer userRole; 75 | 76 | /** 77 | * 星球编号 78 | */ 79 | private String planetCode; 80 | 81 | private static final long serialVersionUID = 1L; 82 | } -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/once/importuser/ImportExcel.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.once.importuser; 2 | 3 | import com.alibaba.excel.EasyExcel; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 导入 Excel 9 | * 10 | * @author 程序员鱼皮 11 | * @from 编程导航知识星球 12 | */ 13 | public class ImportExcel { 14 | 15 | /** 16 | * 读取数据 17 | */ 18 | public static void main(String[] args) { 19 | // todo 记得改为自己的测试文件 20 | String fileName = "E:\\星球项目\\yupao-backend\\src\\main\\resources\\testExcel.xlsx"; 21 | // readByListener(fileName); 22 | synchronousRead(fileName); 23 | } 24 | 25 | /** 26 | * 监听器读取 27 | * 28 | * @param fileName 29 | */ 30 | public static void readByListener(String fileName) { 31 | EasyExcel.read(fileName, XingQiuTableUserInfo.class, new TableListener()).sheet().doRead(); 32 | } 33 | 34 | // [加入我们](https://yupi.icu) 从 0 到 1 项目实战,经验拉满!10+ 原创项目手把手教程、7 日项目提升训练营、1000+ 项目经验笔记、60+ 编程经验分享直播 35 | 36 | /** 37 | * 同步读 38 | * 39 | * @param fileName 40 | */ 41 | public static void synchronousRead(String fileName) { 42 | // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish 43 | List totalDataList = 44 | EasyExcel.read(fileName).head(XingQiuTableUserInfo.class).sheet().doReadSync(); 45 | for (XingQiuTableUserInfo xingQiuTableUserInfo : totalDataList) { 46 | System.out.println(xingQiuTableUserInfo); 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/once/importuser/ImportXingQiuUser.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.once.importuser; 2 | 3 | import com.alibaba.excel.EasyExcel; 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.stream.Collectors; 9 | 10 | /** 11 | * 导入星球用户到数据库 12 | * 13 | * @author 程序员鱼皮 14 | * @from 编程导航知识星球 15 | */ 16 | public class ImportXingQiuUser { 17 | 18 | public static void main(String[] args) { 19 | // todo 记得改为自己的测试文件 20 | String fileName = "E:\\星球项目\\yupao-backend\\src\\main\\resources\\prodExcel.xlsx"; 21 | // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish 22 | List userInfoList = 23 | EasyExcel.read(fileName).head(XingQiuTableUserInfo.class).sheet().doReadSync(); 24 | System.out.println("总数 = " + userInfoList.size()); 25 | Map> listMap = 26 | userInfoList.stream() 27 | .filter(userInfo -> StringUtils.isNotEmpty(userInfo.getUsername())) 28 | .collect(Collectors.groupingBy(XingQiuTableUserInfo::getUsername)); 29 | for (Map.Entry> stringListEntry : listMap.entrySet()) { 30 | if (stringListEntry.getValue().size() > 1) { 31 | System.out.println("username = " + stringListEntry.getKey()); 32 | System.out.println("1"); 33 | } 34 | } 35 | System.out.println("不重复昵称数 = " + listMap.keySet().size()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/once/importuser/InsertUsers.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.once.importuser; 2 | 3 | import com.yupi.yupao.mapper.UserMapper; 4 | import com.yupi.yupao.model.domain.User; 5 | import org.springframework.stereotype.Component; 6 | import org.springframework.util.StopWatch; 7 | 8 | import javax.annotation.Resource; 9 | 10 | /** 11 | * 导入用户任务 12 | * 13 | * @author 程序员鱼皮 14 | * @from 编程导航知识星球 15 | */ 16 | @Component 17 | public class InsertUsers { 18 | 19 | @Resource 20 | private UserMapper userMapper; 21 | 22 | /** 23 | * 批量插入用户 24 | */ 25 | // @Scheduled(initialDelay = 5000, fixedRate = Long.MAX_VALUE) 26 | public void doInsertUsers() { 27 | StopWatch stopWatch = new StopWatch(); 28 | System.out.println("goodgoodgood"); 29 | stopWatch.start(); 30 | final int INSERT_NUM = 1000; 31 | for (int i = 0; i < INSERT_NUM; i++) { 32 | User user = new User(); 33 | user.setUsername("假鱼皮"); 34 | user.setUserAccount("fakeyupi"); 35 | user.setAvatarUrl("https://636f-codenav-8grj8px727565176-1256524210.tcb.qcloud.la/img/logo.png"); 36 | user.setGender(0); 37 | user.setUserPassword("12345678"); 38 | user.setPhone("123"); 39 | user.setEmail("123@qq.com"); 40 | user.setTags("[]"); 41 | user.setUserStatus(0); 42 | user.setUserRole(0); 43 | user.setPlanetCode("11111111"); 44 | userMapper.insert(user); 45 | } 46 | stopWatch.stop(); 47 | System.out.println(stopWatch.getTotalTimeMillis()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/once/importuser/TableListener.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.once.importuser; 2 | 3 | import com.alibaba.excel.context.AnalysisContext; 4 | import com.alibaba.excel.read.listener.ReadListener; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | /** 8 | * Excel 读取监听 9 | * 10 | * @author 程序员鱼皮 11 | * @from 编程导航知识星球 12 | */ 13 | @Slf4j 14 | public class TableListener implements ReadListener { 15 | 16 | /** 17 | * 这个每一条数据解析都会来调用 18 | * 19 | * @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()} 20 | * @param context 21 | */ 22 | @Override 23 | public void invoke(XingQiuTableUserInfo data, AnalysisContext context) { 24 | System.out.println(data); 25 | } 26 | 27 | /** 28 | * 所有数据解析完成了 都会来调用 29 | * 30 | * @param context 31 | */ 32 | @Override 33 | public void doAfterAllAnalysed(AnalysisContext context) { 34 | System.out.println("已解析完成"); 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/once/importuser/XingQiuTableUserInfo.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.once.importuser; 2 | 3 | import com.alibaba.excel.annotation.ExcelProperty; 4 | import lombok.Data; 5 | 6 | /** 7 | * 星球表格用户信息 8 | */ 9 | @Data 10 | public class XingQiuTableUserInfo { 11 | 12 | /** 13 | * id 14 | */ 15 | @ExcelProperty("成员编号") 16 | private String planetCode; 17 | 18 | /** 19 | * 用户昵称 20 | */ 21 | @ExcelProperty("成员昵称") 22 | private String username; 23 | 24 | // [加入编程导航](https://t.zsxq.com/0emozsIJh) 入门捷径+交流答疑+项目实战+求职指导,帮你自学编程不走弯路 25 | 26 | } -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/service/TeamService.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.yupi.yupao.model.domain.Team; 5 | import com.yupi.yupao.model.domain.User; 6 | import com.yupi.yupao.model.dto.TeamQuery; 7 | import com.yupi.yupao.model.request.TeamJoinRequest; 8 | import com.yupi.yupao.model.request.TeamQuitRequest; 9 | import com.yupi.yupao.model.request.TeamUpdateRequest; 10 | import com.yupi.yupao.model.vo.TeamUserVO; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | * 队伍服务 16 | * 17 | * @author 程序员鱼皮 18 | * @from 编程导航知识星球 19 | */ 20 | public interface TeamService extends IService { 21 | 22 | /** 23 | * 创建队伍 24 | * 25 | * @param team 26 | * @param loginUser 27 | * @return 28 | */ 29 | long addTeam(Team team, User loginUser); 30 | 31 | /** 32 | * 搜索队伍 33 | * 34 | * @param teamQuery 35 | * @param isAdmin 36 | * @return 37 | */ 38 | List listTeams(TeamQuery teamQuery, boolean isAdmin); 39 | 40 | /** 41 | * 更新队伍 42 | * 43 | * @param teamUpdateRequest 44 | * @param loginUser 45 | * @return 46 | */ 47 | boolean updateTeam(TeamUpdateRequest teamUpdateRequest, User loginUser); 48 | 49 | /** 50 | * 加入队伍 51 | * 52 | * @param teamJoinRequest 53 | * @return 54 | */ 55 | boolean joinTeam(TeamJoinRequest teamJoinRequest, User loginUser); 56 | 57 | /** 58 | * 退出队伍 59 | * 60 | * @param teamQuitRequest 61 | * @param loginUser 62 | * @return 63 | */ 64 | boolean quitTeam(TeamQuitRequest teamQuitRequest, User loginUser); 65 | 66 | // [加入学习圈](https://t.zsxq.com/0emozsIJh) 从 0 到 1 项目实战,经验拉满!10+ 原创项目手把手教程、1000+ 项目经验笔记、7 日项目提升训练营、60+ 编程经验分享直播 67 | 68 | /** 69 | * 删除(解散)队伍 70 | * 71 | * @param id 72 | * @param loginUser 73 | * @return 74 | */ 75 | boolean deleteTeam(long id, User loginUser); 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.service; 2 | 3 | import com.yupi.yupao.common.BaseResponse; 4 | import com.yupi.yupao.model.domain.User; 5 | import com.baomidou.mybatisplus.extension.service.IService; 6 | import com.yupi.yupao.model.vo.UserVO; 7 | 8 | import javax.servlet.http.HttpServletRequest; 9 | import java.util.List; 10 | 11 | /** 12 | * 用户服务 13 | * 14 | * @author 程序员鱼皮 15 | * @from 编程导航知识星球 16 | */ 17 | public interface UserService extends IService { 18 | 19 | /** 20 | * 用户注册 21 | * 22 | * @param userAccount 用户账户 23 | * @param userPassword 用户密码 24 | * @param checkPassword 校验密码 25 | * @param planetCode 星球编号 26 | * @return 新用户 id 27 | */ 28 | long userRegister(String userAccount, String userPassword, String checkPassword, String planetCode); 29 | 30 | /** 31 | * 用户登录 32 | * 33 | * @param userAccount 用户账户 34 | * @param userPassword 用户密码 35 | * @param request 36 | * @return 脱敏后的用户信息 37 | */ 38 | User userLogin(String userAccount, String userPassword, HttpServletRequest request); 39 | 40 | /** 41 | * 用户脱敏 42 | * 43 | * @param originUser 44 | * @return 45 | */ 46 | User getSafetyUser(User originUser); 47 | 48 | /** 49 | * 用户注销 50 | * 51 | * @param request 52 | * @return 53 | */ 54 | int userLogout(HttpServletRequest request); 55 | 56 | /** 57 | * 根据标签搜索用户 58 | * 59 | * @param tagNameList 60 | * @return 61 | */ 62 | List searchUsersByTags(List tagNameList); 63 | 64 | /** 65 | * 更新用户信息 66 | * @param user 67 | * @return 68 | */ 69 | int updateUser(User user, User loginUser); 70 | 71 | /** 72 | * 获取当前登录用户信息 73 | * @return 74 | */ 75 | User getLoginUser(HttpServletRequest request); 76 | 77 | /** 78 | * 是否为管理员 79 | * 80 | * @param request 81 | * @return 82 | */ 83 | boolean isAdmin(HttpServletRequest request); 84 | 85 | /** 86 | * 是否为管理员 87 | * 88 | * @param loginUser 89 | * @return 90 | */ 91 | boolean isAdmin(User loginUser); 92 | 93 | /** 94 | * 匹配用户 95 | * @param num 96 | * @param loginUser 97 | * @return 98 | */ 99 | List matchUsers(long num, User loginUser); 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/service/UserTeamService.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.yupi.yupao.model.domain.UserTeam; 5 | 6 | /** 7 | * 用户队伍服务 8 | * 9 | * @author 程序员鱼皮 10 | * @from 编程导航知识星球 11 | */ 12 | public interface UserTeamService extends IService { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/service/impl/TeamServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 5 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 6 | import com.yupi.yupao.common.ErrorCode; 7 | import com.yupi.yupao.exception.BusinessException; 8 | import com.yupi.yupao.model.domain.User; 9 | import com.yupi.yupao.model.domain.UserTeam; 10 | import com.yupi.yupao.model.dto.TeamQuery; 11 | import com.yupi.yupao.model.enums.TeamStatusEnum; 12 | import com.yupi.yupao.model.request.TeamJoinRequest; 13 | import com.yupi.yupao.model.request.TeamQuitRequest; 14 | import com.yupi.yupao.model.request.TeamUpdateRequest; 15 | import com.yupi.yupao.model.vo.TeamUserVO; 16 | import com.yupi.yupao.model.vo.UserVO; 17 | import com.yupi.yupao.service.TeamService; 18 | import com.yupi.yupao.model.domain.Team; 19 | import com.yupi.yupao.mapper.TeamMapper; 20 | import com.yupi.yupao.service.UserService; 21 | import com.yupi.yupao.service.UserTeamService; 22 | import org.apache.commons.collections4.CollectionUtils; 23 | import org.apache.commons.lang3.StringUtils; 24 | import org.apache.commons.lang3.time.CalendarUtils; 25 | import org.apache.commons.lang3.time.DateUtils; 26 | import org.apache.poi.ss.formula.functions.T; 27 | import org.redisson.api.RLock; 28 | import org.redisson.api.RedissonClient; 29 | import org.springframework.beans.BeanUtils; 30 | import org.springframework.data.redis.core.ValueOperations; 31 | import org.springframework.stereotype.Service; 32 | import org.springframework.transaction.annotation.Transactional; 33 | 34 | import javax.annotation.Resource; 35 | import java.util.ArrayList; 36 | import java.util.Date; 37 | import java.util.List; 38 | import java.util.Optional; 39 | import java.util.concurrent.TimeUnit; 40 | 41 | /** 42 | * 队伍服务实现类 43 | * 44 | * @author 程序员鱼皮 45 | * @from 编程导航知识星球 46 | */ 47 | @Service 48 | public class TeamServiceImpl extends ServiceImpl 49 | implements TeamService { 50 | 51 | @Resource 52 | private UserTeamService userTeamService; 53 | 54 | @Resource 55 | private UserService userService; 56 | 57 | @Resource 58 | private RedissonClient redissonClient; 59 | 60 | @Override 61 | @Transactional(rollbackFor = Exception.class) 62 | public long addTeam(Team team, User loginUser) { 63 | // 1. 请求参数是否为空? 64 | if (team == null) { 65 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 66 | } 67 | // 2. 是否登录,未登录不允许创建 68 | if (loginUser == null) { 69 | throw new BusinessException(ErrorCode.NOT_LOGIN); 70 | } 71 | final long userId = loginUser.getId(); 72 | // 3. 校验信息 73 | // 1. 队伍人数 > 1 且 <= 20 74 | int maxNum = Optional.ofNullable(team.getMaxNum()).orElse(0); 75 | if (maxNum < 1 || maxNum > 20) { 76 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍人数不满足要求"); 77 | } 78 | // 2. 队伍标题 <= 20 79 | String name = team.getName(); 80 | if (StringUtils.isBlank(name) || name.length() > 20) { 81 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍标题不满足要求"); 82 | } 83 | // 3. 描述 <= 512 84 | String description = team.getDescription(); 85 | if (StringUtils.isNotBlank(description) && description.length() > 512) { 86 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍描述过长"); 87 | } 88 | // 4. status 是否公开(int)不传默认为 0(公开) 89 | int status = Optional.ofNullable(team.getStatus()).orElse(0); 90 | TeamStatusEnum statusEnum = TeamStatusEnum.getEnumByValue(status); 91 | if (statusEnum == null) { 92 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍状态不满足要求"); 93 | } 94 | // 5. 如果 status 是加密状态,一定要有密码,且密码 <= 32 95 | String password = team.getPassword(); 96 | if (TeamStatusEnum.SECRET.equals(statusEnum)) { 97 | if (StringUtils.isBlank(password) || password.length() > 32) { 98 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码设置不正确"); 99 | } 100 | } 101 | // 6. 超时时间 > 当前时间 102 | Date expireTime = team.getExpireTime(); 103 | if (new Date().after(expireTime)) { 104 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "超时时间 > 当前时间"); 105 | } 106 | // 7. 校验用户最多创建 5 个队伍 107 | // todo 有 bug,可能同时创建 100 个队伍 108 | QueryWrapper queryWrapper = new QueryWrapper<>(); 109 | queryWrapper.eq("userId", userId); 110 | long hasTeamNum = this.count(queryWrapper); 111 | if (hasTeamNum >= 5) { 112 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户最多创建 5 个队伍"); 113 | } 114 | // 8. 插入队伍信息到队伍表 115 | team.setId(null); 116 | team.setUserId(userId); 117 | boolean result = this.save(team); 118 | Long teamId = team.getId(); 119 | if (!result || teamId == null) { 120 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "创建队伍失败"); 121 | } 122 | // 9. 插入用户 => 队伍关系到关系表 123 | UserTeam userTeam = new UserTeam(); 124 | userTeam.setUserId(userId); 125 | userTeam.setTeamId(teamId); 126 | userTeam.setJoinTime(new Date()); 127 | result = userTeamService.save(userTeam); 128 | if (!result) { 129 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "创建队伍失败"); 130 | } 131 | return teamId; 132 | } 133 | 134 | @Override 135 | public List listTeams(TeamQuery teamQuery, boolean isAdmin) { 136 | QueryWrapper queryWrapper = new QueryWrapper<>(); 137 | // 组合查询条件 138 | if (teamQuery != null) { 139 | Long id = teamQuery.getId(); 140 | if (id != null && id > 0) { 141 | queryWrapper.eq("id", id); 142 | } 143 | List idList = teamQuery.getIdList(); 144 | if (CollectionUtils.isNotEmpty(idList)) { 145 | queryWrapper.in("id", idList); 146 | } 147 | String searchText = teamQuery.getSearchText(); 148 | if (StringUtils.isNotBlank(searchText)) { 149 | queryWrapper.and(qw -> qw.like("name", searchText).or().like("description", searchText)); 150 | } 151 | String name = teamQuery.getName(); 152 | if (StringUtils.isNotBlank(name)) { 153 | queryWrapper.like("name", name); 154 | } 155 | String description = teamQuery.getDescription(); 156 | if (StringUtils.isNotBlank(description)) { 157 | queryWrapper.like("description", description); 158 | } 159 | Integer maxNum = teamQuery.getMaxNum(); 160 | // 查询最大人数相等的 161 | if (maxNum != null && maxNum > 0) { 162 | queryWrapper.eq("maxNum", maxNum); 163 | } 164 | Long userId = teamQuery.getUserId(); 165 | // 根据创建人来查询 166 | if (userId != null && userId > 0) { 167 | queryWrapper.eq("userId", userId); 168 | } 169 | // 根据状态来查询 170 | Integer status = teamQuery.getStatus(); 171 | TeamStatusEnum statusEnum = TeamStatusEnum.getEnumByValue(status); 172 | if (statusEnum == null) { 173 | statusEnum = TeamStatusEnum.PUBLIC; 174 | } 175 | if (!isAdmin && statusEnum.equals(TeamStatusEnum.PRIVATE)) { 176 | throw new BusinessException(ErrorCode.NO_AUTH); 177 | } 178 | queryWrapper.eq("status", statusEnum.getValue()); 179 | } 180 | // 不展示已过期的队伍 181 | // expireTime is null or expireTime > now() 182 | queryWrapper.and(qw -> qw.gt("expireTime", new Date()).or().isNull("expireTime")); 183 | List teamList = this.list(queryWrapper); 184 | if (CollectionUtils.isEmpty(teamList)) { 185 | return new ArrayList<>(); 186 | } 187 | List teamUserVOList = new ArrayList<>(); 188 | // 关联查询创建人的用户信息 189 | for (Team team : teamList) { 190 | Long userId = team.getUserId(); 191 | if (userId == null) { 192 | continue; 193 | } 194 | User user = userService.getById(userId); 195 | TeamUserVO teamUserVO = new TeamUserVO(); 196 | BeanUtils.copyProperties(team, teamUserVO); 197 | // 脱敏用户信息 198 | if (user != null) { 199 | UserVO userVO = new UserVO(); 200 | BeanUtils.copyProperties(user, userVO); 201 | teamUserVO.setCreateUser(userVO); 202 | } 203 | teamUserVOList.add(teamUserVO); 204 | } 205 | return teamUserVOList; 206 | } 207 | 208 | @Override 209 | public boolean updateTeam(TeamUpdateRequest teamUpdateRequest, User loginUser) { 210 | if (teamUpdateRequest == null) { 211 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 212 | } 213 | Long id = teamUpdateRequest.getId(); 214 | if (id == null || id <= 0) { 215 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 216 | } 217 | Team oldTeam = this.getById(id); 218 | if (oldTeam == null) { 219 | throw new BusinessException(ErrorCode.NULL_ERROR, "队伍不存在"); 220 | } 221 | // 只有管理员或者队伍的创建者可以修改 222 | if (oldTeam.getUserId() != loginUser.getId() && !userService.isAdmin(loginUser)) { 223 | throw new BusinessException(ErrorCode.NO_AUTH); 224 | } 225 | TeamStatusEnum statusEnum = TeamStatusEnum.getEnumByValue(teamUpdateRequest.getStatus()); 226 | if (statusEnum.equals(TeamStatusEnum.SECRET)) { 227 | if (StringUtils.isBlank(teamUpdateRequest.getPassword())) { 228 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "加密房间必须要设置密码"); 229 | } 230 | } 231 | Team updateTeam = new Team(); 232 | BeanUtils.copyProperties(teamUpdateRequest, updateTeam); 233 | return this.updateById(updateTeam); 234 | } 235 | 236 | @Override 237 | public boolean joinTeam(TeamJoinRequest teamJoinRequest, User loginUser) { 238 | if (teamJoinRequest == null) { 239 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 240 | } 241 | Long teamId = teamJoinRequest.getTeamId(); 242 | Team team = getTeamById(teamId); 243 | Date expireTime = team.getExpireTime(); 244 | if (expireTime != null && expireTime.before(new Date())) { 245 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍已过期"); 246 | } 247 | Integer status = team.getStatus(); 248 | TeamStatusEnum teamStatusEnum = TeamStatusEnum.getEnumByValue(status); 249 | if (TeamStatusEnum.PRIVATE.equals(teamStatusEnum)) { 250 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "禁止加入私有队伍"); 251 | } 252 | String password = teamJoinRequest.getPassword(); 253 | if (TeamStatusEnum.SECRET.equals(teamStatusEnum)) { 254 | if (StringUtils.isBlank(password) || !password.equals(team.getPassword())) { 255 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误"); 256 | } 257 | } 258 | // 该用户已加入的队伍数量 259 | long userId = loginUser.getId(); 260 | // 只有一个线程能获取到锁 261 | RLock lock = redissonClient.getLock("yupao:join_team"); 262 | try { 263 | // 抢到锁并执行 264 | while (true) { 265 | if (lock.tryLock(0, -1, TimeUnit.MILLISECONDS)) { 266 | System.out.println("getLock: " + Thread.currentThread().getId()); 267 | QueryWrapper userTeamQueryWrapper = new QueryWrapper<>(); 268 | userTeamQueryWrapper.eq("userId", userId); 269 | long hasJoinNum = userTeamService.count(userTeamQueryWrapper); 270 | if (hasJoinNum > 5) { 271 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "最多创建和加入 5 个队伍"); 272 | } 273 | // 不能重复加入已加入的队伍 274 | userTeamQueryWrapper = new QueryWrapper<>(); 275 | userTeamQueryWrapper.eq("userId", userId); 276 | userTeamQueryWrapper.eq("teamId", teamId); 277 | long hasUserJoinTeam = userTeamService.count(userTeamQueryWrapper); 278 | if (hasUserJoinTeam > 0) { 279 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户已加入该队伍"); 280 | } 281 | // 已加入队伍的人数 282 | long teamHasJoinNum = this.countTeamUserByTeamId(teamId); 283 | if (teamHasJoinNum >= team.getMaxNum()) { 284 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍已满"); 285 | } 286 | // 修改队伍信息 287 | UserTeam userTeam = new UserTeam(); 288 | userTeam.setUserId(userId); 289 | userTeam.setTeamId(teamId); 290 | userTeam.setJoinTime(new Date()); 291 | return userTeamService.save(userTeam); 292 | } 293 | } 294 | } catch (InterruptedException e) { 295 | log.error("doCacheRecommendUser error", e); 296 | return false; 297 | } finally { 298 | // 只能释放自己的锁 299 | if (lock.isHeldByCurrentThread()) { 300 | System.out.println("unLock: " + Thread.currentThread().getId()); 301 | lock.unlock(); 302 | } 303 | } 304 | } 305 | 306 | @Override 307 | @Transactional(rollbackFor = Exception.class) 308 | public boolean quitTeam(TeamQuitRequest teamQuitRequest, User loginUser) { 309 | if (teamQuitRequest == null) { 310 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 311 | } 312 | Long teamId = teamQuitRequest.getTeamId(); 313 | Team team = getTeamById(teamId); 314 | long userId = loginUser.getId(); 315 | UserTeam queryUserTeam = new UserTeam(); 316 | queryUserTeam.setTeamId(teamId); 317 | queryUserTeam.setUserId(userId); 318 | QueryWrapper queryWrapper = new QueryWrapper<>(queryUserTeam); 319 | long count = userTeamService.count(queryWrapper); 320 | if (count == 0) { 321 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "未加入队伍"); 322 | } 323 | long teamHasJoinNum = this.countTeamUserByTeamId(teamId); 324 | // 队伍只剩一人,解散 325 | if (teamHasJoinNum == 1) { 326 | // 删除队伍 327 | this.removeById(teamId); 328 | } else { 329 | // 队伍还剩至少两人 330 | // 是队长 331 | if (team.getUserId() == userId) { 332 | // 把队伍转移给最早加入的用户 333 | // 1. 查询已加入队伍的所有用户和加入时间 334 | QueryWrapper userTeamQueryWrapper = new QueryWrapper<>(); 335 | userTeamQueryWrapper.eq("teamId", teamId); 336 | userTeamQueryWrapper.last("order by id asc limit 2"); 337 | List userTeamList = userTeamService.list(userTeamQueryWrapper); 338 | if (CollectionUtils.isEmpty(userTeamList) || userTeamList.size() <= 1) { 339 | throw new BusinessException(ErrorCode.SYSTEM_ERROR); 340 | } 341 | UserTeam nextUserTeam = userTeamList.get(1); 342 | Long nextTeamLeaderId = nextUserTeam.getUserId(); 343 | // 更新当前队伍的队长 344 | Team updateTeam = new Team(); 345 | updateTeam.setId(teamId); 346 | updateTeam.setUserId(nextTeamLeaderId); 347 | boolean result = this.updateById(updateTeam); 348 | if (!result) { 349 | throw new BusinessException(ErrorCode.SYSTEM_ERROR, "更新队伍队长失败"); 350 | } 351 | } 352 | } 353 | // 移除关系 354 | return userTeamService.remove(queryWrapper); 355 | } 356 | 357 | @Override 358 | @Transactional(rollbackFor = Exception.class) 359 | public boolean deleteTeam(long id, User loginUser) { 360 | // 校验队伍是否存在 361 | Team team = getTeamById(id); 362 | long teamId = team.getId(); 363 | // 校验你是不是队伍的队长 364 | if (team.getUserId() != loginUser.getId()) { 365 | throw new BusinessException(ErrorCode.NO_AUTH, "无访问权限"); 366 | } 367 | // 移除所有加入队伍的关联信息 368 | QueryWrapper userTeamQueryWrapper = new QueryWrapper<>(); 369 | userTeamQueryWrapper.eq("teamId", teamId); 370 | boolean result = userTeamService.remove(userTeamQueryWrapper); 371 | if (!result) { 372 | throw new BusinessException(ErrorCode.SYSTEM_ERROR, "删除队伍关联信息失败"); 373 | } 374 | // 删除队伍 375 | return this.removeById(teamId); 376 | } 377 | 378 | /** 379 | * 根据 id 获取队伍信息 380 | * 381 | * @param teamId 382 | * @return 383 | */ 384 | private Team getTeamById(Long teamId) { 385 | if (teamId == null || teamId <= 0) { 386 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 387 | } 388 | Team team = this.getById(teamId); 389 | if (team == null) { 390 | throw new BusinessException(ErrorCode.NULL_ERROR, "队伍不存在"); 391 | } 392 | return team; 393 | } 394 | 395 | /** 396 | * 获取某队伍当前人数 397 | * 398 | * @param teamId 399 | * @return 400 | */ 401 | private long countTeamUserByTeamId(long teamId) { 402 | QueryWrapper userTeamQueryWrapper = new QueryWrapper<>(); 403 | userTeamQueryWrapper.eq("teamId", teamId); 404 | return userTeamService.count(userTeamQueryWrapper); 405 | } 406 | } 407 | 408 | 409 | 410 | 411 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/service/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 5 | import com.google.gson.Gson; 6 | import com.google.gson.TypeAdapter; 7 | import com.google.gson.reflect.TypeToken; 8 | import com.yupi.yupao.common.ErrorCode; 9 | import com.yupi.yupao.constant.UserConstant; 10 | import com.yupi.yupao.exception.BusinessException; 11 | import com.yupi.yupao.model.domain.User; 12 | import com.yupi.yupao.model.vo.UserVO; 13 | import com.yupi.yupao.service.UserService; 14 | import com.yupi.yupao.mapper.UserMapper; 15 | import com.yupi.yupao.utils.AlgorithmUtils; 16 | import lombok.extern.slf4j.Slf4j; 17 | import org.apache.commons.lang3.StringUtils; 18 | import org.apache.commons.math3.util.Pair; 19 | import org.springframework.stereotype.Service; 20 | import org.springframework.util.CollectionUtils; 21 | import org.springframework.util.DigestUtils; 22 | 23 | import javax.annotation.Resource; 24 | import javax.servlet.http.HttpServletRequest; 25 | import java.util.*; 26 | import java.util.regex.Matcher; 27 | import java.util.regex.Pattern; 28 | import java.util.stream.Collectors; 29 | import java.util.stream.Stream; 30 | 31 | import static com.yupi.yupao.constant.UserConstant.USER_LOGIN_STATE; 32 | 33 | /** 34 | * 用户服务实现类 35 | * 36 | * @author 程序员鱼皮 37 | * @from 编程导航知识星球 38 | */ 39 | @Service 40 | @Slf4j 41 | public class UserServiceImpl extends ServiceImpl 42 | implements UserService { 43 | 44 | @Resource 45 | private UserMapper userMapper; 46 | 47 | /** 48 | * 盐值,混淆密码 49 | */ 50 | private static final String SALT = "yupi"; 51 | 52 | @Override 53 | public long userRegister(String userAccount, String userPassword, String checkPassword, String planetCode) { 54 | // 1. 校验 55 | if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword, planetCode)) { 56 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空"); 57 | } 58 | if (userAccount.length() < 4) { 59 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号过短"); 60 | } 61 | if (userPassword.length() < 8 || checkPassword.length() < 8) { 62 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过短"); 63 | } 64 | if (planetCode.length() > 5) { 65 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "星球编号过长"); 66 | } 67 | // 账户不能包含特殊字符 68 | String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]"; 69 | Matcher matcher = Pattern.compile(validPattern).matcher(userAccount); 70 | if (matcher.find()) { 71 | return -1; 72 | } 73 | // 密码和校验密码相同 74 | if (!userPassword.equals(checkPassword)) { 75 | return -1; 76 | } 77 | // 账户不能重复 78 | QueryWrapper queryWrapper = new QueryWrapper<>(); 79 | queryWrapper.eq("userAccount", userAccount); 80 | long count = userMapper.selectCount(queryWrapper); 81 | if (count > 0) { 82 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复"); 83 | } 84 | // 星球编号不能重复 85 | queryWrapper = new QueryWrapper<>(); 86 | queryWrapper.eq("planetCode", planetCode); 87 | count = userMapper.selectCount(queryWrapper); 88 | if (count > 0) { 89 | throw new BusinessException(ErrorCode.PARAMS_ERROR, "编号重复"); 90 | } 91 | // 2. 加密 92 | String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes()); 93 | // 3. 插入数据 94 | User user = new User(); 95 | user.setUserAccount(userAccount); 96 | user.setUserPassword(encryptPassword); 97 | user.setPlanetCode(planetCode); 98 | boolean saveResult = this.save(user); 99 | if (!saveResult) { 100 | return -1; 101 | } 102 | return user.getId(); 103 | } 104 | 105 | // [加入编程导航](https://www.code-nav.cn/) 入门捷径+交流答疑+项目实战+求职指导,帮你自学编程不走弯路 106 | 107 | @Override 108 | public User userLogin(String userAccount, String userPassword, HttpServletRequest request) { 109 | // 1. 校验 110 | if (StringUtils.isAnyBlank(userAccount, userPassword)) { 111 | return null; 112 | } 113 | if (userAccount.length() < 4) { 114 | return null; 115 | } 116 | if (userPassword.length() < 8) { 117 | return null; 118 | } 119 | // 账户不能包含特殊字符 120 | String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]"; 121 | Matcher matcher = Pattern.compile(validPattern).matcher(userAccount); 122 | if (matcher.find()) { 123 | return null; 124 | } 125 | // 2. 加密 126 | String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes()); 127 | // 查询用户是否存在 128 | QueryWrapper queryWrapper = new QueryWrapper<>(); 129 | queryWrapper.eq("userAccount", userAccount); 130 | queryWrapper.eq("userPassword", encryptPassword); 131 | User user = userMapper.selectOne(queryWrapper); 132 | // 用户不存在 133 | if (user == null) { 134 | log.info("user login failed, userAccount cannot match userPassword"); 135 | return null; 136 | } 137 | // 3. 用户脱敏 138 | User safetyUser = getSafetyUser(user); 139 | // 4. 记录用户的登录态 140 | request.getSession().setAttribute(USER_LOGIN_STATE, safetyUser); 141 | return safetyUser; 142 | } 143 | 144 | /** 145 | * 用户脱敏 146 | * 147 | * @param originUser 148 | * @return 149 | */ 150 | @Override 151 | public User getSafetyUser(User originUser) { 152 | if (originUser == null) { 153 | return null; 154 | } 155 | User safetyUser = new User(); 156 | safetyUser.setId(originUser.getId()); 157 | safetyUser.setUsername(originUser.getUsername()); 158 | safetyUser.setUserAccount(originUser.getUserAccount()); 159 | safetyUser.setAvatarUrl(originUser.getAvatarUrl()); 160 | safetyUser.setGender(originUser.getGender()); 161 | safetyUser.setPhone(originUser.getPhone()); 162 | safetyUser.setEmail(originUser.getEmail()); 163 | safetyUser.setPlanetCode(originUser.getPlanetCode()); 164 | safetyUser.setUserRole(originUser.getUserRole()); 165 | safetyUser.setUserStatus(originUser.getUserStatus()); 166 | safetyUser.setCreateTime(originUser.getCreateTime()); 167 | safetyUser.setTags(originUser.getTags()); 168 | return safetyUser; 169 | } 170 | 171 | /** 172 | * 用户注销 173 | * 174 | * @param request 175 | */ 176 | @Override 177 | public int userLogout(HttpServletRequest request) { 178 | // 移除登录态 179 | request.getSession().removeAttribute(USER_LOGIN_STATE); 180 | return 1; 181 | } 182 | 183 | /** 184 | * 根据标签搜索用户(内存过滤) 185 | * 186 | * @param tagNameList 用户要拥有的标签 187 | * @return 188 | */ 189 | @Override 190 | public List searchUsersByTags(List tagNameList) { 191 | if (CollectionUtils.isEmpty(tagNameList)) { 192 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 193 | } 194 | // 1. 先查询所有用户 195 | QueryWrapper queryWrapper = new QueryWrapper<>(); 196 | List userList = userMapper.selectList(queryWrapper); 197 | Gson gson = new Gson(); 198 | // 2. 在内存中判断是否包含要求的标签 199 | return userList.stream().filter(user -> { 200 | String tagsStr = user.getTags(); 201 | Set tempTagNameSet = gson.fromJson(tagsStr, new TypeToken>() { 202 | }.getType()); 203 | tempTagNameSet = Optional.ofNullable(tempTagNameSet).orElse(new HashSet<>()); 204 | for (String tagName : tagNameList) { 205 | if (!tempTagNameSet.contains(tagName)) { 206 | return false; 207 | } 208 | } 209 | return true; 210 | }).map(this::getSafetyUser).collect(Collectors.toList()); 211 | } 212 | 213 | @Override 214 | public int updateUser(User user, User loginUser) { 215 | long userId = user.getId(); 216 | if (userId <= 0) { 217 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 218 | } 219 | // todo 补充校验,如果用户没有传任何要更新的值,就直接报错,不用执行 update 语句 220 | // 如果是管理员,允许更新任意用户 221 | // 如果不是管理员,只允许更新当前(自己的)信息 222 | if (!isAdmin(loginUser) && userId != loginUser.getId()) { 223 | throw new BusinessException(ErrorCode.NO_AUTH); 224 | } 225 | User oldUser = userMapper.selectById(userId); 226 | if (oldUser == null) { 227 | throw new BusinessException(ErrorCode.NULL_ERROR); 228 | } 229 | return userMapper.updateById(user); 230 | } 231 | 232 | @Override 233 | public User getLoginUser(HttpServletRequest request) { 234 | if (request == null) { 235 | return null; 236 | } 237 | Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE); 238 | if (userObj == null) { 239 | throw new BusinessException(ErrorCode.NO_AUTH); 240 | } 241 | return (User) userObj; 242 | } 243 | 244 | /** 245 | * 是否为管理员 246 | * 247 | * @param request 248 | * @return 249 | */ 250 | @Override 251 | public boolean isAdmin(HttpServletRequest request) { 252 | // 仅管理员可查询 253 | Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE); 254 | User user = (User) userObj; 255 | return user != null && user.getUserRole() == UserConstant.ADMIN_ROLE; 256 | } 257 | 258 | /** 259 | * 是否为管理员 260 | * 261 | * @param loginUser 262 | * @return 263 | */ 264 | @Override 265 | public boolean isAdmin(User loginUser) { 266 | return loginUser != null && loginUser.getUserRole() == UserConstant.ADMIN_ROLE; 267 | } 268 | 269 | @Override 270 | public List matchUsers(long num, User loginUser) { 271 | QueryWrapper queryWrapper = new QueryWrapper<>(); 272 | queryWrapper.select("id", "tags"); 273 | queryWrapper.isNotNull("tags"); 274 | List userList = this.list(queryWrapper); 275 | String tags = loginUser.getTags(); 276 | Gson gson = new Gson(); 277 | List tagList = gson.fromJson(tags, new TypeToken>() { 278 | }.getType()); 279 | // 用户列表的下标 => 相似度 280 | List> list = new ArrayList<>(); 281 | // 依次计算所有用户和当前用户的相似度 282 | for (int i = 0; i < userList.size(); i++) { 283 | User user = userList.get(i); 284 | String userTags = user.getTags(); 285 | // 无标签或者为当前用户自己 286 | if (StringUtils.isBlank(userTags) || user.getId() == loginUser.getId()) { 287 | continue; 288 | } 289 | List userTagList = gson.fromJson(userTags, new TypeToken>() { 290 | }.getType()); 291 | // 计算分数 292 | long distance = AlgorithmUtils.minDistance(tagList, userTagList); 293 | list.add(new Pair<>(user, distance)); 294 | } 295 | // 按编辑距离由小到大排序 296 | List> topUserPairList = list.stream() 297 | .sorted((a, b) -> (int) (a.getValue() - b.getValue())) 298 | .limit(num) 299 | .collect(Collectors.toList()); 300 | // 原本顺序的 userId 列表 301 | List userIdList = topUserPairList.stream().map(pair -> pair.getKey().getId()).collect(Collectors.toList()); 302 | QueryWrapper userQueryWrapper = new QueryWrapper<>(); 303 | userQueryWrapper.in("id", userIdList); 304 | // 1, 3, 2 305 | // User1、User2、User3 306 | // 1 => User1, 2 => User2, 3 => User3 307 | Map> userIdUserListMap = this.list(userQueryWrapper) 308 | .stream() 309 | .map(user -> getSafetyUser(user)) 310 | .collect(Collectors.groupingBy(User::getId)); 311 | List finalUserList = new ArrayList<>(); 312 | for (Long userId : userIdList) { 313 | finalUserList.add(userIdUserListMap.get(userId).get(0)); 314 | } 315 | return finalUserList; 316 | } 317 | 318 | /** 319 | * 根据标签搜索用户(SQL 查询版) 320 | * 321 | * @param tagNameList 用户要拥有的标签 322 | * @return 323 | */ 324 | @Deprecated 325 | private List searchUsersByTagsBySQL(List tagNameList) { 326 | if (CollectionUtils.isEmpty(tagNameList)) { 327 | throw new BusinessException(ErrorCode.PARAMS_ERROR); 328 | } 329 | QueryWrapper queryWrapper = new QueryWrapper<>(); 330 | // 拼接 and 查询 331 | // like '%Java%' and like '%Python%' 332 | for (String tagName : tagNameList) { 333 | queryWrapper = queryWrapper.like("tags", tagName); 334 | } 335 | List userList = userMapper.selectList(queryWrapper); 336 | return userList.stream().map(this::getSafetyUser).collect(Collectors.toList()); 337 | } 338 | 339 | } 340 | 341 | 342 | 343 | 344 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/service/impl/UserTeamServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.service.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import com.yupi.yupao.service.UserTeamService; 5 | import com.yupi.yupao.model.domain.UserTeam; 6 | import com.yupi.yupao.mapper.UserTeamMapper; 7 | import org.springframework.stereotype.Service; 8 | 9 | /** 10 | * 用户队伍服务实现类 11 | * 12 | * @author 程序员鱼皮 13 | * @from 编程导航知识星球 14 | */ 15 | @Service 16 | public class UserTeamServiceImpl extends ServiceImpl 17 | implements UserTeamService { 18 | 19 | } 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main/java/com/yupi/yupao/utils/AlgorithmUtils.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.utils; 2 | 3 | import java.util.List; 4 | import java.util.Objects; 5 | 6 | /** 7 | * 算法工具类 8 | * 9 | * @author 程序员鱼皮 10 | * @from 编程导航知识星球 11 | */ 12 | public class AlgorithmUtils { 13 | 14 | /** 15 | * 编辑距离算法(用于计算最相似的两组标签) 16 | * 原理:https://blog.csdn.net/DBC_121/article/details/104198838 17 | * 18 | * @param tagList1 19 | * @param tagList2 20 | * @return 21 | */ 22 | public static int minDistance(List tagList1, List tagList2) { 23 | int n = tagList1.size(); 24 | int m = tagList2.size(); 25 | 26 | if (n * m == 0) { 27 | return n + m; 28 | } 29 | 30 | int[][] d = new int[n + 1][m + 1]; 31 | for (int i = 0; i < n + 1; i++) { 32 | d[i][0] = i; 33 | } 34 | 35 | for (int j = 0; j < m + 1; j++) { 36 | d[0][j] = j; 37 | } 38 | 39 | for (int i = 1; i < n + 1; i++) { 40 | for (int j = 1; j < m + 1; j++) { 41 | int left = d[i - 1][j] + 1; 42 | int down = d[i][j - 1] + 1; 43 | int left_down = d[i - 1][j - 1]; 44 | if (!Objects.equals(tagList1.get(i - 1), tagList2.get(j - 1))) { 45 | left_down += 1; 46 | } 47 | d[i][j] = Math.min(left, Math.min(down, left_down)); 48 | } 49 | } 50 | return d[n][m]; 51 | } 52 | 53 | // [编程学习交流圈](https://www.code-nav.cn/) 连接万名编程爱好者,一起优秀!20000+ 小伙伴交流分享、40+ 大厂嘉宾一对一答疑、100+ 各方向编程交流群、4000+ 编程问答参考 54 | 55 | /** 56 | * 编辑距离算法(用于计算最相似的两个字符串) 57 | * 原理:https://blog.csdn.net/DBC_121/article/details/104198838 58 | * 59 | * @param word1 60 | * @param word2 61 | * @return 62 | */ 63 | public static int minDistance(String word1, String word2) { 64 | int n = word1.length(); 65 | int m = word2.length(); 66 | 67 | if (n * m == 0) { 68 | return n + m; 69 | } 70 | 71 | int[][] d = new int[n + 1][m + 1]; 72 | for (int i = 0; i < n + 1; i++) { 73 | d[i][0] = i; 74 | } 75 | 76 | for (int j = 0; j < m + 1; j++) { 77 | d[0][j] = j; 78 | } 79 | 80 | for (int i = 1; i < n + 1; i++) { 81 | for (int j = 1; j < m + 1; j++) { 82 | int left = d[i - 1][j] + 1; 83 | int down = d[i][j - 1] + 1; 84 | int left_down = d[i - 1][j - 1]; 85 | if (word1.charAt(i - 1) != word2.charAt(j - 1)) { 86 | left_down += 1; 87 | } 88 | d[i][j] = Math.min(left, Math.min(down, left_down)); 89 | } 90 | } 91 | return d[n][m]; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/resources/application-prod.yml: -------------------------------------------------------------------------------- 1 | # 线上配置文件 2 | # @author 程序员鱼皮 3 | # @from 编程导航知识星球 4 | # 注意开源时这个文件不要提交、或者不要填真实配置 5 | spring: 6 | # DataSource Config 7 | datasource: 8 | driver-class-name: com.mysql.cj.jdbc.Driver 9 | url: jdbc:mysql://localhost:3306/yupao 10 | username: root 11 | password: 123456 12 | # session 失效时间 13 | session: 14 | timeout: 86400 15 | server: 16 | address: 0.0.0.0 17 | 18 | # [编程知识星球](https://yupi.icu) 零基础快速入门编程,不走弯路!30+ 原创学习路线和专栏、1000+ 编程精华文章、500+ 编程学习指南、20T+ 编程资源汇总 -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | # 公共配置文件 2 | # @author 程序员鱼皮 3 | # @from 编程导航知识星球 4 | spring: 5 | profiles: 6 | active: dev 7 | application: 8 | name: yupao-backend 9 | # DataSource Config 10 | datasource: 11 | driver-class-name: com.mysql.cj.jdbc.Driver 12 | url: jdbc:mysql://localhost:3306/yupao?serverTimezone=Asia/Shanghai 13 | username: root 14 | password: 123456 15 | # session 失效时间(分钟) 16 | session: 17 | timeout: 86400 18 | store-type: redis 19 | mvc: 20 | pathmatch: 21 | matching-strategy: ANT_PATH_MATCHER 22 | # redis 配置 23 | redis: 24 | port: 6379 25 | host: localhost 26 | database: 1 27 | server: 28 | port: 8080 29 | servlet: 30 | context-path: /api 31 | session: 32 | cookie: 33 | domain: localhost 34 | mybatis-plus: 35 | configuration: 36 | map-underscore-to-camel-case: false 37 | # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl 38 | global-config: 39 | db-config: 40 | logic-delete-field: isDelete # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2) 41 | logic-delete-value: 1 # 逻辑已删除值(默认为 1) 42 | logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) 43 | -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | 伙伴匹配系统 by 程序员鱼皮 - 编程导航知识星球 https://yupi.icu -------------------------------------------------------------------------------- /src/main/resources/mapper/TeamMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | id,name,description, 27 | maxNum,expireTime,userId, 28 | status,password,createTime, 29 | updateTime,isDelete 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/resources/mapper/UserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | id,username,userAccount, 28 | avatarUrl,gender,userPassword, 29 | phone,email,userStatus, 30 | createTime,updateTime,isDelete, 31 | userRole,planetCode 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/main/resources/mapper/UserTeamMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | id,userId,teamId, 23 | joinTime,createTime,updateTime, 24 | isDelete 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/resources/prodExcel.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liyupi/yupao-backend-public/80c3cf6485543aee4876c61806ff2a917b6f9e62/src/main/resources/prodExcel.xlsx -------------------------------------------------------------------------------- /src/main/resources/testExcel.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liyupi/yupao-backend-public/80c3cf6485543aee4876c61806ff2a917b6f9e62/src/main/resources/testExcel.xlsx -------------------------------------------------------------------------------- /src/main/resources/请尊重原创: -------------------------------------------------------------------------------- 1 | [加入编程导航](https://t.zsxq.com/0emozsIJh) 入门捷径+交流答疑+项目实战+求职指导,帮你自学编程不走弯路 -------------------------------------------------------------------------------- /src/test/.copyright: -------------------------------------------------------------------------------- 1 | [加入编程导航](https://yupi.icu) 深耕编程提升【两年半】、国内净值【最高】的编程社群、用心服务【20000+】求学者、帮你自学编程【不走弯路】 -------------------------------------------------------------------------------- /src/test/java/com/yupi/yupao/MyApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | import org.springframework.util.DigestUtils; 6 | 7 | import java.security.NoSuchAlgorithmException; 8 | 9 | /** 10 | * 测试类 11 | * 12 | * @author 程序员鱼皮 13 | * @from 编程导航知识星球 14 | */ 15 | @SpringBootTest 16 | class MyApplicationTest { 17 | 18 | @Test 19 | void testDigest() throws NoSuchAlgorithmException { 20 | String newPassword = DigestUtils.md5DigestAsHex(("abcd" + "mypassword").getBytes()); 21 | System.out.println(newPassword); 22 | } 23 | 24 | @Test 25 | void contextLoads() { 26 | 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/yupi/yupao/service/AlgorithmUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.service; 2 | 3 | import com.yupi.yupao.utils.AlgorithmUtils; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | /** 10 | * 算法工具类测试 11 | * 12 | * @author 程序员鱼皮 13 | * @from 编程导航知识星球 14 | */ 15 | public class AlgorithmUtilsTest { 16 | 17 | 18 | @Test 19 | void test() { 20 | String str1 = "鱼皮是狗"; 21 | String str2 = "鱼皮不是狗"; 22 | String str3 = "负责人 [yupi](https://t.zsxq.com/0emozsIJh)"; 23 | // String str4 = "鱼皮是猫"; 24 | // 1 25 | int score1 = AlgorithmUtils.minDistance(str1, str2); 26 | // 3 27 | int score2 = AlgorithmUtils.minDistance(str1, str3); 28 | System.out.println(score1); 29 | System.out.println(score2); 30 | } 31 | 32 | @Test 33 | void testCompareTags() { 34 | List tagList1 = Arrays.asList("Java", "大一", "男"); 35 | List tagList2 = Arrays.asList("Java", "大一", "女"); 36 | List tagList3 = Arrays.asList("Python", "大二", "女"); 37 | // 1 38 | int score1 = AlgorithmUtils.minDistance(tagList1, tagList2); 39 | // 3 40 | int score2 = AlgorithmUtils.minDistance(tagList1, tagList3); 41 | System.out.println(score1); 42 | System.out.println(score2); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/com/yupi/yupao/service/InsertUsersTest.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.service; 2 | 3 | import com.yupi.yupao.model.domain.User; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.util.StopWatch; 7 | 8 | import javax.annotation.Resource; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.concurrent.*; 12 | 13 | /** 14 | * 导入用户测试 15 | * 16 | * @author 程序员鱼皮 17 | * @from 编程导航知识星球 18 | */ 19 | @SpringBootTest 20 | public class InsertUsersTest { 21 | 22 | @Resource 23 | private UserService userService; 24 | 25 | private ExecutorService executorService = new ThreadPoolExecutor(40, 1000, 10000, TimeUnit.MINUTES, new ArrayBlockingQueue<>(10000)); 26 | 27 | /** 28 | * 批量插入用户 29 | */ 30 | @Test 31 | public void doInsertUsers() { 32 | StopWatch stopWatch = new StopWatch(); 33 | stopWatch.start(); 34 | final int INSERT_NUM = 100000; 35 | List userList = new ArrayList<>(); 36 | for (int i = 0; i < INSERT_NUM; i++) { 37 | User user = new User(); 38 | user.setUsername("原_创 【鱼_皮】https://t.zsxq.com/0emozsIJh"); 39 | user.setUserAccount("fakeyupi"); 40 | user.setAvatarUrl("https://636f-codenav-8grj8px727565176-1256524210.tcb.qcloud.la/img/logo.png"); 41 | user.setGender(0); 42 | user.setUserPassword("12345678"); 43 | user.setPhone("123"); 44 | user.setEmail("123@qq.com"); 45 | user.setTags("[]"); 46 | user.setUserStatus(0); 47 | user.setUserRole(0); 48 | user.setPlanetCode("11111111"); 49 | userList.add(user); 50 | } 51 | // 20 秒 10 万条 52 | userService.saveBatch(userList, 10000); 53 | stopWatch.stop(); 54 | System.out.println(stopWatch.getTotalTimeMillis()); 55 | } 56 | 57 | /** 58 | * 并发批量插入用户 59 | */ 60 | @Test 61 | public void doConcurrencyInsertUsers() { 62 | StopWatch stopWatch = new StopWatch(); 63 | stopWatch.start(); 64 | // 分十组 65 | int batchSize = 5000; 66 | int j = 0; 67 | List> futureList = new ArrayList<>(); 68 | for (int i = 0; i < 100; i++) { 69 | List userList = new ArrayList<>(); 70 | while (true) { 71 | j++; 72 | User user = new User(); 73 | user.setUsername("假鱼皮"); 74 | user.setUserAccount("fakeyupi"); 75 | user.setAvatarUrl("https://636f-codenav-8grj8px727565176-1256524210.tcb.qcloud.la/img/logo.png"); 76 | user.setGender(0); 77 | user.setUserPassword("12345678"); 78 | user.setPhone("123"); 79 | user.setEmail("123@qq.com"); 80 | user.setTags("[]"); 81 | user.setUserStatus(0); 82 | user.setUserRole(0); 83 | user.setPlanetCode("11111111"); 84 | userList.add(user); 85 | if (j % batchSize == 0) { 86 | break; 87 | } 88 | } 89 | // 异步执行 90 | CompletableFuture future = CompletableFuture.runAsync(() -> { 91 | System.out.println("threadName: " + Thread.currentThread().getName()); 92 | userService.saveBatch(userList, batchSize); 93 | }, executorService); 94 | futureList.add(future); 95 | } 96 | CompletableFuture.allOf(futureList.toArray(new CompletableFuture[]{})).join(); 97 | // 20 秒 10 万条 98 | stopWatch.stop(); 99 | System.out.println(stopWatch.getTotalTimeMillis()); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/test/java/com/yupi/yupao/service/RedisTest.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.service; 2 | 3 | import com.yupi.yupao.model.domain.User; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | import org.springframework.data.redis.core.ValueOperations; 9 | 10 | import javax.annotation.Resource; 11 | 12 | /** 13 | * Redis 测试 14 | * 15 | * @author 程序员鱼皮 16 | * @from 编程导航知识星球 17 | */ 18 | @SpringBootTest 19 | public class RedisTest { 20 | 21 | @Resource 22 | private RedisTemplate redisTemplate; 23 | 24 | @Test 25 | void test() { 26 | ValueOperations valueOperations = redisTemplate.opsForValue(); 27 | // 增 28 | valueOperations.set("yupiString", "dog"); 29 | valueOperations.set("yupiInt", 1); 30 | valueOperations.set("yupiDouble", 2.0); 31 | User user = new User(); 32 | user.setId(1L); 33 | user.setUsername("yupi"); 34 | valueOperations.set("yupiUser", user); 35 | // 查 36 | Object yupi = valueOperations.get("yupiString"); 37 | Assertions.assertTrue("dog".equals((String) yupi)); 38 | yupi = valueOperations.get("yupiInt"); 39 | Assertions.assertTrue(1 == (Integer) yupi); 40 | yupi = valueOperations.get("yupiDouble"); 41 | Assertions.assertTrue(2.0 == (Double) yupi); 42 | System.out.println(valueOperations.get("yupiUser")); 43 | valueOperations.set("yupiString", "dog"); 44 | redisTemplate.delete("yupiString"); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/yupi/yupao/service/RedissonTest.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.service; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.redisson.api.RList; 5 | import org.redisson.api.RLock; 6 | import org.redisson.api.RMap; 7 | import org.redisson.api.RedissonClient; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | 10 | import javax.annotation.Resource; 11 | import java.util.ArrayList; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.concurrent.TimeUnit; 16 | 17 | /** 18 | * Redisson 测试 19 | * 20 | * @author 程序员鱼皮 21 | * @from 编程导航知识星球 22 | */ 23 | @SpringBootTest 24 | public class RedissonTest { 25 | 26 | @Resource 27 | private RedissonClient redissonClient; 28 | 29 | @Test 30 | void test() { 31 | // list,数据存在本地 JVM 内存中 32 | List list = new ArrayList<>(); 33 | list.add("yupi"); 34 | System.out.println("list:" + list.get(0)); 35 | 36 | list.remove(0); 37 | 38 | // 数据存在 redis 的内存中 39 | RList rList = redissonClient.getList("test-list"); 40 | rList.add("yupi"); 41 | System.out.println("rlist:" + rList.get(0)); 42 | rList.remove(0); 43 | 44 | // map 45 | Map map = new HashMap<>(); 46 | map.put("yupi", 10); 47 | map.get("yupi"); 48 | 49 | RMap map1 = redissonClient.getMap("test-map"); 50 | 51 | // set 52 | 53 | // stack 54 | 55 | 56 | } 57 | 58 | // [编程知识星球](https://yupi.icu) 连接万名编程爱好者,一起优秀!20000+ 小伙伴交流分享、100+ 各方向编程交流群、40+ 大厂嘉宾一对一答疑、4000+ 编程问答参考 59 | 60 | @Test 61 | void testWatchDog() { 62 | RLock lock = redissonClient.getLock("yupao:precachejob:docache:lock"); 63 | try { 64 | // 只有一个线程能获取到锁 65 | if (lock.tryLock(0, -1, TimeUnit.MILLISECONDS)) { 66 | Thread.sleep(300000); 67 | System.out.println("getLock: " + Thread.currentThread().getId()); 68 | } 69 | } catch (InterruptedException e) { 70 | System.out.println(e.getMessage()); 71 | } finally { 72 | // 只能释放自己的锁 73 | if (lock.isHeldByCurrentThread()) { 74 | System.out.println("unLock: " + Thread.currentThread().getId()); 75 | lock.unlock(); 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/com/yupi/yupao/service/UserServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.yupi.yupao.service; 2 | 3 | import com.yupi.yupao.model.domain.User; 4 | import org.junit.Assert; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | 9 | import javax.annotation.Resource; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | 13 | /** 14 | * 用户服务测试 15 | * 16 | * @author 程序员鱼皮 17 | * @from 编程导航知识星球 18 | */ 19 | @SpringBootTest 20 | public class UserServiceTest { 21 | 22 | @Resource 23 | private UserService userService; 24 | 25 | @Test 26 | public void testAddUser() { 27 | User user = new User(); 28 | user.setUsername("本项目_所属 [程序员鱼皮](https://t.zsxq.com/0emozsIJh)\n"); 29 | user.setUserAccount("123"); 30 | user.setAvatarUrl("https://636f-codenav-8grj8px727565176-1256524210.tcb.qcloud.la/img/logo.png"); 31 | user.setGender(0); 32 | user.setUserPassword("xxx"); 33 | user.setPhone("123"); 34 | user.setEmail("456"); 35 | boolean result = userService.save(user); 36 | System.out.println(user.getId()); 37 | Assertions.assertTrue(result); 38 | } 39 | 40 | @Test 41 | public void testUpdateUser() { 42 | User user = new User(); 43 | user.setId(1L); 44 | user.setUsername("dogYupi"); 45 | user.setUserAccount("123"); 46 | user.setAvatarUrl("https://636f-codenav-8grj8px727565176-1256524210.tcb.qcloud.la/img/logo.png"); 47 | user.setGender(0); 48 | user.setUserPassword("xxx"); 49 | user.setPhone("123"); 50 | user.setEmail("456"); 51 | boolean result = userService.updateById(user); 52 | Assertions.assertTrue(result); 53 | } 54 | 55 | @Test 56 | public void testDeleteUser() { 57 | boolean result = userService.removeById(1L); 58 | Assertions.assertTrue(result); 59 | } 60 | 61 | @Test 62 | public void testGetUser() { 63 | User user = userService.getById(1L); 64 | Assertions.assertNotNull(user); 65 | } 66 | 67 | @Test 68 | void userRegister() { 69 | String userAccount = "yupi"; 70 | String userPassword = "12345678"; 71 | String checkPassword = "12345678"; 72 | String planetCode = "1"; 73 | long result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode); 74 | Assertions.assertEquals(-1, result); 75 | userAccount = "yu"; 76 | result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode); 77 | Assertions.assertEquals(-1, result); 78 | userAccount = "yupi"; 79 | userPassword = "123456"; 80 | result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode); 81 | Assertions.assertEquals(-1, result); 82 | userAccount = "yu pi"; 83 | userPassword = "12345678"; 84 | result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode); 85 | Assertions.assertEquals(-1, result); 86 | checkPassword = "123456789"; 87 | result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode); 88 | Assertions.assertEquals(-1, result); 89 | userAccount = "dogYupi"; 90 | checkPassword = "12345678"; 91 | result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode); 92 | Assertions.assertEquals(-1, result); 93 | userAccount = "yupi"; 94 | result = userService.userRegister(userAccount, userPassword, checkPassword, planetCode); 95 | Assertions.assertEquals(-1, result); 96 | } 97 | 98 | @Test 99 | public void testSearchUsersByTags() { 100 | List tagNameList = Arrays.asList("java", "python"); 101 | List userList = userService.searchUsersByTags(tagNameList); 102 | Assert.assertNotNull(userList); 103 | } 104 | } -------------------------------------------------------------------------------- /保护知识,尊重原创: -------------------------------------------------------------------------------- 1 | 作者 [程序员鱼皮](https://space.bilibili.com/12890453/) 2 | --------------------------------------------------------------------------------