├── .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 | 
10 |
11 |
12 |
13 | ## 项目简介
14 |
15 | [编程导航知识星球](https://yuyuanweb.feishu.cn/wiki/VC1qwmX9diCBK3kidyec74vFnde) 原创项目,一个帮助大家找到志同道合的伙伴的移动端网站(APP 风格)。包括用户登录注册、更新个人信息、按标签搜索用户、推荐相似用户、组队等功能。
16 |
17 | 主页:
18 |
19 | 
20 |
21 |
22 |
23 | 找伙伴:
24 |
25 | 
26 |
27 |
28 |
29 | 组队功能:
30 |
31 | 
32 |
33 |
34 |
35 | 创建队伍:
36 |
37 | 
38 |
39 |
40 |
41 | 个人信息及修改:
42 |
43 | 
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 | 
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 | 
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