伙伴匹配(后端)-- 组队功能

文章目录

  • 需求分析
  • 数据库表设计
  • 基础接口开发
  • 系统设计及开发
    • 创建队伍
      • 业务逻辑
      • 细化业务层代码
      • 优化完成控制层
    • 查询队伍列表
      • 业务逻辑
      • vo层
      • 业务层代码
      • 接口代码
    • 修改队伍信息
      • 业务逻辑
      • 同样在请求包里封装一个用户登录请求体
      • 接口修改
      • 业务实现类
    • 用户可以加入队伍
      • 同样在请求包里封装一个用户加入队伍请求体
      • 接口实现
      • 业务层实现
    • 用户退出队伍
      • 业务逻辑
      • 新建退出请求体
      • 接口实现
      • 业务实现
    • 队长解散队伍
      • 业务逻辑
      • 接口实现
      • 业务实现
    • 获取当前用户已加入的队伍
    • 获取当前用户已创建的队伍
  • 遇到的bug:循环依赖问题 todo

需求分析

用户可以创建队伍,设置队伍的人数,队伍名称,描述,超时时间
用户可以加入队伍(其他人,未满,未过期)
用户可以退出队伍(队长退出,权限转移给第二早加入的用户)
创建人可以修改队伍信息
队长可以解散队伍
用户可以分享队伍

数据库表设计

队伍表 team
字段:

  • id 主键 bigint
  • name 队伍名称
  • description 描述
  • maxNum 最大人数
  • expireTime 过期时间
  • userId 创建人id
  • status 0-公开,1-私有,2-加密
  • password密码
  • createTime
  • updateTime
  • isDelete
-- 队伍表
create table team
(
    id          bigint auto_increment comment 'id' primary key,
    name        varchar(256)       not null comment '队伍名称',
    description varchar(1024) null comment '描述',
    maxNum      int      default 1 not null comment '最大人数',
    expireTime  datetime null comment '过期时间',
    userId      bigint comment '用户id(队长 id)',
    status      int      default 0 not null comment '0 - 公开,1 - 私有,2 - 加密',
    password    varchar(512) null comment '密码',
    createTime  datetime default CURRENT_TIMESTAMP null comment '创建时间',
    updateTime  datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP,
    isDelete    tinyint  default 0 not null comment '是否删除'
) comment '队伍';

两个关系:

  1. 用户加了哪些队伍
  2. 队伍里有哪些用户

方式:

  1. 建立用户-队伍关系表 teamId userId(便于修改,查询性能高,不用全表遍历)
  2. 用户表补充已加入队伍,队伍表补充已加入的用户字段 (便于查询,不用写多对多的代码,可以直接根据队伍查用户,根据用户查队伍)

用户-队伍表 user_team
字段:

  • id主键
  • userId 用户id
  • teamId 队伍id
  • joinTime 加入时间
  • createTime
  • updateTime
  • isDelete
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 '用户队伍关系';

基础接口开发

:::info
为什么需要请求参数包装类?
:::

  1. 请求参数名称/类型和实体类不一样
  2. 有一些参数用不到,如果要自动生成接口文档,会增加理解成本
  3. 多个实体类映射到同一个对象

:::info
为什么需要包装类?
:::
有些字段需要隐藏,不能返回给前端
或者有些字段某些方法是不关心的
在model.dto下新建一个类

@Data
public class TeamQuery {
    /**
     * id
     */
    private Long id;

    /**
     * 队伍名称
     */
    private String name;

    /**
     * 描述
     */
    private String description;

    /**
     * 最大人数
     */
    private Integer maxNum;

    /**
     * 用户id
     */
    private Long userId;

    /**
     * 0 - 公开,1 - 私有,2 - 加密
     */
    private Integer status;
}

在common包下新建分页请求参数包装类

@Data
public class PageRequest implements Serializable {

    private static final long serialVersionUID = -4162304142710323660L;

    /**
     * 页面大小
     */
    protected int pageSize;

    /**
     * 当前是第几页
     */
    protected int pageNum;
}

同时在TeamQuery上继承这个分页请求,后面接口会用上
image.png
add添加队伍,delete删除队伍,update更新队伍,list查询,list/page分页查询
先硬写增删改查 —— > 细化逻辑

    @PostMapping("/add")
    public BaseResponse<Long> addTeam(@RequestBody Team team) {
        if (team == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        boolean save = teamService.save(team);
        if (!save) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "添加失败");
        }
        return ResultUtils.success(team.getId());
    }

    @PostMapping("/delete")
    public BaseResponse<Boolean> deleteTeam(@RequestBody Long id) {
        if (id <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        boolean result = teamService.removeById(id);
        if (!result) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "删除失败");
        }
        return ResultUtils.success(true);
    }

    @PostMapping("/update")
    public BaseResponse<Boolean> updateTeam(@RequestBody Team team) {
        if (team == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        boolean result = teamService.updateById(team);
        if (!result) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "更新失败");
        }
        return ResultUtils.success(true);
    }

    @GetMapping("/get")
    public BaseResponse<Team> getTeamById(Long id) {
        if (id <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Team team = teamService.getById(id);
        if (team == null) {
            throw new BusinessException(ErrorCode.NULL_ERROR);
        }
        return ResultUtils.success(team);
    }

    /**
     * 前端闯入一些信息,可以查询到与这些信息相匹配的队伍信息
     * @param teamQuery
     * @return
     */
    @GetMapping("/list")
    public BaseResponse<List<Team>> listTeams(TeamQuery teamQuery) {
        if (teamQuery == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Team team = new Team();
        BeanUtils.copyProperties(teamQuery,team);
        QueryWrapper<Team> queryWrapper = new QueryWrapper<>(team);
        List<Team> teamList = teamService.list(queryWrapper);
        return ResultUtils.success(teamList);
    }

    @GetMapping("/list/page")
    public BaseResponse<Page<Team>> listTeamsByPage(TeamQuery teamQuery){
        if(teamQuery == null){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Team team = new Team();
        BeanUtils.copyProperties(teamQuery,team);
        QueryWrapper<Team> teamQueryWrapper = new QueryWrapper<>(team);
        int current = teamQuery.getPageNum();
        int pageSize = teamQuery.getPageSize();
        Page<Team> page = new Page<>(current,pageSize);
        Page<Team> pageTeams = teamService.page(page, teamQueryWrapper);
        return ResultUtils.success(pageTeams);
    }

系统设计及开发

创建队伍

业务逻辑

细化业务层代码

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long addTeam(Team team, User loginUser) {
        //1.请求参数是否为空
        if (team == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        //2.是否登录,未登录不允许创建
        if (loginUser == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN);
        }
        //3.检验信息
        //(1).队伍人数>1且<=20
        int maxNum = Optional.ofNullable(team.getMaxNum()).orElse(0);
        if (maxNum <= 1 || maxNum > 20) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍人数不符合要求");
        }
        //(2).队伍标题 <=20
        String name = team.getName();
        if (name.length() > 20) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍标题过长");
        }
        // 3. 描述<= 512
        String description = team.getDescription();
        if (description.length() > 0 && description.length() > 512) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍描述过长");
        }
        //4.status 是否公开,不传默认为0
        Integer status = Optional.ofNullable(team.getStatus()).orElse(0);
        TeamStatusEnum teamStatusEnum = TeamStatusEnum.getEnumByValue(status);
        if (teamStatusEnum == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍状态不满足要求");
        }
        //5.如果status是加密状态,一定要密码 且密码<=32
        String password = team.getPassword();
        if (TeamStatusEnum.SECRET.equals(teamStatusEnum)) {
            if (StringUtils.isBlank(password) || password.length() > 32) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码设置不正确");
            }
        }
        //6.超出时间 > 当前时间
        Date expireTime = team.getExpireTime();
        if (new Date().after(expireTime)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "超时时间 > 过期时间");
        }
        //7.校验用户最多创建5个队伍
        //todo 有bug。可能同时创建100个队伍
        Long userId = loginUser.getId();
        QueryWrapper<Team> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userId", userId);
        long count = this.count(queryWrapper);
        if (count >= 5) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户最多创建5个队伍");
        }
        //8.插入队伍消息到队伍表
        team.setId(null);
        team.setUserId(userId);
        boolean save = this.save(team);
        if (!save) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "创建队伍失败");
        }
        //9. 插入用户 ==> 队伍关系 到关系表
        UserTeam userTeam = new UserTeam();
        Long teamId = team.getId();
        userTeam.setTeamId(teamId);
        userTeam.setUserId(userId);
        userTeam.setJoinTime(new Date());
        boolean result = userTeamService.save(userTeam);
        if(!result){
            throw new BusinessException(ErrorCode.SYSTEM_ERROR,"创建队伍失败");
        }
        return teamId;
    }

优化完成控制层

这里我们因为完善了业务层,所以在controller层可以简便下代码,我们需要新建一个队伍添加请求封装类(便于前端知道该输入哪些参数) 新的请求封装类位于model包里的request包

@Data
public class TeamAddRequest {

    private static final long serialVersionUID = -4162304142710323660L;

    /**
     * 队伍名称
     */
    private String name;

    /**
     * 描述
     */
    private String description;

    /**
     * 最大人数
     */
    private Integer maxNum;

    /**
     * 过期时间
     */
    private Date expireTime;

    /**
     * 用户id
     */
    private Long userId;

    /**
     * 0 - 公开,1 - 私有,2 - 加密
     */
    private Integer status;

    /**
     * 密码
     */
    private String password;
}

修改的addTeam接口如下:

    @PostMapping("/add")
    public BaseResponse<Long> addTeam(@RequestBody TeamAddRequest teamAddRequest, HttpServletRequest request) {
        if (teamAddRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Team team = new Team();
        BeanUtils.copyProperties(teamAddRequest,team);
        User loginUser = userService.getLoginUser(request);
        Long teamId = teamService.addTeam(team, loginUser);
        return ResultUtils.success(teamId);
    }

查询队伍列表

业务逻辑

分页展示队伍的列表,根据名称,最大人数搜索队伍,信息流中不展示已过期的队伍

  1. 从请求参数中取出队伍名称,如果存在则作为查询条件
  2. 不展示已过期队伍
  3. 可以通过某个关键词同时对名称和描述进行查询
  4. 只有管理员才能查看加密还有未公开的房间
  5. 关联查询已加入队伍的用户信息

关联查询是什么来着???(数据库知识又忘了)image.png

vo层

是返回给前端的数据封装类(脱敏)
在model包新建vo包,并创建TeamUserVO(队伍和用户信息封装类),UserVO类(用户封装类),这两个类是返回给前端看

/**
 * 队伍和用户信息封装类(脱敏)
 *
 * @author yupi
 */
@Data
public class TeamUserVO implements Serializable {

    private static final long serialVersionUID = 163478861968488713L;
    /**
     * id
     */
    private Long id;

    /**
     * 队伍名称
     */
    private String name;

    /**
     * 描述
     */
    private String description;

    /**
     * 最大人数
     */
    private Integer maxNum;

    /**
     * 过期时间
     */
    private Date expireTime;

    /**
     * 用户id
     */
    private Long userId;

    /**
     * 0 - 公开,1 - 私有,2 - 加密
     */
    private Integer status;

    /**
     * 创建时间
     */
    private Date createTime;

    /**
     * 更新时间
     */
    private Date updateTime;

    /**
     * 创建人用户信息
     */
    private UserVO createUser;

    /**
     * 已加入的用户数
     */
    private Integer hasJoinNum;

    /**
     * 是否已加入队伍
     */
    private boolean hasJoin = false;
}
/**
 * 用户包装类(脱敏)
 */
@Data
public class UserVO implements Serializable {
    
    /**
     * id
     */
    private long id;

    /**
     * 用户昵称
     */
    private String username;

    /**
     * 账号
     */
    private String userAccount;

    /**
     * 用户头像
     */
    private String avatarUrl;

    /**
     * 性别
     */
    private Integer gender;

    /**
     * 电话
     */
    private String phone;

    /**
     * 邮箱
     */
    private String email;

    /**
     * 标签列表 json
     */
    private String tags;

    /**
     * 状态 0 - 正常
     */
    private Integer userStatus;

    /**
     * 创建时间
     */
    private Date createTime;

    /**
     * 
     */
    private Date updateTime;

    /**
     * 用户角色 0 - 普通用户 1 - 管理员
     */
    private Integer userRole;

    /**
     * 星球编号
     */
    private String planetCode;

    private static final long serialVersionUID = 1L;
}

(todo)问:dto与vo有什么区别?
vo是封装了返回给前端的数据对象,是为了防止数据暴露
dto是封装了请求参数对象,跟request包下一样,是前端需要用到的,前端拿到这个可以看自己需要用什么

业务层代码

    @Override
    public List<TeamUserVO> listTeams(TeamQuery teamQuery, boolean isAdmin) {
        if(teamQuery == null){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        QueryWrapper<Team> queryWrapper = new QueryWrapper<>();
        String searchText = teamQuery.getSearchText();
        if(StringUtils.isNotBlank(searchText)){
            queryWrapper.and(qw -> qw.like("name",searchText).or().like("description",searchText));
        }
        String name = teamQuery.getName();
        if(StringUtils.isNotBlank(name)){
            queryWrapper.like("name",name);
        }
        String description = teamQuery.getDescription();
        if(StringUtils.isNotBlank(description)){
            queryWrapper.like("description",description);
        }
        Integer maxNum = teamQuery.getMaxNum();
        if(maxNum != null && maxNum > 0){
            queryWrapper.eq("maxNum",maxNum);
        }
        Long userId = teamQuery.getUserId();
        if(userId != null && userId > 0) {
            queryWrapper.eq("userId",userId);
        }
        //状态校验
        Integer status = teamQuery.getStatus();
        TeamStatusEnum teamStatusEnum = TeamStatusEnum.getEnumByValue(status);
        if(teamStatusEnum == null){
            teamStatusEnum = TeamStatusEnum.PUBLIC;
        }

        if(!isAdmin && !TeamStatusEnum.PUBLIC.equals(teamStatusEnum)){
            throw new BusinessException(ErrorCode.NO_AUTH);
        }
        queryWrapper.eq("status",teamStatusEnum.getValue());
        //不展示已过期的队伍
       //要求查询结果中的 "expireTime" 字段的值要么大于当前时间(未过期),要么为空(未设置过期时间)。
        queryWrapper.and(qw -> qw.gt("expireTime",new Date()).or().isNull("expireTime"));
        //查询出来
        List<Team> teamList = this.list(queryWrapper);
        if(CollectionUtils.isEmpty(teamList)){
            return new ArrayList<>();
        }

        List<TeamUserVO> teamUserVOList = new ArrayList<>();
        for(Team team : teamList){
            //关联查询创建人的用户信息?
            userId = team.getUserId();
            if(userId == null){
                continue;
            }
            TeamUserVO teamUserVO = new TeamUserVO();
            BeanUtils.copyProperties(team,teamUserVO);
            User user = userService.getUserById(userId);
            //脱敏信息
            if(user != null){
                UserVO userVO = new UserVO();
                BeanUtils.copyProperties(user,userVO);
                teamUserVO.setCreateUser(userVO);
            }
            teamUserVOList.add(teamUserVO);
        }
        return teamUserVOList;
    }
}

接口代码

    @GetMapping("/list")
    public BaseResponse<List<TeamUserVO>> listTeams(TeamQuery teamQuery,HttpServletRequest request) {
        if (teamQuery == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        boolean isAdmin = userService.isAdmin(request);
        List<TeamUserVO> teamList = teamService.listTeams(teamQuery,isAdmin);
        return ResultUtils.success(teamList);
    }

在这里记录一下,自己第一次debug找出错误的经历,什么都不传应该是全部都能查出来的,但是却什么都没有查出来,之前一直不会看,这一个好好分析了一下,用快捷键一步一步往下走,发现到这里的时候status为null,还去数据库查询了,应该是查teamStatusEnum.getValue()才对,把status改为teamStatusEnum.getValue()就ok了
image.png

修改队伍信息

业务逻辑

  1. 请求参数是否为空
  2. 查询队伍是否存在
  3. 只有管理员和队伍的创建者可以修改
  4. 用户传入的新值和老值一致,就不用update了(可自行实现,降低数据库使用次数)
  5. 如果队伍状态为加密,必须要有密码才能修改
  6. 更新成功

同样在请求包里封装一个用户登录请求体

/**
 * 用户登录请求体
 *
 * @author shaosao
 */
@Data
public class TeamUpdateRequest implements Serializable {

    private static final long serialVersionUID = -6043915331807008592L;

    /**
     * id
     */
    private Long id;

    /**
     * 队伍名称
     */
    private String name;

    /**
     * 描述
     */
    private String description;

    /**
     * 过期时间
     */
    private Date expireTime;

    /**
     * 0 - 公开,1 - 私有,2 - 加密
     */
    private Integer status;

    /**
     * 密码
     */
    private String password;
}

接口修改

    @PostMapping("/update")
    public BaseResponse<Boolean> updateTeam(@RequestBody TeamUpdateRequest teamUpdateRequest,HttpServletRequest request) {
        if (teamUpdateRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        User loginUser = userService.getLoginUser(request);
        boolean result = teamService.updateTeam(teamUpdateRequest,loginUser);
        if (!result) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "更新失败");
        }
        return ResultUtils.success(true);
    }

业务实现类

 @Override
    public boolean updateTeam(TeamUpdateRequest teamUpdateRequest,User loginUser) {
        if (teamUpdateRequest == null){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Long id = teamUpdateRequest.getId();
        if(id == null || id < 0){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Team oldTeam = this.getById(id);
        boolean isAdmin = userService.isAdmin(loginUser);
        if(oldTeam.getUserId() != loginUser.getId() && !isAdmin){
            throw new BusinessException(ErrorCode.NO_AUTH);
        }
        String password = teamUpdateRequest.getPassword();
        Integer status = teamUpdateRequest.getStatus();
        TeamStatusEnum statusEnum = TeamStatusEnum.getEnumByValue(status);
        if(statusEnum.equals(TeamStatusEnum.SECRET)){
            if(StringUtils.isBlank(password)){
                throw new BusinessException(ErrorCode.PARAMS_ERROR,"加密房间必须要设置密码");
            }
        }
        Team team = new Team();
        BeanUtils.copyProperties(teamUpdateRequest,team);
        return this.updateById(team);
    }

用户可以加入队伍

  1. 用户最多加入5个队伍 todo
  2. 只能加入未满,未过期
  3. 不能重复加入已加入的队伍(幂等性)
  4. 如果加入的队伍是加密的,必须密码匹配才可以
  5. 禁止加入私有的队伍
  6. 新增队伍-用户关联信息

(数据库查询操作放在后面,节省性能)
todo 注意并发请求时可能会出现问题

同样在请求包里封装一个用户加入队伍请求体

/**
 * 用户加入队伍请求体
 *
 * @author shaosao
 */
@Data
public class TeamJoinRequest implements Serializable {

    private static final long serialVersionUID = -24663018187059425L;

    /**
     * id
     */
    private Long teamId;

    /**
     * 密码
     */
    private String password;
}

接口实现

    @PostMapping("/join")
    public BaseResponse<Boolean> joinTeam(@RequestBody TeamJoinRequest teamJoinRequest,HttpServletRequest request){
        if(teamJoinRequest == null){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        User loginUSer = userService.getLoginUser(request);
        boolean result = teamService.joinTeam(teamJoinRequest,loginUSer);
        return ResultUtils.success(result);
    }

业务层实现

 @Override
    public boolean joinTeam(TeamJoinRequest teamJoinRequest, User loginUSer) {
        if (teamJoinRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Long teamId = teamJoinRequest.getTeamId();
        if(teamId == null || teamId <= 0){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Team team = teamService.getById(teamId);
        if (team == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"队伍不存在");
        }

        //用户只能加入未过期的队伍
        Date expireTime = team.getExpireTime();
        if (expireTime != null && expireTime.before(new Date())) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户只能加入未过期的队伍");
        }
        //如果加入的队伍是加密的,必须密码匹配才可以
        String password = teamJoinRequest.getPassword();
        Integer status = team.getStatus();
        TeamStatusEnum statusEnum = TeamStatusEnum.getEnumByValue(status);
        if (TeamStatusEnum.SECRET.equals(statusEnum)) {
            if (StringUtils.isBlank(password) || !password.equals(team.getPassword())) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误");
            }
        }
        //禁止加入私有的队伍
        if (TeamStatusEnum.PRIVATE.equals(statusEnum)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "禁止加入私有队伍");
        }
        //用户最多加入五个队伍
        Long userId = loginUSer.getId();
        QueryWrapper<UserTeam> userTeamQueryWrapper = new QueryWrapper<>();
        userTeamQueryWrapper.eq("userId", userId);
        long count = userTeamService.count(userTeamQueryWrapper);
        if (count > 5) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "最多创建和加入五个队伍");
        }
        //用户只能加入未满的队伍
        userTeamQueryWrapper = new QueryWrapper<>();
        userTeamQueryWrapper.eq("teamId", teamId);
        count = userTeamService.count(userTeamQueryWrapper);
        if (count >= team.getMaxNum()) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍已满");
        }
        //.不能重复加入已加入的队伍(幂等性)
        userTeamQueryWrapper = new QueryWrapper<>();
        userTeamQueryWrapper.eq("userId", userId);
        userTeamQueryWrapper.eq("teamId", teamId);
        count = userTeamService.count(userTeamQueryWrapper);
        if(count > 0){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"用户已加入该队伍");
        }
        //新增用户-队伍关系
        UserTeam userTeam = new UserTeam();
        userTeam.setUserId(userId);
        userTeam.setTeamId(teamId);
        boolean save = userTeamService.save(userTeam);
        return save;
    }

用户退出队伍

业务逻辑

image.png

新建退出请求体

	@Data
    public class TeamQuitRequest implements Serializable {
        private static final long serialVersionUID = -2038884913144640407L;
        /**
        *  id
        */
        private Long teamId;
    }

接口实现

    @PostMapping("/quit")
    public BaseResponse<Boolean> quitTeam(@RequestBody TeamQuitRequest teamQuitRequest,HttpServletRequest request){
        if(teamQuitRequest == null){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        User loginUser = userService.getLoginUser(request);
        boolean result =  teamService.quitTeam(teamQuitRequest,loginUser);
        return ResultUtils.success(result);
    }

业务实现

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean quitTeam(TeamQuitRequest teamQuitRequest, User loginUser) {
        if (teamQuitRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Long teamId = teamQuitRequest.getTeamId();
        //缓解缓存穿透问题
        if (teamId == null || teamId <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Team team = getById(teamId);
        if (team == null) {
            throw new BusinessException(ErrorCode.NULL_ERROR, "队伍不存在");
        }
        //校验是否已经加入队伍,只能退出已加入队伍
        Long userId = loginUser.getId();
        UserTeam userTeam = new UserTeam();
        userTeam.setUserId(userId);
        userTeam.setTeamId(teamId);
        QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>(userTeam);
        long count = userTeamService.count(queryWrapper);
        if (count == 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "未加入该队伍");
        }
        //校验队伍人数
        QueryWrapper TqueryWrapper = new QueryWrapper<>();
        TqueryWrapper.eq("teamId", teamId);
        count = userTeamService.count(TqueryWrapper);
        //1.队伍只剩一人
        if (count == 1) {
            this.removeById(teamId);
            return userTeamService.remove(queryWrapper);
        } else {
            //2.队伍还剩其他人
            //2.1是队长
            if (team.getUserId().equals(userId)) {
                //权限转移给第二早加入的用户
                //1.查询前两个最早加入用户
                QueryWrapper<UserTeam>userTeamQueryWrapper = new QueryWrapper<>();
                userTeamQueryWrapper.eq("teamId", teamId);
                userTeamQueryWrapper.last("order by id asc limit 2");
                List<UserTeam> userTeamList = userTeamService.list(userTeamQueryWrapper);
                if (CollectionUtils.isEmpty(userTeamList) || userTeamList.size() <= 1) {
                    throw new BusinessException(ErrorCode.SYSTEM_ERROR);
                }
                //权限转移给第二个用户
                userTeam = userTeamList.get(1);
                Team updateTeam = new Team();
                //更新队伍表
                updateTeam.setUserId(userTeam.getUserId());
                updateTeam.setId(teamId);
                boolean result = this.updateById(updateTeam);
                if (!result) {
                    throw new BusinessException(ErrorCode.SYSTEM_ERROR, "更新队长失败");
                }
                //把之前队伍-用户关系表删除
                return userTeamService.remove(queryWrapper);
            } else {
                //2.2不是队长
                return userTeamService.remove(queryWrapper);
            }
        }
    }

队长解散队伍

业务逻辑

  1. 校验请求参数(队伍id)
  2. 校验队伍是否存在
  3. 校验你是不是队伍的队长
  4. 移出所有加入队伍的关联信息
  5. 删除队伍

接口实现

    @PostMapping("/delete")
    public BaseResponse<Boolean> deleteTeam(@RequestBody Long id,HttpServletRequest request) {
        if (id <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        User loginUser = userService.getLoginUser(request);
        boolean result = teamService.deleteTeam(id,loginUser);
        if (!result) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "删除失败");
        }
        return ResultUtils.success(true);
    }

业务实现

 @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteTeam(Long id, User loginUser) {
        if(id == null || id <= 0){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Team team = this.getById(id);
        if(team == null){
            throw new BusinessException(ErrorCode.NULL_ERROR,"队伍不存在");
        }
        Long userId = loginUser.getId();
        if(!team.getUserId().equals(userId)){
            throw new BusinessException(ErrorCode.NO_AUTH,"无访问权限");
        }
        UserTeam userTeam = new UserTeam();
        userTeam.setTeamId(id);
        QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>(userTeam);
        boolean result = userTeamService.remove(queryWrapper);
        if(!result){
            throw new BusinessException(ErrorCode.SYSTEM_ERROR);
        }
        boolean ans = teamService.removeById(id);
        return ans;
    }

获取当前用户已加入的队伍

(开复用 listTeam 方法,只新增查询条件,不做修改,开闭原则)

    /**
     * 获取我创建的队伍
     *
     * @param teamQuery
     * @param request
     * @return
     */
    @GetMapping("/list/my/create")
    public BaseResponse<List<TeamUserVO>> listMyCreateTeams(TeamQuery teamQuery, HttpServletRequest request) {
        if (teamQuery == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        User loginUser = userService.getLoginUser(request);
        teamQuery.setUserId(loginUser.getId());
        List<TeamUserVO> teamList = teamService.listTeams(teamQuery, true);
        return ResultUtils.success(teamList);
    }

获取当前用户已创建的队伍

我们查询加入的队伍需要用到id的列表,所以在Teamquery里增加idList字段
image.png
获取我加入的队伍

    /**
     * 获取我加入的队伍
     *
     * @param teamQuery
     * @param request
     * @return
     */
    @GetMapping("/list/my/join")
    public BaseResponse<List<TeamUserVO>> listMyJoinTeams(TeamQuery teamQuery, HttpServletRequest request) {
        if (teamQuery == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        User loginUser = userService.getLoginUser(request);
        QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userId", loginUser.getId());
        List<UserTeam> userTeamList = userTeamService.list(queryWrapper);
        // 取出不重复的队伍 id
        // teamId userId
        Map<Long, List<UserTeam>> listMap = userTeamList.stream()
                .collect(Collectors.groupingBy(UserTeam::getTeamId));
        List<Long> idList = new ArrayList<>(listMap.keySet());
        teamQuery.setIdList(idList);
        List<TeamUserVO> teamList = teamService.listTeams(teamQuery, true);
        return ResultUtils.success(teamList);
    }

修改下listTeam方法,加一层校验
image.png

遇到的bug:循环依赖问题 todo

image.png(springboot 2.6出现的问题)
todo 什么是循环依赖?可以结合bean的生命周期去理解
解决方案:spring.main.allow-circular-references=true 设置自动打破循环

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/595443.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

电脑问题2【彻底删除CompatTelRunner】

彻底删除CompatTelRunner 电脑偶尔会运行CompatTelRunner造成CPU占用的资源非常大,所以这里要想办法彻底关闭他 本文摘录于&#xff1a;https://mwell.tech/archives/539只是做学习备份之用&#xff0c;绝无抄袭之意&#xff0c;有疑惑请联系本人&#xff01; 解决办法是进入W…

WinForm DataGridView 垂直滑动条显示异常

WinForm DataGridView的垂直滑动条不正常显示&#xff0c;当总行高超过控件高度&#xff08;控件高度为227及以下不会出现该问题&#xff09;时&#xff0c;右下角会出现一个灰框&#xff0c;因为表格控件位处TabControl下&#xff0c;当切换其他选项卡后再切回来时&#xff0c…

Python类方法探秘:从单例模式到版本控制

引言&#xff1a; 在Python编程中&#xff0c;类方法作为一种特殊的实例方法&#xff0c;以其独特的魅力在众多编程范式中脱颖而出。它们不仅提供了无需实例即可调用的便捷性&#xff0c;还在设计模式、版本控制等方面发挥着重要作用。本文将通过几个生动的示例&#xff0c;带您…

RS2057XH功能和参数介绍及规格书

RS2057XH 是一款由润石科技&#xff08;Runic Semiconductor&#xff09;生产的模拟开关芯片&#xff0c;其主要功能和参数如下&#xff1a; 产品特点&#xff1a; 低电压操作&#xff1a;支持低至1.8V的工作电压&#xff0c;适用于低功耗应用。 高带宽&#xff1a;具有300MHz的…

库存管理方法有哪些?

库存管理对于企业的成本控制、运营效率及市场竞争力至关重要。有效的库存管理能够确保生产连续、降低成本、提升效率&#xff0c;进而增强企业市场竞争力。本文旨在介绍几种主流的库存管理方法&#xff0c;包括高效利用仓库管理软件&#xff0c;定量库存管理法、定期库存管理法…

配电网变压器容量选择与变损计算方法及python简易实现

1. 配电网变压器容量选择方法 1.1. 配电网变压器容量选择方法 在选择变压器容量时&#xff0c;需要考虑的最大因素是负荷的峰值&#xff08;或称为最大需求&#xff09;&#xff0c;同时也要考虑变压器的效率、预期负载系数&#xff08;负载占额定容量的比例&#xff09;、以…

2024数维杯数学建模B题思路分析

文章目录 1 赛题思路2 比赛日期和时间3 竞赛信息4 建模常见问题类型4.1 分类问题4.2 优化问题4.3 预测问题4.4 评价问题 5 建模资料 1 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 2 比赛日期和时间 报名截止时间&#xff1a;2024…

multipass launch失败:launch failed: Remote ““ is unknown or unreachable.

具体问题情况如下&#xff1a; C:\WINDOWS\system32>multipass launch --name my-vm 20.04launch failed: Remote "" is unknown or unreachable.​C:\WINDOWS\system32>multipass lsNo instances found.​C:\WINDOWS\system32>multipass startlaunch fail…

Linux 磁盘管理命令fdisk mount umount mkfs mkfs.ext2

文章目录 3.Linux 磁盘管理命令3.4 fdisk&#xff1a;磁盘分区案例练习 3.5 mount&#xff1a;挂载文件系统案例练习 3.6 umount&#xff1a;卸载文件系统案例练习 3.7 mkfs&#xff1a;建立各种文件系统案例练习 3.8 mkfs.ext2&#xff1a;建立一个 Ext2/Ext3 文件系统案例练习…

深度学习中的归一化:BN,LN,IN,GN的优缺点

目录 深度学习中归一化的作用常见归一化的优缺点 深度学习中归一化的作用 加速训练过程 归一化可以加速深度学习模型的训练过程。通过调整输入数据的尺度&#xff0c;归一化有助于改善优化算法的收敛速度。这是因为归一化后的数据具有相似的尺度&#xff0c;使得梯度下降等优化…

Redis - Zset 有序集合

前言 它保留了集合不能有重复成员的特点&#xff0c;但与集合不同的是&#xff0c;有序集合中的每个元素都有⼀个唯⼀的浮点类型的分数&#xff08;score&#xff09;与之关联&#xff0c;有序集合中的元素是可以维护有序性的&#xff0c;但这个有序不是⽤下标作为排序依据⽽是…

数据分析师 医学spss数据分析,游程检验(Run Test)是一种非参数性统计假设的检验方法,也称为“连贯检验”,医学统计学

游程检验&#xff08;Run Test&#xff09;是一种非参数性统计假设的检验方法&#xff0c;也称为“连贯检验”。它是基于样本标志表现排列所形成的游程&#xff08;即连续出现相同数值的序列&#xff09;的多少进行判断的检验方法。游程检验主要用于两个独立样本的比较和观测结…

TC3xx MTU概述(1)

目录 1.MTU基本功能 2.MBIST 3.小结 1.MTU基本功能 在TC3xx中&#xff0c;MTU(Memory Unit Test)被用来管理控制芯片内部各种RAM的测试、初始化和数据完整性检查。 既然MTU主要是管理和控制&#xff0c;那干活的想必另有他人。所以在该平台中&#xff0c;我们可以看到SRAM…

C++:哈希表和unordered系列容器的封装

一、unordered系列关联式容器的介绍 在C98中&#xff0c;STL提供了底层为红黑树结构的一系列关联式容器&#xff0c;在查询时效率可达到log2N&#xff0c;即最差情况下需要比较红黑树的高度次&#xff0c;当树中的节点非常多时&#xff0c;查询效率也不理想。最好的查询是&…

Python语言在地球科学中地理、气象、气候变化、水文、生态、传感器等数据可视化到常见数据分析方法的使用

Python是功能强大、免费、开源&#xff0c;实现面向对象的编程语言&#xff0c;Python能够运行在Linux、Windows、Macintosh、AIX操作系统上及不同平台&#xff08;x86和arm&#xff09;&#xff0c;Python简洁的语法和对动态输入的支持&#xff0c;再加上解释性语言的本质&…

深度学习之视觉特征提取器——AlexNet

AlexNet 参考资料&#xff1a; &#xff08;1&#xff09;ImageNet十年历任霸主之AlexNet - 知乎 (zhihu.com) &#xff08;2&#xff09;AlexNet - Wikipedia 引入 AlexNet在2012年以第一名在Top-1分类精度霸榜ImageNet&#xff0c;并超过第二名近10个百分点&#xff0c;…

On Hold 频发!又3本期刊被标记为On Hold ,大家谨慎投递!

【SciencePub学术】On Hold 频发&#xff01;小编在查阅资料的时候发现又有3本期刊被标记为On Hold 了&#xff0c;今天小编给大家详细介绍一下这3本期刊。 来源&#xff1a;科睿唯安官网 Results in Physics 1 期刊概况 【期刊简介】IF&#xff1a;5.3&#xff0c;JCR1区&am…

RockChip Android13 NFC SL6320移植

环境:RK3568 Android13 一:驱动移植 1、驱动 将SL6320驱动代码拷贝至kernel-5.10/drivers/misc/sl6320/ 特殊说明:勿将驱动代码放置于kernel-5.10/drivers/nfc/目录下,会导致sl6320驱动生成设备节点时因/dev/nfc节点以创建而加载失败。 2、DTS 本次硬件设计电路走I2C协…

获取京东商品详情,API返回值说明全攻略

京东商品详情API是开发者获取京东平台上商品详细信息的重要工具。通过调用API并解析返回的响应数据&#xff0c;您可以快速获取商品的各项属性&#xff0c;如商品ID、标题、价格、图片等。下面&#xff0c;我们将为您详细介绍京东商品详情API的返回值说明&#xff0c;帮助您更好…

Spring - 8 ( 10000 字 Spring 入门级教程 )

一&#xff1a; MyBatis 1.1 引入 MyBatis 我们学习 MySQL 数据库时&#xff0c;已经学习了 JDBC 来操作数据库, 但是 JDBC 操作太复杂了. 我们先来回顾⼀下 JDBC 的操作流程: 创建数据库连接池 DataSource通过 DataSource 获取数据库连接 Connection编写要执行带 ? 占位符…