[TIL] 랜덤 파티 매칭 구현 1차

YJin·2025년 6월 10일

[내배캠 Spring 6기_TIL]

목록 보기
45/56

스케줄러 사용해서 구현

// 실행 후 1분마다 실행 // MEMO : 유저가 매칭 여러개 동시에 돌리는 경우엔 partyInvitation 정보에 파티 매칭 정보도 적어놔야하나
	@Scheduled(fixedDelay = 60000)
	public void runMatching() {
		// 유저를 기준으로 맞는 파티 매칭
		// MatchingStatus 가 MATCHING 인 유저들만 불러오기
		// 유저에게 파티 초대 상태가 없는 파티인지 확인 필요
		// 유저별로 제일 적합한 파티 하나 추천

		// MEMO : 다 불러와도 되나?
		// MEMO : 있는지 체크하고 그다음에 불러오는 방식 vs (지금) 다 불러오고 체크
		List<UserMatchCond> matchingUserList =
			userMatchCondRepository.findAllByMatchStatus(MatchStatus.MATCHING);
		// 매칭 중인 유저가 없는 경우
		if (matchingUserList.isEmpty()) {
			return;
		}

		List<PartyMatchCond> matchingPartyList = partyMatchCondRepository.findAll();
		// 매칭 중인 파티가 없는 경우
		if (matchingPartyList.isEmpty()) {
			return;
		}

		// 매칭 알고리즘
		List<PartyInvitation> matchedList = new ArrayList<>();

		for (UserMatchCond matchingUser : matchingUserList) {
			int bestScore = 0;
			List<Long> bestScorePartyCondIdList = new ArrayList<>(List.of(0L));

			// 1. 필터링
			Stream<PartyMatchCond> partyStream = matchingPartyList.stream();

			// 초대 이력이 없는 파티들만 필터링
			List<Long> invitedPartyIdList = partyInvitationRepository.findAllPartyIdByUser(matchingUser.getUser());
			partyStream = partyStream.filter(p -> invitedPartyIdList.contains(p.getParty().getId()));

			// 가게 필터링
			if (matchingUser.getStores() != null && matchingUser.getStores().isEmpty()) {
				partyStream = matchStore(matchingUser, partyStream);
			}

			// 위치 필터링
			if (matchingUser.getRegion() != null) {
				partyStream = matchRegion(matchingUser, partyStream);
			}

			// 성별 필터링
			if (matchingUser.getUserGender() != null) {
				partyStream = matchGender(matchingUser, partyStream);
			}

			// 나이 필터링
			partyStream = matchAgeRange(matchingUser, partyStream);

			// 2. 가중치 계산
			// 카테고리 일치, 날짜 범위
			List<PartyMatchCond> filteredParty = partyStream.toList();
			for (PartyMatchCond matchingParty : filteredParty) {
				int nowMatchingScore = 0;
				nowMatchingScore += calculateCategoryScore(matchingUser, matchingParty);
				nowMatchingScore += calculateMeetingDateScore(matchingUser, matchingParty);

				// 동일 최고점 추가 (0이면 무시)
				if (nowMatchingScore != 0 && nowMatchingScore == bestScore) {
					bestScorePartyCondIdList.add(matchingParty.getId());
				}
				// 갱신
				else if (nowMatchingScore > bestScore) {
					bestScore = nowMatchingScore;
					bestScorePartyCondIdList.clear();
					bestScorePartyCondIdList.add(matchingParty.getId());
				}
			}

			// 최종 최고 점수 파티와 매칭
			// 1) 필터링-가중치 계산 이후에도 모든 파티가 0점인 경우, 2)최고 점수인 파티가 여러 개인 경우
			PartyMatchCond selectedParty = null;
			if (bestScore == 0 || bestScorePartyCondIdList.size() > 1) {
				selectedParty = filteredParty.get(
					ThreadLocalRandom.current().nextInt(0, filteredParty.size()));
			}
			if (bestScorePartyCondIdList.size() == 1) {
				selectedParty = filteredParty.get(0);
			}
			if (selectedParty == null) {
				log.warn("랜덤 매칭 중 매칭 가능한 파티 찾기에 실패하였습니다. User ID: {}, UserMathCond ID: {}",
					matchingUser.getUser().getId(), matchingUser.getId());
				continue;
			}
			matchingUser.setMatchStatus(MatchStatus.WAITING_HOST);
			matchedList.add(new PartyInvitation(
				selectedParty.getParty(), matchingUser.getUser(), InvitationType.RANDOM, InvitationStatus.WAITING));
		}
profile
백엔드 개발도 락이다

0개의 댓글