浪花 - 后端接口完善

2024-01-29 00:04
文章标签 接口 完善 浪花

本文主要是介绍浪花 - 后端接口完善,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、队伍已加入用户数量

1. 封装的响应对象 UserTeamVO 新增字段 hasJoinNum

2. 查询队伍 id 列表

3. 分组过滤,将 team_id 相同的 userTeam 分到同一组

4. 获取每一组的 userTeam 数量,即一个 team_id 对应几个userTeam(用户数量)

5. 设置加入的队员数量 hasJoinNum 返回给前端

// 查询加入队伍的用户数
QueryWrapper<UserTeam> userTeamJoinNumQW = new QueryWrapper<>();
userTeamJoinNumQW.in("team_id", teamIdList);
List<UserTeam> userTeamList = userTeamService.list(userTeamJoinNumQW);
// 队伍 id => 加入该队伍的用户列表
Map<Long, List<UserTeam>> teamIdUserTeamList = userTeamList.stream().collect(Collectors.groupingBy(UserTeam::getTeamId));
teamList.forEach(team ->  {team.setHasJoinNum(teamIdUserTeamList.getOrDefault(team.getId(),new ArrayList<>()).size());
});

二、重复加入队伍的问题

1. 问题:高并发场景下,用户疯狂点击加入队伍,可能会重复加入同一个队伍

  • 一个请求开启一个线程,多次点击加入队伍,多个线程进入,判断用户是否已加入该队伍时都是未加入,都去执行加入队伍的业务
  • 出现同一个用户重复加入同一个队伍的情况,用户 - 队伍关系表中添加了多条记录,且已加入队伍的人数异常增加

2. 解决:使用 synchronized 关键字给判断队伍和加入队伍这段逻辑加锁

3. 优化:调整锁的粒度,分析锁的范围

  • 不同用户可以加入不同队伍,如果给整段都加上锁,不同用户加入时可能会阻塞,降低性能
  • 锁用户:同一个用户不能重复加入同一个队伍
  • 锁队伍:同一个用户不能同时加入多个队伍(否则可能突破“每个用户最多创建和加入 5 个队伍的限制”)

注意:数据库插入数据之前,判断的都是用户未加入该队伍 / 用户创建和加入的队伍不满 5 个,如果把锁用户和锁队伍分开,一个线程拿到锁之后判断用户,结束判断去获取队伍的锁,线程 2 就可以拿到用户锁了,但是线程 1 还没有结束插入数据的业务(队伍锁),线程 2 也可以执行,不过是多等了一会,所以锁的范围要到将数据插入数据库完成才能释放锁,之后其他线程获取到锁再去判断数据库,此时数据库已经更改,判断才是有效的

4. 仍存在问题

  • synchronized 同步锁是 JVM 提供的,保存在 JVM(常量池)中,集群模式下,每台 JVM  不共享锁数据,每台 JVM 都可以有一个线程获取到同步锁,锁失效
  • 解决方法:使用 Redisson 提供的分布式锁,或 Redis 自主实现分布式锁(存在问题,但大多数场景可用)

5. 使用分布式锁解决重复加入队伍的问题

  • 创建 RedissonClient 客户端实例
  • 获取锁对象
  • 尝试获取锁:获取成功执行业务,失败等待重试或直接返回
  • 释放锁
// 1. 用户最多创建和加入 5 个队伍
RLock lock = redissonClient.getLock(JOIN_TEAM_USER_LOCK);
try {while (true) {// 获取到锁执行业务if (lock.tryLock(0,-1, TimeUnit.MILLISECONDS)) {log.info("get redisson lock" + Thread.currentThread().getId());Long userId = loginUser.getId();QueryWrapper<UserTeam> userTeamQueryWrapper = new QueryWrapper<>();userTeamQueryWrapper.eq("user_id", userId);long hasJoinNum = userTeamService.count(userTeamQueryWrapper);if (hasJoinNum >= 5) {throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户最多创建和加入 5 个队伍");}// 2. 队伍必须存在,只能加入未满、未过期的队伍Long teamId = teamJoinRequest.getTeamId();Team team = this.getTeamById(teamId);userTeamQueryWrapper = new QueryWrapper<>();userTeamQueryWrapper.eq("team_id", teamId);long teamHasJoinNum = userTeamService.count(userTeamQueryWrapper);if (teamHasJoinNum >= team.getMaxNum()) {throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍人数已满");}Date expireTime = team.getExpireTime();if (expireTime != null && expireTime.before(new Date())) {throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍已过期");}// 3. 不能加入自己的队伍,不能重复加入已加入的队伍(幂等性)
//        if (team.getUserId() == userId) {
//            throw new BusinessException(ErrorCode.PARAMS_ERROR,"不能加入自己创建的队伍");
//        }userTeamQueryWrapper = new QueryWrapper<>();userTeamQueryWrapper.eq("team_id", teamId);userTeamQueryWrapper.eq("user_id", userId);long alreadyJoinNum = userTeamService.count(userTeamQueryWrapper);if (alreadyJoinNum > 0) {throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户已加入该队伍");}// 4. 禁止加入私有的队伍Integer status = team.getStatus();TeamStatusEnum teamStatusEnum = TeamStatusEnum.getTeamEnumByValue(status);if (teamStatusEnum.equals(TeamStatusEnum.PRIVATE)) {throw new BusinessException(ErrorCode.PARAMS_ERROR, "禁止加入私有的队伍");}// 5. 如果加入的队伍是加密的,必须密码匹配才可以String password = teamJoinRequest.getPassword();if (teamStatusEnum.equals(TeamStatusEnum.SECRET)) {if (StringUtils.isBlank(password) || !password.equals(team.getPassword())) {throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误");}}// 6. 新增队伍 - 用户关联信息UserTeam userTeam = new UserTeam();userTeam.setUserId(userId);userTeam.setTeamId(teamId);userTeam.setJoinTime(new Date());return userTeamService.save(userTeam);}}
} catch (InterruptedException e) {log.error("redisson join team error", e);return false;
} finally {log.info("redisson unlock" + Thread.currentThread().getId());lock.unlock();
}

这篇关于浪花 - 后端接口完善的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/655195

相关文章

requests处理token鉴权接口和jsonpath使用方式

《requests处理token鉴权接口和jsonpath使用方式》文章介绍了如何使用requests库进行token鉴权接口的处理,包括登录提取token并保存,还详述了如何使用jsonpath表达... 目录requests处理token鉴权接口和jsonpath使用json数据提取工具总结reques

python调用dubbo接口的实现步骤

《python调用dubbo接口的实现步骤》本文主要介绍了python调用dubbo接口的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编... 目录 ​​其他实现方式与注意事项​​ ​​高级技巧与集成​​用 python 提供 Dubbo 接口

Java中接口和抽象类的异同以及具体的使用场景

《Java中接口和抽象类的异同以及具体的使用场景》文章主要介绍了Java中接口(Interface)和抽象类(AbstractClass)的区别和联系,包括相同点和不同点,以及它们在实际开发中的具体使... 目录一、接口和抽象类的 “相同点”二、接口和抽象类的 “核心区别”关键区别详解(避免踩坑)三、具体使

MySQL中C接口的实现

《MySQL中C接口的实现》本节内容介绍使用C/C++访问数据库,包括对数据库的增删查改操作,主要是学习一些接口的调用,具有一定的参考价值,感兴趣的可以了解一下... 目录准备mysql库使用mysql库编译文件官方API文档对象的创建和关闭链接数据库下达sql指令select语句前言:本节内容介绍使用C/

基于Go语言开发一个 IP 归属地查询接口工具

《基于Go语言开发一个IP归属地查询接口工具》在日常开发中,IP地址归属地查询是一个常见需求,本文将带大家使用Go语言快速开发一个IP归属地查询接口服务,有需要的小伙伴可以了解下... 目录功能目标技术栈项目结构核心代码(main.go)使用方法扩展功能总结在日常开发中,IP 地址归属地查询是一个常见需求:

SpringBoot实现不同接口指定上传文件大小的具体步骤

《SpringBoot实现不同接口指定上传文件大小的具体步骤》:本文主要介绍在SpringBoot中通过自定义注解、AOP拦截和配置文件实现不同接口上传文件大小限制的方法,强调需设置全局阈值远大于... 目录一  springboot实现不同接口指定文件大小1.1 思路说明1.2 工程启动说明二 具体实施2

基于Redisson实现分布式系统下的接口限流

《基于Redisson实现分布式系统下的接口限流》在高并发场景下,接口限流是保障系统稳定性的重要手段,本文将介绍利用Redisson结合Redis实现分布式环境下的接口限流,具有一定的参考价值,感兴趣... 目录分布式限流的核心挑战基于 Redisson 的分布式限流设计思路实现步骤引入依赖定义限流注解实现

SpringBoot实现RSA+AES自动接口解密的实战指南

《SpringBoot实现RSA+AES自动接口解密的实战指南》在当今数据泄露频发的网络环境中,接口安全已成为开发者不可忽视的核心议题,RSA+AES混合加密方案因其安全性高、性能优越而被广泛采用,本... 目录一、项目依赖与环境准备1.1 Maven依赖配置1.2 密钥生成与配置二、加密工具类实现2.1

使用Python的requests库调用API接口的详细步骤

《使用Python的requests库调用API接口的详细步骤》使用Python的requests库调用API接口是开发中最常用的方式之一,它简化了HTTP请求的处理流程,以下是详细步骤和实战示例,涵... 目录一、准备工作:安装 requests 库二、基本调用流程(以 RESTful API 为例)1.

SpringBoot+Redis防止接口重复提交问题

《SpringBoot+Redis防止接口重复提交问题》:本文主要介绍SpringBoot+Redis防止接口重复提交问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不... 目录前言实现思路代码示例测试总结前言在项目的使用使用过程中,经常会出现某些操作在短时间内频繁提交。例