From e14578f48254c9902b747fa761d4b1e1fcf2c734 Mon Sep 17 00:00:00 2001 From: gengminy Date: Mon, 30 Jan 2023 16:04:38 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat=20:=20=EC=84=9C=EB=B2=84=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=8A=AC=EB=9E=99=20=EC=95=8C=EB=A6=BC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DuDoong-Api/build.gradle | 1 - .../response/GlobalExceptionHandler.java | 8 +- .../api/config/slack/SlackApiProvider.java | 109 ++++++++++++++++++ .../api/event/controller/EventController.java | 6 + .../src/main/resources/application.yml | 5 + .../config/slack/SlackApiConfig.java | 21 ++++ .../resources/application-infrastructure.yml | 3 + 7 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 DuDoong-Api/src/main/java/band/gosrock/api/config/slack/SlackApiProvider.java create mode 100644 DuDoong-Infrastructure/src/main/java/band/gosrock/infrastructure/config/slack/SlackApiConfig.java diff --git a/DuDoong-Api/build.gradle b/DuDoong-Api/build.gradle index 39e18a3a..41a10b78 100644 --- a/DuDoong-Api/build.gradle +++ b/DuDoong-Api/build.gradle @@ -14,7 +14,6 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springdoc:springdoc-openapi-ui:1.6.12' implementation 'org.springframework.boot:spring-boot-starter-security' - implementation project(':DuDoong-Domain') implementation project(':DuDoong-Common') implementation project(':DuDoong-Infrastructure') diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/config/response/GlobalExceptionHandler.java b/DuDoong-Api/src/main/java/band/gosrock/api/config/response/GlobalExceptionHandler.java index 6328a933..214faf42 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/config/response/GlobalExceptionHandler.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/config/response/GlobalExceptionHandler.java @@ -1,6 +1,8 @@ package band.gosrock.api.config.response; +import band.gosrock.api.config.security.SecurityUtils; +import band.gosrock.api.config.slack.SlackApiProvider; import band.gosrock.common.dto.ErrorReason; import band.gosrock.common.dto.ErrorResponse; import band.gosrock.common.exception.BaseErrorCode; @@ -37,6 +39,7 @@ @RequiredArgsConstructor public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + private final SlackApiProvider slackApiProvider; // /** Json 날짜 형식 파싱에 대한 에러 핸들러 */ // @ExceptionHandler({ InvalidFormatException.class, DateTimeParseException.class}) // public ResponseEntity JsonParseExceptionHandler( @@ -163,6 +166,7 @@ public ResponseEntity DuDoongDynamicExceptionHandler( protected ResponseEntity handleException(Exception e, HttpServletRequest request) throws IOException { final ContentCachingRequestWrapper cachingRequest = (ContentCachingRequestWrapper) request; + final Long userId = SecurityUtils.getCurrentUserId(); String url = UriComponentsBuilder.fromHttpRequest(new ServletServerHttpRequest(request)) .build() @@ -176,8 +180,8 @@ protected ResponseEntity handleException(Exception e, HttpServlet internalServerError.getCode(), internalServerError.getReason(), url); - // TODO : 슬랙 오류 발송 - // slackProvider.sendError(url, cachingRequest, e); + + slackApiProvider.sendError(cachingRequest, e, userId); return ResponseEntity.status(HttpStatus.valueOf(internalServerError.getStatus())) .body(errorResponse); } diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/config/slack/SlackApiProvider.java b/DuDoong-Api/src/main/java/band/gosrock/api/config/slack/SlackApiProvider.java new file mode 100644 index 00000000..79474755 --- /dev/null +++ b/DuDoong-Api/src/main/java/band/gosrock/api/config/slack/SlackApiProvider.java @@ -0,0 +1,109 @@ +package band.gosrock.api.config.slack; + +import static com.slack.api.model.block.Blocks.divider; +import static com.slack.api.model.block.Blocks.section; +import static com.slack.api.model.block.composition.BlockCompositions.plainText; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.slack.api.methods.MethodsClient; +import com.slack.api.methods.SlackApiException; +import com.slack.api.methods.request.chat.ChatPostMessageRequest; +import com.slack.api.model.block.Blocks; +import com.slack.api.model.block.LayoutBlock; +import com.slack.api.model.block.composition.MarkdownTextObject; +import com.slack.api.model.block.composition.TextObject; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.web.util.ContentCachingRequestWrapper; + +@Component +@RequiredArgsConstructor +@Slf4j +public class SlackApiProvider { + private final MethodsClient methodsClient; + private final ObjectMapper objectMapper; + + @Value("${spring.profiles.active}") + private String ACTIVE_PROFILE; + + @Value("${slack.webhook.id}") + private String CHANNEL_ID; + + private final int MAX_LEN = 500; + + @Async + public void sendError(ContentCachingRequestWrapper cachingRequest, Exception e, Long userId) + throws IOException { + if (Arrays.asList("staging", "prod").contains(ACTIVE_PROFILE)) { + executeSendError(cachingRequest, e, userId); + } + } + + private void executeSendError( + ContentCachingRequestWrapper cachingRequest, Exception e, Long userId) + throws IOException { + final String url = cachingRequest.getRequestURL().toString(); + final String method = cachingRequest.getMethod(); + final String body = + objectMapper.readTree(cachingRequest.getContentAsByteArray()).toString(); + final String exceptionAsString = Arrays.toString(e.getStackTrace()); + final int cutLength = Math.min(exceptionAsString.length(), MAX_LEN); + + final String errorMessage = e.getMessage(); + final String errorStack = exceptionAsString.substring(0, cutLength); + final String errorUserIP = cachingRequest.getRemoteAddr(); + + List layoutBlocks = new ArrayList<>(); + layoutBlocks.add( + Blocks.header( + headerBlockBuilder -> + headerBlockBuilder.text(plainText("Error Detection")))); + layoutBlocks.add(divider()); + + MarkdownTextObject errorUserIdMarkdown = + MarkdownTextObject.builder().text("* User Id :*\n" + userId).build(); + MarkdownTextObject errorUserIpMarkdown = + MarkdownTextObject.builder().text("* User IP :*\n" + errorUserIP).build(); + layoutBlocks.add( + section( + section -> + section.fields(List.of(errorUserIdMarkdown, errorUserIpMarkdown)))); + + MarkdownTextObject methodMarkdown = + MarkdownTextObject.builder() + .text("* Request Addr :*\n" + method + " : " + url) + .build(); + MarkdownTextObject bodyMarkdown = + MarkdownTextObject.builder().text("* Request Body :*\n" + body).build(); + List fields = List.of(methodMarkdown, bodyMarkdown); + layoutBlocks.add(section(section -> section.fields(fields))); + + layoutBlocks.add(divider()); + + MarkdownTextObject errorNameMarkdown = + MarkdownTextObject.builder().text("* Message :*\n" + errorMessage).build(); + MarkdownTextObject errorStackMarkdown = + MarkdownTextObject.builder().text("* Stack Trace :*\n" + errorStack).build(); + layoutBlocks.add( + section(section -> section.fields(List.of(errorNameMarkdown, errorStackMarkdown)))); + + ChatPostMessageRequest chatPostMessageRequest = + ChatPostMessageRequest.builder() + .channel(CHANNEL_ID) + .text("") + .blocks(layoutBlocks) + .build(); + try { + methodsClient.chatPostMessage(chatPostMessageRequest); + } catch (SlackApiException slackApiException) { + log.error(slackApiException.toString()); + } + } +} diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/event/controller/EventController.java b/DuDoong-Api/src/main/java/band/gosrock/api/event/controller/EventController.java index 83cf638c..f41b1c50 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/event/controller/EventController.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/event/controller/EventController.java @@ -42,6 +42,12 @@ public PageResponse getAllEventByHostId( return readHostEventListUseCase.execute(hostId, pageable); } + @Operation(summary = "test") + @GetMapping("/asd") + public void getAllEventByHostId() throws Exception { + throw new Exception(); + } + @Operation(summary = "공연 기본 정보를 등록하여, 새로운 이벤트(공연)를 생성합니다") @PostMapping public EventResponse createEvent(@RequestBody @Valid CreateEventRequest createEventRequest) { diff --git a/DuDoong-Api/src/main/resources/application.yml b/DuDoong-Api/src/main/resources/application.yml index 36c210be..52345da2 100644 --- a/DuDoong-Api/src/main/resources/application.yml +++ b/DuDoong-Api/src/main/resources/application.yml @@ -17,6 +17,11 @@ server: servlet: context-path: /api forward-headers-strategy: framework + + +slack: + webhook: + id: ${SLACK_WEBHOOK_ID} --- spring: config: diff --git a/DuDoong-Infrastructure/src/main/java/band/gosrock/infrastructure/config/slack/SlackApiConfig.java b/DuDoong-Infrastructure/src/main/java/band/gosrock/infrastructure/config/slack/SlackApiConfig.java new file mode 100644 index 00000000..4577e415 --- /dev/null +++ b/DuDoong-Infrastructure/src/main/java/band/gosrock/infrastructure/config/slack/SlackApiConfig.java @@ -0,0 +1,21 @@ +package band.gosrock.infrastructure.config.slack; + + +import com.slack.api.Slack; +import com.slack.api.methods.MethodsClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SlackApiConfig { + + @Value("${slack.webhook.token}") + private String token; + + @Bean + public MethodsClient getClient() { + Slack slackClient = Slack.getInstance(); + return slackClient.methods(token); + } +} diff --git a/DuDoong-Infrastructure/src/main/resources/application-infrastructure.yml b/DuDoong-Infrastructure/src/main/resources/application-infrastructure.yml index dfee52b6..309ab90d 100644 --- a/DuDoong-Infrastructure/src/main/resources/application-infrastructure.yml +++ b/DuDoong-Infrastructure/src/main/resources/application-infrastructure.yml @@ -12,6 +12,9 @@ spring: port: ${REDIS_PORT:6379} password: ${REDIS_PASSWORD:} +slack: + webhook: + token: ${SLACK_WEBHOOK_TOKEN} --- spring: config: From 8371c5dc07468b3b3d6d800ff282b05371eb90de Mon Sep 17 00:00:00 2001 From: gengminy Date: Mon, 30 Jan 2023 16:07:56 +0900 Subject: [PATCH 2/3] =?UTF-8?q?chore=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=9A=A9=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gosrock/api/event/controller/EventController.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/event/controller/EventController.java b/DuDoong-Api/src/main/java/band/gosrock/api/event/controller/EventController.java index f41b1c50..5bd65650 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/event/controller/EventController.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/event/controller/EventController.java @@ -14,13 +14,14 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; -import javax.validation.Valid; import lombok.RequiredArgsConstructor; import org.springdoc.api.annotations.ParameterObject; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; import org.springframework.web.bind.annotation.*; +import javax.validation.Valid; + @SecurityRequirement(name = "access-token") @Tag(name = "이벤트(공연) 관련 컨트롤러") @RestController @@ -42,12 +43,6 @@ public PageResponse getAllEventByHostId( return readHostEventListUseCase.execute(hostId, pageable); } - @Operation(summary = "test") - @GetMapping("/asd") - public void getAllEventByHostId() throws Exception { - throw new Exception(); - } - @Operation(summary = "공연 기본 정보를 등록하여, 새로운 이벤트(공연)를 생성합니다") @PostMapping public EventResponse createEvent(@RequestBody @Valid CreateEventRequest createEventRequest) { From 8b43015cb6090bc9918299f2073ec92994d5dd1f Mon Sep 17 00:00:00 2001 From: gengminy Date: Tue, 31 Jan 2023 11:50:54 +0900 Subject: [PATCH 3/3] style : spotless apply --- .../band/gosrock/api/event/controller/EventController.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/DuDoong-Api/src/main/java/band/gosrock/api/event/controller/EventController.java b/DuDoong-Api/src/main/java/band/gosrock/api/event/controller/EventController.java index 5bd65650..83cf638c 100644 --- a/DuDoong-Api/src/main/java/band/gosrock/api/event/controller/EventController.java +++ b/DuDoong-Api/src/main/java/band/gosrock/api/event/controller/EventController.java @@ -14,14 +14,13 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; +import javax.validation.Valid; import lombok.RequiredArgsConstructor; import org.springdoc.api.annotations.ParameterObject; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; import org.springframework.web.bind.annotation.*; -import javax.validation.Valid; - @SecurityRequirement(name = "access-token") @Tag(name = "이벤트(공연) 관련 컨트롤러") @RestController