Skip to content

Commit

Permalink
[DEV-254] refactor : 뉴스 업로드 시 이미지 파일을 S3에 업로드하여 관리하도록 수정
Browse files Browse the repository at this point in the history
  • Loading branch information
JuJaeng2 committed Feb 14, 2025
1 parent 0a468b2 commit 27e036c
Show file tree
Hide file tree
Showing 10 changed files with 103 additions and 33 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ dependencies {
implementation 'com.google.auth:google-auth-library-oauth2-http:1.19.0'
implementation 'com.google.oauth-client:google-oauth-client-jetty:1.34.1'

// AWS S3
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'


}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ public class BackOfficeController {

@PostMapping("/contents/news")
@Operation(summary = "뉴스 콘텐츠 즉시 업로드", description = "API가 호출되면 즉시 뉴스 콘텐츠 업로드 동작을 수행합니다.")
public ResponseEntity<String> uploadNews(@Valid @RequestBody NewsRequest newsRequest) {
public ResponseEntity<String> uploadNews(@Valid @ModelAttribute NewsRequest newsRequest) {
newsService.uploadNews(newsRequest);
return ResponseEntity.ok("콘텐츠 등록 완료");
}

@PostMapping("/contents/news/scheduled/{scheduledAt}")
@Operation(summary = "뉴스 콘텐츠 업로드 예약", description = "뉴스 콘텐츠 업로드 날짜를 설정하고 예약 합니다.")
public ResponseEntity<String> scheduleContentUpload(
@Valid @RequestBody NewsRequest newsRequest,
@Valid @ModelAttribute NewsRequest newsRequest,
@PathVariable("scheduledAt") LocalDate scheduledAt) {
scheduledNewsService.scheduleUploadNews(newsRequest, scheduledAt);
return ResponseEntity.ok("콘텐츠 등록 완료");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,34 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import com.onedreamus.project.thisismoney.model.dto.NewsRequest;
import com.onedreamus.project.thisismoney.model.dto.ScheduledNewsRequest;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;

@Converter
public class NewsRequestConverter implements AttributeConverter<NewsRequest, String> {
public class NewsRequestConverter implements AttributeConverter<ScheduledNewsRequest, String> {

private final ObjectMapper objectMapper = new ObjectMapper();

@Override
public String convertToDatabaseColumn(NewsRequest newsRequest) {
if (newsRequest == null) {
public String convertToDatabaseColumn(ScheduledNewsRequest scheduledNewsRequest) {
if (scheduledNewsRequest == null) {
return null;
}
try {
return objectMapper.writeValueAsString(newsRequest);
return objectMapper.writeValueAsString(scheduledNewsRequest);
} catch (Exception e) {
throw new IllegalArgumentException("Error converting NewsRequest to JSON string", e);
}
}

@Override
public NewsRequest convertToEntityAttribute(String dbData) {
public ScheduledNewsRequest convertToEntityAttribute(String dbData) {
if (dbData == null) {
return null;
}
try {
return objectMapper.readValue(dbData, NewsRequest.class);
return objectMapper.readValue(dbData, ScheduledNewsRequest.class);
} catch (Exception e) {
throw new IllegalArgumentException("Error converting JSON string to NewsRequest", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.web.multipart.MultipartFile;

@AllArgsConstructor
@NoArgsConstructor
Expand All @@ -19,8 +20,8 @@ public class NewsRequest {
@NotBlank(message = "뉴스 제목은 필수 값 입니다.")
private String title; // 뉴스 제목

@NotBlank(message = "썸네일 URL은 필수 값 입니다.")
private String thumbnailUrl; // 썸네일 URL

private MultipartFile thumbnailImage; // 썸네일 URL

@NotBlank(message = "원본 링크는 필수 값 입니다.")
private String originalLink; // 기사 원본 링크
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.onedreamus.project.thisismoney.model.dto;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
public class ScheduledNewsRequest {

private String title; // 뉴스 제목
private String thumbnailUrl; // 썸네일 URL
private String originalLink; // 기사 원본 링크
private String newsAgency; // 뉴스 업로드한 에이전시
private List<DictionarySentenceRequest> dictionarySentenceList;

public static ScheduledNewsRequest from(NewsRequest newsRequest, String thumbnailUrl) {
return ScheduledNewsRequest.builder()
.title(newsRequest.getTitle())
.thumbnailUrl(thumbnailUrl)
.originalLink(newsRequest.getOriginalLink())
.newsAgency(newsRequest.getNewsAgency())
.dictionarySentenceList(newsRequest.getDictionarySentenceList())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@
public class ScheduledNewsResponse {

private Integer id;
private NewsRequest newsRequest;
private ScheduledNewsRequest newsRequest;
private LocalDate scheduledAt;

public static ScheduledNewsResponse from(ScheduledNews scheduledNews) {
return ScheduledNewsResponse.builder()
.id(scheduledNews.getId())
.newsRequest(scheduledNews.getNewsRequest())
.newsRequest(scheduledNews.getScheduledNewsRequest())
.scheduledAt(scheduledNews.getScheduledAt())
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.onedreamus.project.thisismoney.model.converter.NewsRequestConverter;
import com.onedreamus.project.thisismoney.model.dto.NewsRequest;
import com.onedreamus.project.thisismoney.model.dto.ScheduledNewsRequest;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
Expand Down Expand Up @@ -32,13 +33,13 @@ public class ScheduledNews {
@Column(columnDefinition = "jsonb")
@Convert(converter = NewsRequestConverter.class)
@JdbcTypeCode(SqlTypes.JSON) // Hibernate 6에 json 타입임을 알림
private NewsRequest newsRequest;
private ScheduledNewsRequest scheduledNewsRequest;

private LocalDate scheduledAt; // 업로드 날짜.

public static ScheduledNews from(NewsRequest newsRequest, LocalDate scheduledAt) {
public static ScheduledNews from(ScheduledNewsRequest scheduledNewsRequest, LocalDate scheduledAt, String thumbnailUrl) {
return ScheduledNews.builder()
.newsRequest(newsRequest)
.scheduledNewsRequest(scheduledNewsRequest)
.scheduledAt(scheduledAt)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package com.onedreamus.project.thisismoney.scheduler;

import com.onedreamus.project.thisismoney.model.dto.ScheduledNewsRequest;
import com.onedreamus.project.thisismoney.model.entity.News;
import com.onedreamus.project.thisismoney.model.entity.ScheduledNews;
import com.onedreamus.project.thisismoney.service.NewsService;
import com.onedreamus.project.thisismoney.service.ScheduledNewsService;

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

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
Expand All @@ -22,15 +26,23 @@ public class ScheduledTasks {
public void uploadScheduledNews() {
LocalDate now = LocalDate.now();
Optional<ScheduledNews> scheduledNewsOptional =
scheduledNewsService.getScheduledNewsByScheduledAt(now);
scheduledNewsService.getScheduledNewsByScheduledAt(now);
if (scheduledNewsOptional.isEmpty()) {
log.info("[{} : 오늘 예약된 업로드 예정 뉴스 콘텐츠가 없습니다.]", now);
return;
}

ScheduledNews scheduledNews = scheduledNewsOptional.get();
ScheduledNewsRequest scheduledNewsRequest = scheduledNews.getScheduledNewsRequest();

newsService.uploadNews(
News.from(
scheduledNewsRequest.getTitle(),
scheduledNewsRequest.getThumbnailUrl(),
scheduledNewsRequest.getNewsAgency(),
scheduledNewsRequest.getOriginalLink()),
scheduledNewsRequest.getDictionarySentenceList());

newsService.uploadNews(scheduledNews.getNewsRequest());
log.info("[{} : 뉴스 콘테츠 업로드 완료]", now);
scheduledNewsService.deleteScheduledNews(scheduledNews);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.onedreamus.project.thisismoney.service;

import com.onedreamus.project.global.exception.ErrorCode;
import com.onedreamus.project.global.s3.ImageCategory;
import com.onedreamus.project.global.s3.S3Uploader;
import com.onedreamus.project.global.util.NumberFormatter;
import com.onedreamus.project.thisismoney.exception.DictionaryException;
import com.onedreamus.project.thisismoney.exception.NewsException;
Expand Down Expand Up @@ -35,6 +37,7 @@ public class NewsService {
private final UsersStudyDaysRepository usersStudyDaysRepository;
private final DictionaryService dictionaryService;
private final AgencyService agencyService;
private final S3Uploader s3Uploader;

public CursorResult<NewsListResponse> getNewList(Integer cursor, Integer size) {
PageRequest pageRequest = PageRequest.of(0, size + 1);
Expand Down Expand Up @@ -184,53 +187,63 @@ public Optional<News> getNewsById(Integer newsId) {
}



/**
* <p>뉴스 콘텐츠 즉시 등록</p>
*
* @param newsRequest
*/
@Transactional
public void uploadNews(NewsRequest newsRequest) {

// 새로운 뉴스 콘텐츠 저장
String thumbnailUrl = s3Uploader.uploadMultipartFileByStream(newsRequest.getThumbnailImage(), ImageCategory.THUMBNAIL);
News newNews = newsRepository.save(News.from(
newsRequest.getTitle(),
newsRequest.getThumbnailUrl(),
newsRequest.getNewsAgency(),
newsRequest.getOriginalLink()));
newsRequest.getTitle(),
thumbnailUrl,
newsRequest.getNewsAgency(),
newsRequest.getOriginalLink()));

uploadNews(newNews, newsRequest.getDictionarySentenceList());
}

/**
* <p>뉴스 콘텐츠 등록</p>
* @param news
* @param dictionarySentenceRequests
*/
public void uploadNews(News news, List<DictionarySentenceRequest> dictionarySentenceRequests) {

// 기존에 없던 뉴스사이면 저장
agencyService.saveIfNotExist(newsRequest.getNewsAgency());
agencyService.saveIfNotExist(news.getNewsAgency());

for (DictionarySentenceRequest request : newsRequest.getDictionarySentenceList()) {
for (DictionarySentenceRequest request : dictionarySentenceRequests) {
Dictionary dictionary;
// 기존 단어 재사용 하는 경우
if (request.getDictionaryId() != null) {
dictionary = dictionaryService.getDictionaryById(request.getDictionaryId())
.orElseThrow(() -> new DictionaryException(ErrorCode.DICTIONARY_NOT_EXIST));
.orElseThrow(() -> new DictionaryException(ErrorCode.DICTIONARY_NOT_EXIST));

} else { // 새로운 용어를 등록하여 매핑하는 경우
// 새로운 용어 생성
String markedDefinition = addHighlightMarkDefinition(request.getDictionaryDefinition(), request.getDictionaryTerm());
dictionary = dictionaryService.saveNewDictionary(
Dictionary.from(request.getDictionaryTerm(), markedDefinition,
request.getDictionaryDescription()));
Dictionary.from(request.getDictionaryTerm(), markedDefinition,
request.getDictionaryDescription()));
}

// 하이라이팅 필요한 부분에 <mark> 표시 추가
String markedSentence = addHighlightMark(
request.getSentenceValue(), request.getStartIdx(), request.getEndIdx());
request.getSentenceValue(), request.getStartIdx(), request.getEndIdx());

// 문장 저장
Sentence newSentence = sentenceRepository.save(Sentence.from(markedSentence, newNews));
Sentence newSentence = sentenceRepository.save(Sentence.from(markedSentence, news));

// 용어-문장 저장
dictionarySentenceRepository.save(DictionarySentence.from(dictionary, newSentence));
}


}
}


private String addHighlightMarkDefinition(String definition, String term){
char[] charArr = definition.toCharArray();
char[] termArr = term.toCharArray();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.onedreamus.project.thisismoney.service;

import com.onedreamus.project.global.s3.ImageCategory;
import com.onedreamus.project.global.s3.S3Uploader;
import com.onedreamus.project.thisismoney.model.dto.NewsRequest;
import com.onedreamus.project.thisismoney.model.dto.ScheduledNewsRequest;
import com.onedreamus.project.thisismoney.model.dto.ScheduledNewsResponse;
import com.onedreamus.project.thisismoney.model.entity.ScheduledNews;
import com.onedreamus.project.thisismoney.repository.ScheduledNewsRepository;
Expand All @@ -20,13 +23,16 @@
public class ScheduledNewsService {

private final ScheduledNewsRepository scheduledNewsRepository;
private final S3Uploader s3Uploader;

/**
* <p>예약 뉴스 콘텐츠 등록</p>
* 매일 새벽 6시에 업로드 되도록 설정됨.
*/
public void scheduleUploadNews(NewsRequest newsRequest, LocalDate scheduledAt) {
scheduledNewsRepository.save(ScheduledNews.from(newsRequest, scheduledAt));
String thumbnailUrl = s3Uploader.uploadMultipartFileByStream(newsRequest.getThumbnailImage(), ImageCategory.THUMBNAIL);
ScheduledNewsRequest scheduledNewsRequest = ScheduledNewsRequest.from(newsRequest, thumbnailUrl);
scheduledNewsRepository.save(ScheduledNews.from(scheduledNewsRequest, scheduledAt, thumbnailUrl));
}


Expand Down

0 comments on commit 27e036c

Please sign in to comment.