Skip to content

Commit

Permalink
[FEAT] 랭킹 서버 - API 서버간 통신 추가 (#696)
Browse files Browse the repository at this point in the history
* chore: 코드 위치 변경 및 패키지 이동

* chore: 코드 위치 변경 및 패키지 이동

* chore: 코드 위치 변경 및 패키지 이동

* chore: 코드 위치 변경 및 패키지 이동

* feat: Rank API 예외 코드 반영

* feat: 이벤트 수집 API를 하나의 컨트롤러로 정리

* feat: 시큐리티 설정에 Public 경로 추가 (Shared)

* feat: Event 타입 정의

* feat: Event Producer (전송자)

* feat: Event Consumer (소비자)

* feat: 랭킹 집계 모듈

* chore: 코드 위치 변경 및 패키지 이동

* chore: 코드 위치 변경 및 패키지 이동

* chore: 스웨거에 타입 명시 추가

* chore: 스웨거에 시큐리티 스키마 제거

* feat: 랭킹 서비스 리팩토링

* chore: 주석 업데이트

* fix: 204 NoContent로 반환

* fix: 이벤트에 pickId 추가

* fix: 이벤트 리스너 디버깅 추가

* fix: uri 에 언더스코어 제거

* chore: application yaml 에 경로 추가

* chore: SecurityRequirement 어노테이션 제거

* refactor: 이름 변경 및 dto core 모듈로 이동

* refactor: 이름 변경 및 api endpoint 수정 (rank -> ranking)

* feat: 랭킹 API 정보 통신을 위한 RestClient 구현

* chore: 이벤트 api 엔드포인트 통일
  • Loading branch information
kimminkyeu authored Dec 8, 2024
1 parent 921c37a commit 171ee91
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ public class EventController {
* 내가 나의 북마크를 클릭했을 때 프론트엔드가 보내는 이벤트
*/
@PostMapping("/pick/create")
@SecurityRequirement(name = "access-token")
@Operation(
summary = "사용자의 북마크 생성 이벤트 수집",
description = "[로그인 필요] 서버에게 사용자 자신의 북마크 생성을 알립니다."
Expand Down Expand Up @@ -69,7 +68,7 @@ public ResponseEntity<Void> bookmarkView(
* [공개 api]
* 공유된 폴더에서 링크를 클릭했을 때 프론트엔드가 보내는 이벤트
*/
@PostMapping("/shared/read")
@PostMapping("/shared/view")
@Operation(
summary = "공개 폴더의 북마크 조회 이벤트 수집",
description = "서버에게 공개 폴더의 어떤 북마크가 조회됬는지 알립니다."
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package techpick.api.application.ranking.controller;

import java.time.LocalDate;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import techpick.api.infrastructure.ranking.RankingRepository;
import techpick.api.application.ranking.dto.RankingByViewCount;

/**
* techpick-ranking 서버로 부터 데이터를 받아와 뿌려준다.
*/
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/suggestion")
@Tag(name = "추천/소개 API", description = "링크, 픽 등에 대한 소개")
public class RankingApiController {

private final RankingRepository rankingRepository;

/**
* 주별, 일별 조회 수를 기반 으로 추천 한다.
* - 조회수 기반 집계
*/
@GetMapping("/view")
@Operation(summary = "기간 별 인기 링크 Top 10", description = "기간 별 인기 조회수 글을 10개씩 획득 합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "조회 성공")
})
public ResponseEntity<RankingByViewCount> getSuggestionByViewCount(
) {
var LIMIT = 10;
var currentDay = LocalDate.now();
var before1Day = currentDay.minusDays(1);
var before7Days = currentDay.minusDays(7);

var dailyRanking = rankingRepository.getLinkRanking(currentDay, currentDay, LIMIT).getBody();
var weeklyRanking = rankingRepository.getLinkRanking(before7Days, before1Day, LIMIT).getBody();
var response = new RankingByViewCount(dailyRanking, weeklyRanking);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package techpick.api.application.ranking.dto;

import java.util.List;

import io.swagger.v3.oas.annotations.media.Schema;
import techpick.core.dto.LinkInfoWithViewCount;

public record RankingByViewCount(
// today's hot pick
@Schema(description = "오늘 하루 동안 인기 있는 링크 Top 10")
List<LinkInfoWithViewCount> daily,

// weekly hot pick
@Schema(description = "지난 7일동안 인기 있던 링크 Top 10")
List<LinkInfoWithViewCount> weekly
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package techpick.api.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.support.RestClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;

import techpick.api.infrastructure.ranking.RankingRepository;

/**
* 외부 서버와 통신하는 것을 Http Interface 방식으로 사용하기 위한 설정. <br>
* - https://docs.spring.io/spring-framework/reference/integration/rest-clients.html#rest-http-interface
*/
@Configuration
public class HttpApiConfiguration {

@Value("${server-url.ranking-server}")
private String rankingServerUrl;

@Bean
public RankingRepository rankingApi() {
var restClient = RestClient.create(rankingServerUrl);
var adapter = RestClientAdapter.create(restClient);
var proxy = HttpServiceProxyFactory.builderFor(adapter).build();
return proxy.createClient(RankingRepository.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package techpick.api.infrastructure.ranking;

import java.time.LocalDate;
import java.util.List;

import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.GetExchange;

import techpick.core.dto.LinkInfoWithViewCount;

public interface RankingRepository {

/**
* @author minkyeu kim
* 랭킹 서버와 통신하기 위한 Http Interface. <br>
* 형식은 techpick-api 모듈의 컨트롤러와 일치합니다.
*/
@GetExchange("/ranking/link")
ResponseEntity<List<LinkInfoWithViewCount>> getLinkRanking(
@RequestParam("date_begin") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate dateBegin,
@RequestParam("date_end") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate dateEnd,
@RequestParam(required = false, defaultValue = "5") Integer limit
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package techpick.core.dto;

/**
* api 서버와 ranking 서버가 통신할 때 쓰이는 DTO
* 양쪽 모듈에서 모두 사용되므로 core에 위치.
*/
public record LinkInfoWithViewCount(
String url,
Long count
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ spring:
port: ${RABBITMQ_PORT}
username: ${RABBITMQ_USERNAME}
password: ${RABBITMQ_PASSWORD}
server-url: # 테크픽 서버간 통신을 위한 주소
api-server: http://localhost:8080/api # 운영 api 서버
ranking-server: http://localhost:8082/api # 랭킹 서버
---
spring:
config:
Expand All @@ -48,6 +51,9 @@ spring:
port: ${RABBITMQ_PORT}
username: ${RABBITMQ_USERNAME}
password: ${RABBITMQ_PASSWORD}
server-url: # 테크픽 서버간 통신을 위한 주소
api-server: http://techpick.server.api/api # 운영 api 서버 (컨테이너)
ranking-server: http://techpick.server.ranking/api # 랭킹 서버 (컨테이너)
---
spring:
config:
Expand All @@ -65,6 +71,9 @@ spring:
port: ${RABBITMQ_PORT}
username: ${RABBITMQ_USERNAME}
password: ${RABBITMQ_PASSWORD}
server-url: # 테크픽 서버간 통신을 위한 주소
api-server: http://techpick.server.api/api # 운영 api 서버 (컨테이너)
ranking-server: http://techpick.server.ranking/api # 랭킹 서버 (컨테이너)
# 운영서버에서는 p6spy 로깅을 사용하지 않음
# @Profile 로 처리했었지만, logback 때문인지 제대로 적용되지 않아 yaml 에 추가
# by psh
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,23 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import techpick.ranking.domain.pick.PickRankingService;
import techpick.ranking.domain.pick.PickViewRankingResult;
import techpick.core.dto.LinkInfoWithViewCount;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/rank")
@RequestMapping("/api/ranking")
@Tag(name = "Rank API", description = "랭킹 API")
public class RankingController {

private final PickRankingService pickRankingService;

@GetMapping("/link/view")
@GetMapping("/link")
@Operation(summary = "링크 조회수 기반 Top N{:limit}개 조회", description = "조회수가 높은 순부터 N{:limit} 개를 반환합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "조회수 랭킹 획득 성공"),
@ApiResponse(responseCode = "404", description = "유효하지 않는 날짜입니다.")
})
public ResponseEntity<List<PickViewRankingResult>> getLinkRank(
public ResponseEntity<List<LinkInfoWithViewCount>> getLinkRank(
@Parameter(description = "범위 시작 날짜 (결과에 포함됩니다)", example = "2024-12-08")
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date_begin,
@Parameter(description = "범위 종료 날짜 (결과에 포함됩니다)", example = "2024-12-10")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import techpick.core.annotation.TechpickAnnotation;
import techpick.core.dto.LinkInfoWithViewCount;
import techpick.ranking.exeption.ApiRankException;
import techpick.ranking.infra.PickViewCount;
import techpick.ranking.infra.PickViewCountRepository;
Expand All @@ -33,7 +34,7 @@ public class PickRankingService {
* API 호출은 일별, 월별, 연별을 나눠서 호출하도록 변경하면 해결 가능.
*/
@TechpickAnnotation.MeasureTime
public List<PickViewRankingResult> getLinksOrderByViewCount(LocalDate startDate, LocalDate endDate, int limit) {
public List<LinkInfoWithViewCount> getLinksOrderByViewCount(LocalDate startDate, LocalDate endDate, int limit) {
if (startDate == null || endDate == null)
throw ApiRankException.INVALID_DATE_RANGE();
if (startDate.isAfter(endDate))
Expand All @@ -46,7 +47,8 @@ public List<PickViewRankingResult> getLinksOrderByViewCount(LocalDate startDate,
);
return MapUtil.sortByValue(toUrlCountPair(pickViewCountList), MapUtil.SortBy.DESCENDING)
.entrySet().stream()
.map(v -> new PickViewRankingResult(v.getKey(), v.getValue()))
.map(v -> new LinkInfoWithViewCount(v.getKey(), v.getValue()))
.limit(limit)
.toList();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ logging:
data:
mongodb:
core:
MongoTemplate: DEBUG # MongoDB Query Logging
# MongoTemplate: DEBUG # MongoDB Query Logging
spring:
profiles:
include:
Expand Down

0 comments on commit 171ee91

Please sign in to comment.