diff --git a/build.gradle b/build.gradle index de18ad2a..395a8b80 100644 --- a/build.gradle +++ b/build.gradle @@ -32,6 +32,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'com.github.seratch:jslack:3.4.2' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' testCompileOnly 'org.projectlombok:lombok' diff --git a/src/main/java/ac/knu/likeknu/LikeKnuApplication.java b/src/main/java/ac/knu/likeknu/LikeKnuApplication.java index 712e2ca0..9b46f2e3 100644 --- a/src/main/java/ac/knu/likeknu/LikeKnuApplication.java +++ b/src/main/java/ac/knu/likeknu/LikeKnuApplication.java @@ -2,7 +2,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableAsync; +@EnableAsync @SpringBootApplication public class LikeKnuApplication { diff --git a/src/main/java/ac/knu/likeknu/config/SecurityConfiguration.java b/src/main/java/ac/knu/likeknu/config/SecurityConfiguration.java index eacb23bc..e54236c8 100644 --- a/src/main/java/ac/knu/likeknu/config/SecurityConfiguration.java +++ b/src/main/java/ac/knu/likeknu/config/SecurityConfiguration.java @@ -29,8 +29,8 @@ public class SecurityConfiguration { @Bean public SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospector handlerMappingIntrospector) throws Exception { - RequestMatcher requestMatcher = new MvcRequestMatcher(handlerMappingIntrospector, "/api/**"); - return http.authorizeHttpRequests(registry -> registry.requestMatchers(requestMatcher).permitAll() + RequestMatcher apiRequestMatcher = new MvcRequestMatcher(handlerMappingIntrospector, "/api/**"); + return http.authorizeHttpRequests(registry -> registry.requestMatchers(apiRequestMatcher).permitAll() .anyRequest().authenticated()) .formLogin(security -> security.loginPage("/admin/login") .defaultSuccessUrl("/admin/messages") diff --git a/src/main/java/ac/knu/likeknu/domain/MainHeaderMessage.java b/src/main/java/ac/knu/likeknu/domain/MainHeaderMessage.java index 560e8af1..65860136 100644 --- a/src/main/java/ac/knu/likeknu/domain/MainHeaderMessage.java +++ b/src/main/java/ac/knu/likeknu/domain/MainHeaderMessage.java @@ -1,6 +1,7 @@ package ac.knu.likeknu.domain; import ac.knu.likeknu.exception.BusinessException; +import ac.knu.likeknu.exception.ErrorMessage; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -31,7 +32,7 @@ protected MainHeaderMessage() { public MainHeaderMessage(String message) { this(); if (message.length() > 16) { - throw new BusinessException("메시지는 16자 이하이어야 합니다!"); + throw new BusinessException(ErrorMessage.INVALID_MAIN_MESSAGE_SIZE); } this.message = message; } diff --git a/src/main/java/ac/knu/likeknu/domain/value/RouteType.java b/src/main/java/ac/knu/likeknu/domain/value/RouteType.java index e4089100..0751de61 100644 --- a/src/main/java/ac/knu/likeknu/domain/value/RouteType.java +++ b/src/main/java/ac/knu/likeknu/domain/value/RouteType.java @@ -13,7 +13,7 @@ public static RouteType of(String type) { return Arrays.stream(values()) .filter(routeType -> isSame(type, routeType)) .findAny() - .orElseThrow(() -> new BusinessException(ErrorMessage.INVALID_ROUTE_TYPE)); + .orElseThrow(() -> new BusinessException(ErrorMessage.INVALID_MAIN_MESSAGE_SIZE)); } private static boolean isSame(String type, RouteType routeType) { diff --git a/src/main/java/ac/knu/likeknu/exception/BusinessExceptionHandler.java b/src/main/java/ac/knu/likeknu/exception/BusinessExceptionHandler.java index 7bd80446..1151eb9d 100644 --- a/src/main/java/ac/knu/likeknu/exception/BusinessExceptionHandler.java +++ b/src/main/java/ac/knu/likeknu/exception/BusinessExceptionHandler.java @@ -1,15 +1,26 @@ package ac.knu.likeknu.exception; +import ac.knu.likeknu.service.SlackService; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +@Slf4j @RestControllerAdvice public class BusinessExceptionHandler { + private final SlackService slackService; + + public BusinessExceptionHandler(SlackService slackService) { + this.slackService = slackService; + } + @ExceptionHandler(value = BusinessException.class) protected ResponseEntity businessExceptionHandler(BusinessException exception) { String message = exception.getMessage(); + log.info("sd", exception); + slackService.sendMessage(message); return ResponseEntity.badRequest().body(message); } } diff --git a/src/main/java/ac/knu/likeknu/exception/ErrorMessage.java b/src/main/java/ac/knu/likeknu/exception/ErrorMessage.java index 04ee6b1a..8b100ee6 100644 --- a/src/main/java/ac/knu/likeknu/exception/ErrorMessage.java +++ b/src/main/java/ac/knu/likeknu/exception/ErrorMessage.java @@ -3,4 +3,5 @@ public class ErrorMessage { public static final String INVALID_ROUTE_TYPE = "Invalid city bus route type!"; + public static final String INVALID_MAIN_MESSAGE_SIZE = "메시지는 16자 이하이어야 합니다!"; } diff --git a/src/main/java/ac/knu/likeknu/service/SlackService.java b/src/main/java/ac/knu/likeknu/service/SlackService.java new file mode 100644 index 00000000..f40346a8 --- /dev/null +++ b/src/main/java/ac/knu/likeknu/service/SlackService.java @@ -0,0 +1,50 @@ +package ac.knu.likeknu.service; + +import com.github.seratch.jslack.Slack; +import com.github.seratch.jslack.api.model.Attachment; +import com.github.seratch.jslack.api.webhook.Payload; +import com.github.seratch.jslack.api.webhook.WebhookResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.Collections; + +@Slf4j +@Service +public class SlackService { + + private static final String CHANNEL_NAME = "#서버-오류"; + + @Value("${slack.webhook}") + private String webhook; + + @Async + public void sendMessage(String contents) { + String message = contents + System.lineSeparator(); + Payload payload = buildPayload(message); + + try { + WebhookResponse webhookResponse = Slack.getInstance() + .send(webhook, payload); + if (webhookResponse.getCode() == HttpStatus.OK.value()) { + log.info("Success send to slack!"); + } + log.info(webhookResponse.getMessage()); + } catch (IOException e) { + log.error("Unexpected Error! WebHook:" + webhook); + } + } + + private Payload buildPayload(String message) { + return Payload.builder() + .attachments(Collections.singletonList(Attachment.builder() + .channelName(CHANNEL_NAME) + .build())) + .text(message) + .build(); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 1cad54cb..24c6e4b4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,3 +6,5 @@ spring.jpa.hibernate.ddl-auto=validate admin.username=${ADMIN_USERNAME} admin.password=${ADMIN_PASSWORD} + +slack.webhook=${SLACK_WEBHOOK}