Skip to content
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 @@ -31,45 +31,57 @@ public ParsingService(@Value("${static-image.url}") final String basicThumbnail)
this.BASIC_THUMBNAIL = basicThumbnail;
}

public OgResponse getOg(String linkUrl) throws IOException {
try {
String title = getTitle(linkUrl);
log.info(title);
String image = getImage(linkUrl);
log.info(image);
return OgResponse.of(
title == null || title.isBlank() ? "기본 토스트 제목" : title,
image == null || image.isBlank() ? BASIC_THUMBNAIL : image
);
}catch (HttpStatusException | SSLHandshakeException e){
return OgResponse.of("15자 내로 제목을 지어주세요.", BASIC_THUMBNAIL);
}catch (ConnectException e){
throw new BadRequestException(Error.BAD_REQUEST_URL, Error.BAD_REQUEST_URL.getMessage());
}
public OgResponse getOg(String linkUrl) {
String title = getTitle(linkUrl);
log.info(title);
String image = getImage(linkUrl);
log.info(image);
return OgResponse.of(
title == null || title.isBlank() ? "기본 토스트 제목" : title,
image == null || image.isBlank() ? BASIC_THUMBNAIL : image
);
}
// public String getOg(String linkUrl) throws IOException {
// String image = getImage(linkUrl);
// return image == null || image.isBlank() ? BASIC_THUMBNAIL : image;
// }

private String getTitle(String linkUrl) throws IOException {
private String getTitle(String linkUrl) {
try {
Document doc = Jsoup.connect(linkUrl).get();
Document doc = Jsoup.connect(linkUrl)
.followRedirects(true) // 리다이렉션 자동 따라가기
.maxBodySize(1024*1024) // 페이지 크기 제한 없음
.timeout(10000)
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.get();
Elements ogTitleElements = doc.select("meta[property=og:title]");
Elements titleElements = doc.select("head").select("title");
if (ogTitleElements.isEmpty() && titleElements.isEmpty()) {
return null;
log.info("[NOT FOUND] og 데이터, html header 뜯었는데 결과 없음.");
return "15자 내로 제목을 지어주세요.";
}
return ogTitleElements.isEmpty()?titleElements.get(0).text(): ogTitleElements.get(0).attr("content");
}catch (org.jsoup.HttpStatusException e){
return null;
log.info("[ERROR] title 파싱 중 http status 에러 발생");
return "15자 내로 제목을 지어주세요.";
} catch (SSLHandshakeException e){
log.info("[ERROR] 너무 오래된 사이트라 handshake 규칙이 맞지 않습니다.");
return "15자 내로 제목을 지어주세요.";
} catch (IOException e){
log.info("[ERROR] title 파싱 중 에러 발생");
return "15자 내로 제목을 지어주세요.";
}

}

private String getImage(String linkUrl){
try {
Document doc = Jsoup.connect(linkUrl).get();
Document doc = Jsoup.connect(linkUrl)
.followRedirects(true) // 리다이렉션 자동 따라가기
.maxBodySize(1024*1024) // 페이지 크기 제한 없음
.timeout(10000)
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.get();

Elements iframes = doc.select("iframe");
Elements ogBlogImage = new Elements();
if (!iframes.isEmpty()){
Expand All @@ -82,7 +94,10 @@ private String getImage(String linkUrl){
return findImageAnywhere(ogImageElements, ogImage, ogBlogImage);
}catch (MalformedURLException e){
throw new CustomException(Error.MALFORMED_URL_EXEPTION,Error.MALFORMED_URL_EXEPTION.getMessage());
}catch (org.jsoup.HttpStatusException e){
}catch (org.jsoup.HttpStatusException e) {
return null;
}catch (SSLHandshakeException e){
log.info("[ERROR] 너무 오래된 사이트라 handshake 규칙이 맞지 않습니다.");
return null;
}catch (IOException e){
throw new CustomException(Error.NOT_FOUND_IMAGE_EXCEPTION, Error.NOT_FOUND_IMAGE_EXCEPTION.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public class ToastController {
@Deprecated
public ApiResponse getOgAdvanced(
@RequestBody OgRequestDto ogRequestDto
) throws IOException {
) {
return ApiResponse.success(Success.PARSING_OG_SUCCESS, parsingService.getOg(ogRequestDto.linkUrl()));
}

Expand Down Expand Up @@ -72,7 +72,7 @@ public ApiResponse<IsReadResponse> updateIsRead(
public ApiResponse deleteToast( //나중에 softDelete로 변경
@UserId Long userId,
@RequestParam Long toastId
) throws IOException {
) {
toastService.deleteToast(userId, toastId);
return ApiResponse.success(Success.DELETE_TOAST_SUCCESS);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,30 +55,21 @@ public void createToast(Long userId, SaveToastDto saveToastDto){
throw new CustomException(Error.BAD_REQUEST_EMPTY_URL, Error.BAD_REQUEST_EMPTY_URL.getMessage());
}
//토스트 생성
try {
OgResponse res = parsingService.getOg(saveToastDto.linkUrl());
//byte 배열로 읽어들임.
log.info(res.titleAdvanced());
log.info(res.imageAdvanced());
String imageString = checkIsBasicImage(res.imageAdvanced());
// // ImagePresignedUrlResponse realRes = getUploadPreSignedUrl(res.imageAdvanced());
// log.info(realRes.fileName());
// log.info(realRes.preSignedUrl());


//presigned url
Toast toast = Toast.builder()
.user(presentUser)
.linkUrl(saveToastDto.linkUrl())
.title(res.titleAdvanced())
.thumbnailUrl(imageString)
.build();
// 만약 유저에게 만들어져있는 카테고리가 없는지 확인하고
checkCategoryIsEmpty(toast, saveToastDto.categoryId());
toastRepository.save(toast);
} catch (IOException e ) { //여기서 에러 발생 시 외부 s3 문제일 수 도 있으므로 500으로 에러 예상 범위 알림.
throw new CustomException(Error.CREATE_TOAST_PROCCESS_EXCEPTION, Error.CREATE_TOAST_PROCCESS_EXCEPTION.getMessage());
}
OgResponse res = parsingService.getOg(saveToastDto.linkUrl());
//byte 배열로 읽어들임.
log.info(res.titleAdvanced());
log.info(res.imageAdvanced());
String imageString = checkIsBasicImage(res.imageAdvanced());

Toast toast = Toast.builder()
.user(presentUser)
.linkUrl(saveToastDto.linkUrl())
.title(res.titleAdvanced())
.thumbnailUrl(imageString)
.build();
// 만약 유저에게 만들어져있는 카테고리가 없는지 확인하고
checkCategoryIsEmpty(toast, saveToastDto.categoryId());
toastRepository.save(toast);

}
@Transactional
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.app.toaster.parse.service;

import static org.assertj.core.api.Assertions.assertThat;

import com.app.toaster.parse.controller.response.OgResponse;
import com.app.toaster.toast.controller.request.SaveToastDto;
import java.io.IOException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class ParsingServiceTest {

@InjectMocks
private ParsingService parsingService;

@Test
@DisplayName("리다이렉션 Url에 대해서도 open graph 파싱이 잘된다.")
void getOgWhenRedirect302Url() throws IOException {
// given
String redirectUrl = createRedirect302CaseFixture().linkUrl();

// when - 직접 Jsoup으로 테스트
Document doc = Jsoup.connect(redirectUrl)
.followRedirects(true)
.maxBodySize(1024 * 1024)
.timeout(10000)
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.get();

Elements ogTitleElements = doc.select("meta[property=og:title]");
Elements ogImageElements = doc.select("meta[property=og:image]");
Elements titleElements = doc.select("title");

// then - Jsoup 레벨에서 먼저 검증
System.out.println("OG Title: " + (ogTitleElements.isEmpty() ? "없음" : ogTitleElements.attr("content")));
System.out.println("OG Image: " + (ogImageElements.isEmpty() ? "없음" : ogImageElements.attr("content")));
System.out.println("Title: " + (titleElements.isEmpty() ? "없음" : titleElements.text()));

// when - 서비스 레벨 테스트
OgResponse result = parsingService.getOg(redirectUrl);

// then
assertThat(result).isNotNull();
assertThat(result.titleAdvanced()).isNotBlank();
assertThat(result.imageAdvanced()).isNotBlank();

// 추가 검증
assertThat(result.titleAdvanced()).isNotEqualTo("기본 토스트 제목");
assertThat(result.imageAdvanced()).isNotEqualTo("BASIC_THUMBNAIL_URL");
}

private SaveToastDto createRedirect302CaseFixture(){
return new SaveToastDto("https://digital.mk.co.kr/news_link.php?year=2025&no=469576", 1L);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ void setUp() {

@Test
@DisplayName("Toast 생성 성공 테스트")
void createToast_Success() throws IOException {
void createToast_Success() {
// given
Long userId = 1L;

Expand Down