import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.rainbowsea.yupao.common.ErrorCode;
import com.rainbowsea.yupao.exception.BusinessException;
import com.rainbowsea.yupao.mapper.TeamMapper;
import com.rainbowsea.yupao.model.Team;
import com.rainbowsea.yupao.model.User;
import com.rainbowsea.yupao.model.UserTeam;
import com.rainbowsea.yupao.model.dto.TeamQuery;
import com.rainbowsea.yupao.model.enums.TeamStatusEnum;
import com.rainbowsea.yupao.model.request.TeamJoinRequest;
import com.rainbowsea.yupao.model.request.TeamQuitRequest;
import com.rainbowsea.yupao.model.request.TeamUpdateRequest;
import com.rainbowsea.yupao.model.vo.TeamUserVO;
import com.rainbowsea.yupao.model.vo.UserVO;
import com.rainbowsea.yupao.service.TeamService;
import com.rainbowsea.yupao.service.UserService;
import com.rainbowsea.yupao.service.UserTeamService;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
/**
*
*/
@Service
public class TeamServiceImpl extends ServiceImpl<TeamMapper, Team>
implements TeamService {
@Resource
private UserTeamService userTeamService;
@Resource
private UserService userService;
@Resource
private RedissonClient redissonClient;
/***
* 添加队伍
* @param team 队伍
* @param loginUser User 用户
* @return
*/
@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);
}
final long userId = loginUser.getId();
// 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 (StringUtils.isBlank(name) || name.length() > 20) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍标题不满足要求");
}
// 3. 描述 <= 512
String description = team.getDescription();
if (StringUtils.isNotBlank(description) && description.length() > 512) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍描述过长");
}
// 4. 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, "队伍状态不满足要求");
}
// 5. 如果 status 是加密状态,一定要有密码,且密码 <= 32
String password = team.getPassword();
if (TeamStatusEnum.SECRET.equals(statusEnum)) {
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 个队伍
QueryWrapper<Team> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userId", userId);
long hasTeamNum = this.count(queryWrapper);
if (hasTeamNum >= 5) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户最多创建 5 个队伍");
}
// 8. 插入队伍信息到队伍表
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, "创建队伍失败");
}
// 9. 插入用户 => 队伍关系到关系表
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;
}
}
补充:事务注解
java复制代码
@Transaction(rollbackFor = Exception.class) // 在方法上添加上注解,启动事务控制
void public main() {
}
查询队伍列表
分页展示队伍列表,根据名称、最大人数等搜索队伍PO,信息流中不展示已过期的队伍。
从请求参数中取出队伍名称等查询条件,如果存在则作为查询条件
需要登录,才能查询
不展示已过期的队伍 (根据过期时间筛选)
可以通过某个关键词同时对名称和描述查询
只有管理员才能查看加密还有非公开的房间
关联查询已加入队伍的用户信息
关联查询已加入队伍的用户信息(可能会很耗费性能,建议大家用自己写SQL的方式实现)
自己写 SQL
java复制代码
// 1. 自己写 SQL
// 查询队伍和创建人的信息
// select * from team t left join user u on t.userId = u.id
// 查询队伍和已加入队伍成员的信息
// select *
// from team t
// left join user_team ut on t.id = ut.teamId
// left join user u on ut.userId = u.id;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.rainbowsea.yupao.common.ErrorCode;
import com.rainbowsea.yupao.exception.BusinessException;
import com.rainbowsea.yupao.mapper.TeamMapper;
import com.rainbowsea.yupao.model.Team;
import com.rainbowsea.yupao.model.User;
import com.rainbowsea.yupao.model.UserTeam;
import com.rainbowsea.yupao.model.dto.TeamQuery;
import com.rainbowsea.yupao.model.enums.TeamStatusEnum;
import com.rainbowsea.yupao.model.request.TeamJoinRequest;
import com.rainbowsea.yupao.model.request.TeamQuitRequest;
import com.rainbowsea.yupao.model.request.TeamUpdateRequest;
import com.rainbowsea.yupao.model.vo.TeamUserVO;
import com.rainbowsea.yupao.model.vo.UserVO;
import com.rainbowsea.yupao.service.TeamService;
import com.rainbowsea.yupao.service.UserService;
import com.rainbowsea.yupao.service.UserTeamService;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
/**
*
*/
@Service
public class TeamServiceImpl extends ServiceImpl<TeamMapper, Team>
implements TeamService {
@Resource
private UserTeamService userTeamService;
@Resource
private UserService userService;
@Resource
private RedissonClient redissonClient;
/**
* 搜索队伍
* @param teamQuery
* @param isAdmin
* @return
*/
@Override
public List<TeamUserVO> listTeams(TeamQuery teamQuery, boolean isAdmin) {
QueryWrapper<Team> queryWrapper = new QueryWrapper<>();
// 组合查询条件
if (teamQuery != null) {
Long id = teamQuery.getId();
if (id != null && id > 0) {
queryWrapper.eq("id", id);
}
List<Long> idList = teamQuery.getIdList();
if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(idList)) {
queryWrapper.in("id", idList);
}
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 statusEnum = TeamStatusEnum.getEnumByValue(status);
if (statusEnum == null) {
statusEnum = TeamStatusEnum.PUBLIC;
}
if (!isAdmin && statusEnum.equals(TeamStatusEnum.PRIVATE)) {
throw new BusinessException(ErrorCode.NO_AUTH);
}
queryWrapper.eq("status", statusEnum.getValue());
}
// 不展示已过期的队伍
// expireTime is null or expireTime > now()
queryWrapper.and(qw -> qw.gt("expireTime", new Date()).or().isNull("expireTime"));
List<Team> teamList = this.list(queryWrapper);
if (org.apache.commons.collections4.CollectionUtils.isEmpty(teamList)) {
return new ArrayList<>();
}
List<TeamUserVO> teamUserVOList = new ArrayList<>();
// 关联查询创建人的用户信息
for (Team team : teamList) {
Long userId = team.getUserId();
if (userId == null) {
continue;
}
User user = userService.getById(userId);
TeamUserVO teamUserVO = new TeamUserVO();
BeanUtils.copyProperties(team, teamUserVO);
// 脱敏用户信息
if (user != null) {
UserVO userVO = new UserVO();
BeanUtils.copyProperties(user, userVO);
teamUserVO.setCreateUser(userVO);
}
teamUserVOList.add(teamUserVO);
}
return teamUserVOList;
}
}
}
修改队伍信息
判断请求参数是否为空
查询队伍是否存在
只有管理员或者队伍的创建者可以修改
如果用户传入的新值和老值一致,就不用update了 (可自行实现,降低数据库使用次数)
如果队伍状态改为加密,必须要有密码
更新成功
java复制代码
/**
* 更新队伍内容
*
* @param teamUpdateRequest teamUpdateRequest 对象
* @return Boolean 更新成功 true,否则 false
*/
@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);
}
java复制代码
/**
* 更新队伍
* @param teamUpdateRequest
* @param loginUser
* @return
*/
@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);
if (oldTeam == null) {
throw new BusinessException(ErrorCode.NULL_ERROR, "队伍不存在");
}
// 只有管理员或者队伍的创建者可以修改
if (!oldTeam.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) {
throw new BusinessException(ErrorCode.NO_AUTH);
}
TeamStatusEnum statusEnum = TeamStatusEnum.getEnumByValue(teamUpdateRequest.getStatus());
if (statusEnum.equals(TeamStatusEnum.SECRET)) {
if (StringUtils.isBlank(teamUpdateRequest.getPassword())) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "加密房间必须要设置密码");
}
}
Team updateTeam = new Team();
BeanUtils.copyProperties(teamUpdateRequest, updateTeam);
return this.updateById(updateTeam);
}
用户可以加入队伍
其他人、未满、未过期,允许加入多个队伍,但是要有个上限PO
用户最多加入 5个队伍
队伍必须存在,只能加入未满、未过期的队伍
不能加入自己的队伍,不能重复加入已加入的队伍 (幂等性)
禁止加入私有的队伍
如果加入的队伍是加密的,必须密码匹配才可以
新增队伍-用户关联信息
注意,一定要加上事务注解!!!!
java复制代码
/**
* 加入队伍
* @param teamJoinRequest
* @param request
* @return BaseResponse<Boolean>
*/
@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);
}
java复制代码
/**
* 加入队伍
*
* @param teamJoinRequest
* @param loginUser
* @return boolean
*/
@Override
public boolean joinTeam(TeamJoinRequest teamJoinRequest, User loginUser) {
if (teamJoinRequest == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
Long teamId = teamJoinRequest.getTeamId();
Team team = getTeamById(teamId);
Date expireTime = team.getExpireTime();
if (expireTime != null && expireTime.before(new Date())) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍已过期");
}
Integer status = team.getStatus();
TeamStatusEnum teamStatusEnum = TeamStatusEnum.getEnumByValue(status);
if (TeamStatusEnum.PRIVATE.equals(teamStatusEnum)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "禁止加入私有队伍");
}
String password = teamJoinRequest.getPassword();
if (TeamStatusEnum.SECRET.equals(teamStatusEnum)) {
if (StringUtils.isBlank(password) || !password.equals(team.getPassword())) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误");
}
}
// 该用户已加入的队伍数量
long userId = loginUser.getId();
// 只有一个线程能获取到锁
RLock lock = redissonClient.getLock("yupao:join_team");
try {
// 抢到锁并执行
while (true) {
if (lock.tryLock(0, -1, TimeUnit.MILLISECONDS)) {
System.out.println("getLock: " + Thread.currentThread().getId());
QueryWrapper<UserTeam> userTeamQueryWrapper = new QueryWrapper<>();
userTeamQueryWrapper.eq("userId", userId);
long hasJoinNum = userTeamService.count(userTeamQueryWrapper);
if (hasJoinNum > 5) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "最多创建和加入 5 个队伍");
}
// 不能重复加入已加入的队伍
userTeamQueryWrapper = new QueryWrapper<>();
userTeamQueryWrapper.eq("userId", userId);
userTeamQueryWrapper.eq("teamId", teamId);
long hasUserJoinTeam = userTeamService.count(userTeamQueryWrapper);
if (hasUserJoinTeam > 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户已加入该队伍");
}
// 已加入队伍的人数
long teamHasJoinNum = this.countTeamUserByTeamId(teamId);
if (teamHasJoinNum >= team.getMaxNum()) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍已满");
}
// 修改队伍信息
UserTeam userTeam = new UserTeam();
userTeam.setUserId(userId);
userTeam.setTeamId(teamId);
userTeam.setJoinTime(new Date());
return userTeamService.save(userTeam);
}
}
} catch (InterruptedException e) {
log.error("doCacheRecommendUser error", e);
return false;
} finally {
// 只能释放自己的锁
if (lock.isHeldByCurrentThread()) {
System.out.println("unLock: " + Thread.currentThread().getId());
lock.unlock();
}
}
}
用户可以退出队伍
请求参数:用户 ID
校验队伍是否存在
校验我是否已加入队伍
如果队伍
只剩一人,队伍解散,(只剩一人,说明本身自己就是队长)
还有其他人
如果是队长退出队伍,权限转移给第二早加入的用户一一先来后到(只用取id最小的2 条数据)
非队长,自己退出队伍
java复制代码
/**
* 退出队伍,
* @param teamQuitRequest
* @param request
* @return BaseResponse<Boolean>
*/
@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);
}
java复制代码
/**
* 退出队伍
* @param teamQuitRequest
* @param loginUser
* @return
*/
@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();
Team team = this.getById(teamId);
//Team team = getTeamById(teamId);
long userId = loginUser.getId();
UserTeam queryUserTeam = new UserTeam();
queryUserTeam.setTeamId(teamId);
queryUserTeam.setUserId(userId);
QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>(queryUserTeam);
long count = userTeamService.count(queryWrapper);
if (count == 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "未加入队伍");
}
long teamHasJoinNum = this.countTeamUserByTeamId(teamId);
// 队伍只剩一人,解散
if (teamHasJoinNum == 1) {
// 删除队伍
this.removeById(teamId);
} else {
// 队伍还剩至少两人
// 是队长
if (team.getUserId() == 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 nextUserTeam = userTeamList.get(1);
Long nextTeamLeaderId = nextUserTeam.getUserId();
// 更新当前队伍的队长
Team updateTeam = new Team();
updateTeam.setId(teamId);
updateTeam.setUserId(nextTeamLeaderId);
boolean result = this.updateById(updateTeam);
if (!result) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "更新队伍队长失败");
}
}
}
// 移除关系
return userTeamService.remove(queryWrapper);
}
队长可以解散队伍
请求参数:队伍 id
业务流程:
校验请求参数
校验队伍是否存在
校验你是不是队伍的队长
移除所有加入队伍的关联信息
删除队伍
java复制代码
/**
* 删除队伍,移除队伍
*
* @param deleteRequest 队伍的 id
* @return Boolean 移除成功 true,否则 false
*/
@PostMapping("/delete")
public BaseResponse<Boolean> deleteTeam(@RequestBody DeleteRequest deleteRequest, HttpServletRequest request) {
if (deleteRequest == null || deleteRequest.getId() <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
long id = deleteRequest.getId();
User loginUser = userService.getLoginUser(request);
boolean result = teamService.deleteTeam(id, loginUser);
if (!result) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "删除失败");
}
return ResultUtils.success(true);
}
java复制代码
/**
* 删除队伍
* @param id 队伍中队长的 ID
* @param loginUser
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteTeam(long id, User loginUser) {
// 校验队伍是否存在
Team team = this.getTeamById(id);
Long teamId = team.getId();
// 校验你是不是队伍的队长
if (!team.getUserId().equals(loginUser.getId())) {
throw new BusinessException(ErrorCode.NO_AUTH, "不是队长,无权限删除");
}
// 移除所有加入队伍的关联信息
QueryWrapper<UserTeam> userTeamQueryWrapper = new QueryWrapper<>();
userTeamQueryWrapper.eq("teamId", teamId);
boolean result = userTeamService.remove(userTeamQueryWrapper);
if(!result) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR,"删除队伍关联信息失败");
}
// 删除队伍
return this.removeById(teamId);
}