浪花 - 后端接口完善

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

相关文章

Deepseek R1模型本地化部署+API接口调用详细教程(释放AI生产力)

《DeepseekR1模型本地化部署+API接口调用详细教程(释放AI生产力)》本文介绍了本地部署DeepSeekR1模型和通过API调用将其集成到VSCode中的过程,作者详细步骤展示了如何下载和... 目录前言一、deepseek R1模型与chatGPT o1系列模型对比二、本地部署步骤1.安装oll

MyBatis-Flex BaseMapper的接口基本用法小结

《MyBatis-FlexBaseMapper的接口基本用法小结》本文主要介绍了MyBatis-FlexBaseMapper的接口基本用法小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具... 目录MyBATis-Flex简单介绍特性基础方法INSERT① insert② insertSelec

Spring排序机制之接口与注解的使用方法

《Spring排序机制之接口与注解的使用方法》本文介绍了Spring中多种排序机制,包括Ordered接口、PriorityOrdered接口、@Order注解和@Priority注解,提供了详细示例... 目录一、Spring 排序的需求场景二、Spring 中的排序机制1、Ordered 接口2、Pri

Idea实现接口的方法上无法添加@Override注解的解决方案

《Idea实现接口的方法上无法添加@Override注解的解决方案》文章介绍了在IDEA中实现接口方法时无法添加@Override注解的问题及其解决方法,主要步骤包括更改项目结构中的Languagel... 目录Idea实现接China编程口的方法上无法添加@javascriptOverride注解错误原因解决方

Java function函数式接口的使用方法与实例

《Javafunction函数式接口的使用方法与实例》:本文主要介绍Javafunction函数式接口的使用方法与实例,函数式接口如一支未完成的诗篇,用Lambda表达式作韵脚,将代码的机械美感... 目录引言-当代码遇见诗性一、函数式接口的生物学解构1.1 函数式接口的基因密码1.2 六大核心接口的形态学

详解Java如何向http/https接口发出请求

《详解Java如何向http/https接口发出请求》这篇文章主要为大家详细介绍了Java如何实现向http/https接口发出请求,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 用Java发送web请求所用到的包都在java.net下,在具体使用时可以用如下代码,你可以把它封装成一

Java后端接口中提取请求头中的Cookie和Token的方法

《Java后端接口中提取请求头中的Cookie和Token的方法》在现代Web开发中,HTTP请求头(Header)是客户端与服务器之间传递信息的重要方式之一,本文将详细介绍如何在Java后端(以Sp... 目录引言1. 背景1.1 什么是 HTTP 请求头?1.2 为什么需要提取请求头?2. 使用 Spr

CSP 2023 提高级第一轮 CSP-S 2023初试题 完善程序第二题解析 未完

一、题目阅读 (最大值之和)给定整数序列 a0,⋯,an−1,求该序列所有非空连续子序列的最大值之和。上述参数满足 1≤n≤105 和 1≤ai≤108。 一个序列的非空连续子序列可以用两个下标 ll 和 rr(其中0≤l≤r<n0≤l≤r<n)表示,对应的序列为 al,al+1,⋯,ar​。两个非空连续子序列不同,当且仅当下标不同。 例如,当原序列为 [1,2,1,2] 时,要计算子序列 [

Java 后端接口入参 - 联合前端VUE 使用AES完成入参出参加密解密

加密效果: 解密后的数据就是正常数据: 后端:使用的是spring-cloud框架,在gateway模块进行操作 <dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>30.0-jre</version></dependency> 编写一个AES加密

java线程深度解析(一)——java new 接口?匿名内部类给你答案

http://blog.csdn.net/daybreak1209/article/details/51305477 一、内部类 1、内部类初识 一般,一个类里主要包含类的方法和属性,但在Java中还提出在类中继续定义类(内部类)的概念。 内部类的定义:类的内部定义类 先来看一个实例 [html]  view plain copy pu