心链9----组队功能开发以及请求参数包装类和包装类实现

心链 — 伙伴匹配系统

组队功能开发

需求分析

理想的应用场景

我要跟别人一起参加竞赛或者做项目,可以发起队伍或者加入别人的队伍

用户可以 创建 一个队伍,设置队伍的人数、队伍名称(标题)、描述、超时时间 P0

  1. 队长、剩余的人数
  2. 聊天?
  3. 公开 或 private 或加密
  4. 用户创建队伍最多 5 个

展示队伍列表,根据名称搜索队伍  P0,信息流中不展示已过期的队伍
修改队伍信息 P0 ~ P1
用户可以加入队伍(其他人、未满、未过期),允许加入多个队伍,但是要有个上限  P0
是否需要队长同意?筛选审批?
用户可以退出队伍(如果队长 退出,权限转移给第二早加入的用户 —— 先来后到) P1
队长可以解散队伍 P0

分享队伍 =》 邀请其他用户加入队伍 P1
业务流程:

  1. 生成分享链接(分享二维码)
  2. 用户访问链接,可以点击加入

队伍人满后发送消息通知 P1

数据库表设计

队伍表 team
字段:

  • id 主键 bigint(最简单、连续,放 url 上比较简短,但缺点是爬虫)
  • 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',
  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 '队伍';

用户 - 队伍表 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 '用户队伍关系';

两个关系:

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

方式:

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

后端代码

实体生成

实体生成team和user-team
使用MybatisX-Generator生成domain,service和mapper文件,然后把生成的文件都移到对应的目录里面,别忘了把mapper.xml里的路径改成自己对应的。

如果直接将生成的文件拉到对应的文件,就会自动修改mapper.xml的路径

PS:别忘了在team和user_team类中的is_delete字段添加@TableLogic注解,实现逻辑删除
image.png

队伍controller接口

①增删改查
②PageRequest(序列化)---- TeamQuery继承
③自己测试 http://localhost:8080/api/doc.html#/home

@RestController
@RequestMapping("/user")
@CrossOrigin(origins = {"http://localhost:5173/"})
@Slf4j
public class TeamController {

        @Resource
        private UserService userService;

        @Resource
        private RedisTemplate redisTemplate;

        @Resource
        private TeamService teamService;

        @PostMapping("/add")
        public BaseResponse<Long> addTeam(@RequestBody Team team){
            if (team == null){
                throw new BusinessException(ErrorCode.NULL_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.NULL_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);
        }


        @GetMapping("/list")
        public BaseResponse<List<Team>> listTeams(TeamQuery teamQuery) {
            if (teamQuery == null) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR);
            }
                Team team = new Team();
                BeanUtils.copyProperties(team, teamQuery);
                QueryWrapper<Team> queryWrapper = new QueryWrapper<>(team);
                List<Team> teamList = teamService.list(queryWrapper);
                return ResultUtils.success(teamList);
            }
    
              @GetMapping("/list/page")
        public BaseResponse<Page<Team>> listPageTeams(TeamQuery teamQuery) {
            if (teamQuery == null) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR);
            }
            Team team = new Team();
            BeanUtils.copyProperties(teamQuery, team);
            Page<Team> page = new Page<>(teamQuery.getPageNum(),teamQuery.getPageSize());
            QueryWrapper<Team> queryWrapper = new QueryWrapper<>(team);
            Page<Team> resultPage = teamService.page(page,queryWrapper);
            return ResultUtils.success(resultPage);
        }
            }

:::success

这边我们需要新建请求参数包装类和包装类,原因如下:
为什么需要请求参数包装类?
  1. 请求参数名称 / 类型和实体类不一样
  2. 有一些参数用不到,如果要自动生成接口文档,会增加理解成本
  3. 对个实体类映射到同一个对象
为什么需要包装类?

可能有些字段需要隐藏,不能返回给前端
或者有些字段某些方法是不关心的

在model包里新建一个dto包,写一个包装类TeamQuery
:::

/**
 * 队伍查询封装类
 * @TableName team
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class TeamQuery extends PageRequest {
    /**
     * id
     */
    private Long id;

    /**
     * id 列表
     */
    private List<Long> idList;

    /**
     * 搜索关键词(同时对队伍名称和描述搜索)
     */
    private String searchText;

    /**
     * 队伍名称
     */
    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;
}

接口系统设计

1、创建队伍

用户可以 创建 一个队伍,设置队伍的人数、队伍名称(标题)、描述、超时时间 P0
队长、剩余的人数
聊天?
公开 或 private 或加密
信息流中不展示已过期的队伍

  1. 请求参数是否为空?
  2. 是否登录,未登录不允许创建
  3. 校验信息
    1. 队伍人数 > 1 且 <= 20
    2. 队伍标题 <= 20
    3. 描述 <= 512
    4. status 是否公开(int)不传默认为 0(公开)
    5. 如果 status 是加密状态,一定要有密码,且密码 <= 32
    6. 超时时间 > 当前时间
    7. 校验用户最多创建 5 个队伍
  4. 插入队伍信息到队伍表
  5. 插入用户 => 队伍关系到关系表
    @PostMapping("/add")
    public BaseResponse<Long> addTeam(@RequestBody TeamAddRequest teamAddRequest, HttpServletRequest request){
        if (teamAddRequest == null){
            throw new BusinessException(ErrorCode.NULL_ERROR);
        }
        User logininUser = userService.getLogininUser(request);
        Team team = new Team();
        BeanUtils.copyProperties(teamAddRequest,team);
        long teamId = teamService.addTeam(team,logininUser);
        return ResultUtils.success(teamId);
    }

public interface TeamService extends IService<Team> {

    /**
     *   添加队伍
     * @param team
     * @param loginUser
     * @return
     */
    long addTeam(Team team, User loginUser);
}
@Service
public class TeamServiceImpl extends ServiceImpl<TeamMapper, Team>
    implements TeamService{

    @Resource
    UserTeamService userTeamService;

    @Transactional(rollbackFor = Exception.class)
    @Override
    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
        Integer maxNum = Optional.ofNullable(team.getMaxNum()).orElse(0);
        if (maxNum < 1 || maxNum >20){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"队伍人数不符合要求");
        }
        //  b. 队伍标题 <= 20
        String name = team.getName();
        if (StringUtils.isBlank(name) || name.length() > 20) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍标题不满足要求");
        }
        //  c. 描述 <= 512
        String description = team.getDescription();
        if (StringUtils.isNotBlank(description) && description.length() > 512) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍描述过长");
        }
        //  d. status 是否公开(int)不传默认为 0(公开)
        int status = Optional.ofNullable(team.getStatus()).orElse(0);
        TeamStatusEnum statusEnum = TeamStatusEnum.getEnumByValue(status);
        if (statusEnum == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍状态不满足要求");
        }
        //  e. 如果 status 是加密状态,一定要有密码,且密码 <= 32
        String password = team.getPassword();
        if (TeamStatusEnum.SECRET.equals(statusEnum)) {
            if (StringUtils.isBlank(password) || password.length() > 32) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码设置不正确");
            }
        }
        //  f. 超时时间 > 当前时间
        Date expireTime = team.getExpireTime();
        if (new Date().after(expireTime)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "超时时间 > 当前时间");
        }
        //  g. 校验用户最多创建 5 个队伍
        // todo 有 bug,可能同时创建 100 个队伍
        QueryWrapper<Team> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userId", userId);
        long hasTeamNum = this.count(queryWrapper);
        if (hasTeamNum >= 5) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户最多创建 5 个队伍");
        }
        //4. 插入队伍信息到队伍表
        team.setId(null);
        team.setUserId(userId);
        boolean result = this.save(team);
        Long teamId = team.getId();
        if (!result || teamId == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "创建队伍失败");
        }
        //5. 插入用户 => 队伍关系到关系表
        UserTeam userTeam = new UserTeam();
        userTeam.setUserId(userId);
        userTeam.setTeamId(teamId);
        userTeam.setJoinTime(new Date());
        result = userTeamService.save(userTeam);
        if (!result) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "创建队伍失败");
        }
        return teamId;
    }
}
/**
 * 队伍状态枚举
 */
public enum TeamStatusEnum {

    PUBLIC(0, "公开"),
    PRIVATE(1, "私有"),
    SECRET(2, "加密");

    private int value;

    private String text;

    public static TeamStatusEnum getEnumByValue(Integer value) {
        if (value == null) {
            return null;
        }
        TeamStatusEnum[] values = TeamStatusEnum.values();
        for (TeamStatusEnum teamStatusEnum : values) {
            if (teamStatusEnum.getValue() == value) {
                return teamStatusEnum;
            }
        }
        return null;
    }

    TeamStatusEnum(int value, String text) {
        this.value = value;
        this.text = text;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
}
/**
 * 用户添加队伍请求体
 *
 * @author yupi
 */
@Data
public class TeamAddRequest implements Serializable {

    private static final long serialVersionUID = 3191241716373120793L;

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

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

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

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

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

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

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

ps:这里过期时间的获取可从控制台输入一下代码来实现,单单的输入年月日会导致数据库里的时间增加8小时(应该是时区的问题)
image.png
多次发送添加请求,当插入5次之后,再插入会报错
image.png

  • 16
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值