<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      伙伴匹配系統(tǒng)(移動(dòng)端 H5 網(wǎng)站(APP 風(fēng)格)基于Spring Boot 后端 + Vue3 - 05

      伙伴匹配系統(tǒng)(移動(dòng)端 H5 網(wǎng)站(APP 風(fēng)格)基于Spring Boot 后端 + Vue3 - 05

      項(xiàng)目地址:

      @

      系統(tǒng)(接口)設(shè)計(jì)

      創(chuàng)建隊(duì)伍

      用戶可以創(chuàng)建一個(gè)隊(duì)伍,設(shè)置隊(duì)伍的人數(shù)、隊(duì)伍名稱(標(biāo)題)、描述、超時(shí)時(shí)間 PO

      隊(duì)長(zhǎng)、剩余的人數(shù)

      聊天?

      公開(kāi) 或 private 或加密

      信息流中不展示已過(guò)期的隊(duì)伍

      1. 請(qǐng)求參數(shù)是否為空?
      2. 是否登錄,未登錄不允許創(chuàng)建
      3. 校驗(yàn)信息
        1. 隊(duì)伍人數(shù)>1且<=20
        2. 隊(duì)伍標(biāo)題<= 20
        3. 描述 <= 512
        4. status 是否公開(kāi)(int)不傳默認(rèn)為(公開(kāi))
        5. 如果 status 是加密狀態(tài),一定要有密碼,且密碼<= 32
        6. 超時(shí)時(shí)間》當(dāng)前時(shí)間
        7. 校驗(yàn)用戶最多創(chuàng)建5個(gè)隊(duì)伍
      4. 插入隊(duì)伍信息到隊(duì)伍表
      5. 插入用戶=>隊(duì)伍關(guān)系到關(guān)系表

      加入不同的隊(duì)伍,搶的不是同一個(gè)資源,就不需要,搶鎖了。可以將鎖的范圍縮小一些。

      用戶和隊(duì)伍 Id,都要鎖一下。

      • 同一個(gè)用戶,同時(shí)刻只允許你加入一個(gè)隊(duì)伍,一個(gè)用戶不可以一次性加入 10 個(gè)隊(duì)伍。
      • sysnchronized 是(加在對(duì)象上的),String.valueOf(id).intern 表示根據(jù) Id 是,每次生成的是同一個(gè)對(duì)象的地址,不會(huì)新 new 一個(gè) String 對(duì)象,新 new 就是不同的對(duì)象,不同的鎖了。

      
      
      import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
      import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
      import com.rainbowsea.yupao.common.BaseResponse;
      import com.rainbowsea.yupao.common.DeleteRequest;
      import com.rainbowsea.yupao.common.ErrorCode;
      import com.rainbowsea.yupao.utils.ResultUtils;
      import com.rainbowsea.yupao.exception.BusinessException;
      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.request.TeamAddRequest;
      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.service.TeamService;
      import com.rainbowsea.yupao.service.UserService;
      import com.rainbowsea.yupao.service.UserTeamService;
      import io.swagger.annotations.Api;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.beans.BeanUtils;
      import org.springframework.web.bind.annotation.CrossOrigin;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.RequestBody;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      import javax.annotation.Resource;
      import javax.servlet.http.HttpServletRequest;
      import java.util.ArrayList;
      import java.util.List;
      import java.util.Map;
      import java.util.Set;
      import java.util.stream.Collectors;
      
      @RestController
      @RequestMapping("/team")
      @Api("接口文檔的一個(gè)別名處理定義 TeamController ")
      @CrossOrigin(origins = {"http://localhost:5173","http://localhost:3000"})  // 配置前端訪問(wèn)路徑的放行,可以配置多個(gè)
      @Slf4j
      public class TeamController {
      
          @Resource
          private UserService userService;
      
          @Resource
          private TeamService teamService;
      
          @Resource
          private UserTeamService userTeamService;
      
      
          /**
           * 插入 team 隊(duì)伍,添加隊(duì)伍
           *
           * @param teamAddRequest
           * @return teamId
           */
          @PostMapping("/add")
          public BaseResponse<Long> addTeam(@RequestBody TeamAddRequest teamAddRequest, HttpServletRequest request) {
              if (teamAddRequest == null) {
                  throw new BusinessException(ErrorCode.PARAMS_ERROR);
              }
      
              User loginUser = userService.getLoginUser(request);
              Team team = new Team();
              BeanUtils.copyProperties(teamAddRequest, team);
              long teamId = teamService.addTeam(team, loginUser);
      
              return ResultUtils.success(teamId);
          }
      
      }
      
      
      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;
      
      
          /***
           * 添加隊(duì)伍
           * @param team 隊(duì)伍
           * @param loginUser User 用戶
           * @return
           */
          @Override
          @Transactional(rollbackFor = Exception.class)
          public long addTeam(Team team, User loginUser) {
              // 1. 請(qǐng)求參數(shù)是否為空?
              if (team == null) {
                  throw new BusinessException(ErrorCode.PARAMS_ERROR);
              }
              // 2. 是否登錄,未登錄不允許創(chuàng)建
              if (loginUser == null) {
                  throw new BusinessException(ErrorCode.NOT_LOGIN);
              }
              final long userId = loginUser.getId();
              // 3. 校驗(yàn)信息
              //   1. 隊(duì)伍人數(shù) > 1 且 <= 20
              int maxNum = Optional.ofNullable(team.getMaxNum()).orElse(0);
              if (maxNum < 1 || maxNum > 20) {
                  throw new BusinessException(ErrorCode.PARAMS_ERROR, "隊(duì)伍人數(shù)不滿足要求");
              }
              //   2. 隊(duì)伍標(biāo)題 <= 20
              String name = team.getName();
              if (StringUtils.isBlank(name) || name.length() > 20) {
                  throw new BusinessException(ErrorCode.PARAMS_ERROR, "隊(duì)伍標(biāo)題不滿足要求");
              }
              //   3. 描述 <= 512
              String description = team.getDescription();
              if (StringUtils.isNotBlank(description) && description.length() > 512) {
                  throw new BusinessException(ErrorCode.PARAMS_ERROR, "隊(duì)伍描述過(guò)長(zhǎng)");
              }
              //   4. status 是否公開(kāi)(int)不傳默認(rèn)為 0(公開(kāi))
              int status = Optional.ofNullable(team.getStatus()).orElse(0);
              TeamStatusEnum statusEnum = TeamStatusEnum.getEnumByValue(status);
              if (statusEnum == null) {
                  throw new BusinessException(ErrorCode.PARAMS_ERROR, "隊(duì)伍狀態(tài)不滿足要求");
              }
              //   5. 如果 status 是加密狀態(tài),一定要有密碼,且密碼 <= 32
              String password = team.getPassword();
              if (TeamStatusEnum.SECRET.equals(statusEnum)) {
                  if (StringUtils.isBlank(password) || password.length() > 32) {
                      throw new BusinessException(ErrorCode.PARAMS_ERROR, "密碼設(shè)置不正確");
                  }
              }
              // 6. 超時(shí)時(shí)間 > 當(dāng)前時(shí)間
              Date expireTime = team.getExpireTime();
              if (new Date().after(expireTime)) {
                  throw new BusinessException(ErrorCode.PARAMS_ERROR, "超時(shí)時(shí)間 > 當(dāng)前時(shí)間");
              }
              // 7. 校驗(yàn)用戶最多創(chuàng)建 5 個(gè)隊(duì)伍
              // todo 有 bug,可能同時(shí)創(chuàng)建 100 個(gè)隊(duì)伍
              QueryWrapper<Team> queryWrapper = new QueryWrapper<>();
              queryWrapper.eq("userId", userId);
              long hasTeamNum = this.count(queryWrapper);
              if (hasTeamNum >= 5) {
                  throw new BusinessException(ErrorCode.PARAMS_ERROR, "用戶最多創(chuàng)建 5 個(gè)隊(duì)伍");
              }
              // 8. 插入隊(duì)伍信息到隊(duì)伍表
              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, "創(chuàng)建隊(duì)伍失敗");
              }
              // 9. 插入用戶  => 隊(duì)伍關(guān)系到關(guān)系表
              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, "創(chuàng)建隊(duì)伍失敗");
              }
              return teamId;
          }
      
      
      }
      

      補(bǔ)充:事務(wù)注解

      @Transaction(rollbackFor = Exception.class)  // 在方法上添加上注解,啟動(dòng)事務(wù)控制
      void public main() {
          
      }
      

      查詢隊(duì)伍列表

      分頁(yè)展示隊(duì)伍列表,根據(jù)名稱、最大人數(shù)等搜索隊(duì)伍PO,信息流中不展示已過(guò)期的隊(duì)伍。

      1. 從請(qǐng)求參數(shù)中取出隊(duì)伍名稱等查詢條件,如果存在則作為查詢條件
      2. 需要登錄,才能查詢
      3. 不展示已過(guò)期的隊(duì)伍 (根據(jù)過(guò)期時(shí)間篩選)
      4. 可以通過(guò)某個(gè)關(guān)鍵詞同時(shí)對(duì)名稱和描述查詢
      5. 只有管理員才能查看加密還有非公開(kāi)的房間
      6. 關(guān)聯(lián)查詢已加入隊(duì)伍的用戶信息
      7. 關(guān)聯(lián)查詢已加入隊(duì)伍的用戶信息(可能會(huì)很耗費(fèi)性能,建議大家用自己寫(xiě)SQL的方式實(shí)現(xiàn))

      自己寫(xiě) SQL

      // 1. 自己寫(xiě) SQL
      // 查詢隊(duì)伍和創(chuàng)建人的信息
      // select * from team t left join user u on t.userId = u.id
      // 查詢隊(duì)伍和已加入隊(duì)伍成員的信息
      // 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.plugins.pagination.Page;
      import com.rainbowsea.yupao.common.BaseResponse;
      import com.rainbowsea.yupao.common.DeleteRequest;
      import com.rainbowsea.yupao.common.ErrorCode;
      import com.rainbowsea.yupao.utils.ResultUtils;
      import com.rainbowsea.yupao.exception.BusinessException;
      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.request.TeamAddRequest;
      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.service.TeamService;
      import com.rainbowsea.yupao.service.UserService;
      import com.rainbowsea.yupao.service.UserTeamService;
      import io.swagger.annotations.Api;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.beans.BeanUtils;
      import org.springframework.web.bind.annotation.CrossOrigin;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.RequestBody;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      import javax.annotation.Resource;
      import javax.servlet.http.HttpServletRequest;
      import java.util.ArrayList;
      import java.util.List;
      import java.util.Map;
      import java.util.Set;
      import java.util.stream.Collectors;
      
      @RestController
      @RequestMapping("/team")
      @Api("接口文檔的一個(gè)別名處理定義 TeamController ")
      @CrossOrigin(origins = {"http://localhost:5173","http://localhost:3000"})  // 配置前端訪問(wèn)路徑的放行,可以配置多個(gè)
      @Slf4j
      public class TeamController {
      
          @Resource
          private UserService userService;
      
          @Resource
          private TeamService teamService;
      
          @Resource
          private UserTeamService userTeamService;
      
      /**
           * 顯示隊(duì)伍列表,私有的不顯示
           **/
          @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);
              // 1、查詢隊(duì)伍列表
              List<TeamUserVO> teamList = teamService.listTeams(teamQuery, isAdmin);
              final List<Long> teamIdList = teamList.stream().map(TeamUserVO::getId).collect(Collectors.toList());
              // 2、判斷當(dāng)前用戶是否已加入隊(duì)伍
              QueryWrapper<UserTeam> userTeamQueryWrapper = new QueryWrapper<>();
              try {
                  User loginUser = userService.getLoginUser(request);
                  userTeamQueryWrapper.eq("userId", loginUser.getId());
                  userTeamQueryWrapper.in("teamId", teamIdList);
                  List<UserTeam> userTeamList = userTeamService.list(userTeamQueryWrapper);
                  // 已加入的隊(duì)伍 id 集合
                  Set<Long> hasJoinTeamIdSet = userTeamList.stream().map(UserTeam::getTeamId).collect(Collectors.toSet());
                  teamList.forEach(team -> {
                      boolean hasJoin = hasJoinTeamIdSet.contains(team.getId());
                      team.setHasJoin(hasJoin);
                  });
              } catch (Exception e) {
              }
              // 3、查詢已加入隊(duì)伍的人數(shù)
              QueryWrapper<UserTeam> userTeamJoinQueryWrapper = new QueryWrapper<>();
              userTeamJoinQueryWrapper.in("teamId", teamIdList);
              List<UserTeam> userTeamList = userTeamService.list(userTeamJoinQueryWrapper);
              // 隊(duì)伍 id => 加入這個(gè)隊(duì)伍的用戶列表
              Map<Long, List<UserTeam>> teamIdUserTeamList = userTeamList.stream().collect(Collectors.groupingBy(UserTeam::getTeamId));
              teamList.forEach(team -> team.setHasJoinNum(teamIdUserTeamList.getOrDefault(team.getId(), new ArrayList<>()).size()));
              return ResultUtils.success(teamList);
          }
      }
      
      
      
      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;
      
      
      
          /**
           * 搜索隊(duì)伍
           * @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();
                  // 查詢最大人數(shù)相等的
                  if (maxNum != null && maxNum > 0) {
                      queryWrapper.eq("maxNum", maxNum);
                  }
                  Long userId = teamQuery.getUserId();
                  // 根據(jù)創(chuàng)建人來(lái)查詢
                  if (userId != null && userId > 0) {
                      queryWrapper.eq("userId", userId);
                  }
                  // 根據(jù)狀態(tài)來(lái)查詢
                  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());
              }
              // 不展示已過(guò)期的隊(duì)伍
              // 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<>();
              // 關(guān)聯(lián)查詢創(chuàng)建人的用戶信息
              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;
          }
              }
      }
      

      修改隊(duì)伍信息

      1. 判斷請(qǐng)求參數(shù)是否為空
      2. 查詢隊(duì)伍是否存在
      3. 只有管理員或者隊(duì)伍的創(chuàng)建者可以修改
      4. 如果用戶傳入的新值和老值一致,就不用update了 (可自行實(shí)現(xiàn),降低數(shù)據(jù)庫(kù)使用次數(shù))
      5. 如果隊(duì)伍狀態(tài)改為加密,必須要有密碼
      6. 更新成功
      
          /**
           * 更新隊(duì)伍內(nèi)容
           *
           * @param teamUpdateRequest teamUpdateRequest 對(duì)象
           * @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);
          }
      
      
          /**
           * 更新隊(duì)伍
           * @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, "隊(duì)伍不存在");
              }
              // 只有管理員或者隊(duì)伍的創(chuàng)建者可以修改
              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, "加密房間必須要設(shè)置密碼");
                  }
              }
              Team updateTeam = new Team();
              BeanUtils.copyProperties(teamUpdateRequest, updateTeam);
              return this.updateById(updateTeam);
          }
      

      用戶可以加入隊(duì)伍

      其他人、未滿、未過(guò)期,允許加入多個(gè)隊(duì)伍,但是要有個(gè)上限PO

      1. 用戶最多加入 5個(gè)隊(duì)伍
      2. 隊(duì)伍必須存在,只能加入未滿、未過(guò)期的隊(duì)伍
      3. 不能加入自己的隊(duì)伍,不能重復(fù)加入已加入的隊(duì)伍 (冪等性)
      4. 禁止加入私有的隊(duì)伍
      5. 如果加入的隊(duì)伍是加密的,必須密碼匹配才可以
      6. 新增隊(duì)伍-用戶關(guān)聯(lián)信息

      注意,一定要加上事務(wù)注解!!!!

      
          /**
           * 加入隊(duì)伍
           * @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);
          }
      
      
          /**
           * 加入隊(duì)伍
           *
           * @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, "隊(duì)伍已過(guò)期");
              }
              Integer status = team.getStatus();
              TeamStatusEnum teamStatusEnum = TeamStatusEnum.getEnumByValue(status);
              if (TeamStatusEnum.PRIVATE.equals(teamStatusEnum)) {
                  throw new BusinessException(ErrorCode.PARAMS_ERROR, "禁止加入私有隊(duì)伍");
              }
              String password = teamJoinRequest.getPassword();
              if (TeamStatusEnum.SECRET.equals(teamStatusEnum)) {
                  if (StringUtils.isBlank(password) || !password.equals(team.getPassword())) {
                      throw new BusinessException(ErrorCode.PARAMS_ERROR, "密碼錯(cuò)誤");
                  }
              }
              // 該用戶已加入的隊(duì)伍數(shù)量
              long userId = loginUser.getId();
              // 只有一個(gè)線程能獲取到鎖
              RLock lock = redissonClient.getLock("yupao:join_team");
              try {
                  // 搶到鎖并執(zhí)行
                  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, "最多創(chuàng)建和加入 5 個(gè)隊(duì)伍");
                          }
                          // 不能重復(fù)加入已加入的隊(duì)伍
                          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, "用戶已加入該隊(duì)伍");
                          }
                          // 已加入隊(duì)伍的人數(shù)
                          long teamHasJoinNum = this.countTeamUserByTeamId(teamId);
                          if (teamHasJoinNum >= team.getMaxNum()) {
                              throw new BusinessException(ErrorCode.PARAMS_ERROR, "隊(duì)伍已滿");
                          }
                          // 修改隊(duì)伍信息
                          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();
                  }
              }
          }
      

      用戶可以退出隊(duì)伍

      請(qǐng)求參數(shù):用戶 ID

      1. 校驗(yàn)隊(duì)伍是否存在
      2. 校驗(yàn)我是否已加入隊(duì)伍
      3. 如果隊(duì)伍
        1. 只剩一人,隊(duì)伍解散,(只剩一人,說(shuō)明本身自己就是隊(duì)長(zhǎng))
        2. 還有其他人
        3. 如果是隊(duì)長(zhǎng)退出隊(duì)伍,權(quán)限轉(zhuǎn)移給第二早加入的用戶一一先來(lái)后到(只用取id最小的2 條數(shù)據(jù))
        4. 非隊(duì)長(zhǎng),自己退出隊(duì)伍
      
          /**
           * 退出隊(duì)伍,
           * @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);
          }
      
       /**
           * 退出隊(duì)伍
           * @param teamQuitRequest
           * @param loginUser
           * @return
           */
          @Override
          @Transactional(rollbackFor = Exception.class)  // 添加上事務(wù)
          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, "未加入隊(duì)伍");
              }
              long teamHasJoinNum = this.countTeamUserByTeamId(teamId);
              // 隊(duì)伍只剩一人,解散
              if (teamHasJoinNum == 1) {
                  // 刪除隊(duì)伍
                  this.removeById(teamId);
              } else {
                  // 隊(duì)伍還剩至少兩人
                  // 是隊(duì)長(zhǎng)
                  if (team.getUserId() == userId) {
                      // 把隊(duì)伍轉(zhuǎn)移給最早加入的用戶
                      // 1. 查詢已加入隊(duì)伍的所有用戶和加入時(shí)間
                      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();
                      // 更新當(dāng)前隊(duì)伍的隊(duì)長(zhǎng)
                      Team updateTeam = new Team();
                      updateTeam.setId(teamId);
                      updateTeam.setUserId(nextTeamLeaderId);
                      boolean result = this.updateById(updateTeam);
                      if (!result) {
                          throw new BusinessException(ErrorCode.SYSTEM_ERROR, "更新隊(duì)伍隊(duì)長(zhǎng)失敗");
                      }
                  }
              }
              // 移除關(guān)系
              return userTeamService.remove(queryWrapper);
          }
      
      

      隊(duì)長(zhǎng)可以解散隊(duì)伍

      請(qǐng)求參數(shù):隊(duì)伍 id

      業(yè)務(wù)流程:

      1. 校驗(yàn)請(qǐng)求參數(shù)
      2. 校驗(yàn)隊(duì)伍是否存在
      3. 校驗(yàn)?zāi)闶遣皇顷?duì)伍的隊(duì)長(zhǎng)
      4. 移除所有加入隊(duì)伍的關(guān)聯(lián)信息
      5. 刪除隊(duì)伍
      
          /**
           * 刪除隊(duì)伍,移除隊(duì)伍
           *
           * @param deleteRequest 隊(duì)伍的 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);
          }
      
      
          /**
           * 刪除隊(duì)伍
           * @param id 隊(duì)伍中隊(duì)長(zhǎng)的 ID
           * @param loginUser
           * @return
           */
          @Override
          @Transactional(rollbackFor = Exception.class)
          public boolean deleteTeam(long id, User loginUser) {
              // 校驗(yàn)隊(duì)伍是否存在
              Team team = this.getTeamById(id);
              Long teamId = team.getId();
              // 校驗(yàn)?zāi)闶遣皇顷?duì)伍的隊(duì)長(zhǎng)
              if (!team.getUserId().equals(loginUser.getId())) {
                  throw new BusinessException(ErrorCode.NO_AUTH, "不是隊(duì)長(zhǎng),無(wú)權(quán)限刪除");
              }
      
              // 移除所有加入隊(duì)伍的關(guān)聯(lián)信息
              QueryWrapper<UserTeam> userTeamQueryWrapper = new QueryWrapper<>();
              userTeamQueryWrapper.eq("teamId", teamId);
              boolean result = userTeamService.remove(userTeamQueryWrapper);
              if(!result) {
                  throw new BusinessException(ErrorCode.SYSTEM_ERROR,"刪除隊(duì)伍關(guān)聯(lián)信息失敗");
              }
              // 刪除隊(duì)伍
              return this.removeById(teamId);
      
          }
      
      

      獲取當(dāng)前用戶創(chuàng)建的隊(duì)伍:包括:私有的,公開(kāi)的,加密的,只要是自己創(chuàng)建的

      
          /**
           * 獲取當(dāng)前用戶創(chuàng)建的隊(duì)伍:包括:私有的,公開(kāi)的,加密的,只要是自己創(chuàng)建的
           * @param teamQuery
           * @param request
           * @return
           * @Author: RainbowSea
           */
          @GetMapping("/list/my/create")
          public BaseResponse<List<TeamUserVO>> listMyCreateTeams(TeamQuery teamQuery, HttpServletRequest request) {
              if (teamQuery == null) {
                  throw new BusinessException(ErrorCode.PARAMS_ERROR);
              }
      
              // 獲取當(dāng)前登錄用戶
              User loginUser = userService.getLoginUser(request);
      
              // 設(shè)置查詢條件,查詢當(dāng)前用戶創(chuàng)建的團(tuán)隊(duì)
              teamQuery.setUserId(loginUser.getId());
      
              // 構(gòu)建查詢條件
              QueryWrapper<Team> queryWrapper = new QueryWrapper<>();
              queryWrapper.eq("userId", teamQuery.getUserId()); // 根據(jù) `userId` 字段進(jìn)行查詢
      
              // 查詢所有隊(duì)伍
              List<Team> teamList = teamService.list(queryWrapper);
      
              // 轉(zhuǎn)換為 TeamUserVO 并加入必要字段
              List<TeamUserVO> teamUserVOList = teamList.stream()
                      .map(team -> {
                          TeamUserVO teamUserVO = new TeamUserVO();
                          teamUserVO.setId(team.getId());
                          teamUserVO.setName(team.getName());
                          teamUserVO.setDescription(team.getDescription());
                          teamUserVO.setUserId(team.getUserId());
                          teamUserVO.setCreateTime(team.getCreateTime());
                          teamUserVO.setUpdateTime(team.getUpdateTime());
                          teamUserVO.setMaxNum(team.getMaxNum());
                          teamUserVO.setExpireTime(team.getExpireTime());
                          teamUserVO.setDescription(team.getDescription());
                          teamUserVO.setStatus(team.getStatus());
                          // 其他字段的映射
                          return teamUserVO;
                      }).collect(Collectors.toList());
      
              // 獲取當(dāng)前用戶已加入的隊(duì)伍
              List<Long> teamIdList = teamUserVOList.stream().map(TeamUserVO::getId).collect(Collectors.toList());
      
              QueryWrapper<UserTeam> userTeamQueryWrapper = new QueryWrapper<>();
              try {
                  userTeamQueryWrapper.eq("userId", loginUser.getId());
                  userTeamQueryWrapper.in("teamId", teamIdList);
                  List<UserTeam> userTeamList = userTeamService.list(userTeamQueryWrapper);
                  // 已加入的隊(duì)伍 id 集合
                  Set<Long> hasJoinTeamIdSet = userTeamList.stream().map(UserTeam::getTeamId).collect(Collectors.toSet());
                  teamUserVOList.forEach(team -> {
                      boolean hasJoin = hasJoinTeamIdSet.contains(team.getId());
                      team.setHasJoin(hasJoin);
                  });
              } catch (Exception e) {
                  // 處理異常情況,日志記錄等
              }
      
              // 查詢已加入隊(duì)伍的人數(shù)
              QueryWrapper<UserTeam> userTeamJoinQueryWrapper = new QueryWrapper<>();
              userTeamJoinQueryWrapper.in("teamId", teamIdList);
              List<UserTeam> userTeamList = userTeamService.list(userTeamJoinQueryWrapper);
      
              // 隊(duì)伍 id => 加入這個(gè)隊(duì)伍的用戶列表
              Map<Long, List<UserTeam>> teamIdUserTeamList = userTeamList.stream()
                      .collect(Collectors.groupingBy(UserTeam::getTeamId));
      
              teamUserVOList.forEach(team ->
                      team.setHasJoinNum(teamIdUserTeamList.getOrDefault(team.getId(), new ArrayList<>()).size())
              );
      
              // 返回所有隊(duì)伍信息
              return ResultUtils.success(teamUserVOList);
          }
      

      獲取當(dāng)前用戶加入的隊(duì)伍,包括:私有的,公開(kāi)的,加密的,只要是自己加入的

      
          /**
           * 獲取當(dāng)前用戶加入的隊(duì)伍
           * 包括:私有的,公開(kāi)的,加密的,只要是自己加入的
           * @Author: RainbowSea
           */
          @GetMapping("/list/my/join")
          public BaseResponse<List<TeamUserVO>> listMyJoinTeams(TeamQuery teamQuery, HttpServletRequest request) {
              if (teamQuery == null) {
                  throw new BusinessException(ErrorCode.PARAMS_ERROR);
              }
      
              // 獲取當(dāng)前登錄用戶
              User loginUser = userService.getLoginUser(request);
      
              // 先查詢用戶已加入的隊(duì)伍
              QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>();
              queryWrapper.eq("userId", loginUser.getId());
              List<UserTeam> userTeamList = userTeamService.list(queryWrapper);
      
              // 提取不重復(fù)的隊(duì)伍 ID
              Map<Long, List<UserTeam>> listMap = userTeamList.stream().collect(Collectors.groupingBy(UserTeam::getTeamId));
              ArrayList<Long> idList = new ArrayList<>(listMap.keySet());
      
              // 設(shè)置查詢條件,只查詢當(dāng)前用戶已加入的隊(duì)伍
              teamQuery.setIdList(idList);
      
              // 構(gòu)建查詢條件
              QueryWrapper<Team> teamQueryWrapper = new QueryWrapper<>();
              if (teamQuery.getIdList() != null && !teamQuery.getIdList().isEmpty()) {
                  teamQueryWrapper.in("id", teamQuery.getIdList());  // 根據(jù)隊(duì)伍ID查詢
              }
      
              // 如果有搜索條件,加入搜索條件
              if (teamQuery.getSearchText() != null && !teamQuery.getSearchText().isEmpty()) {
                  teamQueryWrapper.like("name", teamQuery.getSearchText())  // 根據(jù)隊(duì)伍名稱進(jìn)行模糊搜索
                          .or().like("description", teamQuery.getSearchText());  // 或者根據(jù)隊(duì)伍描述進(jìn)行模糊搜索
              }
      
              // 分頁(yè)處理
              teamQueryWrapper.last("LIMIT " + ((teamQuery.getPageNum() - 1) * teamQuery.getPageSize()) + ", " + teamQuery.getPageSize());
      
              // 查詢隊(duì)伍列表
              List<Team> teamList = teamService.list(teamQueryWrapper);
      
              // 轉(zhuǎn)換為 TeamUserVO 并加入必要字段
              List<TeamUserVO> teamUserVOList = teamList.stream()
                      .map(team -> {
                          TeamUserVO teamUserVO = new TeamUserVO();
                          teamUserVO.setId(team.getId());
                          teamUserVO.setName(team.getName());
                          teamUserVO.setDescription(team.getDescription());
                          teamUserVO.setUserId(team.getUserId());
                          teamUserVO.setCreateTime(team.getCreateTime());
                          teamUserVO.setUpdateTime(team.getUpdateTime());
                          teamUserVO.setMaxNum(team.getMaxNum());
                          teamUserVO.setExpireTime(team.getExpireTime());
                          teamUserVO.setStatus(team.getStatus());
                          // 其他字段的映射
                          return teamUserVO;
                      }).collect(Collectors.toList());
      
              // 獲取當(dāng)前用戶已加入的隊(duì)伍
              Set<Long> hasJoinTeamIdSet = userTeamList.stream().map(UserTeam::getTeamId).collect(Collectors.toSet());
              teamUserVOList.forEach(team -> {
                  boolean hasJoin = hasJoinTeamIdSet.contains(team.getId());
                  team.setHasJoin(hasJoin);  // 是否已經(jīng)加入
              });
      
              // 查詢已加入隊(duì)伍的人數(shù)
              QueryWrapper<UserTeam> userTeamJoinQueryWrapper = new QueryWrapper<>();
              userTeamJoinQueryWrapper.in("teamId", idList);
              List<UserTeam> userTeamListForCount = userTeamService.list(userTeamJoinQueryWrapper);
      
              // 隊(duì)伍 ID => 加入該隊(duì)伍的用戶列表
              Map<Long, List<UserTeam>> teamIdUserTeamList = userTeamListForCount.stream()
                      .collect(Collectors.groupingBy(UserTeam::getTeamId));
      
              teamUserVOList.forEach(team ->
                      team.setHasJoinNum(teamIdUserTeamList.getOrDefault(team.getId(), new ArrayList<>()).size())  // 設(shè)置已加入人數(shù)
              );
      
              // 返回所有隊(duì)伍信息
              return ResultUtils.success(teamUserVOList);
          }
      
      

      推薦算法:隨機(jī)匹配

      需求背景:為了幫助大家更快的發(fā)現(xiàn)和自己新區(qū)相同的朋友。

      思考:匹配 1 個(gè)還是匹配多個(gè)。

      答:匹配多個(gè),并且按照匹配的相似度從高到低排序

      思考:怎么匹配?(根據(jù)什么匹配)

      答:這里我們根據(jù)用戶 user 表當(dāng)中設(shè)置的 tags 屬性進(jìn)行匹配。

      還可以根據(jù) user_team 匹配加入相同隊(duì)伍的用戶。

      問(wèn)題本質(zhì):找到有相似標(biāo)簽的用戶

      舉例:

      • 用戶 A: 【Java,大一,男】
      • 用戶 B: 【Java,大二,男】
      • 用戶 C: 【Python,大二,女】
      • 用戶 D: 【Java,大一,女】

      怎么匹配?

      1. 找到有共同標(biāo)簽最多的用戶(ToPN)
      2. 共同標(biāo)簽越多,分?jǐn)?shù)越高,排在越前面
      3. 如果沒(méi)有匹配的用戶,隨機(jī)推薦幾個(gè)(降級(jí)方案)

      兩種算法:

      1. 編輯距離算法:https://blog.csdn.net/DBC_121/article/details/104198838

      最小編輯距離:就是一個(gè)字符串 1 通過(guò)對(duì)其字符串最少多少次增刪改字符的操作可以變成 字符串 2(一樣的)

      1. 余弦相似度算法:(如果需要為對(duì)應(yīng)標(biāo)簽帶權(quán)重進(jìn)行計(jì)算,比如:學(xué)什么方向最重要,性別相對(duì)次要)

      這里我們采用的是編輯距離算法:

      package com.rainbowsea.yupao.utils;
      
      import java.util.Collections;
      import java.util.List;
      import java.util.Objects;
      
      /**
       * 算法工具類
       * 編輯距離算法(用于計(jì)算最相似的兩組標(biāo)簽)
       * 原理:https://blog.csdn.net/DBC_121/article/details/104198838
       *
       */
      public class AlgorithmUtils {
      
          /**
           * 編輯距離算法(用于計(jì)算最相似的兩組標(biāo)簽)
           * 原理:https://blog.csdn.net/DBC_121/article/details/104198838
           *
           * @param tagList1
           * @param tagList2
           * @return
           */
          public static int minDistance(List<String> tagList1, List<String> tagList2) {
              // 在開(kāi)始計(jì)算的時(shí)候,進(jìn)行一個(gè)排序,讓其中的tag 標(biāo)簽的內(nèi)容,是相同等內(nèi)容排序比較對(duì)比
              Collections.sort(tagList1);
              Collections.sort(tagList2);
              int n = tagList1.size();
              int m = tagList2.size();
      
              if (n * m == 0) {
                  return n + m;
              }
      
              int[][] d = new int[n + 1][m + 1];
              for (int i = 0; i < n + 1; i++) {
                  d[i][0] = i;
              }
      
              for (int j = 0; j < m + 1; j++) {
                  d[0][j] = j;
              }
      
              for (int i = 1; i < n + 1; i++) {
                  for (int j = 1; j < m + 1; j++) {
                      int left = d[i - 1][j] + 1;
                      int down = d[i][j - 1] + 1;
                      int left_down = d[i - 1][j - 1];
                      if (!Objects.equals(tagList1.get(i - 1), tagList2.get(j - 1))) {
                          left_down += 1;
                      }
                      d[i][j] = Math.min(left, Math.min(down, left_down));
                  }
              }
              return d[n][m];
          }
      
      
          /**
           * 編輯距離算法(用于計(jì)算最相似的兩個(gè)字符串)
           * 原理:https://blog.csdn.net/DBC_121/article/details/104198838
           *
           * @param word1
           * @param word2
           * @return
           */
          public static int minDistance(String word1, String word2) {
              int n = word1.length();
              int m = word2.length();
      
              if (n * m == 0) {
                  return n + m;
              }
      
              int[][] d = new int[n + 1][m + 1];
              for (int i = 0; i < n + 1; i++) {
                  d[i][0] = i;
              }
      
              for (int j = 0; j < m + 1; j++) {
                  d[0][j] = j;
              }
      
              for (int i = 1; i < n + 1; i++) {
                  for (int j = 1; j < m + 1; j++) {
                      int left = d[i - 1][j] + 1;
                      int down = d[i][j - 1] + 1;
                      int left_down = d[i - 1][j - 1];
                      if (word1.charAt(i - 1) != word2.charAt(j - 1)) {
                          left_down += 1;
                      }
                      d[i][j] = Math.min(left, Math.min(down, left_down));
                  }
              }
              return d[n][m];
          }
      }
      
      

      怎么對(duì)所有用戶匹配,取 TOP ?

      直接取出所有用戶,依次和當(dāng)前用計(jì)算分?jǐn)?shù),取 TOPN (50W 花費(fèi) 54 秒 )

      優(yōu)化方法:

      1. 切忌不要在數(shù)據(jù)量大的時(shí)候循環(huán)輸出日志(取消掉日志后 20 秒),這里指的是 MyBatisPlus 的輸出日志在 yaml 當(dāng)中的配置。
      2. Map 存了所有的分?jǐn)?shù)信息,占用內(nèi)存。

      解決:維護(hù)一個(gè)固定長(zhǎng)度的有序集合(sortedSet),只需要保留分?jǐn)?shù)最高的幾個(gè)用戶集合(利用時(shí)間換空間)

      eg: 【5,3,4,6,7】 取 TOP 5 即可,id 為 1 的用戶就不用放進(jìn)去了。

      細(xì)節(jié):

      1. 剔除自己,自己不需要匹配查詢
      2. 盡量只查需要的數(shù)據(jù):
        1. 過(guò)濾掉標(biāo)簽為空的用戶
        2. 根據(jù)部分標(biāo)簽用戶(前提是能區(qū)分出來(lái)那個(gè)標(biāo)簽比較重要)
        3. 只查需要的數(shù)據(jù)(比如只查 Id 和 tags ),不要用 * 。
      3. 提前查?(使用定時(shí)任務(wù))
        1. 提前把所有用戶給緩存(不適用于經(jīng)常更新的數(shù)據(jù))
        2. 提前運(yùn)算出來(lái)結(jié)果,緩存(針對(duì)一些重點(diǎn)用戶,提前緩存)

      類比大數(shù)據(jù)推薦機(jī)制:

      大數(shù)據(jù)推薦場(chǎng)景:比如說(shuō)有幾十億個(gè)商品,難道要查出來(lái)所有的商品?難道要對(duì)所有的數(shù)據(jù)計(jì)算一遍相似度?

      大數(shù)據(jù)推薦流程:

      • 檢索—> 召回—> 粗排—> 精排—> 重排序等等等。
      • 檢索:盡可能多的查符合要求的數(shù)據(jù)(比如:按記錄查)
      • 召回:查詢可能要用到的數(shù)據(jù)(不做運(yùn)算)
      • 粗排:粗略排序,簡(jiǎn)單地運(yùn)算(運(yùn)算相對(duì)輕量)
      • 精排:精細(xì)排序,確定固定排位

      這里我們使用的方式:

      
      import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
      import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
      import com.rainbowsea.yupao.common.BaseResponse;
      import com.rainbowsea.yupao.common.ErrorCode;
      import com.rainbowsea.yupao.utils.ResultUtils;
      import com.rainbowsea.yupao.exception.BusinessException;
      import com.rainbowsea.yupao.model.User;
      import com.rainbowsea.yupao.model.request.UserLoginRequest;
      import com.rainbowsea.yupao.model.request.UserRegisterRequest;
      import com.rainbowsea.yupao.service.UserService;
      import io.swagger.annotations.Api;
      import lombok.extern.slf4j.Slf4j;
      import org.apache.commons.lang3.StringUtils;
      import org.springframework.data.redis.core.RedisTemplate;
      import org.springframework.data.redis.core.ValueOperations;
      import org.springframework.util.CollectionUtils;
      import org.springframework.web.bind.annotation.CrossOrigin;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.RequestBody;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RequestParam;
      import org.springframework.web.bind.annotation.RestController;
      import java.util.concurrent.TimeUnit;
      
      import javax.annotation.Resource;
      import javax.servlet.http.HttpServletRequest;
      import java.util.ArrayList;
      import java.util.List;
      import java.util.stream.Collectors;
      
      import static com.rainbowsea.yupao.contant.UserConstant.USER_LOGIN_STATE;
      
      @RestController
      @RequestMapping("/user")
      @Api("接口文檔的一個(gè)別名處理定義 UserController")
      @CrossOrigin(origins = {"http://localhost:5173","http://localhost:3000"})  // 配置前端訪問(wèn)路徑的放行,可以配置多個(gè)
      @Slf4j
      public class UserController {
      
      
          @Resource
          private UserService userService;
      
          @Resource
          private RedisTemplate<String, Object> redisTemplate;
          /**
           * 獲取最匹配的用戶
           *
           * @param num
           * @param request
           * @return
           */
          @GetMapping("/match")
          public BaseResponse<List<User>> matchUsers(long num, HttpServletRequest request) {
              if (num <= 0 || num > 20) {
                  throw new BusinessException(ErrorCode.PARAMS_ERROR);
              }
              User user = userService.getLoginUser(request);
              return ResultUtils.success(userService.matchUsers(num, user));
          }
      
      }
      
      
      
      import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
      import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
      import com.google.gson.Gson;
      import com.google.gson.reflect.TypeToken;
      import com.rainbowsea.yupao.common.ErrorCode;
      import com.rainbowsea.yupao.contant.UserConstant;
      import com.rainbowsea.yupao.exception.BusinessException;
      import com.rainbowsea.yupao.model.User;
      import com.rainbowsea.yupao.service.UserService;
      import com.rainbowsea.yupao.mapper.UserMapper;
      import com.rainbowsea.yupao.utils.AlgorithmUtils;
      import lombok.extern.slf4j.Slf4j;
      import org.apache.commons.lang3.StringUtils;
      import org.apache.commons.math3.util.Pair;
      import org.apache.poi.ss.formula.functions.Now;
      import org.springframework.stereotype.Service;
      import org.springframework.util.CollectionUtils;
      import org.springframework.util.DigestUtils;
      
      import javax.annotation.Resource;
      import javax.servlet.http.HttpServletRequest;
      
      import java.util.ArrayList;
      import java.util.HashSet;
      import java.util.List;
      import java.util.Map;
      import java.util.Optional;
      import java.util.Set;
      import java.util.stream.Collectors;
      
      import static com.rainbowsea.yupao.contant.UserConstant.USER_LOGIN_STATE;
      
      /**
       * @author RainbowSea
       * @description 針對(duì)表【user(用戶)】的數(shù)據(jù)庫(kù)操作Service實(shí)現(xiàn)
       * @createDate 2025-04-14 16:03:21
       */
      @Service
      @Slf4j
      public class UserServiceImpl extends ServiceImpl<UserMapper, User>
              implements UserService {
      
          /**
           * 加鹽 混淆,讓密碼更加沒(méi)有規(guī)律,更加安全一些
           */
          private static final String SALT = "rainbowsea";
      
      
          @Resource
          private UserMapper userMapper;
      
      
      
          /**
           * 推薦(前端心動(dòng)模式)的推薦算法,推薦用戶
           * @param num
           * @param loginUser
           * @return
           */
          @Override
          public List<User> matchUsers(long num, User loginUser) {
              QueryWrapper<User> queryWrapper = new QueryWrapper<>();
              queryWrapper.select("id", "tags");
              queryWrapper.isNotNull("tags");
              List<User> userList = this.list(queryWrapper);
              String tags = loginUser.getTags();
              Gson gson = new Gson();
              List<String> tagList = gson.fromJson(tags, new TypeToken<List<String>>() {
              }.getType());
              // 用戶列表的下標(biāo) => 相似度
              List<Pair<User, Long>> list = new ArrayList<>();
              // 依次計(jì)算所有用戶和當(dāng)前用戶的相似度
              for (int i = 0; i < userList.size(); i++) {
                  User user = userList.get(i);
                  String userTags = user.getTags();
                  // 無(wú)標(biāo)簽或者為當(dāng)前用戶自己
                  if (StringUtils.isBlank(userTags) || user.getId().equals(loginUser.getId())) {
                      continue;
                  }
                  List<String> userTagList = gson.fromJson(userTags, new TypeToken<List<String>>() {
                  }.getType());
                  // 計(jì)算分?jǐn)?shù)
                  long distance = AlgorithmUtils.minDistance(tagList, userTagList);
                  list.add(new Pair<>(user, distance));
              }
              // 按編輯距離由小到大排序
              List<Pair<User, Long>> topUserPairList = list.stream()
                      .sorted((a, b) -> (int) (a.getValue() - b.getValue()))
                      .limit(num)
                      .collect(Collectors.toList());
              // 原本順序的 userId 列表
              List<Long> userIdList = topUserPairList.stream().map(pair -> pair.getKey().getId()).collect(Collectors.toList());
              QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
              userQueryWrapper.in("id", userIdList);
              // 1, 3, 2
              // User1、User2、User3
              // 1 => User1, 2 => User2, 3 => User3
              Map<Long, List<User>> userIdUserListMap = this.list(userQueryWrapper)
                      .stream()
                      .map(user -> getSafetyUser(user))
                      .collect(Collectors.groupingBy(User::getId));
              List<User> finalUserList = new ArrayList<>();
              for (Long userId : userIdList) {
                  finalUserList.add(userIdUserListMap.get(userId).get(0));
              }
              return finalUserList;
          }
      }
      
      package com.rainbowsea.yupao.utils;
      
      import java.util.Collections;
      import java.util.List;
      import java.util.Objects;
      
      /**
       * 算法工具類
       * 編輯距離算法(用于計(jì)算最相似的兩組標(biāo)簽)
       * 原理:https://blog.csdn.net/DBC_121/article/details/104198838
       *
       */
      public class AlgorithmUtils {
      
          /**
           * 編輯距離算法(用于計(jì)算最相似的兩組標(biāo)簽)
           * 原理:https://blog.csdn.net/DBC_121/article/details/104198838
           *
           * @param tagList1
           * @param tagList2
           * @return
           */
          public static int minDistance(List<String> tagList1, List<String> tagList2) {
              // 在開(kāi)始計(jì)算的時(shí)候,進(jìn)行一個(gè)排序,讓其中的tag 標(biāo)簽的內(nèi)容,是相同等內(nèi)容排序比較對(duì)比
              Collections.sort(tagList1);
              Collections.sort(tagList2);
              int n = tagList1.size();
              int m = tagList2.size();
      
              if (n * m == 0) {
                  return n + m;
              }
      
              int[][] d = new int[n + 1][m + 1];
              for (int i = 0; i < n + 1; i++) {
                  d[i][0] = i;
              }
      
              for (int j = 0; j < m + 1; j++) {
                  d[0][j] = j;
              }
      
              for (int i = 1; i < n + 1; i++) {
                  for (int j = 1; j < m + 1; j++) {
                      int left = d[i - 1][j] + 1;
                      int down = d[i][j - 1] + 1;
                      int left_down = d[i - 1][j - 1];
                      if (!Objects.equals(tagList1.get(i - 1), tagList2.get(j - 1))) {
                          left_down += 1;
                      }
                      d[i][j] = Math.min(left, Math.min(down, left_down));
                  }
              }
              return d[n][m];
          }
      
      
          /**
           * 編輯距離算法(用于計(jì)算最相似的兩個(gè)字符串)
           * 原理:https://blog.csdn.net/DBC_121/article/details/104198838
           *
           * @param word1
           * @param word2
           * @return
           */
          public static int minDistance(String word1, String word2) {
              int n = word1.length();
              int m = word2.length();
      
              if (n * m == 0) {
                  return n + m;
              }
      
              int[][] d = new int[n + 1][m + 1];
              for (int i = 0; i < n + 1; i++) {
                  d[i][0] = i;
              }
      
              for (int j = 0; j < m + 1; j++) {
                  d[0][j] = j;
              }
      
              for (int i = 1; i < n + 1; i++) {
                  for (int j = 1; j < m + 1; j++) {
                      int left = d[i - 1][j] + 1;
                      int down = d[i][j - 1] + 1;
                      int left_down = d[i - 1][j - 1];
                      if (word1.charAt(i - 1) != word2.charAt(j - 1)) {
                          left_down += 1;
                      }
                      d[i][j] = Math.min(left, Math.min(down, left_down));
                  }
              }
              return d[n][m];
          }
      }
      
      

      分表學(xué)習(xí)建議:

      mycat,sharding sphere 框架

      一致性 hash 算法

      前端簡(jiǎn)單的功能調(diào)整

      權(quán)限整理

      • 加入隊(duì)伍按鈕:僅非隊(duì)伍創(chuàng)建人、且未加入隊(duì)伍的人可見(jiàn)
      • 更新隊(duì)伍按鈕:僅創(chuàng)建人可見(jiàn)
      • 解散隊(duì)伍按鈕:僅創(chuàng)建人可見(jiàn)
      • 退出隊(duì)伍按鈕:創(chuàng)建人不可見(jiàn),僅已加入隊(duì)伍的人可見(jiàn)
      1. 僅加入隊(duì)伍和創(chuàng)建隊(duì)伍的人能看到隊(duì)伍操作按鈕(listTeam接口要能獲取我加入的隊(duì)伍狀態(tài))

      方案1:前端查詢我加入了哪些隊(duì)伍列表,然后判斷每個(gè)隊(duì)伍id 是否在列表中(前端要多發(fā)一次請(qǐng)

      求)

      方案 2:在后端去做上述事情 (推薦)

      解決:使用router.beforeEach,根據(jù)要跳轉(zhuǎn)頁(yè)面的url 路徑匹配 config/routes 配置的 title 字段。

      1. 前端導(dǎo)航欄死【標(biāo)題】問(wèn)題,實(shí)時(shí)動(dòng)態(tài)顯示前端標(biāo)題信息
      2. 沒(méi)有登錄無(wú)法查詢信息,強(qiáng)制登錄,自動(dòng)跳轉(zhuǎn)到登錄頁(yè),

      解決:axios 全局配置響應(yīng)攔截、并且添加重定向

      1. 區(qū)分公開(kāi)和加密房間;加入有密碼的房間,要指定密碼
      2. 展示已加入隊(duì)伍人數(shù)

      最后:

      “在這個(gè)最后的篇章中,我要表達(dá)我對(duì)每一位讀者的感激之情。你們的關(guān)注和回復(fù)是我創(chuàng)作的動(dòng)力源泉,我從你們身上吸取了無(wú)盡的靈感與勇氣。我會(huì)將你們的鼓勵(lì)留在心底,繼續(xù)在其他的領(lǐng)域奮斗。感謝你們,我們總會(huì)在某個(gè)時(shí)刻再次相遇。”

      posted @ 2025-08-20 10:55  Rainbow-Sea  閱讀(116)  評(píng)論(1)    收藏  舉報(bào)
      主站蜘蛛池模板: 国产无套内射普通话对白| 久热这里只有精品在线观看| 东京热人妻无码一区二区AV| 美女自卫慰黄网站| 天天做天天爱夜夜爽导航| 国产成人一区二区三区免费| 安图县| 最近中文国语字幕在线播放| 九九热视频在线免费观看| 综合亚洲网| 久热久热久热久热久热久热| 日韩黄色av一区二区三区| 色老头亚洲成人免费影院| 成人一区二区三区久久精品| 少妇人妻偷人精品免费| 看免费的无码区特aa毛片| 中文字幕在线精品人妻| 97se亚洲国产综合自在线观看 | 色综合天天综合天天更新| 精品人妻日韩中文字幕| 国产精品麻豆欧美日韩ww| 三级黄色片一区二区三区| 一区二区三区精品偷拍| 国产在线精品无码二区| 色一伊人区二区亚洲最大| 青青草无码免费一二三区| 国产福利在线观看免费第一福利| 亚欧洲乱码视频在线专区| 在线亚洲妇色中文色综合| 国产精品中文第一字幕| 国产一区二区在线激情往| 国产精品白浆在线观看免费| 成人亚洲欧美一区二区三区| 99久久er热在这里只有精品99| 午夜精品福利亚洲国产| 国产强奷在线播放免费| 国产360激情盗摄全集| 亚洲一区二区三区自拍偷拍| 最新AV中文字幕无码专区| 亚洲人成小说网站色在线| 日本一区二区三区激情视频 |