需求分析:
理想的应用场景:
我需要去创建一个队伍,这也是伙伴匹配项目的核心,别人可以加入队伍一起做某一件事情。
1:用户可以 创建 一个队伍,设置队伍的人数、队伍名称(标题)、描述、超时时间
- 队长、剩余的人数
- 聊天?
- 公开 或 private 或加密
- 用户创建队伍最多 5 个
2:展示队伍列表,根据名称搜索队伍 ,信息流中不展示已过期的队伍
3:修改队伍信息
4:用户可以加入队伍(其他人、未满、未过期),允许加入多个队伍,但是要有个上限
5:用户可以退出队伍(如果队长 退出,权限转移给第二早加入的用户 —— 先来后到)
6:队长可以解散队伍
7:分享队伍 =》 邀请其他用户加入队伍
需求分析先想到这里,等后面真正写代码的时候再看看有没有什么需要补充
数据库表设计:
我们需要思考,要完成上面这个功能,我们需要几张表
第一反应肯定是要一张队伍表 Team
但是我们继续想,如果只有一张队伍表,你这个队伍中肯定有成员,
一个成员可以加不同的队伍
一个队伍肯定有很多不同的成员
这里就设计到多对多的关系了
数据库表设计如何解决多对多的问题?
有两种方式
1:建立一张关联表 userteam(便于修改,查询性能高一点,可以选择这个,不用全表遍历)
2:在用户表和队伍表中添加字段(便于查询,不用写多对多的代码,可以直接根据队伍查用户、根据用户查队伍)
比如在用户表中添加队伍字段,说明这个用户加入了那些队伍
在队伍表中添加用户字段,说明有哪些用户加入了这个队伍
但是这样确实是不需要再开一张表,查询起来也快
不过我感觉这样有一个问题:就是当你的队伍失效或者解散的时候,你要修改在这个队伍中的每个用户的队伍字段,这就很麻烦了
当然这个项目采用关联表会比较合适,以后做项目得照情况分析。
Team表字段:
create table team
(
id bigint auto_increment comment 'id'
primary key,
name varchar(256) not null comment '队伍名称',
description varchar(1024) null comment '描述',
maxNum int default 1 not null comment '最大人数',
expireTime datetime null comment '过期时间',
userId bigint comment '用户id',
status int default 0 not null comment '0 - 公开,1 - 私有,2 - 加密',
password varchar(512) null comment '密码',
createTime datetime default CURRENT_TIMESTAMP null comment '创建时间',
updateTime datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP,
isDelete tinyint default 0 not null comment '是否删除'
)
comment '队伍';
userteam关联表字段:
create table user_team
(
id bigint auto_increment comment 'id'
primary key,
userId bigint comment '用户id',
teamId bigint comment '队伍id',
joinTime datetime null comment '加入时间',
createTime datetime default CURRENT_TIMESTAMP null comment '创建时间',
updateTime datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP,
isDelete tinyint default 0 not null comment '是否删除'
)
comment '用户队伍关系';
下面一步就是编写后端代码了
后端代码编写:
1:实体生成:
利用Mybatis-plus的插件生成Team,UserTeam实体类
Mapper及其注解 ,Service。
2:基本的增删改查:
这个直接用一下Mybatis-plus的框架写起来很快
package com.usercenter.usercenterproject.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.usercenter.usercenterproject.Pojo.*;
import com.usercenter.usercenterproject.Pojo.Request.PageRequest;
import com.usercenter.usercenterproject.Pojo.Request.TeamAddRequest;
import com.usercenter.usercenterproject.Pojo.dto.TeamQuery;
import com.usercenter.usercenterproject.exception.BusinessException;
import com.usercenter.usercenterproject.service.TeamService;
import com.usercenter.usercenterproject.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/*
队伍接口
*/
@RestController
@RequestMapping("/team")
@Slf4j
@CrossOrigin
public class TeamController {
@Resource
private TeamService teamService;
@Resource
private UserService userService;
@PostMapping("/add")
public BaseResponse<Long> addTeam(@RequestBody TeamAddRequest teamAddRequest, HttpServletRequest request){
if(teamAddRequest==null){
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
log.info("增加队伍"+teamAddRequest);
User loginUser = userService.getCurrent(request);
Team team = new Team();
BeanUtils.copyProperties(teamAddRequest,team);
final Long save = teamService.addTeam(team, loginUser);
return ResultUtils.success(save);
}
@DeleteMapping("/delete")
public BaseResponse<Boolean> deleteTeam(Long id){
if(id<0){
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
log.info("删除队伍"+id);
final boolean flag = teamService.removeById(id);
if(!flag){
throw new BusinessException(ErrorCode.SYSTEM_ERROR);
}
return ResultUtils.success(flag);
}
@PostMapping("/update")
public BaseResponse<Boolean> updateTeam(@RequestBody Team team){
if(team==null){
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
log.info("修改队伍信息"+team);
final boolean flag = teamService.updateById(team);
if(!flag){
throw new BusinessException(ErrorCode.SYSTEM_ERROR);
}
return ResultUtils.success(flag);
}
@GetMapping("/get")
public BaseResponse<Team> getTeam(Long id){
if(id<0){
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
log.info("查询队伍信息"+id);
final Team team = teamService.getById(id);
if(team==null){
throw new BusinessException(ErrorCode.SYSTEM_ERROR);
}
return ResultUtils.success(team);
}
@GetMapping("/list")
public BaseResponse<List<Team>> listTeam(TeamQuery teamQuery){
if (teamQuery==null){
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
log.info("批量查询队伍信息"+teamQuery);
Team team = new Team();
BeanUtils.copyProperties(team,teamQuery);
QueryWrapper<Team> queryWrapper = new QueryWrapper<>(team);
final List<Team> teamList = teamService.list(queryWrapper);
return ResultUtils.success(teamList);
}
@GetMapping("/list/page")
public BaseResponse<Page<Team>> listpageTeam(TeamQuery teamQuery){
if (teamQuery==null){
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
log.info("分页查询队伍信息"+teamQuery);
Page<Team> page = new Page<>(teamQuery.getPageNum(),teamQuery.getPageSize());
Team team = new Team();
BeanUtils.copyProperties(teamQuery,team);
QueryWrapper<Team> queryWrapper = new QueryWrapper<>(team);
Page<Team> teamPage = teamService.page(page, queryWrapper);
return ResultUtils.success(teamPage);
}
}
这里的addTeam方法已经被改过了。
注意点:
1:全量查询时(listTeam):封装了一个TeamQuery这样一个dto用来接收前端参数,其实也是告诉前端,只需要传这个实体里面的属性即可,(这个蛮好理解,有些字段前端不需要,比如id,当前时间字段)
BeanUtils:
在Java中,BeanUtils是Apache Commons库中的一个类,用于对Java Bean对象进行复制、克隆、属性拷贝
这里就用了一个复制属性的方法
BeanUtils.copyProperties(team,teamQuery);
将teamQuery里面的属性赋值给我们新建的一个team对象,然后用Mybatis-plus里的list方法进行查询(Mybatis-plus里的list方法必须要用Team对象进行查询)
2:分页查询时,我们也创建了一个实体类PageRequest
@Data
public class PageRequest {
/*
页面大小
*/
int pageSize = 10;
/*
当前是第几页
*/
int pageNum = 1;
}
然后我们用TeamQuery去继承这个PageRequest,这样我们在进行分页查询时,TeamQuery里面也会有分页的参数。
3:添加队伍功能具体设计:
首先编写代码之前,我们需要想好,我们添加队伍这个需求的具体逻辑
- 请求参数是否为空?
- 是否登录,未登录不允许创建
- 校验信息
-
- 队伍人数 > 1 且 <= 20
- 队伍标题 <= 20
- 描述 <= 512
- status 是否公开(int)不传默认为 0(公开)
- 如果 status 是加密状态,一定要有密码,且密码 <= 32
- 超时时间 > 当前时间
- 校验用户最多创建 5 个队伍
- 插入队伍信息到队伍表
- 插入用户 => 队伍关系到关系表
总结下来就是前四步是校验,后两步是将信息插入数据库中(队伍表和关系表)
具体分析这段代码:
Controller层代码在上面已经写了
Service层代码:
@Override
@Transactional(rollbackFor = Exception.class)
public Long addTeam(Team team, User loginUser) {
//1. 请求参数是否为空?
if (team == null) {
throw new BusinessException(ErrorCode.NULL_ERROR);
}
//2. 是否登录,未登录不允许创建
if (loginUser == null) {
throw new BusinessException(ErrorCode.NOT_LOGIN);
}
final Long userId = loginUser.getId();
//3. 校验信息
//a. 队伍人数 > 1 且 <= 20
int maxNum = Optional.ofNullable(team.getMaxNum()).orElse(0);
if (maxNum < 1 || maxNum > 20) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "队伍人数错误");
}
//b. 队伍标题 <= 20
String name = team.getName();
if (StringUtils.isBlank(name) || name.length() > 20) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "队伍标头错误");
}
//c. 描述 <= 512
String description = team.getDescription();
if (StringUtils.isBlank(description) && description.length() > 512) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "描述信息错误");
}
//d. status 是否公开(int)不传默认为 0(公开)
Integer status = team.getStatus();
TeamStatusConstant teamStatusConstant = TeamStatusConstant.getEnumByValue(status);
if (teamStatusConstant == null) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "队伍状态不满足要求");
}
//e. 如果 status 是加密状态,一定要有密码,且密码 <= 32
String password = team.getPassword();
if (TeamStatusConstant.SECRET.equals(status)) {
if (StringUtils.isBlank(password) || password.length() > 32) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "密码错误");
}
}
//f. 超时时间 > 当前时间
Date expireTime = team.getExpireTime();
if (new Date().after(expireTime)) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "时间错误");
}
//g. 校验用户最多创建 5 个队伍
QueryWrapper<Team> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("id", userId);
final long count = this.count(queryWrapper);
if (count > 5) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "用户创建队伍过多");
}
//4. 插入队伍信息到队伍表
team.setId(null);
team.setUserId(userId);
final boolean save1 = this.save(team);
if (!save1) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "插入队伍错误");
}
//5. 插入用户 => 队伍关系到关系
UserTeam userTeam = new UserTeam();
userTeam.setUserId(userId);
userTeam.setTeamId(team.getId());
userTeam.setCreateTime(new Date());
boolean save = userTeamService.save(userTeam);
if (!save) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "创建队伍失败");
}
return team.getId();
}
Optional类:
Java的Optional类是在Java 8中引入的一个类,用于解决空指针异常的问题,Java的Optional类是一种优雅地处理可能为空的对象的机制
int maxNum = Optional.ofNullable(team.getMaxNum()).orElse(0);
这段就是判断这个team.getMaxNum是否为空,如果为空,就添一个默认值0
如果不为空就为原值
StringUtils类:
这个类提供了很多用于处理字符串的实用工具方法
if (StringUtils.isBlank(name) || name.length() > 20) {}
这一段就是判断这个name字符串是否为空,如果为空,就返回true
测试(Knife4j):
在添加队伍功能的逻辑中有对当前登录用户的校验
所以测试之前记得登录
状态码返回200说明添加成功,在数据库中也看到了对应的队伍
测试时碰到的小坑:
我一开始输入的过期时间是2024-7-14
直接给我报了一个日期格式不匹配的错误
后面问了GPT
把过期时间改成了2024-07-14
这才测试成功
Cannot deserialize value of type `java.util.Date` from String "2024 7 14":