본문 바로가기

Proj/구구모

추천 게시글 기능 구현

1. 개요

 추천 게시글 기능이다. 우리 팀은 회의 결과 회원가입에서 선호 종목을 받고, 다음 규칙에 따라 게시글을 추천하기로 했다

  • 선호 종목이 있는 회원은 선호 종목 내에서 무작위 순서로 게시글 조회
  • 선호 종목이 없거나 로그인하지 않은 회원은 모든 게시글에서 무작위 순서로 게시글 조회
  • 추천 게시글은 총 8개 조회

위 규칙에 맞춰 구현해보자.

2. ERD

 

회원에 선호 종목을 일대다 관계로 잇고, 종목을 받았다.

 

3. 구현

  3-1 Service

    public <T extends SimplePostDto> List<T> findRecommendPost(CustomUserDetails principal) {
        //토큰에서

        Member member;

        if (principal == null) {
            member = null;
        } else {
            member=memberRepository.findByUsername(principal.getUsername())
                    .orElseThrow(()->
                            new NoAuthorizationException("추천 글 조회 실패: 접근 권한이 없습니다.")
                    );

            if (member.getStatus() != MemberStatus.active) {
                member = null;
            }

        }

        List<SimplePostQueryDto> recommendPost = postRepository.findRecommendPost(member);


        return recommendPost.stream()
                .map(p -> convertToTransDto(p))
                .map(r -> (T) r)
                .collect(Collectors.toList());
    }

 토큰을 받아 회원의 로그인 여부, 상태 등을 확인하고 repository에서 게시글을 조회하여 반환하는 코드이다. 프론트에서는 게시글 검색, 조회에서 사용했던 것과 같은 컴포넌트를 사용하므로 같은 방식으로 가공하여 반환한다.

  3-2 Repository

    @Override
    public List<SimplePostQueryDto> findRecommendPost(Member member) {

        List<FavoriteSport> favoriteSports = member!=null?member.getFavoriteSports(): Collections.emptyList();

        List<SimplePostQueryDto> result = queryFactory.select(new QSimplePostQueryDto(
                        post.id.as("postId"),
                        meeting.meetingType,
                        meeting.status,
                        meeting.gameType,
                        meeting.location,
                        post.title,
                        meeting.meetingDateTime,
                        meeting.meetingDays,
                        meeting.meetingMemberNum,
                        meeting.meetingDeadline,
                        bookmark.isNotNull().as("isBookmarked")
                ))
                .from(post)
                .leftJoin(post.meeting, meeting)
                .leftJoin(bookmark).on(bookmark.post.eq(post), hasMember(member))
                .where(
                        post.isDelete.isFalse(), meeting.status.eq("RECRUIT"),
                        favoriteSportsEq(favoriteSports)
                          
                )
                .orderBy(Expressions.numberTemplate(Double.class, "function('random')").asc())
                .limit(8)
                .fetch();

        return result;

    }

  게시글 조회 때와 같은 데이터를 불러오되, 선호 종목을 확인하고, postgresql의 random() 함수를 이용하여 정렬한다. 그 후 8개를 위에서부터 뽑아 반환한다.

    private BooleanBuilder favoriteSportsEq(List<FavoriteSport> favoriteSports) {
        if (favoriteSports.isEmpty()) {
            return null;
        }

        BooleanBuilder booleanBuilder = new BooleanBuilder();

        for (FavoriteSport favoriteSport : favoriteSports) {
            booleanBuilder.or(meeting.gameType.eq(favoriteSport.getGameType()));
        }

        return booleanBuilder;
    }

 선호 종목이 없을 경우 모든 게시글 중 뽑아야 하므로 null을 반환, 있을 경우 booleanBuilder를 사용하여 조건을 추가한 후 반환한다.

 

  3-3 Controller

    @GetMapping("/recommend")
    public <T extends SimplePostDto> ApiResponse<List<T>> findRecommendPost(
            @AuthenticationPrincipal CustomUserDetails principal) {
        return ApiResponse.createSuccess(postService.findRecommendPost(principal));
    }

 토큰을 받아 이를 서비스에 넘겨 선호 종목을 필터터링하고, 서비스 계층에서 받은 리스트를 그대로 반환하면 된다.

 

4. 후기

다음 릴리즈 시간이 다가와서 쉬운 것 먼저 처리하고자 이를 먼저 구현했다. 다음은 시간에 따른 모임 정보 처리를 할 생각이다.