Skip to content

Commit

Permalink
[FEAT] 조회수 기반 랭킹 모듈 구현 (#649)
Browse files Browse the repository at this point in the history
  • Loading branch information
kimminkyeu authored Dec 2, 2024
1 parent 74d41f7 commit 5da7b09
Show file tree
Hide file tree
Showing 31 changed files with 217 additions and 588 deletions.
1 change: 0 additions & 1 deletion backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ subprojects {
// RabbitMQ
implementation 'org.springframework.boot:spring-boot-starter-amqp'
implementation 'org.springframework.amqp:spring-amqp:3.2.0'
implementation 'com.rabbitmq:amqp-client:5.23.0'

// lombok annotation
compileOnly 'org.projectlombok:lombok'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(
scanBasePackages = {"techpick.api", "techpick.core", "techpick.security"}
// scanBasePackages = {"techpick.api", "techpick.core", "techpick.security"}
)
public class TechPickApiApplication {
public static void main(String[] args) {
Expand Down
2 changes: 2 additions & 0 deletions backend/techpick-api/src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ spring:
profiles:
include:
- core
application:
name: techpick-api

springdoc:
swagger-ui:
Expand Down
3 changes: 3 additions & 0 deletions backend/techpick-api/src/main/resources/logback-spring.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@
<!-- ``````````````````````` LOGGER SETTING ```````````````````````````` -->
<!-- "additivity=false"를 하면 해당 <logger>에서만 로깅 되고, 다른 곳으로 전달 되지 않는다.
(ref: https://mkyong.com/logging/logback-duplicate-log-messages/) -->
<!-- <logger name="root" level="debug" additivity="false">-->
<!-- <appender-ref ref="CONSOLE"/>-->
<!-- </logger>-->

<!-- (1) 프로젝트 전체 로그 설정 -->
<logger name="techpick.api" level="info" additivity="false">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(
scanBasePackages = {"techpick.batch", "techpick.core"}
// scanBasePackages = {"techpick.batch", "techpick.core"}
)
public class TechPickBatchApplication {
public static void main(String[] args) {
SpringApplication.run(TechPickBatchApplication.class, args);
}
public static void main(String[] args) {
SpringApplication.run(TechPickBatchApplication.class, args);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
import org.springframework.stereotype.Component;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import techpick.core.annotation.TechpickAnnotation;

/**
* @author minkyeu kim
* 컨트롤러의 반환 값을 이용해서 이벤트를 생성하고 전송하는 AOP
* sendEvent 어노테이션의 type parameter를 함께 설정합니다.
*/
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
Expand All @@ -31,9 +33,8 @@ public void sendEvent(
) {
var responseBody = response.getBody();
if (Objects.isNull(responseBody)) {
throw new EventProcessingError(
String.format("%s : ResponseBody가 null이여서 이벤트로 변환할 수 없습니다.", jp.getSignature())
);
log.error("{} : ResponseBody가 null이여서 이벤트로 변환할 수 없습니다.", jp.getSignature());
return;
}
eventSender.sendEvent(responseBody.toEvent(sendEvent.type()/* 생성/조회/삭제/수정 */));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ public static final class QUEUE {
public static final String Q1 = "queue.event-q1";
}

@Value("${spring.application.name}")
private String appName;

@Value("${spring.rabbitmq.host}")
private String host;

Expand Down Expand Up @@ -67,6 +70,7 @@ Binding directBinding(DirectExchange directExchange, Queue queue1) {
@Bean
ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setConnectionNameStrategy(cn -> appName + "-" + cn);
connectionFactory.setHost(host);
connectionFactory.setPort(port);
connectionFactory.setUsername(username);
Expand Down
24 changes: 15 additions & 9 deletions backend/techpick-core/src/main/resources/application-core.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
spring:
application:
name: techpick-server
output:
ansi:
enabled: always
Expand All @@ -14,12 +12,6 @@ spring:
dialect: org.hibernate.dialect.MySQL8Dialect
open-in-view: false

rabbitmq:
host: ${RABBITMQ_HOST}
port: ${RABBITMQ_PORT}
username: ${RABBITMQ_USERNAME}
password: ${RABBITMQ_PASSWORD}

#logging: # /* Transaction Logging */
# level:
# org.springframework.orm.jpa: DEBUG
Expand All @@ -43,6 +35,11 @@ spring:
username: ${DOCKER_MYSQL_USERNAME}
password: ${DOCKER_MYSQL_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver
rabbitmq:
host: ${LOCAL_RABBITMQ_HOST}
port: ${RABBITMQ_PORT}
username: ${RABBITMQ_USERNAME}
password: ${RABBITMQ_PASSWORD}

---

Expand All @@ -62,6 +59,11 @@ spring:
username: ${DOCKER_MYSQL_USERNAME}
password: ${DOCKER_MYSQL_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver
rabbitmq:
host: ${RABBITMQ_HOST}
port: ${RABBITMQ_PORT}
username: ${RABBITMQ_USERNAME}
password: ${RABBITMQ_PASSWORD}

---

Expand All @@ -81,5 +83,9 @@ spring:
username: ${DOCKER_MYSQL_USERNAME}
password: ${DOCKER_MYSQL_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver

rabbitmq:
host: ${RABBITMQ_HOST}
port: ${RABBITMQ_PORT}
username: ${RABBITMQ_USERNAME}
password: ${RABBITMQ_PASSWORD}
---
2 changes: 2 additions & 0 deletions backend/techpick-ranking/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ repositories {
}

dependencies {
implementation project(":techpick-core")

implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'

testImplementation platform('org.junit:junit-bom:5.10.0')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;

@SpringBootApplication(
exclude = {DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class}
// scanBasePackages = {"techpick.core"}
// exclude = {DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class}
)
public class TechPickRankingApplication {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package techpick.ranking;

import java.time.LocalDate;

import org.springframework.data.mongodb.core.mapping.Document;

import jakarta.persistence.Id;
import lombok.Getter;

@Document(collection = "link_view_count")
@Getter
public class LinkViewCount {

@Id
private String id;

private final LocalDate date;

private final String url;

private Long count;

public LinkViewCount(LocalDate date, String url, Long count) {
this.date = date;
this.url = url;
this.count = count;
}

public LinkViewCount incrementCount() {
++this.count;
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package techpick.ranking;

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

import org.springframework.data.domain.Limit;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface LinkViewCountRepository extends MongoRepository<LinkViewCount, String> {

Optional<LinkViewCount> findLinkViewCountByDateAndUrl(LocalDate date, String url);

List<LinkViewCount> findAllByDateOrderByCountDesc(LocalDate date);

List<LinkViewCount> findByDate(LocalDate date, Sort sort, Limit limit);

List<LinkViewCount> findByDateBetweenOrderByCountDesc(
LocalDate startDate,
LocalDate endDate,
Limit limit
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package techpick.ranking;

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

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.data.domain.Limit;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import techpick.core.rabbitmq.Event;
import techpick.core.rabbitmq.RabbitmqConfig;

@Slf4j
@Service
@RequiredArgsConstructor
public class LinkViewRankingService {

private final LinkViewCountRepository linkViewCountRepository;

@RabbitListener(queues = {RabbitmqConfig.QUEUE.Q1})
public void readViewEvent(Event event) {
var date = event.getOccuredOn().toLocalDate();
var url = event.getUrl();
var linkViewCount = linkViewCountRepository
.findLinkViewCountByDateAndUrl(date, url)
.orElseGet(() -> new LinkViewCount(date, url, 0L))
.incrementCount();
linkViewCountRepository.save(linkViewCount);
}

// DAY-RANK : GET TOP N (= count)
public List<LinkViewCount> getTopViewsByDay(int maxCount) {
var currentDay = LocalDate.now();
return linkViewCountRepository.findByDate(currentDay, Sort.by(Sort.Direction.DESC, "count"),
Limit.of(maxCount));
}

// WEEK-RANK : GET TOP N (= count)
// 오늘 기준 지난 일주일
public List<LinkViewCount> getTopViewsByPast7Days(int maxCount) {
var currentDay = LocalDate.now();
return linkViewCountRepository.findByDateBetweenOrderByCountDesc(
currentDay.minusDays(7),
currentDay.plusDays(1), // TODO: GT를 GTE로 바꾸는 방법이 뭘까?
Limit.of(maxCount)
);
}

// MONT:-RANK : GET TOP N (= count)
public List<LinkViewCount> getTopViewByPast30Days(int maxCount) {
var currentMonth = LocalDate.now();
return linkViewCountRepository.findByDateBetweenOrderByCountDesc(
currentMonth.minusDays(30),
currentMonth.plusDays(1), // TODO: GT를 GTE로 바꾸는 방법이 뭘까?
Limit.of(maxCount)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package techpick.ranking;

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

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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;

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

// TODO: 시간 나라별 기준 이런거 검토 필요합니다.
private final LinkViewRankingService linkViewRankingService;

@GetMapping("/daily")
public ResponseEntity<?> getDailyRanking(
@RequestParam int count
) {
var result = linkViewRankingService.getTopViewsByDay(count);
return ResponseEntity.ok(result);
}

@GetMapping("/weekly")
public ResponseEntity<?> getWeeklyRanking(
@RequestParam int count
) {
var result = linkViewRankingService.getTopViewsByPast7Days(count);
return ResponseEntity.ok(result);
}

@GetMapping("/monthly")
public ResponseEntity<?> getMonthlyRanking(
@RequestParam int count
) {
var result = linkViewRankingService.getTopViewByPast30Days(count);
return ResponseEntity.ok(result);
}
}

This file was deleted.

Loading

0 comments on commit 5da7b09

Please sign in to comment.