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

feat: 배치 예외 처리 AOP 구현, 예외 발생 시 Slack에 알림 전송 #59

Merged
merged 3 commits into from
Jul 15, 2023
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
2 changes: 2 additions & 0 deletions pyonsnalcolor-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-batch:2.7.12'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:3.1.7'
testImplementation 'org.springframework.batch:spring-batch-test:4.3.8'
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation("net.gpedro.integrations.slack:slack-webhook:1.4.0")

// FCM
implementation 'com.google.firebase:firebase-admin:9.2.0'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.pyonsnalcolor.aop;

import com.pyonsnalcolor.exception.PyonsnalcolorBatchException;
import lombok.extern.slf4j.Slf4j;
import net.gpedro.integrations.slack.SlackApi;
import net.gpedro.integrations.slack.SlackAttachment;
import net.gpedro.integrations.slack.SlackField;
import net.gpedro.integrations.slack.SlackMessage;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@Slf4j
@Aspect
@Component
public class BatchExceptionAspect {

@Value("${slack.url}")
private String SLACK_URL;

@Pointcut("execution(* com.pyonsnalcolor.batch.service..*(..))")
private void allBatchService() {}

@AfterThrowing(value = "allBatchService()", throwing = "exception")
public void catchBatchException(JoinPoint joinPoint, PyonsnalcolorBatchException exception) {
log.error("[{}] 배치 실행 중 {}에서 예외가 발생하였습니다.", exception.getClass().getSimpleName(),
joinPoint.getTarget().getClass().getSimpleName());
Comment on lines +32 to +35
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이후에 @Around으로 바꿔서
크롤링 후에 새 상품 slack 알림, 실패 시 slack 알림 등 일괄 처리하기

log.error("[custom exception] {} {} ", exception.getErrorCode().name(), exception.getErrorCode().getMessage());
log.error("[origin exception] {} {}", exception.getOriginException().getClass().getSimpleName(),
exception.getOriginException().getMessage());

sendErrorSlackAlarm(joinPoint, exception);
}

private void sendErrorSlackAlarm(JoinPoint joinPoint, PyonsnalcolorBatchException exception) {
SlackApi api = new SlackApi(SLACK_URL);

SlackMessage errorSlackMessage = createErrorSlackMessage(joinPoint, exception);
api.call(errorSlackMessage);
}

private SlackMessage createErrorSlackMessage(JoinPoint joinPoint, PyonsnalcolorBatchException exception) {
SlackMessage slackMessage = new SlackMessage();
slackMessage.setText("Batch Error Occur");
slackMessage.setIcon(":beverage_box:");
slackMessage.setUsername("pyonsnalcolor");

SlackAttachment slackAttachment = new SlackAttachment();
slackAttachment.setFallback("Batch Error");
slackAttachment.setTitle("배치 실행중 예외가 발생하였습니다.");
slackAttachment.setColor("#ffff00");
slackAttachment.setText(Arrays.toString(exception.getStackTrace()));
slackAttachment.setFields (
List.of(
new SlackField().setTitle("Occurred Time").setValue(LocalDateTime.now().toString()),
new SlackField().setTitle("Occurred Class")
.setValue(joinPoint.getTarget().getClass().getSimpleName()),
new SlackField().setTitle("Custom Message").setValue(exception.getErrorCode().getMessage()),
new SlackField().setTitle("Origin Exception")
.setValue(exception.getOriginException().getClass().getSimpleName()),
new SlackField().setTitle("Origin Exception Message")
.setValue(exception.getOriginException().getMessage())
)
);
slackMessage.setAttachments(Collections.singletonList(slackAttachment));
return slackMessage;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.pyonsnalcolor.batch.service.cu;

import com.pyonsnalcolor.exception.PyonsnalcolorBatchException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
Expand All @@ -9,12 +10,15 @@
import java.io.IOException;
import java.net.SocketTimeoutException;

import static com.pyonsnalcolor.exception.model.BatchErrorCode.*;
import static com.pyonsnalcolor.exception.model.BatchErrorCode.BATCH_UNAVAILABLE;

public interface CuDescriptionBatch {

String CU_DESCRIPTION_PAGE_URL = "https://cu.bgfretail.com/product/view.do";
int TIMEOUT = 15000;

default String getDescription(Element element, String productType) throws Exception {
default String getDescription(Element element, String productType) {
String productCode = getProductCode(element);

try {
Expand All @@ -28,11 +32,13 @@ default String getDescription(Element element, String productType) throws Except
Elements elements = doc.select("ul.prodExplain li");
return elements.text();
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("URL 주소가 유효하지 않습니다.");
throw new PyonsnalcolorBatchException(INVALID_ACCESS, e);
} catch (SocketTimeoutException e) {
throw new SocketTimeoutException("연결 시간이 초과하였습니다.");
throw new PyonsnalcolorBatchException(TIME_OUT, e);
} catch (IOException e) {
throw new IOException("연결에 실패하였습니다.");
throw new PyonsnalcolorBatchException(IO_EXCEPTION, e);
} catch (Exception e) {
throw new PyonsnalcolorBatchException(BATCH_UNAVAILABLE, e);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.pyonsnalcolor.batch.service.cu;

import com.pyonsnalcolor.batch.service.EventBatchService;
import com.pyonsnalcolor.exception.PyonsnalcolorBatchException;
import com.pyonsnalcolor.product.entity.BaseEventProduct;
import com.pyonsnalcolor.product.enumtype.Category;
import com.pyonsnalcolor.product.enumtype.EventType;
Expand All @@ -15,11 +16,14 @@
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.IOException;
import java.net.SocketTimeoutException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import static com.pyonsnalcolor.exception.model.BatchErrorCode.*;
import static com.pyonsnalcolor.product.entity.UUIDGenerator.generateId;

@Service("CuEvent")
Expand All @@ -40,10 +44,15 @@ protected List<BaseEventProduct> getAllProducts() {

try {
return getProducts();
} catch (IllegalArgumentException e) {
throw new PyonsnalcolorBatchException(INVALID_ACCESS, e);
} catch (SocketTimeoutException e) {
throw new PyonsnalcolorBatchException(TIME_OUT, e);
} catch (IOException e) {
throw new PyonsnalcolorBatchException(IO_EXCEPTION, e);
} catch (Exception e) {
log.error("CU 이벤트 상품 조회하는 데 실패했습니다.", e);
throw new PyonsnalcolorBatchException(BATCH_UNAVAILABLE, e);
}
return null; // 이후에 에러 처리 관련 수정 - getAllProducts() 호출하는 쪽에 throw
}

private List<BaseEventProduct> getProducts() throws Exception {
Expand Down Expand Up @@ -78,12 +87,7 @@ private BaseEventProduct convertToBaseEventProduct(Element element) {
String eventTypeTag = element.select("div.badge").first().text();
EventType eventType = getCuEventType(eventTypeTag);

String description = null;
try {
description = getDescription(element, "event");
} catch (Exception e) {
log.error("CU 이벤트 상품의 상세 정보를 조회할 수 없습니다.", e);
}
String description = getDescription(element, "event");
Category category = Category.matchCategoryByProductName(name);
Tag tag = Tag.findTag(name);

Expand Down Expand Up @@ -116,6 +120,7 @@ private EventType getCuEventType(String eventTypeTag) {
if (eventTypeTag.equals("2+1")) {
return EventType.TWO_TO_ONE;
}
throw new IllegalArgumentException("CU 이벤트 타입을 찾을 수 없습니다.");
throw new PyonsnalcolorBatchException(INVALID_PRODUCT_TYPE,
new IllegalArgumentException("CU 이벤트 타입이 기존 엔티티와 다릅니다."));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.pyonsnalcolor.batch.service.cu;

import com.pyonsnalcolor.batch.service.PbBatchService;
import com.pyonsnalcolor.exception.PyonsnalcolorBatchException;
import com.pyonsnalcolor.exception.model.BatchErrorCode;
import com.pyonsnalcolor.product.entity.BasePbProduct;
import com.pyonsnalcolor.product.enumtype.Category;
import com.pyonsnalcolor.product.enumtype.StoreType;
Expand All @@ -14,6 +16,8 @@
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.IOException;
import java.net.SocketTimeoutException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
Expand All @@ -37,13 +41,18 @@ public CuPbBatchService(PbProductRepository pbProductRepository) {
}

@Override
protected List<BasePbProduct> getAllProducts(){
protected List<BasePbProduct> getAllProducts() {
try {
return getProductsByCategoryAll();
} catch (IllegalArgumentException e) {
throw new PyonsnalcolorBatchException(BatchErrorCode.INVALID_ACCESS, e);
} catch (SocketTimeoutException e) {
throw new PyonsnalcolorBatchException(BatchErrorCode.TIME_OUT, e);
} catch (IOException e) {
throw new PyonsnalcolorBatchException(BatchErrorCode.IO_EXCEPTION, e);
} catch (Exception e) {
log.error("CU PB 상품 조회하는 데 실패했습니다.", e);
throw new PyonsnalcolorBatchException(BatchErrorCode.BATCH_UNAVAILABLE, e);
}
return null; // 이후에 에러 처리 관련 수정 - getAllProducts() 호출하는 쪽에 throw
}

private List<BasePbProduct> getProductsByCategoryAll() throws Exception {
Expand Down Expand Up @@ -82,12 +91,7 @@ private BasePbProduct convertToBasePbProduct(Element element) {
image = SCHEMA + image;
}
String price = element.select("div.price > strong").first().text();
String description = null;
try {
description = getDescription(element, "product");
} catch (Exception e) {
log.error("CU PB 상품의 상세 정보를 조회할 수 없습니다.", e);
}
String description = getDescription(element, "product");
Category category = Category.matchCategoryByProductName(name);
Tag tag = Tag.findTag(name);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ private List<BaseEventProduct> parseProductsData(Elements productElements) {
String giftImage = parseGiftImage(productElement);
EventType eventType = parseEventType(productElement);
String name = parseName(productElement);
String price = convertToNum(productElement.getElementsByClass("price").get(0).text());
String price = productElement.getElementsByClass("price").get(0).text().split(" ")[0];
String originPrice = parseOriginPrice(productElement);
String image = productElement.getElementsByTag("img").attr("src");

Expand All @@ -73,13 +73,6 @@ private List<BaseEventProduct> parseProductsData(Elements productElements) {
return results;
}

private String convertToNum(String price) {
String priceInfo = price.split(" ")[0];
String result = priceInfo.replace(",", "");

return result;
}

private String parseGiftImage(Element productElement) {
Elements giftElement = productElement.select("div.dumgift");
String giftImage = NOT_EXIST;
Expand All @@ -97,7 +90,8 @@ private String parseOriginPrice(Element productElement) {
originPrice = originPriceElement.get(0).text();
}

return originPrice;
String parsedOriginPrice = originPrice.split(" ")[0];
return parsedOriginPrice;
}

private String parseName(Element productElement) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,21 +62,14 @@ private List<BasePbProduct> parseProductsData(Elements productElements) {
Elements itemTitle = productElement.getElementsByClass("itemtitle");
Element element = itemTitle.get(0);
String name = element.getElementsByTag("a").get(0).text();
String price = convertToNum(productElement.getElementsByClass("price").get(0).text());
String price = productElement.getElementsByClass("price").get(0).text().split(" ")[0];
String image = productElement.getElementsByTag("img").attr("src");

results.add(convertToBasePbProduct(image, name, price));
}
return results;
}

private String convertToNum(String price) {
String priceInfo = price.split(" ")[0];
String result = priceInfo.replace(",", "");

return result;
}

private BasePbProduct convertToBasePbProduct(String image, String name, String price) {
Category category = Category.matchCategoryByProductName(name);
Tag tag = Tag.findTag(name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.text.NumberFormat;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -100,7 +101,9 @@ private BaseEventProduct convertToBaseEventProduct(Object product) {
Map<String, Object> productMap = objectMapper.convertValue(product, Map.class);
String image = (String) productMap.get("attFileNm");
String name = (String) productMap.get("goodsNm");
String price = Double.toString((Double) productMap.get("price"));
String price = Double.toString((Double) productMap.get("price")).split("\\.")[0];
int priceInt = Integer.parseInt(price);
String formattedPrice = NumberFormat.getInstance().format(priceInt);
String eventType = (String) ((Map) productMap.get("eventTypeSp")).get("code");
String giftImage = (String) productMap.get("giftAttFileNm");
Category category = Category.matchCategoryByProductName(name);
Expand All @@ -114,7 +117,7 @@ private BaseEventProduct convertToBaseEventProduct(Object product) {
.id(generateId())
.giftImage(giftImage)
.image(image)
.price(price)
.price(formattedPrice)
.name(name)
.category(category)
.tag(tag)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.text.NumberFormat;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -91,14 +92,17 @@ private BasePbProduct convertToBasePbProduct(Object product) {
Map<String, Object> productMap = objectMapper.convertValue(product, Map.class);
String image = (String) productMap.get("attFileNm");
String name = (String) productMap.get("goodsNm");
String price = Double.toString((Double) productMap.get("price"));
String price = Double.toString((Double) productMap.get("price")).split("\\.")[0];;
int priceInt = Integer.parseInt(price);
String formattedPrice = NumberFormat.getInstance().format(priceInt);

Category category = Category.matchCategoryByProductName(name);
Tag tag = Tag.findTag(name);

BasePbProduct basePbProduct = BasePbProduct.builder()
.id(generateId())
.name(name)
.price(price)
.price(formattedPrice)
.storeType(StoreType.GS25)
.updatedTime(LocalDateTime.now())
.image(image)
Expand Down
Loading
Loading