Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/194 : 브리핑 조회수 증가 #196

Merged
merged 5 commits into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ public BriefingResponseDTO.BriefingPreviewListDTO findBriefings(
return BriefingConverter.toBriefingPreviewListDTO(params.getDate(), briefingList);
}

@Transactional(readOnly = true)
@Transactional
public BriefingResponseDTO.BriefingDetailDTO findBriefing(final Long id, Member member) {
briefingCommandService.increaseViewCountById(id);
Boolean isScrap =
Optional.ofNullable(member)
.map(m -> scrapQueryService.existsByMemberIdAndBriefingId(m.getId(), id))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.List;
import java.util.Optional;

import com.example.briefingapi.briefing.implement.service.BriefingCommandService;
import com.example.briefingapi.briefing.implement.service.BriefingQueryService;
import com.example.briefingapi.briefing.presentation.dto.BriefingRequestParam;
import com.example.briefingapi.briefing.presentation.dto.BriefingResponseDTO;
Expand All @@ -20,6 +21,7 @@
public class BriefingV2Facade {

private final BriefingQueryService briefingQueryService;
private final BriefingCommandService briefingCommandService;
private final ScrapQueryService scrapQueryService;
private static final APIVersion version = APIVersion.V2;

Expand All @@ -30,8 +32,9 @@ public BriefingResponseDTO.BriefingPreviewListDTOV2 findBriefings(
return BriefingConverter.toBriefingPreviewListDTOV2(params.getDate(), briefingList);
}

@Transactional(readOnly = true)
@Transactional
public BriefingResponseDTO.BriefingDetailDTOV2 findBriefing(final Long id, Member member) {
briefingCommandService.increaseViewCountById(id);
Boolean isScrap =
Optional.ofNullable(member)
.map(m -> scrapQueryService.existsByMemberIdAndBriefingId(m.getId(), id))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ public Briefing update(
briefing.updateBriefing(title, subTitle, content);
return briefing;
}

public void increaseViewCountById(final Long id) {
briefingRepository.updateViewCountById(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public WebSecurityCustomizer webSecurityCustomizer() {
return (web) ->
web.ignoring()
.requestMatchers(
"","/",
"/",
"/schedule",
"/v3/api-docs",
"/v3/api-docs/**",
Expand Down
37 changes: 37 additions & 0 deletions Briefing-Api/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,40 @@ openai:
token: ${OPEN_API_TOKEN}
url:
chat: https://api.openai.com/v1/chat/completions
---
spring:
config:
activate:
on-profile: test
datasource:
url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
database-platform: org.hibernate.dialect.H2Dialect
properties:
hibernate:
ddl-hbm2ddl:
auto: create
# show_sql: true
# format_sql: true
# use_sql_comments: true
default_batch_fetch_size: 1000
data:
redis:
host: ${REDIS_URL}
port: 6379
jwt:
header: Authorization
# dev server
secret: ${JWT_SECRET}
# secret : ${JWT_SECRET}
authorities-key: authoritiesKey
access-token-validity-in-seconds: 30000 # 30 m
refresh-token-validity-in-seconds: 1210000000 # 14 d

openai:
token: ${OPEN_API_TOKEN}
url:
chat: https://api.openai.com/v1/chat/completions
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package com.example.briefingapi.briefing.presentation;

import com.example.briefingcommon.domain.repository.article.BriefingRepository;
import com.example.briefingcommon.entity.Briefing;
import com.example.briefingcommon.entity.enums.BriefingType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
class BriefingV2ApiTest {
@Autowired
private WebApplicationContext webApplicationContext;
@Autowired
private BriefingRepository briefingRepository;

private MockMvc mockMvc;

@BeforeEach
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}

@Test
@DisplayName("[BriefingV2Api] 상세 조회 - OK")
void 브리핑_상세_조회_OK() throws Exception {
// given
Briefing briefing =
Briefing.builder()
.title("제목")
.subtitle("부제목")
.content("내용")
.ranks(1)
.type(BriefingType.SOCIAL)
.build();
Briefing savedBriefing = briefingRepository.save(briefing);
Long briefingId = savedBriefing.getId();

// when & then
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/v2/briefings/{id}", briefingId);
mockMvc.perform(requestBuilder)
.andExpect(status().isOk())
.andExpect(jsonPath("$.isSuccess").value(true))
.andExpect(jsonPath("$.result.title").value("제목"))
.andExpect(jsonPath("$.result.id").value(briefingId));
}

@Test
@DisplayName("[BriefingV2Api] 상세 조회 - NOT FOUND")
void 브리핑_상세_조회_NOT_FOUND() throws Exception {
// given
Long briefingId = 0L;

// when & then
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/v2/briefings/{id}", briefingId);
mockMvc.perform(requestBuilder)
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.isSuccess").value(false));
}

@Test
@DisplayName("[BriefingV2Api] 상세 조회 - 100명이 동시에 조회하면 100의 조회수가 늘어나야 합니다.")
void 브리핑_상세_조회_동시_요청() throws Exception {
// given
final int numberOfThreads = 100;
ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);
CountDownLatch latch = new CountDownLatch(numberOfThreads);
Briefing briefing =
Briefing.builder()
.title("제목")
.subtitle("부제목")
.content("내용")
.ranks(1)
.type(BriefingType.SOCIAL)
.build();
Briefing savedBriefing = briefingRepository.save(briefing);
Long briefingId = savedBriefing.getId();

// when
for (int i = 0; i < numberOfThreads; i++) {
executorService.execute(
() -> {
try {
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/v2/briefings/{id}", briefingId);
mockMvc.perform(requestBuilder);
} catch (Exception e) {
e.printStackTrace();
}
finally {
latch.countDown();
}
});
}

latch.await();
executorService.shutdown();

// then
Briefing updatedBriefing = briefingRepository.findById(briefingId).orElseThrow();
assertEquals(numberOfThreads, updatedBriefing.getViewCount());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ public class QBriefing extends EntityPathBase<Briefing> {
//inherited
public final DateTimePath<java.time.LocalDateTime> updatedAt = _super.updatedAt;

public final NumberPath<Integer> viewCount = createNumber("viewCount", Integer.class);

public QBriefing(String variable) {
super(Briefing.class, forVariable(variable));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

Expand All @@ -21,4 +22,8 @@ List<Briefing> findAllByTypeAndCreatedAtBetweenOrderByRanks(

@Query("SELECT b from Briefing b where b.ranks = 1 and b.type = :type order by b.createdAt desc")
Page<Briefing> getBestTodayBriefing(@Param("type") BriefingType type, Pageable pageable);

@Modifying
@Query("update Briefing b set b.viewCount = b.viewCount + 1 where b.id = :id")
void updateViewCountById(Long id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@


import lombok.*;
import org.hibernate.annotations.ColumnDefault;

@Entity
@Getter
Expand Down Expand Up @@ -53,6 +54,10 @@ public class Briefing extends BaseDateTimeEntity {
@Enumerated(EnumType.STRING)
private GptModel gptModel = GptModel.GPT_3_5_TURBO;

@ColumnDefault("0")
@Builder.Default
private int viewCount = 0;

public void setScrapCount(Integer scrapCount) {
this.scrapCount = scrapCount;
}
Expand Down
Loading