From c0452cf704f8e4c6526af02f1674e42ddc802c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Wed, 13 Nov 2024 23:03:07 +0900 Subject: [PATCH 01/24] =?UTF-8?q?refactor(Notification):=20=EC=99=B8?= =?UTF-8?q?=EB=B6=80=20=EC=95=8C=EB=A6=BC=20=EC=A0=84=EC=86=A1=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EC=8A=AC=EB=9E=99=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EC=A0=84=EC=86=A1=20=EB=A1=9C=EC=A7=81=EA=B3=BC?= =?UTF-8?q?=EC=9D=98=20=EA=B2=B0=ED=95=A9=EB=8F=84=20=EC=99=84=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/service/MemberBanService.java | 9 +-- .../service/MemberUnbanService.java | 9 +-- .../service/BlacklistIpRegisterService.java | 12 ++-- .../service/BlacklistIpRemoveService.java | 9 +-- .../service/BlacklistIpResetService.java | 10 +-- .../TwoFactorAuthenticationService.java | 21 +++--- .../AbnormalAccessIpRemoveService.java | 7 +- .../AbnormalAccessIpsClearService.java | 10 +-- .../service/BoardRegisterService.java | 10 +-- .../service/ApplicationApplyService.java | 4 +- .../service/BookLoanRequestService.java | 13 ++-- .../service/MemberRoleManagementService.java | 7 +- .../service/MembershipFeeRegisterService.java | 2 +- .../ExternalAccountLockManagementService.java | 19 +++--- ...xternalIpAccessMonitorRegisterService.java | 7 +- .../CustomBasicAuthenticationFilter.java | 41 ++++++------ .../filter/InvalidEndpointAccessFilter.java | 19 +++--- .../auth/filter/JwtAuthenticationFilter.java | 23 ++++--- .../web}/NotificationSettingController.java | 11 ++-- .../NotificationSettingRepository.java | 9 ++- .../out/slack/SlackNotificationSender.java | 19 ++++++ .../adapter/out/slack}/SlackService.java | 42 +++++++----- .../out/slack}/SlackServiceHelper.java | 64 ++++++++++--------- .../mapper/NotificationSettingDtoMapper.java} | 8 +-- .../NotificationSettingUpdateRequestDto.java | 2 +- .../NotificationSettingResponseDto.java | 2 +- .../application}/event/NotificationEvent.java | 7 +- .../event/NotificationListener.java | 27 ++++++++ .../exception/AlertTypeNotFoundException.java | 2 +- .../port/out/NotificationSender.java | 8 +++ .../service}/NotificationSettingService.java | 21 +++--- .../config/NotificationConfig.java | 21 ++++++ .../domain/AlertType.java | 2 +- .../domain/AlertTypeConverter.java | 5 +- .../domain/AlertTypeResolver.java | 4 +- .../domain/ExecutivesAlertType.java | 2 +- .../domain/GeneralAlertType.java | 2 +- .../domain/NotificationSetting.java | 2 +- .../domain/SecurityAlertType.java | 2 +- .../slack/listener/NotificationListener.java | 28 -------- .../api/global/config/SecurityConfig.java | 20 +++--- .../handler/GlobalExceptionHandler.java | 11 ++-- 42 files changed, 318 insertions(+), 235 deletions(-) rename src/main/java/page/clab/api/global/common/{slack/api => notificationSetting/adapter/in/web}/NotificationSettingController.java (81%) rename src/main/java/page/clab/api/global/common/{slack/dao => notificationSetting/adapter/out/persistence}/NotificationSettingRepository.java (52%) create mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackNotificationSender.java rename src/main/java/page/clab/api/global/common/{slack/application => notificationSetting/adapter/out/slack}/SlackService.java (65%) rename src/main/java/page/clab/api/global/common/{slack/application => notificationSetting/adapter/out/slack}/SlackServiceHelper.java (93%) rename src/main/java/page/clab/api/global/common/{slack/dto/mapper/SlackDtoMapper.java => notificationSetting/application/dto/mapper/NotificationSettingDtoMapper.java} (51%) rename src/main/java/page/clab/api/global/common/{slack => notificationSetting/application}/dto/request/NotificationSettingUpdateRequestDto.java (87%) rename src/main/java/page/clab/api/global/common/{slack => notificationSetting/application}/dto/response/NotificationSettingResponseDto.java (67%) rename src/main/java/page/clab/api/global/common/{slack => notificationSetting/application}/event/NotificationEvent.java (70%) create mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java rename src/main/java/page/clab/api/global/common/{slack => notificationSetting/application}/exception/AlertTypeNotFoundException.java (71%) create mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/NotificationSender.java rename src/main/java/page/clab/api/global/common/{slack/application => notificationSetting/application/service}/NotificationSettingService.java (75%) create mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/config/NotificationConfig.java rename src/main/java/page/clab/api/global/common/{slack => notificationSetting}/domain/AlertType.java (58%) rename src/main/java/page/clab/api/global/common/{slack => notificationSetting}/domain/AlertTypeConverter.java (88%) rename src/main/java/page/clab/api/global/common/{slack => notificationSetting}/domain/AlertTypeResolver.java (77%) rename src/main/java/page/clab/api/global/common/{slack => notificationSetting}/domain/ExecutivesAlertType.java (89%) rename src/main/java/page/clab/api/global/common/{slack => notificationSetting}/domain/GeneralAlertType.java (86%) rename src/main/java/page/clab/api/global/common/{slack => notificationSetting}/domain/NotificationSetting.java (94%) rename src/main/java/page/clab/api/global/common/{slack => notificationSetting}/domain/SecurityAlertType.java (95%) delete mode 100644 src/main/java/page/clab/api/global/common/slack/listener/NotificationListener.java diff --git a/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberBanService.java b/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberBanService.java index d83793ba0..10cb46af8 100644 --- a/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberBanService.java +++ b/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberBanService.java @@ -11,8 +11,8 @@ import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberBasicInfoDto; import page.clab.api.external.auth.redisToken.application.port.ExternalManageRedisTokenUseCase; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; -import page.clab.api.global.common.slack.application.SlackService; -import page.clab.api.global.common.slack.domain.SecurityAlertType; +import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; @Service @RequiredArgsConstructor @@ -30,7 +30,7 @@ public class MemberBanService implements BanMemberUseCase { *

해당 멤버의 계정 잠금 정보를 조회하고, 없으면 새로 생성합니다. * Redis에 저장된 해당 멤버의 인증 토큰을 삭제하며, Slack에 밴 알림을 전송합니다.

* - * @param request 현재 요청 객체 + * @param request 현재 요청 객체 * @param memberId 차단할 멤버의 ID * @return 저장된 계정 잠금 정보의 ID */ @@ -58,6 +58,7 @@ private AccountLockInfo createAccountLockInfo(String memberId) { private void sendSlackBanNotification(HttpServletRequest request, String memberId) { String memberName = externalRetrieveMemberUseCase.getMemberBasicInfoById(memberId).getMemberName(); - slackService.sendSecurityAlertNotification(request, SecurityAlertType.MEMBER_BANNED, "ID: " + memberId + ", Name: " + memberName); + slackService.sendSecurityAlertNotification(request, SecurityAlertType.MEMBER_BANNED, + "ID: " + memberId + ", Name: " + memberName); } } diff --git a/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberUnbanService.java b/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberUnbanService.java index 391628d8e..bed2bc2ae 100644 --- a/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberUnbanService.java +++ b/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberUnbanService.java @@ -10,8 +10,8 @@ import page.clab.api.domain.auth.accountLockInfo.domain.AccountLockInfo; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberBasicInfoDto; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; -import page.clab.api.global.common.slack.application.SlackService; -import page.clab.api.global.common.slack.domain.SecurityAlertType; +import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; @Service @RequiredArgsConstructor @@ -28,7 +28,7 @@ public class MemberUnbanService implements UnbanMemberUseCase { *

해당 멤버의 계정 잠금 정보를 조회하고 해제합니다. * 해제된 정보는 저장되며, Slack에 해제 알림이 전송됩니다.

* - * @param request 현재 요청 객체 + * @param request 현재 요청 객체 * @param memberId 해제할 멤버의 ID * @return 업데이트된 계정 잠금 정보의 ID */ @@ -55,6 +55,7 @@ private AccountLockInfo createAccountLockInfo(String memberId) { private void sendSlackUnbanNotification(HttpServletRequest request, String memberId) { String memberName = externalRetrieveMemberUseCase.getMemberBasicInfoById(memberId).getMemberName(); - slackService.sendSecurityAlertNotification(request, SecurityAlertType.MEMBER_UNBANNED, "ID: " + memberId + ", Name: " + memberName); + slackService.sendSecurityAlertNotification(request, SecurityAlertType.MEMBER_UNBANNED, + "ID: " + memberId + ", Name: " + memberName); } } diff --git a/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRegisterService.java b/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRegisterService.java index 6694afdec..896e8c8b5 100644 --- a/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRegisterService.java +++ b/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRegisterService.java @@ -10,8 +10,8 @@ import page.clab.api.domain.auth.blacklistIp.application.port.out.RegisterBlacklistIpPort; import page.clab.api.domain.auth.blacklistIp.application.port.out.RetrieveBlacklistIpPort; import page.clab.api.domain.auth.blacklistIp.domain.BlacklistIp; -import page.clab.api.global.common.slack.application.SlackService; -import page.clab.api.global.common.slack.domain.SecurityAlertType; +import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; @Service @RequiredArgsConstructor @@ -26,10 +26,9 @@ public class BlacklistIpRegisterService implements RegisterBlacklistIpUseCase { * 지정된 IP 주소를 블랙리스트에 등록합니다. * *

해당 IP 주소가 이미 블랙리스트에 존재하는지 확인하고, - * 존재하지 않을 경우 새롭게 등록합니다. - * 새로운 IP가 등록되면 Slack을 통해 보안 알림이 전송됩니다.

+ * 존재하지 않을 경우 새롭게 등록합니다. 새로운 IP가 등록되면 Slack을 통해 보안 알림이 전송됩니다.

* - * @param request 현재 요청 객체 + * @param request 현재 요청 객체 * @param requestDto 블랙리스트에 추가할 IP 주소 정보를 담은 DTO * @return 기존에 존재하거나 새로 추가된 블랙리스트 IP 주소 */ @@ -42,7 +41,8 @@ public String registerBlacklistIp(HttpServletRequest request, BlacklistIpRequest .orElseGet(() -> { BlacklistIp blacklistIp = mapper.fromDto(requestDto); registerBlacklistIpPort.save(blacklistIp); - slackService.sendSecurityAlertNotification(request, SecurityAlertType.BLACKLISTED_IP_ADDED, "Added IP: " + ipAddress); + slackService.sendSecurityAlertNotification(request, SecurityAlertType.BLACKLISTED_IP_ADDED, + "Added IP: " + ipAddress); return ipAddress; }); } diff --git a/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRemoveService.java b/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRemoveService.java index 5de09b640..5d6f9df1d 100644 --- a/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRemoveService.java +++ b/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRemoveService.java @@ -8,8 +8,8 @@ import page.clab.api.domain.auth.blacklistIp.application.port.out.RemoveBlacklistIpPort; import page.clab.api.domain.auth.blacklistIp.application.port.out.RetrieveBlacklistIpPort; import page.clab.api.domain.auth.blacklistIp.domain.BlacklistIp; -import page.clab.api.global.common.slack.application.SlackService; -import page.clab.api.global.common.slack.domain.SecurityAlertType; +import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; @Service @RequiredArgsConstructor @@ -25,7 +25,7 @@ public class BlacklistIpRemoveService implements RemoveBlacklistIpUseCase { *

블랙리스트에 등록된 IP 주소 정보를 조회하고 해당 정보를 삭제합니다. * 삭제가 완료되면 Slack을 통해 보안 알림이 전송됩니다.

* - * @param request 현재 요청 객체 + * @param request 현재 요청 객체 * @param ipAddress 제거할 블랙리스트 IP 주소 * @return 삭제된 블랙리스트 IP 주소 */ @@ -34,7 +34,8 @@ public class BlacklistIpRemoveService implements RemoveBlacklistIpUseCase { public String removeBlacklistIp(HttpServletRequest request, String ipAddress) { BlacklistIp blacklistIp = retrieveBlacklistIpPort.getByIpAddress(ipAddress); removeBlacklistIpPort.delete(blacklistIp); - slackService.sendSecurityAlertNotification(request, SecurityAlertType.BLACKLISTED_IP_REMOVED, "Deleted IP: " + ipAddress); + slackService.sendSecurityAlertNotification(request, SecurityAlertType.BLACKLISTED_IP_REMOVED, + "Deleted IP: " + ipAddress); return blacklistIp.getIpAddress(); } } diff --git a/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpResetService.java b/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpResetService.java index 342a2561f..04078df28 100644 --- a/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpResetService.java +++ b/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpResetService.java @@ -1,6 +1,7 @@ package page.clab.api.domain.auth.blacklistIp.application.service; import jakarta.servlet.http.HttpServletRequest; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -8,10 +9,8 @@ import page.clab.api.domain.auth.blacklistIp.application.port.out.RemoveBlacklistIpPort; import page.clab.api.domain.auth.blacklistIp.application.port.out.RetrieveBlacklistIpPort; import page.clab.api.domain.auth.blacklistIp.domain.BlacklistIp; -import page.clab.api.global.common.slack.application.SlackService; -import page.clab.api.global.common.slack.domain.SecurityAlertType; - -import java.util.List; +import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; @Service @RequiredArgsConstructor @@ -38,7 +37,8 @@ public List resetBlacklistIps(HttpServletRequest request) { .map(BlacklistIp::getIpAddress) .toList(); removeBlacklistIpPort.deleteAll(); - slackService.sendSecurityAlertNotification(request, SecurityAlertType.BLACKLISTED_IP_REMOVED, "Deleted IP: ALL"); + slackService.sendSecurityAlertNotification(request, SecurityAlertType.BLACKLISTED_IP_REMOVED, + "Deleted IP: ALL"); return blacklistedIps; } } diff --git a/src/main/java/page/clab/api/domain/auth/login/application/service/TwoFactorAuthenticationService.java b/src/main/java/page/clab/api/domain/auth/login/application/service/TwoFactorAuthenticationService.java index 655a51a06..609423d36 100644 --- a/src/main/java/page/clab/api/domain/auth/login/application/service/TwoFactorAuthenticationService.java +++ b/src/main/java/page/clab/api/domain/auth/login/application/service/TwoFactorAuthenticationService.java @@ -1,6 +1,7 @@ package page.clab.api.domain.auth.login.application.service; import jakarta.servlet.http.HttpServletRequest; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; @@ -21,11 +22,9 @@ import page.clab.api.external.auth.redisToken.application.port.ExternalManageRedisTokenUseCase; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; import page.clab.api.global.auth.jwt.JwtTokenProvider; -import page.clab.api.global.common.slack.application.SlackService; +import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; import page.clab.api.global.util.HttpReqResUtil; -import java.util.List; - @Service @RequiredArgsConstructor @Qualifier("twoFactorAuthenticationService") @@ -41,7 +40,9 @@ public class TwoFactorAuthenticationService implements ManageLoginUseCase { @Transactional @Override - public LoginResult authenticate(HttpServletRequest request, TwoFactorAuthenticationRequestDto twoFactorAuthenticationRequestDto) throws LoginFailedException, MemberLockedException { + public LoginResult authenticate(HttpServletRequest request, + TwoFactorAuthenticationRequestDto twoFactorAuthenticationRequestDto) + throws LoginFailedException, MemberLockedException { String memberId = twoFactorAuthenticationRequestDto.getMemberId(); MemberLoginInfoDto loginMember = externalRetrieveMemberUseCase.getMemberLoginInfoById(memberId); String totp = twoFactorAuthenticationRequestDto.getTotp(); @@ -55,9 +56,11 @@ public LoginResult authenticate(HttpServletRequest request, TwoFactorAuthenticat return LoginResult.create(header, true); } - private void verifyTwoFactorAuthentication(String memberId, String totp, HttpServletRequest request) throws MemberLockedException, LoginFailedException { + private void verifyTwoFactorAuthentication(String memberId, String totp, HttpServletRequest request) + throws MemberLockedException, LoginFailedException { if (!manageAuthenticatorUseCase.isAuthenticatorValid(memberId, totp)) { - externalRegisterAccountAccessLogUseCase.registerAccountAccessLog(request, memberId, AccountAccessResult.FAILURE); + externalRegisterAccountAccessLogUseCase.registerAccountAccessLog(request, memberId, + AccountAccessResult.FAILURE); externalManageAccountLockUseCase.handleLoginFailure(request, memberId); throw new LoginFailedException("잘못된 인증번호입니다."); } @@ -67,7 +70,8 @@ private void verifyTwoFactorAuthentication(String memberId, String totp, HttpSer private TokenInfo generateAndSaveToken(MemberLoginInfoDto memberInfo) { TokenInfo tokenInfo = jwtTokenProvider.generateToken(memberInfo.getMemberId(), memberInfo.getRole()); String clientIpAddress = HttpReqResUtil.getClientIpAddressIfServletRequestExist(); - externalManageRedisTokenUseCase.saveToken(memberInfo.getMemberId(), memberInfo.getRole(), tokenInfo, clientIpAddress); + externalManageRedisTokenUseCase.saveToken(memberInfo.getMemberId(), memberInfo.getRole(), tokenInfo, + clientIpAddress); return tokenInfo; } @@ -78,7 +82,8 @@ private void sendAdminLoginNotification(HttpServletRequest request, MemberLoginI } @Override - public LoginResult login(HttpServletRequest request, LoginRequestDto requestDto) throws LoginFailedException, MemberLockedException { + public LoginResult login(HttpServletRequest request, LoginRequestDto requestDto) + throws LoginFailedException, MemberLockedException { throw new UnsupportedOperationException("Method not implemented"); } diff --git a/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpRemoveService.java b/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpRemoveService.java index 7789853cb..8dfa0565d 100644 --- a/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpRemoveService.java +++ b/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpRemoveService.java @@ -6,8 +6,8 @@ import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.auth.redisIpAccessMonitor.application.port.in.RemoveAbnormalAccessIpUseCase; import page.clab.api.domain.auth.redisIpAccessMonitor.application.port.out.RemoveIpAccessMonitorPort; -import page.clab.api.global.common.slack.application.SlackService; -import page.clab.api.global.common.slack.domain.SecurityAlertType; +import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; @Service @RequiredArgsConstructor @@ -20,7 +20,8 @@ public class AbnormalAccessIpRemoveService implements RemoveAbnormalAccessIpUseC @Transactional public String removeAbnormalAccessIp(HttpServletRequest request, String ipAddress) { removeIpAccessMonitorPort.deleteById(ipAddress); - slackService.sendSecurityAlertNotification(request, SecurityAlertType.ABNORMAL_ACCESS_IP_DELETED, "Deleted IP: " + ipAddress); + slackService.sendSecurityAlertNotification(request, SecurityAlertType.ABNORMAL_ACCESS_IP_DELETED, + "Deleted IP: " + ipAddress); return ipAddress; } } diff --git a/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpsClearService.java b/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpsClearService.java index 7a5c7dbcb..2127cb788 100644 --- a/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpsClearService.java +++ b/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpsClearService.java @@ -1,6 +1,7 @@ package page.clab.api.domain.auth.redisIpAccessMonitor.application.service; import jakarta.servlet.http.HttpServletRequest; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -8,10 +9,8 @@ import page.clab.api.domain.auth.redisIpAccessMonitor.application.port.out.ClearIpAccessMonitorPort; import page.clab.api.domain.auth.redisIpAccessMonitor.application.port.out.RetrieveIpAccessMonitorPort; import page.clab.api.domain.auth.redisIpAccessMonitor.domain.RedisIpAccessMonitor; -import page.clab.api.global.common.slack.application.SlackService; -import page.clab.api.global.common.slack.domain.SecurityAlertType; - -import java.util.List; +import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; @Service @RequiredArgsConstructor @@ -26,7 +25,8 @@ public class AbnormalAccessIpsClearService implements ClearAbnormalAccessIpsUseC public List clearAbnormalAccessIps(HttpServletRequest request) { List ipAccessMonitors = retrieveIpAccessMonitorPort.findAll(); clearIpAccessMonitorPort.deleteAll(); - slackService.sendSecurityAlertNotification(request, SecurityAlertType.ABNORMAL_ACCESS_IP_DELETED, "Deleted IP: ALL"); + slackService.sendSecurityAlertNotification(request, SecurityAlertType.ABNORMAL_ACCESS_IP_DELETED, + "Deleted IP: ALL"); return ipAccessMonitors; } } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java b/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java index f2e487749..0936df52e 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java @@ -1,5 +1,6 @@ package page.clab.api.domain.community.board.application.service; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -8,17 +9,15 @@ import page.clab.api.domain.community.board.application.port.in.RegisterBoardUseCase; import page.clab.api.domain.community.board.application.port.out.RegisterBoardPort; import page.clab.api.domain.community.board.domain.Board; -import page.clab.api.global.common.slack.domain.SlackBoardInfo; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberDetailedInfoDto; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; import page.clab.api.external.memberManagement.notification.application.port.ExternalSendNotificationUseCase; import page.clab.api.global.common.file.application.UploadedFileService; import page.clab.api.global.common.file.domain.UploadedFile; -import page.clab.api.global.common.slack.application.SlackService; +import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.slack.domain.SlackBoardInfo; import page.clab.api.global.exception.PermissionDeniedException; -import java.util.List; - @Service @RequiredArgsConstructor public class BoardRegisterService implements RegisterBoardUseCase { @@ -48,7 +47,8 @@ public String registerBoard(BoardRequestDto requestDto) throws PermissionDeniedE Board board = mapper.fromDto(requestDto, currentMemberInfo.getMemberId(), uploadedFiles); board.validateAccessPermissionForCreation(currentMemberInfo); if (board.shouldNotifyForNewBoard(currentMemberInfo)) { - externalSendNotificationUseCase.sendNotificationToMember(currentMemberInfo.getMemberId(), "[" + board.getTitle() + "] 새로운 공지사항이 등록되었습니다."); + externalSendNotificationUseCase.sendNotificationToMember(currentMemberInfo.getMemberId(), + "[" + board.getTitle() + "] 새로운 공지사항이 등록되었습니다."); } SlackBoardInfo boardInfo = SlackBoardInfo.create(board, currentMemberInfo); slackService.sendNewBoardNotification(boardInfo); diff --git a/src/main/java/page/clab/api/domain/hiring/application/application/service/ApplicationApplyService.java b/src/main/java/page/clab/api/domain/hiring/application/application/service/ApplicationApplyService.java index 11c69305c..4aa29bf59 100644 --- a/src/main/java/page/clab/api/domain/hiring/application/application/service/ApplicationApplyService.java +++ b/src/main/java/page/clab/api/domain/hiring/application/application/service/ApplicationApplyService.java @@ -10,7 +10,7 @@ import page.clab.api.domain.hiring.application.domain.Application; import page.clab.api.external.hiring.application.application.port.ExternalRetrieveRecruitmentUseCase; import page.clab.api.external.memberManagement.notification.application.port.ExternalSendNotificationUseCase; -import page.clab.api.global.common.slack.application.SlackService; +import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; @Service @RequiredArgsConstructor @@ -21,7 +21,7 @@ public class ApplicationApplyService implements ApplyForApplicationUseCase { private final ExternalSendNotificationUseCase externalSendNotificationUseCase; private final SlackService slackService; private final ApplicationDtoMapper mapper; - + @Transactional @Override public String applyForClub(ApplicationRequestDto requestDto) { diff --git a/src/main/java/page/clab/api/domain/library/bookLoanRecord/application/service/BookLoanRequestService.java b/src/main/java/page/clab/api/domain/library/bookLoanRecord/application/service/BookLoanRequestService.java index 1e759332e..7345ac8da 100644 --- a/src/main/java/page/clab/api/domain/library/bookLoanRecord/application/service/BookLoanRequestService.java +++ b/src/main/java/page/clab/api/domain/library/bookLoanRecord/application/service/BookLoanRequestService.java @@ -17,7 +17,7 @@ import page.clab.api.external.library.book.application.port.ExternalRetrieveBookUseCase; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; import page.clab.api.external.memberManagement.notification.application.port.ExternalSendNotificationUseCase; -import page.clab.api.global.common.slack.application.SlackService; +import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; import page.clab.api.global.common.slack.domain.SlackBookLoanRecordInfo; import page.clab.api.global.exception.CustomOptimisticLockingFailureException; @@ -36,15 +36,13 @@ public class BookLoanRequestService implements RequestBookLoanUseCase { * 도서 대출 신청을 처리합니다. * *

현재 로그인한 멤버의 대출 상태와 한도를 검증한 후, - * 도서의 대출 신청이 이미 존재하는지 확인합니다. - * 대출 신청이 성공적으로 완료되면 멤버와 Slack에 알림을 전송하고, - * 대출 기록을 저장한 후 그 ID를 반환합니다.

+ * 도서의 대출 신청이 이미 존재하는지 확인합니다. 대출 신청이 성공적으로 완료되면 멤버와 Slack에 알림을 전송하고, 대출 기록을 저장한 후 그 ID를 반환합니다.

* * @param requestDto 도서 대출 신청 요청 정보 DTO * @return 저장된 대출 기록의 ID * @throws CustomOptimisticLockingFailureException 동시에 다른 사용자가 대출을 신청하여 충돌이 발생한 경우 예외 발생 - * @throws MaxBorrowLimitExceededException 대출 한도를 초과한 경우 예외 발생 - * @throws BookAlreadyAppliedForLoanException 이미 신청된 도서일 경우 예외 발생 + * @throws MaxBorrowLimitExceededException 대출 한도를 초과한 경우 예외 발생 + * @throws BookAlreadyAppliedForLoanException 이미 신청된 도서일 경우 예외 발생 */ @Transactional @Override @@ -60,7 +58,8 @@ public Long requestBookLoan(BookLoanRecordRequestDto requestDto) throws CustomOp BookLoanRecord bookLoanRecord = BookLoanRecord.create(book.getId(), borrowerInfo); - externalSendNotificationUseCase.sendNotificationToMember(borrowerInfo.getMemberId(), "[" + book.getTitle() + "] 도서 대출 신청이 완료되었습니다."); + externalSendNotificationUseCase.sendNotificationToMember(borrowerInfo.getMemberId(), + "[" + book.getTitle() + "] 도서 대출 신청이 완료되었습니다."); SlackBookLoanRecordInfo bookLoanRecordInfo = SlackBookLoanRecordInfo.create(book, borrowerInfo); slackService.sendNewBookLoanRequestNotification(bookLoanRecordInfo); diff --git a/src/main/java/page/clab/api/domain/memberManagement/member/application/service/MemberRoleManagementService.java b/src/main/java/page/clab/api/domain/memberManagement/member/application/service/MemberRoleManagementService.java index db2ced3c0..8d656dda3 100644 --- a/src/main/java/page/clab/api/domain/memberManagement/member/application/service/MemberRoleManagementService.java +++ b/src/main/java/page/clab/api/domain/memberManagement/member/application/service/MemberRoleManagementService.java @@ -11,8 +11,8 @@ import page.clab.api.domain.memberManagement.member.application.port.out.UpdateMemberPort; import page.clab.api.domain.memberManagement.member.domain.Member; import page.clab.api.domain.memberManagement.member.domain.Role; -import page.clab.api.global.common.slack.application.SlackService; -import page.clab.api.global.common.slack.domain.SecurityAlertType; +import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; @Service @RequiredArgsConstructor @@ -24,7 +24,8 @@ public class MemberRoleManagementService implements ManageMemberRoleUseCase { @Transactional @Override - public String changeMemberRole(HttpServletRequest httpServletRequest, String memberId, ChangeMemberRoleRequest request) { + public String changeMemberRole(HttpServletRequest httpServletRequest, String memberId, + ChangeMemberRoleRequest request) { Member member = retrieveMemberPort.getById(memberId); Role oldRole = member.getRole(); diff --git a/src/main/java/page/clab/api/domain/members/membershipFee/application/service/MembershipFeeRegisterService.java b/src/main/java/page/clab/api/domain/members/membershipFee/application/service/MembershipFeeRegisterService.java index 3d50a19e3..62f3723ff 100644 --- a/src/main/java/page/clab/api/domain/members/membershipFee/application/service/MembershipFeeRegisterService.java +++ b/src/main/java/page/clab/api/domain/members/membershipFee/application/service/MembershipFeeRegisterService.java @@ -11,7 +11,7 @@ import page.clab.api.domain.members.membershipFee.domain.MembershipFee; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; import page.clab.api.external.memberManagement.notification.application.port.ExternalSendNotificationUseCase; -import page.clab.api.global.common.slack.application.SlackService; +import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; import page.clab.api.global.common.slack.domain.SlackMembershipFeeInfo; @Service diff --git a/src/main/java/page/clab/api/external/auth/accountLockInfo/port/ExternalAccountLockManagementService.java b/src/main/java/page/clab/api/external/auth/accountLockInfo/port/ExternalAccountLockManagementService.java index 4a9eb6dde..428ce931b 100644 --- a/src/main/java/page/clab/api/external/auth/accountLockInfo/port/ExternalAccountLockManagementService.java +++ b/src/main/java/page/clab/api/external/auth/accountLockInfo/port/ExternalAccountLockManagementService.java @@ -13,8 +13,8 @@ import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberDetailedInfoDto; import page.clab.api.external.auth.accountLockInfo.application.ExternalManageAccountLockUseCase; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; -import page.clab.api.global.common.slack.application.SlackService; -import page.clab.api.global.common.slack.domain.SecurityAlertType; +import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; @Service @RequiredArgsConstructor @@ -39,7 +39,7 @@ public class ExternalAccountLockManagementService implements ExternalManageAccou * * @param memberId 잠금 해제하려는 멤버의 ID * @throws MemberLockedException 계정이 현재 잠겨 있을 경우 예외 발생 - * @throws LoginFailedException 멤버가 존재하지 않을 경우 예외 발생 + * @throws LoginFailedException 멤버가 존재하지 않을 경우 예외 발생 */ @Transactional @Override @@ -55,17 +55,17 @@ public void handleAccountLockInfo(String memberId) throws MemberLockedException, * 로그인 실패를 처리하고 계정 잠금을 관리합니다. * *

로그인 실패 시 멤버의 존재 여부와 계정 잠금 상태를 확인합니다. - * 로그인 실패 횟수를 증가시키며, 설정된 최대 실패 횟수에 도달하면 계정을 잠그고 - * Slack에 보안 알림을 전송합니다.

+ * 로그인 실패 횟수를 증가시키며, 설정된 최대 실패 횟수에 도달하면 계정을 잠그고 Slack에 보안 알림을 전송합니다.

* - * @param request 현재 HTTP 요청 객체 + * @param request 현재 HTTP 요청 객체 * @param memberId 로그인 실패를 기록할 멤버의 ID * @throws MemberLockedException 계정이 현재 잠겨 있을 경우 예외 발생 - * @throws LoginFailedException 멤버가 존재하지 않을 경우 예외 발생 + * @throws LoginFailedException 멤버가 존재하지 않을 경우 예외 발생 */ @Transactional @Override - public void handleLoginFailure(HttpServletRequest request, String memberId) throws MemberLockedException, LoginFailedException { + public void handleLoginFailure(HttpServletRequest request, String memberId) + throws MemberLockedException, LoginFailedException { ensureMemberExists(memberId); AccountLockInfo accountLockInfo = ensureAccountLockInfo(memberId); validateAccountLockStatus(accountLockInfo); @@ -99,7 +99,8 @@ private void sendSlackLoginFailureNotification(HttpServletRequest request, Strin String memberName = memberInfo.getMemberName(); if (memberInfo.isAdminRole()) { request.setAttribute("member", memberId + " " + memberName); - slackService.sendSecurityAlertNotification(request, SecurityAlertType.REPEATED_LOGIN_FAILURES, "로그인 실패 횟수 초과로 계정이 잠겼습니다."); + slackService.sendSecurityAlertNotification(request, SecurityAlertType.REPEATED_LOGIN_FAILURES, + "로그인 실패 횟수 초과로 계정이 잠겼습니다."); } } } diff --git a/src/main/java/page/clab/api/external/auth/redisIpAccessMonitor/application/service/ExternalIpAccessMonitorRegisterService.java b/src/main/java/page/clab/api/external/auth/redisIpAccessMonitor/application/service/ExternalIpAccessMonitorRegisterService.java index a600c8b3d..e6a63f21e 100644 --- a/src/main/java/page/clab/api/external/auth/redisIpAccessMonitor/application/service/ExternalIpAccessMonitorRegisterService.java +++ b/src/main/java/page/clab/api/external/auth/redisIpAccessMonitor/application/service/ExternalIpAccessMonitorRegisterService.java @@ -10,8 +10,8 @@ import page.clab.api.domain.auth.redisIpAccessMonitor.application.port.out.RetrieveIpAccessMonitorPort; import page.clab.api.domain.auth.redisIpAccessMonitor.domain.RedisIpAccessMonitor; import page.clab.api.external.auth.redisIpAccessMonitor.application.port.ExternalRegisterIpAccessMonitorUseCase; -import page.clab.api.global.common.slack.application.SlackService; -import page.clab.api.global.common.slack.domain.SecurityAlertType; +import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; @Service @RequiredArgsConstructor @@ -29,7 +29,8 @@ public class ExternalIpAccessMonitorRegisterService implements ExternalRegisterI public void registerIpAccessMonitor(HttpServletRequest request, String ipAddress) { RedisIpAccessMonitor redisIpAccessMonitor = getOrCreateRedisIpAccessMonitor(ipAddress); if (redisIpAccessMonitor.isBlocked()) { - slackService.sendSecurityAlertNotification(request, SecurityAlertType.ABNORMAL_ACCESS_IP_BLOCKED, "Blocked IP: " + ipAddress); + slackService.sendSecurityAlertNotification(request, SecurityAlertType.ABNORMAL_ACCESS_IP_BLOCKED, + "Blocked IP: " + ipAddress); } registerIpAccessMonitorPort.save(redisIpAccessMonitor); } diff --git a/src/main/java/page/clab/api/global/auth/filter/CustomBasicAuthenticationFilter.java b/src/main/java/page/clab/api/global/auth/filter/CustomBasicAuthenticationFilter.java index 954d80f94..100c32390 100644 --- a/src/main/java/page/clab/api/global/auth/filter/CustomBasicAuthenticationFilter.java +++ b/src/main/java/page/clab/api/global/auth/filter/CustomBasicAuthenticationFilter.java @@ -4,6 +4,8 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Base64; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.security.authentication.AuthenticationManager; @@ -15,21 +17,17 @@ import page.clab.api.external.auth.blacklistIp.application.port.ExternalRetrieveBlacklistIpUseCase; import page.clab.api.external.auth.redisIpAccessMonitor.application.port.ExternalCheckIpBlockedUseCase; import page.clab.api.global.auth.util.IpWhitelistValidator; -import page.clab.api.global.common.slack.application.SlackService; -import page.clab.api.global.common.slack.domain.SecurityAlertType; +import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; import page.clab.api.global.util.HttpReqResUtil; import page.clab.api.global.util.ResponseUtil; import page.clab.api.global.util.WhitelistPathMatcher; -import java.io.IOException; -import java.util.Base64; - /** * {@code CustomBasicAuthenticationFilter}는 기본 인증 필터를 확장하여 추가적인 보안 기능을 제공합니다. * *

IP 주소 기반 접근 제한, 화이트리스트 경로 검증, 사용자 인증 정보를 바탕으로 - * Slack 보안 알림을 전송하는 기능을 포함합니다. 또한 Swagger 또는 Actuator에 대한 - * 접근이 성공하거나 실패할 경우 이를 Slack에 알립니다.

+ * Slack 보안 알림을 전송하는 기능을 포함합니다. 또한 Swagger 또는 Actuator에 대한 접근이 성공하거나 실패할 경우 이를 Slack에 알립니다.

* *

이 필터는 다음과 같은 추가 검증을 수행합니다:

*
    @@ -65,6 +63,13 @@ public CustomBasicAuthenticationFilter( this.slackService = slackService; } + @NotNull + private static String[] decodeCredentials(String authorizationHeader) { + String base64Credentials = authorizationHeader.substring("Basic ".length()); + String credentials = new String(Base64.getDecoder().decode(base64Credentials)); + return credentials.split(":", 2); + } + @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { @@ -82,7 +87,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse super.doFilterInternal(request, response, chain); } - private boolean authenticateUserCredentials(HttpServletRequest request, HttpServletResponse response) throws IOException { + private boolean authenticateUserCredentials(HttpServletRequest request, HttpServletResponse response) + throws IOException { String authorizationHeader = request.getHeader("Authorization"); if (authorizationHeader == null || !authorizationHeader.startsWith("Basic ")) { response.setHeader("WWW-Authenticate", "Basic realm=\"Please enter your username and password\""); @@ -122,28 +128,25 @@ private boolean verifyIpAddressAccess(HttpServletResponse response) throws IOExc return true; } - @NotNull - private static String[] decodeCredentials(String authorizationHeader) { - String base64Credentials = authorizationHeader.substring("Basic ".length()); - String credentials = new String(Base64.getDecoder().decode(base64Credentials)); - return credentials.split(":", 2); - } - private void sendAuthenticationSuccessAlertSlackMessage(HttpServletRequest request) { String path = request.getRequestURI(); if (WhitelistPathMatcher.isSwaggerIndexEndpoint(path)) { - slackService.sendSecurityAlertNotification(request, SecurityAlertType.API_DOCS_ACCESS,"API 문서에 대한 접근이 허가되었습니다."); + slackService.sendSecurityAlertNotification(request, SecurityAlertType.API_DOCS_ACCESS, + "API 문서에 대한 접근이 허가되었습니다."); } else if (WhitelistPathMatcher.isActuatorRequest(path)) { - slackService.sendSecurityAlertNotification(request, SecurityAlertType.ACTUATOR_ACCESS,"Actuator에 대한 접근이 허가되었습니다."); + slackService.sendSecurityAlertNotification(request, SecurityAlertType.ACTUATOR_ACCESS, + "Actuator에 대한 접근이 허가되었습니다."); } } private void sendAuthenticationFailureAlertSlackMessage(HttpServletRequest request) { String path = request.getRequestURI(); if (WhitelistPathMatcher.isSwaggerIndexEndpoint(path)) { - slackService.sendSecurityAlertNotification(request, SecurityAlertType.API_DOCS_ACCESS,"API 문서에 대한 접근이 거부되었습니다."); + slackService.sendSecurityAlertNotification(request, SecurityAlertType.API_DOCS_ACCESS, + "API 문서에 대한 접근이 거부되었습니다."); } else if (WhitelistPathMatcher.isActuatorRequest(path)) { - slackService.sendSecurityAlertNotification(request, SecurityAlertType.ACTUATOR_ACCESS,"Actuator에 대한 접근이 거부되었습니다."); + slackService.sendSecurityAlertNotification(request, SecurityAlertType.ACTUATOR_ACCESS, + "Actuator에 대한 접근이 거부되었습니다."); } } } diff --git a/src/main/java/page/clab/api/global/auth/filter/InvalidEndpointAccessFilter.java b/src/main/java/page/clab/api/global/auth/filter/InvalidEndpointAccessFilter.java index b8314f9bf..4291b37ff 100644 --- a/src/main/java/page/clab/api/global/auth/filter/InvalidEndpointAccessFilter.java +++ b/src/main/java/page/clab/api/global/auth/filter/InvalidEndpointAccessFilter.java @@ -6,6 +6,7 @@ import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; @@ -14,17 +15,14 @@ import page.clab.api.domain.auth.blacklistIp.domain.BlacklistIp; import page.clab.api.external.auth.blacklistIp.application.port.ExternalRegisterBlacklistIpUseCase; import page.clab.api.external.auth.blacklistIp.application.port.ExternalRetrieveBlacklistIpUseCase; -import page.clab.api.global.common.slack.application.SlackService; -import page.clab.api.global.common.slack.domain.SecurityAlertType; +import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; import page.clab.api.global.util.HttpReqResUtil; import page.clab.api.global.util.ResponseUtil; import page.clab.api.global.util.SecurityPatternChecker; -import java.io.IOException; - /** - * {@code InvalidEndpointAccessFilter}는 서버 내부 파일 및 디렉토리에 대한 비정상적인 접근을 차단하고 - * 보안 경고를 전송하는 필터입니다. + * {@code InvalidEndpointAccessFilter}는 서버 내부 파일 및 디렉토리에 대한 비정상적인 접근을 차단하고 보안 경고를 전송하는 필터입니다. * *

    특정 패턴을 통해 비정상적인 접근 시도를 탐지하며, 비정상적인 경로로 접근을 시도한 IP를 * 블랙리스트에 등록하고, Slack을 통해 보안 경고 메시지를 전송합니다.

    @@ -50,7 +48,8 @@ public class InvalidEndpointAccessFilter extends GenericFilterBean { private final ExternalRetrieveBlacklistIpUseCase externalRetrieveBlacklistIpUseCase; @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; String path = httpRequest.getRequestURI(); boolean isUploadedFileAccess = path.startsWith(fileURL); @@ -75,7 +74,8 @@ private void handleSuspiciousAccess(HttpServletRequest request, HttpServletRespo private void logSuspiciousAccess(HttpServletRequest request, String clientIpAddress) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - String id = (authentication == null || authentication.getName() == null) ? "anonymous" : authentication.getName(); + String id = + (authentication == null || authentication.getName() == null) ? "anonymous" : authentication.getName(); String requestUrl = request.getRequestURI(); String httpMethod = request.getMethod(); int statusCode = HttpServletResponse.SC_FORBIDDEN; @@ -98,6 +98,7 @@ private void sendSecurityAlerts(HttpServletRequest request, String clientIpAddre String blacklistAddedMessage = "Added IP: " + clientIpAddress; slackService.sendSecurityAlertNotification(request, SecurityAlertType.ABNORMAL_ACCESS, abnormalAccessMessage); - slackService.sendSecurityAlertNotification(request, SecurityAlertType.BLACKLISTED_IP_ADDED, blacklistAddedMessage); + slackService.sendSecurityAlertNotification(request, SecurityAlertType.BLACKLISTED_IP_ADDED, + blacklistAddedMessage); } } diff --git a/src/main/java/page/clab/api/global/auth/filter/JwtAuthenticationFilter.java b/src/main/java/page/clab/api/global/auth/filter/JwtAuthenticationFilter.java index 12bda3af8..4a61d3682 100644 --- a/src/main/java/page/clab/api/global/auth/filter/JwtAuthenticationFilter.java +++ b/src/main/java/page/clab/api/global/auth/filter/JwtAuthenticationFilter.java @@ -6,6 +6,7 @@ import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; @@ -16,14 +17,12 @@ import page.clab.api.external.auth.redisIpAccessMonitor.application.port.ExternalCheckIpBlockedUseCase; import page.clab.api.external.auth.redisToken.application.port.ExternalManageRedisTokenUseCase; import page.clab.api.global.auth.jwt.JwtTokenProvider; -import page.clab.api.global.common.slack.application.SlackService; -import page.clab.api.global.common.slack.domain.SecurityAlertType; +import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; import page.clab.api.global.util.HttpReqResUtil; import page.clab.api.global.util.ResponseUtil; import page.clab.api.global.util.WhitelistPathMatcher; -import java.io.IOException; - /** * {@code JwtAuthenticationFilter}는 JWT 토큰을 검증하고, IP 주소 기반 접근 제한을 수행하는 필터입니다. * @@ -52,7 +51,8 @@ public class JwtAuthenticationFilter extends GenericFilterBean { private final ExternalRetrieveBlacklistIpUseCase externalRetrieveBlacklistIpUseCase; @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpServletResponse httpServletResponse = (HttpServletResponse) response; String path = httpServletRequest.getRequestURI(); @@ -71,7 +71,8 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha } private boolean verifyIpAddressAccess(HttpServletResponse response, String clientIpAddress) throws IOException { - if (externalRetrieveBlacklistIpUseCase.existsByIpAddress(clientIpAddress) || externalCheckIpBlockedUseCase.isIpBlocked(clientIpAddress)) { + if (externalRetrieveBlacklistIpUseCase.existsByIpAddress(clientIpAddress) + || externalCheckIpBlockedUseCase.isIpBlocked(clientIpAddress)) { log.info("[{}] : 서비스 이용이 제한된 IP입니다.", clientIpAddress); ResponseUtil.sendErrorResponse(response, HttpServletResponse.SC_UNAUTHORIZED); return false; @@ -79,12 +80,15 @@ private boolean verifyIpAddressAccess(HttpServletResponse response, String clien return true; } - private boolean authenticateToken(HttpServletRequest request, HttpServletResponse response, String clientIpAddress) throws IOException { + private boolean authenticateToken(HttpServletRequest request, HttpServletResponse response, String clientIpAddress) + throws IOException { String token = jwtTokenProvider.resolveToken(request); // 토큰이 존재하고 유효한 경우 if (token != null && jwtTokenProvider.validateToken(token)) { - RedisToken redisToken = jwtTokenProvider.isRefreshToken(token) ? externalManageRedisTokenUseCase.findByRefreshToken(token) : externalManageRedisTokenUseCase.findByAccessToken(token); + RedisToken redisToken = + jwtTokenProvider.isRefreshToken(token) ? externalManageRedisTokenUseCase.findByRefreshToken(token) + : externalManageRedisTokenUseCase.findByAccessToken(token); if (redisToken == null) { log.warn("존재하지 않는 토큰입니다."); ResponseUtil.sendErrorResponse(response, HttpServletResponse.SC_UNAUTHORIZED); @@ -108,7 +112,8 @@ private boolean authenticateToken(HttpServletRequest request, HttpServletRespons private void sendSecurityAlertSlackMessage(HttpServletRequest request, RedisToken redisToken) { if (redisToken.isAdminToken()) { request.setAttribute("member", redisToken.getId()); - slackService.sendSecurityAlertNotification(request, SecurityAlertType.DUPLICATE_LOGIN, "토큰 발급 IP와 다른 IP에서 접속하여 토큰을 삭제하였습니다."); + slackService.sendSecurityAlertNotification(request, SecurityAlertType.DUPLICATE_LOGIN, + "토큰 발급 IP와 다른 IP에서 접속하여 토큰을 삭제하였습니다."); } } } diff --git a/src/main/java/page/clab/api/global/common/slack/api/NotificationSettingController.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingController.java similarity index 81% rename from src/main/java/page/clab/api/global/common/slack/api/NotificationSettingController.java rename to src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingController.java index f242289d1..b20aec49c 100644 --- a/src/main/java/page/clab/api/global/common/slack/api/NotificationSettingController.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingController.java @@ -1,8 +1,9 @@ -package page.clab.api.global.common.slack.api; +package page.clab.api.global.common.notificationSetting.adapter.in.web; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; @@ -11,11 +12,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import page.clab.api.global.common.dto.ApiResponse; -import page.clab.api.global.common.slack.application.NotificationSettingService; -import page.clab.api.global.common.slack.dto.request.NotificationSettingUpdateRequestDto; -import page.clab.api.global.common.slack.dto.response.NotificationSettingResponseDto; - -import java.util.List; +import page.clab.api.global.common.notificationSetting.application.dto.request.NotificationSettingUpdateRequestDto; +import page.clab.api.global.common.notificationSetting.application.dto.response.NotificationSettingResponseDto; +import page.clab.api.global.common.notificationSetting.application.service.NotificationSettingService; @RestController @RequestMapping("/api/v1/notification-settings") diff --git a/src/main/java/page/clab/api/global/common/slack/dao/NotificationSettingRepository.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/persistence/NotificationSettingRepository.java similarity index 52% rename from src/main/java/page/clab/api/global/common/slack/dao/NotificationSettingRepository.java rename to src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/persistence/NotificationSettingRepository.java index e4cab3c38..4be9efb7b 100644 --- a/src/main/java/page/clab/api/global/common/slack/dao/NotificationSettingRepository.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/persistence/NotificationSettingRepository.java @@ -1,10 +1,9 @@ -package page.clab.api.global.common.slack.dao; - -import org.springframework.data.jpa.repository.JpaRepository; -import page.clab.api.global.common.slack.domain.AlertType; -import page.clab.api.global.common.slack.domain.NotificationSetting; +package page.clab.api.global.common.notificationSetting.adapter.out.persistence; import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import page.clab.api.global.common.notificationSetting.domain.AlertType; +import page.clab.api.global.common.notificationSetting.domain.NotificationSetting; public interface NotificationSettingRepository extends JpaRepository { diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackNotificationSender.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackNotificationSender.java new file mode 100644 index 000000000..c7a5ff764 --- /dev/null +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackNotificationSender.java @@ -0,0 +1,19 @@ +package page.clab.api.global.common.notificationSetting.adapter.out.slack; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; +import page.clab.api.global.common.notificationSetting.application.port.out.NotificationSender; + +@Component +@RequiredArgsConstructor +public class SlackNotificationSender implements NotificationSender { + + private final SlackServiceHelper slackServiceHelper; + + @Override + public void sendNotification(NotificationEvent event) { + slackServiceHelper.sendSlackMessage(event.getWebhookUrl(), event.getAlertType(), event.getRequest(), + event.getAdditionalData()); + } +} diff --git a/src/main/java/page/clab/api/global/common/slack/application/SlackService.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackService.java similarity index 65% rename from src/main/java/page/clab/api/global/common/slack/application/SlackService.java rename to src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackService.java index dca15b3a9..8168a5a6c 100644 --- a/src/main/java/page/clab/api/global/common/slack/application/SlackService.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackService.java @@ -1,4 +1,4 @@ -package page.clab.api.global.common.slack.application; +package page.clab.api.global.common.notificationSetting.adapter.out.slack; import jakarta.servlet.http.HttpServletRequest; import org.springframework.context.ApplicationEventPublisher; @@ -7,13 +7,13 @@ import org.springframework.stereotype.Service; import page.clab.api.domain.hiring.application.application.dto.request.ApplicationRequestDto; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberLoginInfoDto; -import page.clab.api.global.common.slack.domain.ExecutivesAlertType; -import page.clab.api.global.common.slack.domain.GeneralAlertType; -import page.clab.api.global.common.slack.domain.SecurityAlertType; +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; +import page.clab.api.global.common.notificationSetting.domain.ExecutivesAlertType; +import page.clab.api.global.common.notificationSetting.domain.GeneralAlertType; +import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; import page.clab.api.global.common.slack.domain.SlackBoardInfo; import page.clab.api.global.common.slack.domain.SlackBookLoanRecordInfo; import page.clab.api.global.common.slack.domain.SlackMembershipFeeInfo; -import page.clab.api.global.common.slack.event.NotificationEvent; import page.clab.api.global.config.SlackConfig; /** @@ -21,7 +21,7 @@ * *

    이 서비스는 `NotificationEvent`를 통해 Slack 알림을 발송하며, * 서버 시작, 서버 오류, 보안 경고, 관리자 로그인 등의 알림 유형을 제공합니다.

    - * + *

    * 주요 기능: *

      *
    • {@link #sendServerErrorNotification(HttpServletRequest, Exception)} - 서버 오류 발생 시 Slack으로 알림을 전송합니다.
    • @@ -48,35 +48,47 @@ public SlackService(ApplicationEventPublisher eventPublisher, SlackConfig slackC } public void sendServerErrorNotification(HttpServletRequest request, Exception e) { - eventPublisher.publishEvent(new NotificationEvent(this, coreTeamWebhookUrl, GeneralAlertType.SERVER_ERROR, request, e)); + eventPublisher.publishEvent( + new NotificationEvent(this, coreTeamWebhookUrl, GeneralAlertType.SERVER_ERROR, request, e)); } - public void sendSecurityAlertNotification(HttpServletRequest request, SecurityAlertType alertType, String additionalMessage) { - eventPublisher.publishEvent(new NotificationEvent(this, coreTeamWebhookUrl, alertType, request, additionalMessage)); + public void sendSecurityAlertNotification(HttpServletRequest request, SecurityAlertType alertType, + String additionalMessage) { + eventPublisher.publishEvent( + new NotificationEvent(this, coreTeamWebhookUrl, alertType, request, additionalMessage)); } public void sendAdminLoginNotification(HttpServletRequest request, MemberLoginInfoDto loginMember) { - eventPublisher.publishEvent(new NotificationEvent(this, coreTeamWebhookUrl, GeneralAlertType.ADMIN_LOGIN, request, loginMember)); + eventPublisher.publishEvent( + new NotificationEvent(this, coreTeamWebhookUrl, GeneralAlertType.ADMIN_LOGIN, request, loginMember)); } public void sendNewApplicationNotification(ApplicationRequestDto applicationRequestDto) { - eventPublisher.publishEvent(new NotificationEvent(this, executivesWebhookUrl, ExecutivesAlertType.NEW_APPLICATION, null, applicationRequestDto)); + eventPublisher.publishEvent( + new NotificationEvent(this, executivesWebhookUrl, ExecutivesAlertType.NEW_APPLICATION, null, + applicationRequestDto)); } public void sendNewBoardNotification(SlackBoardInfo board) { - eventPublisher.publishEvent(new NotificationEvent(this, executivesWebhookUrl, ExecutivesAlertType.NEW_BOARD, null, board)); + eventPublisher.publishEvent( + new NotificationEvent(this, executivesWebhookUrl, ExecutivesAlertType.NEW_BOARD, null, board)); } public void sendNewMembershipFeeNotification(SlackMembershipFeeInfo membershipFee) { - eventPublisher.publishEvent(new NotificationEvent(this, executivesWebhookUrl, ExecutivesAlertType.NEW_MEMBERSHIP_FEE, null, membershipFee)); + eventPublisher.publishEvent( + new NotificationEvent(this, executivesWebhookUrl, ExecutivesAlertType.NEW_MEMBERSHIP_FEE, null, + membershipFee)); } public void sendNewBookLoanRequestNotification(SlackBookLoanRecordInfo bookLoanRecord) { - eventPublisher.publishEvent(new NotificationEvent(this, executivesWebhookUrl, ExecutivesAlertType.NEW_BOOK_LOAN_REQUEST, null, bookLoanRecord)); + eventPublisher.publishEvent( + new NotificationEvent(this, executivesWebhookUrl, ExecutivesAlertType.NEW_BOOK_LOAN_REQUEST, null, + bookLoanRecord)); } @EventListener(ContextRefreshedEvent.class) public void sendServerStartNotification() { - eventPublisher.publishEvent(new NotificationEvent(this, coreTeamWebhookUrl, GeneralAlertType.SERVER_START, null, null)); + eventPublisher.publishEvent( + new NotificationEvent(this, coreTeamWebhookUrl, GeneralAlertType.SERVER_START, null, null)); } } diff --git a/src/main/java/page/clab/api/global/common/slack/application/SlackServiceHelper.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackServiceHelper.java similarity index 93% rename from src/main/java/page/clab/api/global/common/slack/application/SlackServiceHelper.java rename to src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackServiceHelper.java index a3b2c3edf..a2f288bd1 100644 --- a/src/main/java/page/clab/api/global/common/slack/application/SlackServiceHelper.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackServiceHelper.java @@ -1,19 +1,32 @@ -package page.clab.api.global.common.slack.application; +package page.clab.api.global.common.notificationSetting.adapter.out.slack; -import com.slack.api.Slack; -import com.slack.api.model.Attachment; import static com.slack.api.model.block.Blocks.actions; import static com.slack.api.model.block.Blocks.section; -import com.slack.api.model.block.LayoutBlock; import static com.slack.api.model.block.composition.BlockCompositions.markdownText; import static com.slack.api.model.block.composition.BlockCompositions.plainText; import static com.slack.api.model.block.element.BlockElements.asElements; import static com.slack.api.model.block.element.BlockElements.button; + +import com.slack.api.Slack; +import com.slack.api.model.Attachment; +import com.slack.api.model.block.LayoutBlock; import com.slack.api.webhook.Payload; import com.slack.api.webhook.WebhookResponse; import io.ipinfo.api.model.IPResponse; import io.ipinfo.spring.strategies.attribute.AttributeStrategy; import jakarta.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryUsage; +import java.lang.management.OperatingSystemMXBean; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.core.env.Environment; @@ -22,29 +35,16 @@ import org.springframework.stereotype.Component; import page.clab.api.domain.hiring.application.application.dto.request.ApplicationRequestDto; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberLoginInfoDto; -import page.clab.api.global.common.slack.domain.AlertType; -import page.clab.api.global.common.slack.domain.ExecutivesAlertType; -import page.clab.api.global.common.slack.domain.GeneralAlertType; -import page.clab.api.global.common.slack.domain.SecurityAlertType; +import page.clab.api.global.common.notificationSetting.domain.AlertType; +import page.clab.api.global.common.notificationSetting.domain.ExecutivesAlertType; +import page.clab.api.global.common.notificationSetting.domain.GeneralAlertType; +import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; import page.clab.api.global.common.slack.domain.SlackBoardInfo; import page.clab.api.global.common.slack.domain.SlackBookLoanRecordInfo; import page.clab.api.global.common.slack.domain.SlackMembershipFeeInfo; import page.clab.api.global.config.SlackConfig; import page.clab.api.global.util.HttpReqResUtil; -import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.lang.management.MemoryMXBean; -import java.lang.management.MemoryUsage; -import java.lang.management.OperatingSystemMXBean; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; - /** * {@code SlackServiceHelper}는 다양한 알림 유형에 따라 Slack 메시지를 구성하고 전송하는 클래스입니다. * @@ -90,13 +90,14 @@ public SlackServiceHelper(SlackConfig slackConfig, Environment environment, Attr *

      주어진 webhookUrl과 alertType, HttpServletRequest 및 추가 데이터(additionalData)를 사용하여 알림 메시지를 * 비동기적으로 Slack에 전송합니다.

      * - * @param webhookUrl 메시지를 보낼 Slack 웹훅 URL - * @param alertType 알림 유형을 나타내는 {@link AlertType} - * @param request HttpServletRequest 객체, 클라이언트 요청 정보 + * @param webhookUrl 메시지를 보낼 Slack 웹훅 URL + * @param alertType 알림 유형을 나타내는 {@link AlertType} + * @param request HttpServletRequest 객체, 클라이언트 요청 정보 * @param additionalData 추가 데이터 * @return 메시지 전송 성공 여부를 나타내는 CompletableFuture */ - public CompletableFuture sendSlackMessage(String webhookUrl, AlertType alertType, HttpServletRequest request, Object additionalData) { + public CompletableFuture sendSlackMessage(String webhookUrl, AlertType alertType, + HttpServletRequest request, Object additionalData) { List blocks = createBlocks(alertType, request, additionalData); return CompletableFuture.supplyAsync(() -> { Payload payload = Payload.builder() @@ -127,8 +128,8 @@ public CompletableFuture sendSlackMessage(String webhookUrl, AlertType * *

      AlertType에 따라 보안 경고, 일반 알림, 운영진 알림 등 다양한 형식의 메시지를 생성합니다.

      * - * @param alertType 알림 유형 - * @param request HttpServletRequest 객체 + * @param alertType 알림 유형 + * @param request HttpServletRequest 객체 * @param additionalData 추가 데이터 * @return 생성된 LayoutBlock 목록 */ @@ -204,7 +205,8 @@ private List createErrorBlocks(HttpServletRequest request, Exceptio ); } - private List createSecurityAlertBlocks(HttpServletRequest request, AlertType alertType, String additionalMessage) { + private List createSecurityAlertBlocks(HttpServletRequest request, AlertType alertType, + String additionalMessage) { String clientIpAddress = HttpReqResUtil.getClientIpAddressIfServletRequestExist(); String requestUrl = request.getRequestURI(); String queryString = request.getQueryString(); @@ -220,7 +222,8 @@ private List createSecurityAlertBlocks(HttpServletRequest request, markdownText("*Location:*\n" + location), markdownText("*Endpoint:*\n" + fullUrl) ))), - section(section -> section.text(markdownText("*Details:*\n" + alertType.getDefaultMessage() + "\n" + additionalMessage))) + section(section -> section.text( + markdownText("*Details:*\n" + alertType.getDefaultMessage() + "\n" + additionalMessage))) ); } @@ -229,7 +232,8 @@ private List createAdminLoginBlocks(HttpServletRequest request, Mem String location = getLocation(request); return Arrays.asList( - section(section -> section.text(markdownText(String.format(":mechanic: *%s Login*", loginMember.getRole().getDescription())))), + section(section -> section.text( + markdownText(String.format(":mechanic: *%s Login*", loginMember.getRole().getDescription())))), section(section -> section.fields(Arrays.asList( markdownText("*User:*\n" + loginMember.getMemberId() + " " + loginMember.getMemberName()), markdownText("*IP Address:*\n" + clientIpAddress), diff --git a/src/main/java/page/clab/api/global/common/slack/dto/mapper/SlackDtoMapper.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/dto/mapper/NotificationSettingDtoMapper.java similarity index 51% rename from src/main/java/page/clab/api/global/common/slack/dto/mapper/SlackDtoMapper.java rename to src/main/java/page/clab/api/global/common/notificationSetting/application/dto/mapper/NotificationSettingDtoMapper.java index f2b75b6a3..4b092100a 100644 --- a/src/main/java/page/clab/api/global/common/slack/dto/mapper/SlackDtoMapper.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/dto/mapper/NotificationSettingDtoMapper.java @@ -1,11 +1,11 @@ -package page.clab.api.global.common.slack.dto.mapper; +package page.clab.api.global.common.notificationSetting.application.dto.mapper; import org.springframework.stereotype.Component; -import page.clab.api.global.common.slack.domain.NotificationSetting; -import page.clab.api.global.common.slack.dto.response.NotificationSettingResponseDto; +import page.clab.api.global.common.notificationSetting.application.dto.response.NotificationSettingResponseDto; +import page.clab.api.global.common.notificationSetting.domain.NotificationSetting; @Component -public class SlackDtoMapper { +public class NotificationSettingDtoMapper { public NotificationSettingResponseDto toDto(NotificationSetting setting) { return NotificationSettingResponseDto.builder() diff --git a/src/main/java/page/clab/api/global/common/slack/dto/request/NotificationSettingUpdateRequestDto.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/dto/request/NotificationSettingUpdateRequestDto.java similarity index 87% rename from src/main/java/page/clab/api/global/common/slack/dto/request/NotificationSettingUpdateRequestDto.java rename to src/main/java/page/clab/api/global/common/notificationSetting/application/dto/request/NotificationSettingUpdateRequestDto.java index fcd941924..b0db80a2e 100644 --- a/src/main/java/page/clab/api/global/common/slack/dto/request/NotificationSettingUpdateRequestDto.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/dto/request/NotificationSettingUpdateRequestDto.java @@ -1,4 +1,4 @@ -package page.clab.api.global.common.slack.dto.request; +package page.clab.api.global.common.notificationSetting.application.dto.request; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/page/clab/api/global/common/slack/dto/response/NotificationSettingResponseDto.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/dto/response/NotificationSettingResponseDto.java similarity index 67% rename from src/main/java/page/clab/api/global/common/slack/dto/response/NotificationSettingResponseDto.java rename to src/main/java/page/clab/api/global/common/notificationSetting/application/dto/response/NotificationSettingResponseDto.java index ffbbd87b9..8eeaf2256 100644 --- a/src/main/java/page/clab/api/global/common/slack/dto/response/NotificationSettingResponseDto.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/dto/response/NotificationSettingResponseDto.java @@ -1,4 +1,4 @@ -package page.clab.api.global.common.slack.dto.response; +package page.clab.api.global.common.notificationSetting.application.dto.response; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/page/clab/api/global/common/slack/event/NotificationEvent.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationEvent.java similarity index 70% rename from src/main/java/page/clab/api/global/common/slack/event/NotificationEvent.java rename to src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationEvent.java index 59bfb5158..0d107d760 100644 --- a/src/main/java/page/clab/api/global/common/slack/event/NotificationEvent.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationEvent.java @@ -1,9 +1,9 @@ -package page.clab.api.global.common.slack.event; +package page.clab.api.global.common.notificationSetting.application.event; import jakarta.servlet.http.HttpServletRequest; import lombok.Getter; import org.springframework.context.ApplicationEvent; -import page.clab.api.global.common.slack.domain.AlertType; +import page.clab.api.global.common.notificationSetting.domain.AlertType; @Getter public class NotificationEvent extends ApplicationEvent { @@ -13,7 +13,8 @@ public class NotificationEvent extends ApplicationEvent { private final HttpServletRequest request; private final Object additionalData; - public NotificationEvent(Object source, String webhookUrl, AlertType alertType, HttpServletRequest request, Object additionalData) { + public NotificationEvent(Object source, String webhookUrl, AlertType alertType, HttpServletRequest request, + Object additionalData) { super(source); this.webhookUrl = webhookUrl; this.alertType = alertType; diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java new file mode 100644 index 000000000..51c764a21 --- /dev/null +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java @@ -0,0 +1,27 @@ +package page.clab.api.global.common.notificationSetting.application.event; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; +import page.clab.api.global.common.notificationSetting.application.port.out.NotificationSender; +import page.clab.api.global.common.notificationSetting.application.service.NotificationSettingService; +import page.clab.api.global.common.notificationSetting.domain.AlertType; +import page.clab.api.global.common.notificationSetting.domain.NotificationSetting; + +@Component +@RequiredArgsConstructor +public class NotificationListener { + + private final NotificationSettingService settingService; + private final NotificationSender notificationSender; + + @EventListener + public void handleNotificationEvent(NotificationEvent event) { + AlertType alertType = event.getAlertType(); + NotificationSetting setting = settingService.getOrCreateDefaultSetting(alertType); + + if (setting.isEnabled()) { + notificationSender.sendNotification(event); + } + } +} diff --git a/src/main/java/page/clab/api/global/common/slack/exception/AlertTypeNotFoundException.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/exception/AlertTypeNotFoundException.java similarity index 71% rename from src/main/java/page/clab/api/global/common/slack/exception/AlertTypeNotFoundException.java rename to src/main/java/page/clab/api/global/common/notificationSetting/application/exception/AlertTypeNotFoundException.java index c52c39796..94de58360 100644 --- a/src/main/java/page/clab/api/global/common/slack/exception/AlertTypeNotFoundException.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/exception/AlertTypeNotFoundException.java @@ -1,4 +1,4 @@ -package page.clab.api.global.common.slack.exception; +package page.clab.api.global.common.notificationSetting.application.exception; public class AlertTypeNotFoundException extends RuntimeException { diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/NotificationSender.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/NotificationSender.java new file mode 100644 index 000000000..c06f60811 --- /dev/null +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/NotificationSender.java @@ -0,0 +1,8 @@ +package page.clab.api.global.common.notificationSetting.application.port.out; + +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; + +public interface NotificationSender { + + void sendNotification(NotificationEvent event); +} diff --git a/src/main/java/page/clab/api/global/common/slack/application/NotificationSettingService.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/service/NotificationSettingService.java similarity index 75% rename from src/main/java/page/clab/api/global/common/slack/application/NotificationSettingService.java rename to src/main/java/page/clab/api/global/common/notificationSetting/application/service/NotificationSettingService.java index 4ce06b79b..98a69ecda 100644 --- a/src/main/java/page/clab/api/global/common/slack/application/NotificationSettingService.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/service/NotificationSettingService.java @@ -1,23 +1,22 @@ -package page.clab.api.global.common.slack.application; +package page.clab.api.global.common.notificationSetting.application.service; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import page.clab.api.global.common.slack.dao.NotificationSettingRepository; -import page.clab.api.global.common.slack.domain.AlertType; -import page.clab.api.global.common.slack.domain.AlertTypeResolver; -import page.clab.api.global.common.slack.domain.NotificationSetting; -import page.clab.api.global.common.slack.dto.mapper.SlackDtoMapper; -import page.clab.api.global.common.slack.dto.response.NotificationSettingResponseDto; - -import java.util.List; +import page.clab.api.global.common.notificationSetting.adapter.out.persistence.NotificationSettingRepository; +import page.clab.api.global.common.notificationSetting.application.dto.mapper.NotificationSettingDtoMapper; +import page.clab.api.global.common.notificationSetting.application.dto.response.NotificationSettingResponseDto; +import page.clab.api.global.common.notificationSetting.domain.AlertType; +import page.clab.api.global.common.notificationSetting.domain.AlertTypeResolver; +import page.clab.api.global.common.notificationSetting.domain.NotificationSetting; /** * {@code NotificationSettingService}는 알림 설정을 조회 및 업데이트하는 서비스입니다. * *

      이 서비스는 알림 유형에 따라 활성화 또는 비활성화할 수 있는 설정 기능을 제공하며, * 기본 알림 설정을 생성하거나 조회할 수 있습니다.

      - * + *

      * 주요 기능: *

        *
      • {@link #getNotificationSettings()} - 모든 알림 설정을 조회합니다.
      • @@ -31,7 +30,7 @@ public class NotificationSettingService { private final AlertTypeResolver alertTypeResolver; private final NotificationSettingRepository settingRepository; - private final SlackDtoMapper mapper; + private final NotificationSettingDtoMapper mapper; @Transactional(readOnly = true) public List getNotificationSettings() { diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/config/NotificationConfig.java b/src/main/java/page/clab/api/global/common/notificationSetting/config/NotificationConfig.java new file mode 100644 index 000000000..ddbfa22fc --- /dev/null +++ b/src/main/java/page/clab/api/global/common/notificationSetting/config/NotificationConfig.java @@ -0,0 +1,21 @@ +package page.clab.api.global.common.notificationSetting.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackNotificationSender; +import page.clab.api.global.common.notificationSetting.application.port.out.NotificationSender; + +@Configuration +public class NotificationConfig { + + private final SlackNotificationSender slackNotificationSender; + + public NotificationConfig(SlackNotificationSender slackNotificationSender) { + this.slackNotificationSender = slackNotificationSender; + } + + @Bean + public NotificationSender notificationSender() { + return slackNotificationSender; + } +} diff --git a/src/main/java/page/clab/api/global/common/slack/domain/AlertType.java b/src/main/java/page/clab/api/global/common/notificationSetting/domain/AlertType.java similarity index 58% rename from src/main/java/page/clab/api/global/common/slack/domain/AlertType.java rename to src/main/java/page/clab/api/global/common/notificationSetting/domain/AlertType.java index 99c52fd8f..34eac0469 100644 --- a/src/main/java/page/clab/api/global/common/slack/domain/AlertType.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/domain/AlertType.java @@ -1,4 +1,4 @@ -package page.clab.api.global.common.slack.domain; +package page.clab.api.global.common.notificationSetting.domain; public interface AlertType { diff --git a/src/main/java/page/clab/api/global/common/slack/domain/AlertTypeConverter.java b/src/main/java/page/clab/api/global/common/notificationSetting/domain/AlertTypeConverter.java similarity index 88% rename from src/main/java/page/clab/api/global/common/slack/domain/AlertTypeConverter.java rename to src/main/java/page/clab/api/global/common/notificationSetting/domain/AlertTypeConverter.java index 1755b529b..dfa397238 100644 --- a/src/main/java/page/clab/api/global/common/slack/domain/AlertTypeConverter.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/domain/AlertTypeConverter.java @@ -1,11 +1,10 @@ -package page.clab.api.global.common.slack.domain; +package page.clab.api.global.common.notificationSetting.domain; import jakarta.persistence.AttributeConverter; import jakarta.persistence.Converter; -import page.clab.api.global.common.slack.exception.AlertTypeNotFoundException; - import java.util.HashMap; import java.util.Map; +import page.clab.api.global.common.notificationSetting.application.exception.AlertTypeNotFoundException; @Converter(autoApply = true) public class AlertTypeConverter implements AttributeConverter { diff --git a/src/main/java/page/clab/api/global/common/slack/domain/AlertTypeResolver.java b/src/main/java/page/clab/api/global/common/notificationSetting/domain/AlertTypeResolver.java similarity index 77% rename from src/main/java/page/clab/api/global/common/slack/domain/AlertTypeResolver.java rename to src/main/java/page/clab/api/global/common/notificationSetting/domain/AlertTypeResolver.java index f2d580962..7fe3cc2dd 100644 --- a/src/main/java/page/clab/api/global/common/slack/domain/AlertTypeResolver.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/domain/AlertTypeResolver.java @@ -1,7 +1,7 @@ -package page.clab.api.global.common.slack.domain; +package page.clab.api.global.common.notificationSetting.domain; import org.springframework.stereotype.Service; -import page.clab.api.global.common.slack.exception.AlertTypeNotFoundException; +import page.clab.api.global.common.notificationSetting.application.exception.AlertTypeNotFoundException; @Service public class AlertTypeResolver { diff --git a/src/main/java/page/clab/api/global/common/slack/domain/ExecutivesAlertType.java b/src/main/java/page/clab/api/global/common/notificationSetting/domain/ExecutivesAlertType.java similarity index 89% rename from src/main/java/page/clab/api/global/common/slack/domain/ExecutivesAlertType.java rename to src/main/java/page/clab/api/global/common/notificationSetting/domain/ExecutivesAlertType.java index 703b2974f..49e4365c4 100644 --- a/src/main/java/page/clab/api/global/common/slack/domain/ExecutivesAlertType.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/domain/ExecutivesAlertType.java @@ -1,4 +1,4 @@ -package page.clab.api.global.common.slack.domain; +package page.clab.api.global.common.notificationSetting.domain; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/page/clab/api/global/common/slack/domain/GeneralAlertType.java b/src/main/java/page/clab/api/global/common/notificationSetting/domain/GeneralAlertType.java similarity index 86% rename from src/main/java/page/clab/api/global/common/slack/domain/GeneralAlertType.java rename to src/main/java/page/clab/api/global/common/notificationSetting/domain/GeneralAlertType.java index 4b564bde7..3b78b146d 100644 --- a/src/main/java/page/clab/api/global/common/slack/domain/GeneralAlertType.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/domain/GeneralAlertType.java @@ -1,4 +1,4 @@ -package page.clab.api.global.common.slack.domain; +package page.clab.api.global.common.notificationSetting.domain; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/page/clab/api/global/common/slack/domain/NotificationSetting.java b/src/main/java/page/clab/api/global/common/notificationSetting/domain/NotificationSetting.java similarity index 94% rename from src/main/java/page/clab/api/global/common/slack/domain/NotificationSetting.java rename to src/main/java/page/clab/api/global/common/notificationSetting/domain/NotificationSetting.java index c352008a2..d9bd8f47f 100644 --- a/src/main/java/page/clab/api/global/common/slack/domain/NotificationSetting.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/domain/NotificationSetting.java @@ -1,4 +1,4 @@ -package page.clab.api.global.common.slack.domain; +package page.clab.api.global.common.notificationSetting.domain; import jakarta.persistence.Convert; import jakarta.persistence.Entity; diff --git a/src/main/java/page/clab/api/global/common/slack/domain/SecurityAlertType.java b/src/main/java/page/clab/api/global/common/notificationSetting/domain/SecurityAlertType.java similarity index 95% rename from src/main/java/page/clab/api/global/common/slack/domain/SecurityAlertType.java rename to src/main/java/page/clab/api/global/common/notificationSetting/domain/SecurityAlertType.java index 4aa4d1b59..a8b1f6c7c 100644 --- a/src/main/java/page/clab/api/global/common/slack/domain/SecurityAlertType.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/domain/SecurityAlertType.java @@ -1,4 +1,4 @@ -package page.clab.api.global.common.slack.domain; +package page.clab.api.global.common.notificationSetting.domain; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/page/clab/api/global/common/slack/listener/NotificationListener.java b/src/main/java/page/clab/api/global/common/slack/listener/NotificationListener.java deleted file mode 100644 index e8463a03a..000000000 --- a/src/main/java/page/clab/api/global/common/slack/listener/NotificationListener.java +++ /dev/null @@ -1,28 +0,0 @@ -package page.clab.api.global.common.slack.listener; - -import lombok.RequiredArgsConstructor; -import org.springframework.context.event.EventListener; -import org.springframework.stereotype.Component; -import page.clab.api.global.common.slack.application.NotificationSettingService; -import page.clab.api.global.common.slack.application.SlackServiceHelper; -import page.clab.api.global.common.slack.domain.AlertType; -import page.clab.api.global.common.slack.domain.NotificationSetting; -import page.clab.api.global.common.slack.event.NotificationEvent; - -@Component -@RequiredArgsConstructor -public class NotificationListener { - - private final NotificationSettingService settingService; - private final SlackServiceHelper slackServiceHelper; - - @EventListener - public void handleNotificationEvent(NotificationEvent event) { - AlertType alertType = event.getAlertType(); - NotificationSetting setting = settingService.getOrCreateDefaultSetting(alertType); - - if (setting.isEnabled()) { - slackServiceHelper.sendSlackMessage(event.getWebhookUrl(), alertType, event.getRequest(), event.getAdditionalData()); - } - } -} diff --git a/src/main/java/page/clab/api/global/config/SecurityConfig.java b/src/main/java/page/clab/api/global/config/SecurityConfig.java index 0002f24be..5eee6a35d 100644 --- a/src/main/java/page/clab/api/global/config/SecurityConfig.java +++ b/src/main/java/page/clab/api/global/config/SecurityConfig.java @@ -2,6 +2,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; @@ -34,14 +35,12 @@ import page.clab.api.global.auth.jwt.JwtTokenProvider; import page.clab.api.global.auth.util.IpWhitelistValidator; import page.clab.api.global.common.file.application.FileService; -import page.clab.api.global.common.slack.application.SlackService; +import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; import page.clab.api.global.filter.IPinfoSpringFilter; import page.clab.api.global.util.ApiLogger; import page.clab.api.global.util.HttpReqResUtil; import page.clab.api.global.util.ResponseUtil; -import java.io.IOException; - @Configuration @EnableWebSecurity @EnableMethodSecurity @@ -92,15 +91,18 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { UsernamePasswordAuthenticationFilter.class ) .addFilterBefore( - new InvalidEndpointAccessFilter(slackService, fileURL, externalRegisterBlacklistIpUseCase, externalRetrieveBlacklistIpUseCase), + new InvalidEndpointAccessFilter(slackService, fileURL, externalRegisterBlacklistIpUseCase, + externalRetrieveBlacklistIpUseCase), UsernamePasswordAuthenticationFilter.class ) .addFilterBefore( - new CustomBasicAuthenticationFilter(authenticationManager, ipWhitelistValidator, slackService, externalCheckIpBlockedUseCase, externalRetrieveBlacklistIpUseCase), + new CustomBasicAuthenticationFilter(authenticationManager, ipWhitelistValidator, slackService, + externalCheckIpBlockedUseCase, externalRetrieveBlacklistIpUseCase), UsernamePasswordAuthenticationFilter.class ) .addFilterBefore( - new JwtAuthenticationFilter(slackService, jwtTokenProvider, externalManageRedisTokenUseCase, externalCheckIpBlockedUseCase, externalRetrieveBlacklistIpUseCase), + new JwtAuthenticationFilter(slackService, jwtTokenProvider, externalManageRedisTokenUseCase, + externalCheckIpBlockedUseCase, externalRetrieveBlacklistIpUseCase), UsernamePasswordAuthenticationFilter.class ) // .addFilterBefore( @@ -120,11 +122,13 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers(SecurityConstants.PERMIT_ALL).permitAll() .requestMatchers(HttpMethod.GET, SecurityConstants.PERMIT_ALL_API_ENDPOINTS_GET).permitAll() .requestMatchers(HttpMethod.POST, SecurityConstants.PERMIT_ALL_API_ENDPOINTS_POST).permitAll() - .requestMatchers(whitelistPatternsProperties.getWhitelistPatterns()).hasRole(whitelistAccountProperties.getRole()) + .requestMatchers(whitelistPatternsProperties.getWhitelistPatterns()) + .hasRole(whitelistAccountProperties.getRole()) .anyRequest().authenticated(); } - private void handleException(HttpServletRequest request, HttpServletResponse response, Exception exception) throws IOException { + private void handleException(HttpServletRequest request, HttpServletResponse response, Exception exception) + throws IOException { String clientIpAddress = HttpReqResUtil.getClientIpAddressIfServletRequestExist(); String message; int statusCode; diff --git a/src/main/java/page/clab/api/global/handler/GlobalExceptionHandler.java b/src/main/java/page/clab/api/global/handler/GlobalExceptionHandler.java index ac27a3774..7c3ee1d64 100644 --- a/src/main/java/page/clab/api/global/handler/GlobalExceptionHandler.java +++ b/src/main/java/page/clab/api/global/handler/GlobalExceptionHandler.java @@ -7,6 +7,10 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.ConstraintViolationException; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.NoSuchElementException; +import java.util.concurrent.CompletionException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.hibernate.query.sqm.UnknownPathException; @@ -75,7 +79,7 @@ import page.clab.api.global.common.file.exception.FileUploadFailException; import page.clab.api.global.common.file.exception.InvalidFileAttributeException; import page.clab.api.global.common.file.exception.InvalidPathVariableException; -import page.clab.api.global.common.slack.application.SlackService; +import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; import page.clab.api.global.exception.CustomOptimisticLockingFailureException; import page.clab.api.global.exception.DecryptionException; import page.clab.api.global.exception.EncryptionException; @@ -87,11 +91,6 @@ import page.clab.api.global.exception.PermissionDeniedException; import page.clab.api.global.exception.SortingArgumentException; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.NoSuchElementException; -import java.util.concurrent.CompletionException; - @RestControllerAdvice(basePackages = "page.clab.api") @RequiredArgsConstructor @Slf4j From c7f39d2c1d4f31007c78d94738834ec1f0a98b07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Wed, 13 Nov 2024 23:30:49 +0900 Subject: [PATCH 02/24] =?UTF-8?q?refactor(Notification):=20=ED=97=A5?= =?UTF-8?q?=EC=82=AC=EA=B3=A0=EB=82=A0=20=EC=95=84=ED=82=A4=ED=85=8D?= =?UTF-8?q?=EC=B2=98=EC=9D=98=20=EA=B5=AC=EC=A1=B0=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EA=B2=8C=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A5=BC=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=ED=95=98=EA=B3=A0=20=EC=B6=94=EC=83=81=ED=99=94?= =?UTF-8?q?=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...NotificationSettingRetrieveController.java | 30 ++++++++++++++++ ... NotificationSettingUpdateController.java} | 19 +++------- ...NotificationSettingPersistenceAdapter.java | 34 ++++++++++++++++++ .../event/NotificationListener.java | 6 ++-- .../RetrieveNotificationSettingUseCase.java | 9 +++++ .../in/UpdateNotificationSettingUseCase.java | 11 ++++++ .../out/RetrieveNotificationSettingPort.java | 13 +++++++ .../out/UpdateNotificationSettingPort.java | 8 +++++ .../RetrieveNotificationSettingService.java | 36 +++++++++++++++++++ ... => UpdateNotificationSettingService.java} | 34 +++++++----------- 10 files changed, 161 insertions(+), 39 deletions(-) create mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingRetrieveController.java rename src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/{NotificationSettingController.java => NotificationSettingUpdateController.java} (58%) create mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/persistence/NotificationSettingPersistenceAdapter.java create mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/application/port/in/RetrieveNotificationSettingUseCase.java create mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/application/port/in/UpdateNotificationSettingUseCase.java create mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/RetrieveNotificationSettingPort.java create mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/UpdateNotificationSettingPort.java create mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/application/service/RetrieveNotificationSettingService.java rename src/main/java/page/clab/api/global/common/notificationSetting/application/service/{NotificationSettingService.java => UpdateNotificationSettingService.java} (59%) diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingRetrieveController.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingRetrieveController.java new file mode 100644 index 000000000..673db1696 --- /dev/null +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingRetrieveController.java @@ -0,0 +1,30 @@ +package page.clab.api.global.common.notificationSetting.adapter.in.web; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import page.clab.api.global.common.dto.ApiResponse; +import page.clab.api.global.common.notificationSetting.application.dto.response.NotificationSettingResponseDto; +import page.clab.api.global.common.notificationSetting.application.port.in.RetrieveNotificationSettingUseCase; + +@RestController +@RequestMapping("/api/v1/notification-settings") +@RequiredArgsConstructor +@Tag(name = "Notification Setting", description = "알림 설정") +public class NotificationSettingRetrieveController { + + private final RetrieveNotificationSettingUseCase retrieveNotificationSettingUseCase; + + @Operation(summary = "[S] 슬랙 알림 조회", description = "ROLE_SUPER 이상의 권한이 필요함") + @PreAuthorize("hasRole('SUPER')") + @GetMapping("") + public ApiResponse> getNotificationSettings() { + List notificationSettings = retrieveNotificationSettingUseCase.retrieveNotificationSettings(); + return ApiResponse.success(notificationSettings); + } +} diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingController.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingUpdateController.java similarity index 58% rename from src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingController.java rename to src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingUpdateController.java index b20aec49c..bbe0b3342 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingController.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingUpdateController.java @@ -3,34 +3,23 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; -import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import page.clab.api.global.common.dto.ApiResponse; import page.clab.api.global.common.notificationSetting.application.dto.request.NotificationSettingUpdateRequestDto; -import page.clab.api.global.common.notificationSetting.application.dto.response.NotificationSettingResponseDto; -import page.clab.api.global.common.notificationSetting.application.service.NotificationSettingService; +import page.clab.api.global.common.notificationSetting.application.port.in.UpdateNotificationSettingUseCase; @RestController @RequestMapping("/api/v1/notification-settings") @RequiredArgsConstructor @Tag(name = "Notification Setting", description = "알림 설정") -public class NotificationSettingController { +public class NotificationSettingUpdateController { - private final NotificationSettingService notificationSettingService; - - @Operation(summary = "[S] 슬랙 알림 조회", description = "ROLE_SUPER 이상의 권한이 필요함") - @PreAuthorize("hasRole('SUPER')") - @GetMapping("") - public ApiResponse> getNotificationSettings() { - List notificationSettings = notificationSettingService.getNotificationSettings(); - return ApiResponse.success(notificationSettings); - } + private final UpdateNotificationSettingUseCase updateNotificationSettingUseCase; @Operation(summary = "[S] 슬랙 알림 설정 변경", description = "ROLE_SUPER 이상의 권한이 필요함") @PreAuthorize("hasRole('SUPER')") @@ -38,7 +27,7 @@ public ApiResponse> getNotificationSettings public ApiResponse updateNotificationSetting( @Valid @RequestBody NotificationSettingUpdateRequestDto requestDto ) { - notificationSettingService.updateNotificationSetting(requestDto.getAlertType(), requestDto.isEnabled()); + updateNotificationSettingUseCase.updateNotificationSetting(requestDto.getAlertType(), requestDto.isEnabled()); return ApiResponse.success(); } } diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/persistence/NotificationSettingPersistenceAdapter.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/persistence/NotificationSettingPersistenceAdapter.java new file mode 100644 index 000000000..4bae839c3 --- /dev/null +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/persistence/NotificationSettingPersistenceAdapter.java @@ -0,0 +1,34 @@ +package page.clab.api.global.common.notificationSetting.adapter.out.persistence; + +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import page.clab.api.global.common.notificationSetting.application.port.out.RetrieveNotificationSettingPort; +import page.clab.api.global.common.notificationSetting.application.port.out.UpdateNotificationSettingPort; +import page.clab.api.global.common.notificationSetting.domain.AlertType; +import page.clab.api.global.common.notificationSetting.domain.NotificationSetting; + +@Component +@RequiredArgsConstructor +public class NotificationSettingPersistenceAdapter implements + RetrieveNotificationSettingPort, + UpdateNotificationSettingPort { + + private final NotificationSettingRepository repository; + + @Override + public List findAll() { + return repository.findAll(); + } + + @Override + public Optional findByAlertType(AlertType alertType) { + return repository.findByAlertType(alertType); + } + + @Override + public NotificationSetting save(NotificationSetting setting) { + return repository.save(setting); + } +} diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java index 51c764a21..1f7be114b 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java @@ -3,8 +3,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; +import page.clab.api.global.common.notificationSetting.application.port.in.UpdateNotificationSettingUseCase; import page.clab.api.global.common.notificationSetting.application.port.out.NotificationSender; -import page.clab.api.global.common.notificationSetting.application.service.NotificationSettingService; import page.clab.api.global.common.notificationSetting.domain.AlertType; import page.clab.api.global.common.notificationSetting.domain.NotificationSetting; @@ -12,13 +12,13 @@ @RequiredArgsConstructor public class NotificationListener { - private final NotificationSettingService settingService; + private final UpdateNotificationSettingUseCase updateNotificationSettingUseCase; private final NotificationSender notificationSender; @EventListener public void handleNotificationEvent(NotificationEvent event) { AlertType alertType = event.getAlertType(); - NotificationSetting setting = settingService.getOrCreateDefaultSetting(alertType); + NotificationSetting setting = updateNotificationSettingUseCase.getOrCreateDefaultSetting(alertType); if (setting.isEnabled()) { notificationSender.sendNotification(event); diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/port/in/RetrieveNotificationSettingUseCase.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/port/in/RetrieveNotificationSettingUseCase.java new file mode 100644 index 000000000..33bfde964 --- /dev/null +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/port/in/RetrieveNotificationSettingUseCase.java @@ -0,0 +1,9 @@ +package page.clab.api.global.common.notificationSetting.application.port.in; + +import java.util.List; +import page.clab.api.global.common.notificationSetting.application.dto.response.NotificationSettingResponseDto; + +public interface RetrieveNotificationSettingUseCase { + + List retrieveNotificationSettings(); +} diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/port/in/UpdateNotificationSettingUseCase.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/port/in/UpdateNotificationSettingUseCase.java new file mode 100644 index 000000000..3825f9246 --- /dev/null +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/port/in/UpdateNotificationSettingUseCase.java @@ -0,0 +1,11 @@ +package page.clab.api.global.common.notificationSetting.application.port.in; + +import page.clab.api.global.common.notificationSetting.domain.AlertType; +import page.clab.api.global.common.notificationSetting.domain.NotificationSetting; + +public interface UpdateNotificationSettingUseCase { + + void updateNotificationSetting(String alertTypeName, boolean enabled); + + NotificationSetting getOrCreateDefaultSetting(AlertType alertType); +} diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/RetrieveNotificationSettingPort.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/RetrieveNotificationSettingPort.java new file mode 100644 index 000000000..9b81d12b2 --- /dev/null +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/RetrieveNotificationSettingPort.java @@ -0,0 +1,13 @@ +package page.clab.api.global.common.notificationSetting.application.port.out; + +import java.util.List; +import java.util.Optional; +import page.clab.api.global.common.notificationSetting.domain.AlertType; +import page.clab.api.global.common.notificationSetting.domain.NotificationSetting; + +public interface RetrieveNotificationSettingPort { + + List findAll(); + + Optional findByAlertType(AlertType alertType); +} diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/UpdateNotificationSettingPort.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/UpdateNotificationSettingPort.java new file mode 100644 index 000000000..144205601 --- /dev/null +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/UpdateNotificationSettingPort.java @@ -0,0 +1,8 @@ +package page.clab.api.global.common.notificationSetting.application.port.out; + +import page.clab.api.global.common.notificationSetting.domain.NotificationSetting; + +public interface UpdateNotificationSettingPort { + + NotificationSetting save(NotificationSetting setting); +} diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/service/RetrieveNotificationSettingService.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/service/RetrieveNotificationSettingService.java new file mode 100644 index 000000000..3d4d6bc32 --- /dev/null +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/service/RetrieveNotificationSettingService.java @@ -0,0 +1,36 @@ +package page.clab.api.global.common.notificationSetting.application.service; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import page.clab.api.global.common.notificationSetting.application.dto.mapper.NotificationSettingDtoMapper; +import page.clab.api.global.common.notificationSetting.application.dto.response.NotificationSettingResponseDto; +import page.clab.api.global.common.notificationSetting.application.port.in.RetrieveNotificationSettingUseCase; +import page.clab.api.global.common.notificationSetting.application.port.out.RetrieveNotificationSettingPort; + +/** + * {@code RetrieveNotificationSettingService}는 알림 설정을 조회하는 서비스입니다. + * + *

        이 서비스는 알림 설정의 전체 목록을 조회할 수 있는 기능을 제공합니다.

        + *

        + * 주요 기능: + *

          + *
        • {@link #retrieveNotificationSettings()} - 모든 알림 설정을 조회합니다.
        • + *
        + */ +@Service +@RequiredArgsConstructor +public class RetrieveNotificationSettingService implements RetrieveNotificationSettingUseCase { + + private final RetrieveNotificationSettingPort retrieveNotificationSettingPort; + private final NotificationSettingDtoMapper mapper; + + @Transactional(readOnly = true) + @Override + public List retrieveNotificationSettings() { + return retrieveNotificationSettingPort.findAll().stream() + .map(mapper::toDto) + .toList(); + } +} diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/service/NotificationSettingService.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/service/UpdateNotificationSettingService.java similarity index 59% rename from src/main/java/page/clab/api/global/common/notificationSetting/application/service/NotificationSettingService.java rename to src/main/java/page/clab/api/global/common/notificationSetting/application/service/UpdateNotificationSettingService.java index 98a69ecda..c57706d75 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/application/service/NotificationSettingService.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/service/UpdateNotificationSettingService.java @@ -1,60 +1,52 @@ package page.clab.api.global.common.notificationSetting.application.service; -import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import page.clab.api.global.common.notificationSetting.adapter.out.persistence.NotificationSettingRepository; -import page.clab.api.global.common.notificationSetting.application.dto.mapper.NotificationSettingDtoMapper; -import page.clab.api.global.common.notificationSetting.application.dto.response.NotificationSettingResponseDto; +import page.clab.api.global.common.notificationSetting.application.port.in.UpdateNotificationSettingUseCase; +import page.clab.api.global.common.notificationSetting.application.port.out.RetrieveNotificationSettingPort; +import page.clab.api.global.common.notificationSetting.application.port.out.UpdateNotificationSettingPort; import page.clab.api.global.common.notificationSetting.domain.AlertType; import page.clab.api.global.common.notificationSetting.domain.AlertTypeResolver; import page.clab.api.global.common.notificationSetting.domain.NotificationSetting; /** - * {@code NotificationSettingService}는 알림 설정을 조회 및 업데이트하는 서비스입니다. + * {@code UpdateNotificationSettingService}는 알림 설정을 업데이트하는 서비스입니다. * - *

        이 서비스는 알림 유형에 따라 활성화 또는 비활성화할 수 있는 설정 기능을 제공하며, - * 기본 알림 설정을 생성하거나 조회할 수 있습니다.

        + *

        이 서비스는 주어진 알림 유형에 따라 활성화 또는 비활성화할 수 있는 설정을 업데이트할 수 있습니다. + * 또한, 기본 알림 설정이 존재하지 않으면 생성하여 제공합니다.

        *

        * 주요 기능: *

          - *
        • {@link #getNotificationSettings()} - 모든 알림 설정을 조회합니다.
        • *
        • {@link #updateNotificationSetting(String, boolean)} - 주어진 알림 유형에 대해 알림 설정을 업데이트합니다.
        • *
        • {@link #getOrCreateDefaultSetting(AlertType)} - 주어진 알림 유형에 대한 기본 알림 설정을 조회하거나, 존재하지 않으면 생성합니다.
        • *
        */ @Service @RequiredArgsConstructor -public class NotificationSettingService { +public class UpdateNotificationSettingService implements UpdateNotificationSettingUseCase { private final AlertTypeResolver alertTypeResolver; - private final NotificationSettingRepository settingRepository; - private final NotificationSettingDtoMapper mapper; - - @Transactional(readOnly = true) - public List getNotificationSettings() { - return settingRepository.findAll().stream() - .map(mapper::toDto) - .toList(); - } + private final RetrieveNotificationSettingPort retrieveNotificationSettingPort; + private final UpdateNotificationSettingPort updateNotificationSettingPort; @Transactional + @Override public void updateNotificationSetting(String alertTypeName, boolean enabled) { AlertType alertType = alertTypeResolver.resolve(alertTypeName); NotificationSetting setting = getOrCreateDefaultSetting(alertType); setting.updateEnabled(enabled); - settingRepository.save(setting); + updateNotificationSettingPort.save(setting); } @Transactional public NotificationSetting getOrCreateDefaultSetting(AlertType alertType) { - return settingRepository.findByAlertType(alertType) + return retrieveNotificationSettingPort.findByAlertType(alertType) .orElseGet(() -> createAndSaveDefaultSetting(alertType)); } private NotificationSetting createAndSaveDefaultSetting(AlertType alertType) { NotificationSetting defaultSetting = NotificationSetting.createDefault(alertType); - return settingRepository.save(defaultSetting); + return updateNotificationSettingPort.save(defaultSetting); } } From f4d5a1966180d17bb4257a1ae35e959629eb1871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Thu, 14 Nov 2024 00:33:57 +0900 Subject: [PATCH 03/24] =?UTF-8?q?feat(Dependency):=20=EB=94=94=EC=8A=A4?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9B=B9=ED=9B=85=20=EC=97=B0=EA=B2=B0?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20OpenFeign=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 13 ++++++++++++- src/main/java/page/clab/api/ApiApplication.java | 2 ++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ce60c823a..626c601d6 100644 --- a/build.gradle +++ b/build.gradle @@ -23,17 +23,28 @@ java { } } +ext { + springCloudVersion = '2023.0.3' +} + repositories { mavenCentral() maven { url 'https://jitpack.io' } } +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" + } +} + dependencies { // Spring Project implementation 'org.springframework.boot:spring-boot-starter-web' // 웹 MVC implementation 'org.springframework.boot:spring-boot-starter-validation' // 유효성 검사 implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' // 템플릿 엔진 implementation 'org.springframework.boot:spring-boot-starter-webflux' // WebFlux + implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' // OpenFeign // Security implementation 'org.springframework.boot:spring-boot-starter-security' // Spring Security @@ -113,7 +124,7 @@ tasks.named('test') { def querydslDir = layout.buildDirectory.dir("generated/querydsl").get().asFile sourceSets { - main.java.srcDirs += [ querydslDir ] + main.java.srcDirs += [querydslDir] } tasks.withType(JavaCompile).configureEach { diff --git a/src/main/java/page/clab/api/ApiApplication.java b/src/main/java/page/clab/api/ApiApplication.java index 9e890f824..32356719d 100644 --- a/src/main/java/page/clab/api/ApiApplication.java +++ b/src/main/java/page/clab/api/ApiApplication.java @@ -2,7 +2,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; +@EnableFeignClients @SpringBootApplication public class ApiApplication { From 448a3ee24ccfa643471dc9ef1e11809ba967dd4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Thu, 14 Nov 2024 00:37:05 +0900 Subject: [PATCH 04/24] =?UTF-8?q?feat(Notification):=20=EB=94=94=EC=8A=A4?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=ED=98=95?= =?UTF-8?q?=EC=8B=9D=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/DiscordMessage.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/domain/DiscordMessage.java diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/domain/DiscordMessage.java b/src/main/java/page/clab/api/global/common/notificationSetting/domain/DiscordMessage.java new file mode 100644 index 000000000..36059bcf6 --- /dev/null +++ b/src/main/java/page/clab/api/global/common/notificationSetting/domain/DiscordMessage.java @@ -0,0 +1,21 @@ +package page.clab.api.global.common.notificationSetting.domain; + +import java.util.List; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class DiscordMessage { + + private String content; + private List embeds; + + @Getter + @Builder + public static class Embed { + + private String title; + private String description; + } +} From 0e1ec8adedd65a3ea0dc8177a28a82d7666f114e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Thu, 14 Nov 2024 00:37:45 +0900 Subject: [PATCH 05/24] =?UTF-8?q?refactor(Notification):=20Slack=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=ED=8C=A8=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/application/service/BoardRegisterService.java | 2 +- .../application/service/BookLoanRequestService.java | 2 +- .../application/service/MembershipFeeRegisterService.java | 2 +- .../notificationSetting/adapter/out/slack/SlackService.java | 6 +++--- .../adapter/out/slack/SlackServiceHelper.java | 6 +++--- .../domain/SlackBoardInfo.java | 2 +- .../domain/SlackBookLoanRecordInfo.java | 2 +- .../domain/SlackMembershipFeeInfo.java | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) rename src/main/java/page/clab/api/global/common/{slack => notificationSetting}/domain/SlackBoardInfo.java (92%) rename src/main/java/page/clab/api/global/common/{slack => notificationSetting}/domain/SlackBookLoanRecordInfo.java (93%) rename src/main/java/page/clab/api/global/common/{slack => notificationSetting}/domain/SlackMembershipFeeInfo.java (93%) diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java b/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java index 0936df52e..6c0bc00b0 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java @@ -15,7 +15,7 @@ import page.clab.api.global.common.file.application.UploadedFileService; import page.clab.api.global.common.file.domain.UploadedFile; import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; -import page.clab.api.global.common.slack.domain.SlackBoardInfo; +import page.clab.api.global.common.notificationSetting.domain.SlackBoardInfo; import page.clab.api.global.exception.PermissionDeniedException; @Service diff --git a/src/main/java/page/clab/api/domain/library/bookLoanRecord/application/service/BookLoanRequestService.java b/src/main/java/page/clab/api/domain/library/bookLoanRecord/application/service/BookLoanRequestService.java index 7345ac8da..f5c2a01b4 100644 --- a/src/main/java/page/clab/api/domain/library/bookLoanRecord/application/service/BookLoanRequestService.java +++ b/src/main/java/page/clab/api/domain/library/bookLoanRecord/application/service/BookLoanRequestService.java @@ -18,7 +18,7 @@ import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; import page.clab.api.external.memberManagement.notification.application.port.ExternalSendNotificationUseCase; import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; -import page.clab.api.global.common.slack.domain.SlackBookLoanRecordInfo; +import page.clab.api.global.common.notificationSetting.domain.SlackBookLoanRecordInfo; import page.clab.api.global.exception.CustomOptimisticLockingFailureException; @Service diff --git a/src/main/java/page/clab/api/domain/members/membershipFee/application/service/MembershipFeeRegisterService.java b/src/main/java/page/clab/api/domain/members/membershipFee/application/service/MembershipFeeRegisterService.java index 62f3723ff..f3e58d218 100644 --- a/src/main/java/page/clab/api/domain/members/membershipFee/application/service/MembershipFeeRegisterService.java +++ b/src/main/java/page/clab/api/domain/members/membershipFee/application/service/MembershipFeeRegisterService.java @@ -12,7 +12,7 @@ import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; import page.clab.api.external.memberManagement.notification.application.port.ExternalSendNotificationUseCase; import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; -import page.clab.api.global.common.slack.domain.SlackMembershipFeeInfo; +import page.clab.api.global.common.notificationSetting.domain.SlackMembershipFeeInfo; @Service @RequiredArgsConstructor diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackService.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackService.java index 8168a5a6c..d28857897 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackService.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackService.java @@ -11,9 +11,9 @@ import page.clab.api.global.common.notificationSetting.domain.ExecutivesAlertType; import page.clab.api.global.common.notificationSetting.domain.GeneralAlertType; import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; -import page.clab.api.global.common.slack.domain.SlackBoardInfo; -import page.clab.api.global.common.slack.domain.SlackBookLoanRecordInfo; -import page.clab.api.global.common.slack.domain.SlackMembershipFeeInfo; +import page.clab.api.global.common.notificationSetting.domain.SlackBoardInfo; +import page.clab.api.global.common.notificationSetting.domain.SlackBookLoanRecordInfo; +import page.clab.api.global.common.notificationSetting.domain.SlackMembershipFeeInfo; import page.clab.api.global.config.SlackConfig; /** diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackServiceHelper.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackServiceHelper.java index a2f288bd1..b46aabbbc 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackServiceHelper.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackServiceHelper.java @@ -39,9 +39,9 @@ import page.clab.api.global.common.notificationSetting.domain.ExecutivesAlertType; import page.clab.api.global.common.notificationSetting.domain.GeneralAlertType; import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; -import page.clab.api.global.common.slack.domain.SlackBoardInfo; -import page.clab.api.global.common.slack.domain.SlackBookLoanRecordInfo; -import page.clab.api.global.common.slack.domain.SlackMembershipFeeInfo; +import page.clab.api.global.common.notificationSetting.domain.SlackBoardInfo; +import page.clab.api.global.common.notificationSetting.domain.SlackBookLoanRecordInfo; +import page.clab.api.global.common.notificationSetting.domain.SlackMembershipFeeInfo; import page.clab.api.global.config.SlackConfig; import page.clab.api.global.util.HttpReqResUtil; diff --git a/src/main/java/page/clab/api/global/common/slack/domain/SlackBoardInfo.java b/src/main/java/page/clab/api/global/common/notificationSetting/domain/SlackBoardInfo.java similarity index 92% rename from src/main/java/page/clab/api/global/common/slack/domain/SlackBoardInfo.java rename to src/main/java/page/clab/api/global/common/notificationSetting/domain/SlackBoardInfo.java index a12be3cb8..63c0be9d6 100644 --- a/src/main/java/page/clab/api/global/common/slack/domain/SlackBoardInfo.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/domain/SlackBoardInfo.java @@ -1,4 +1,4 @@ -package page.clab.api.global.common.slack.domain; +package page.clab.api.global.common.notificationSetting.domain; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/page/clab/api/global/common/slack/domain/SlackBookLoanRecordInfo.java b/src/main/java/page/clab/api/global/common/notificationSetting/domain/SlackBookLoanRecordInfo.java similarity index 93% rename from src/main/java/page/clab/api/global/common/slack/domain/SlackBookLoanRecordInfo.java rename to src/main/java/page/clab/api/global/common/notificationSetting/domain/SlackBookLoanRecordInfo.java index 1f961750f..fcb02d2ce 100644 --- a/src/main/java/page/clab/api/global/common/slack/domain/SlackBookLoanRecordInfo.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/domain/SlackBookLoanRecordInfo.java @@ -1,4 +1,4 @@ -package page.clab.api.global.common.slack.domain; +package page.clab.api.global.common.notificationSetting.domain; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/page/clab/api/global/common/slack/domain/SlackMembershipFeeInfo.java b/src/main/java/page/clab/api/global/common/notificationSetting/domain/SlackMembershipFeeInfo.java similarity index 93% rename from src/main/java/page/clab/api/global/common/slack/domain/SlackMembershipFeeInfo.java rename to src/main/java/page/clab/api/global/common/notificationSetting/domain/SlackMembershipFeeInfo.java index 02461c71d..55518ec0e 100644 --- a/src/main/java/page/clab/api/global/common/slack/domain/SlackMembershipFeeInfo.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/domain/SlackMembershipFeeInfo.java @@ -1,4 +1,4 @@ -package page.clab.api.global.common.slack.domain; +package page.clab.api.global.common.notificationSetting.domain; import lombok.Builder; import lombok.Getter; From 54d73a7f5575b5c4179d57ac135a781753bdd029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Thu, 14 Nov 2024 02:37:40 +0900 Subject: [PATCH 06/24] =?UTF-8?q?refactor(Notification):=20=EB=B9=84?= =?UTF-8?q?=EC=A6=88=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=B0=9C=ED=96=89=EC=8B=9C=ED=82=A8=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=EB=A5=BC=20=EA=B5=AC=EB=8F=85=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=ED=94=8C=EB=9E=AB=ED=8F=BC=EC=97=90=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=EC=9D=84=20=EB=B3=B4=EB=82=B4=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD,=20=EC=95=8C=EB=A6=BC=EB=B3=84=EB=A1=9C=20?= =?UTF-8?q?=ED=94=8C=EB=9E=AB=ED=8F=BC=EC=9D=84=20=EC=A7=80=EC=A0=95?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=EC=A0=84=EC=86=A1=ED=95=A0=20=EC=88=98=20?= =?UTF-8?q?=EC=9E=88=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/service/MemberBanService.java | 10 +- .../service/MemberUnbanService.java | 10 +- .../service/BlacklistIpRegisterService.java | 13 ++- .../service/BlacklistIpRemoveService.java | 12 ++- .../service/BlacklistIpResetService.java | 12 ++- .../TwoFactorAuthenticationService.java | 9 +- .../AbnormalAccessIpRemoveService.java | 10 +- .../AbnormalAccessIpsClearService.java | 12 ++- .../service/BoardRegisterService.java | 11 ++- .../service/ApplicationApplyService.java | 11 ++- .../service/BookLoanRequestService.java | 9 +- .../service/MemberRoleManagementService.java | 15 ++- .../service/MembershipFeeRegisterService.java | 9 +- .../ExternalAccountLockManagementService.java | 10 +- ...xternalIpAccessMonitorRegisterService.java | 11 ++- .../CustomBasicAuthenticationFilter.java | 31 +++--- .../filter/InvalidEndpointAccessFilter.java | 12 ++- .../auth/filter/JwtAuthenticationFilter.java | 10 +- .../out/slack/SlackNotificationSender.java | 10 +- .../adapter/out/slack/SlackService.java | 94 ------------------- .../adapter/out/slack/SlackServiceHelper.java | 21 ++--- .../event/ApplicationStartupListener.java | 23 +++++ .../application/event/NotificationEvent.java | 5 +- .../event/NotificationListener.java | 57 ++++++++++- .../port/out/NotificationSender.java | 4 +- .../config/NotificationConfig.java | 12 +-- .../config/NotificationConfigProperties.java | 41 ++++++++ .../domain/AlertCategory.java | 8 ++ .../notificationSetting/domain/AlertType.java | 2 + .../domain/ExecutivesAlertType.java | 9 +- .../domain/GeneralAlertType.java | 7 +- .../domain/PlatformType.java | 14 +++ .../domain/SecurityAlertType.java | 27 +++--- .../api/global/config/SecurityConfig.java | 14 +-- .../clab/api/global/config/SlackConfig.java | 26 ----- .../handler/GlobalExceptionHandler.java | 9 +- 36 files changed, 339 insertions(+), 261 deletions(-) delete mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackService.java create mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/application/event/ApplicationStartupListener.java create mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/config/NotificationConfigProperties.java create mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/domain/AlertCategory.java create mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/domain/PlatformType.java delete mode 100644 src/main/java/page/clab/api/global/config/SlackConfig.java diff --git a/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberBanService.java b/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberBanService.java index 10cb46af8..ce2cac1c2 100644 --- a/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberBanService.java +++ b/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberBanService.java @@ -2,6 +2,7 @@ import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.auth.accountLockInfo.application.port.in.BanMemberUseCase; @@ -11,7 +12,7 @@ import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberBasicInfoDto; import page.clab.api.external.auth.redisToken.application.port.ExternalManageRedisTokenUseCase; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; -import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; @Service @@ -22,7 +23,7 @@ public class MemberBanService implements BanMemberUseCase { private final RegisterAccountLockInfoPort registerAccountLockInfoPort; private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; private final ExternalManageRedisTokenUseCase externalManageRedisTokenUseCase; - private final SlackService slackService; + private final ApplicationEventPublisher eventPublisher; /** * 멤버를 영구적으로 차단합니다. @@ -58,7 +59,8 @@ private AccountLockInfo createAccountLockInfo(String memberId) { private void sendSlackBanNotification(HttpServletRequest request, String memberId) { String memberName = externalRetrieveMemberUseCase.getMemberBasicInfoById(memberId).getMemberName(); - slackService.sendSecurityAlertNotification(request, SecurityAlertType.MEMBER_BANNED, - "ID: " + memberId + ", Name: " + memberName); + String additionalMessage = "ID: " + memberId + ", Name: " + memberName; + eventPublisher.publishEvent( + new NotificationEvent(this, SecurityAlertType.MEMBER_BANNED, request, additionalMessage)); } } diff --git a/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberUnbanService.java b/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberUnbanService.java index bed2bc2ae..a205dd1a6 100644 --- a/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberUnbanService.java +++ b/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberUnbanService.java @@ -2,6 +2,7 @@ import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.auth.accountLockInfo.application.port.in.UnbanMemberUseCase; @@ -10,7 +11,7 @@ import page.clab.api.domain.auth.accountLockInfo.domain.AccountLockInfo; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberBasicInfoDto; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; -import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; @Service @@ -20,7 +21,7 @@ public class MemberUnbanService implements UnbanMemberUseCase { private final RetrieveAccountLockInfoPort retrieveAccountLockInfoPort; private final RegisterAccountLockInfoPort registerAccountLockInfoPort; private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; - private final SlackService slackService; + private final ApplicationEventPublisher eventPublisher; /** * 차단된 멤버를 해제합니다. @@ -55,7 +56,8 @@ private AccountLockInfo createAccountLockInfo(String memberId) { private void sendSlackUnbanNotification(HttpServletRequest request, String memberId) { String memberName = externalRetrieveMemberUseCase.getMemberBasicInfoById(memberId).getMemberName(); - slackService.sendSecurityAlertNotification(request, SecurityAlertType.MEMBER_UNBANNED, - "ID: " + memberId + ", Name: " + memberName); + String additionalMessage = "ID: " + memberId + ", Name: " + memberName; + eventPublisher.publishEvent( + new NotificationEvent(this, SecurityAlertType.MEMBER_UNBANNED, request, additionalMessage)); } } diff --git a/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRegisterService.java b/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRegisterService.java index 896e8c8b5..1a6f614c0 100644 --- a/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRegisterService.java +++ b/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRegisterService.java @@ -2,6 +2,7 @@ import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.auth.blacklistIp.application.dto.mapper.BlacklistIpDtoMapper; @@ -10,7 +11,7 @@ import page.clab.api.domain.auth.blacklistIp.application.port.out.RegisterBlacklistIpPort; import page.clab.api.domain.auth.blacklistIp.application.port.out.RetrieveBlacklistIpPort; import page.clab.api.domain.auth.blacklistIp.domain.BlacklistIp; -import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; @Service @@ -19,7 +20,7 @@ public class BlacklistIpRegisterService implements RegisterBlacklistIpUseCase { private final RegisterBlacklistIpPort registerBlacklistIpPort; private final RetrieveBlacklistIpPort retrieveBlacklistIpPort; - private final SlackService slackService; + private final ApplicationEventPublisher eventPublisher; private final BlacklistIpDtoMapper mapper; /** @@ -41,8 +42,12 @@ public String registerBlacklistIp(HttpServletRequest request, BlacklistIpRequest .orElseGet(() -> { BlacklistIp blacklistIp = mapper.fromDto(requestDto); registerBlacklistIpPort.save(blacklistIp); - slackService.sendSecurityAlertNotification(request, SecurityAlertType.BLACKLISTED_IP_ADDED, - "Added IP: " + ipAddress); + + String additionalMessage = "Added IP: " + ipAddress; + eventPublisher.publishEvent( + new NotificationEvent(this, SecurityAlertType.BLACKLISTED_IP_ADDED, request, + additionalMessage)); + return ipAddress; }); } diff --git a/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRemoveService.java b/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRemoveService.java index 5d6f9df1d..fe1304e83 100644 --- a/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRemoveService.java +++ b/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRemoveService.java @@ -2,13 +2,14 @@ import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.auth.blacklistIp.application.port.in.RemoveBlacklistIpUseCase; import page.clab.api.domain.auth.blacklistIp.application.port.out.RemoveBlacklistIpPort; import page.clab.api.domain.auth.blacklistIp.application.port.out.RetrieveBlacklistIpPort; import page.clab.api.domain.auth.blacklistIp.domain.BlacklistIp; -import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; @Service @@ -17,7 +18,7 @@ public class BlacklistIpRemoveService implements RemoveBlacklistIpUseCase { private final RetrieveBlacklistIpPort retrieveBlacklistIpPort; private final RemoveBlacklistIpPort removeBlacklistIpPort; - private final SlackService slackService; + private final ApplicationEventPublisher eventPublisher; /** * 지정된 IP 주소를 블랙리스트에서 제거합니다. @@ -34,8 +35,11 @@ public class BlacklistIpRemoveService implements RemoveBlacklistIpUseCase { public String removeBlacklistIp(HttpServletRequest request, String ipAddress) { BlacklistIp blacklistIp = retrieveBlacklistIpPort.getByIpAddress(ipAddress); removeBlacklistIpPort.delete(blacklistIp); - slackService.sendSecurityAlertNotification(request, SecurityAlertType.BLACKLISTED_IP_REMOVED, - "Deleted IP: " + ipAddress); + + String additionalMessage = "Deleted IP: " + ipAddress; + eventPublisher.publishEvent( + new NotificationEvent(this, SecurityAlertType.BLACKLISTED_IP_REMOVED, request, additionalMessage)); + return blacklistIp.getIpAddress(); } } diff --git a/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpResetService.java b/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpResetService.java index 04078df28..e3f8e6178 100644 --- a/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpResetService.java +++ b/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpResetService.java @@ -3,13 +3,14 @@ import jakarta.servlet.http.HttpServletRequest; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.auth.blacklistIp.application.port.in.ResetBlacklistIpsUseCase; import page.clab.api.domain.auth.blacklistIp.application.port.out.RemoveBlacklistIpPort; import page.clab.api.domain.auth.blacklistIp.application.port.out.RetrieveBlacklistIpPort; import page.clab.api.domain.auth.blacklistIp.domain.BlacklistIp; -import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; @Service @@ -18,7 +19,7 @@ public class BlacklistIpResetService implements ResetBlacklistIpsUseCase { private final RetrieveBlacklistIpPort retrieveBlacklistIpPort; private final RemoveBlacklistIpPort removeBlacklistIpPort; - private final SlackService slackService; + private final ApplicationEventPublisher eventPublisher; /** * 블랙리스트에 등록된 모든 IP 주소를 초기화합니다. @@ -37,8 +38,11 @@ public List resetBlacklistIps(HttpServletRequest request) { .map(BlacklistIp::getIpAddress) .toList(); removeBlacklistIpPort.deleteAll(); - slackService.sendSecurityAlertNotification(request, SecurityAlertType.BLACKLISTED_IP_REMOVED, - "Deleted IP: ALL"); + + String additionalMessage = "Deleted IP: ALL"; + eventPublisher.publishEvent( + new NotificationEvent(this, SecurityAlertType.BLACKLISTED_IP_REMOVED, request, additionalMessage)); + return blacklistedIps; } } diff --git a/src/main/java/page/clab/api/domain/auth/login/application/service/TwoFactorAuthenticationService.java b/src/main/java/page/clab/api/domain/auth/login/application/service/TwoFactorAuthenticationService.java index 609423d36..402cf4ee7 100644 --- a/src/main/java/page/clab/api/domain/auth/login/application/service/TwoFactorAuthenticationService.java +++ b/src/main/java/page/clab/api/domain/auth/login/application/service/TwoFactorAuthenticationService.java @@ -4,6 +4,7 @@ import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.auth.accountAccessLog.domain.AccountAccessResult; @@ -22,7 +23,8 @@ import page.clab.api.external.auth.redisToken.application.port.ExternalManageRedisTokenUseCase; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; import page.clab.api.global.auth.jwt.JwtTokenProvider; -import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; +import page.clab.api.global.common.notificationSetting.domain.GeneralAlertType; import page.clab.api.global.util.HttpReqResUtil; @Service @@ -35,7 +37,7 @@ public class TwoFactorAuthenticationService implements ManageLoginUseCase { private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; private final ExternalRegisterAccountAccessLogUseCase externalRegisterAccountAccessLogUseCase; private final ExternalManageRedisTokenUseCase externalManageRedisTokenUseCase; - private final SlackService slackService; + private final ApplicationEventPublisher eventPublisher; private final JwtTokenProvider jwtTokenProvider; @Transactional @@ -77,7 +79,8 @@ private TokenInfo generateAndSaveToken(MemberLoginInfoDto memberInfo) { private void sendAdminLoginNotification(HttpServletRequest request, MemberLoginInfoDto loginMember) { if (loginMember.isSuperAdminRole()) { - slackService.sendAdminLoginNotification(request, loginMember); + eventPublisher.publishEvent( + new NotificationEvent(this, GeneralAlertType.ADMIN_LOGIN, request, loginMember)); } } diff --git a/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpRemoveService.java b/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpRemoveService.java index 8dfa0565d..394145421 100644 --- a/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpRemoveService.java +++ b/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpRemoveService.java @@ -2,11 +2,12 @@ import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.auth.redisIpAccessMonitor.application.port.in.RemoveAbnormalAccessIpUseCase; import page.clab.api.domain.auth.redisIpAccessMonitor.application.port.out.RemoveIpAccessMonitorPort; -import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; @Service @@ -14,14 +15,15 @@ public class AbnormalAccessIpRemoveService implements RemoveAbnormalAccessIpUseCase { private final RemoveIpAccessMonitorPort removeIpAccessMonitorPort; - private final SlackService slackService; + private final ApplicationEventPublisher eventPublisher; @Override @Transactional public String removeAbnormalAccessIp(HttpServletRequest request, String ipAddress) { removeIpAccessMonitorPort.deleteById(ipAddress); - slackService.sendSecurityAlertNotification(request, SecurityAlertType.ABNORMAL_ACCESS_IP_DELETED, - "Deleted IP: " + ipAddress); + String additionalMessage = "Deleted IP: " + ipAddress; + eventPublisher.publishEvent( + new NotificationEvent(this, SecurityAlertType.ABNORMAL_ACCESS_IP_DELETED, request, additionalMessage)); return ipAddress; } } diff --git a/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpsClearService.java b/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpsClearService.java index 2127cb788..713bf41b7 100644 --- a/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpsClearService.java +++ b/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpsClearService.java @@ -3,13 +3,14 @@ import jakarta.servlet.http.HttpServletRequest; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.auth.redisIpAccessMonitor.application.port.in.ClearAbnormalAccessIpsUseCase; import page.clab.api.domain.auth.redisIpAccessMonitor.application.port.out.ClearIpAccessMonitorPort; import page.clab.api.domain.auth.redisIpAccessMonitor.application.port.out.RetrieveIpAccessMonitorPort; import page.clab.api.domain.auth.redisIpAccessMonitor.domain.RedisIpAccessMonitor; -import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; @Service @@ -18,15 +19,18 @@ public class AbnormalAccessIpsClearService implements ClearAbnormalAccessIpsUseC private final ClearIpAccessMonitorPort clearIpAccessMonitorPort; private final RetrieveIpAccessMonitorPort retrieveIpAccessMonitorPort; - private final SlackService slackService; + private final ApplicationEventPublisher eventPublisher; @Override @Transactional public List clearAbnormalAccessIps(HttpServletRequest request) { List ipAccessMonitors = retrieveIpAccessMonitorPort.findAll(); clearIpAccessMonitorPort.deleteAll(); - slackService.sendSecurityAlertNotification(request, SecurityAlertType.ABNORMAL_ACCESS_IP_DELETED, - "Deleted IP: ALL"); + + String additionalMessage = "Deleted IP: ALL"; + eventPublisher.publishEvent( + new NotificationEvent(this, SecurityAlertType.ABNORMAL_ACCESS_IP_DELETED, request, additionalMessage)); + return ipAccessMonitors; } } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java b/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java index 6c0bc00b0..7493fa1cb 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java @@ -2,6 +2,7 @@ import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.community.board.application.dto.mapper.BoardDtoMapper; @@ -14,7 +15,8 @@ import page.clab.api.external.memberManagement.notification.application.port.ExternalSendNotificationUseCase; import page.clab.api.global.common.file.application.UploadedFileService; import page.clab.api.global.common.file.domain.UploadedFile; -import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; +import page.clab.api.global.common.notificationSetting.domain.ExecutivesAlertType; import page.clab.api.global.common.notificationSetting.domain.SlackBoardInfo; import page.clab.api.global.exception.PermissionDeniedException; @@ -26,7 +28,7 @@ public class BoardRegisterService implements RegisterBoardUseCase { private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; private final ExternalSendNotificationUseCase externalSendNotificationUseCase; private final UploadedFileService uploadedFileService; - private final SlackService slackService; + private final ApplicationEventPublisher eventPublisher; private final BoardDtoMapper mapper; /** @@ -50,8 +52,11 @@ public String registerBoard(BoardRequestDto requestDto) throws PermissionDeniedE externalSendNotificationUseCase.sendNotificationToMember(currentMemberInfo.getMemberId(), "[" + board.getTitle() + "] 새로운 공지사항이 등록되었습니다."); } + SlackBoardInfo boardInfo = SlackBoardInfo.create(board, currentMemberInfo); - slackService.sendNewBoardNotification(boardInfo); + eventPublisher.publishEvent(new NotificationEvent(this, ExecutivesAlertType.NEW_BOARD, null, + boardInfo)); + return registerBoardPort.save(board).getCategory().getKey(); } } diff --git a/src/main/java/page/clab/api/domain/hiring/application/application/service/ApplicationApplyService.java b/src/main/java/page/clab/api/domain/hiring/application/application/service/ApplicationApplyService.java index 4aa29bf59..357a59cde 100644 --- a/src/main/java/page/clab/api/domain/hiring/application/application/service/ApplicationApplyService.java +++ b/src/main/java/page/clab/api/domain/hiring/application/application/service/ApplicationApplyService.java @@ -1,6 +1,7 @@ package page.clab.api.domain.hiring.application.application.service; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.hiring.application.application.dto.mapper.ApplicationDtoMapper; @@ -10,7 +11,8 @@ import page.clab.api.domain.hiring.application.domain.Application; import page.clab.api.external.hiring.application.application.port.ExternalRetrieveRecruitmentUseCase; import page.clab.api.external.memberManagement.notification.application.port.ExternalSendNotificationUseCase; -import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; +import page.clab.api.global.common.notificationSetting.domain.ExecutivesAlertType; @Service @RequiredArgsConstructor @@ -19,7 +21,7 @@ public class ApplicationApplyService implements ApplyForApplicationUseCase { private final RegisterApplicationPort registerApplicationPort; private final ExternalRetrieveRecruitmentUseCase externalRetrieveRecruitmentUseCase; private final ExternalSendNotificationUseCase externalSendNotificationUseCase; - private final SlackService slackService; + private final ApplicationEventPublisher eventPublisher; private final ApplicationDtoMapper mapper; @Transactional @@ -30,7 +32,10 @@ public String applyForClub(ApplicationRequestDto requestDto) { String applicationType = application.getApplicationTypeForNotificationPrefix(); externalSendNotificationUseCase.sendNotificationToAdmins(applicationType + requestDto.getStudentId() + " " + requestDto.getName() + "님이 지원하였습니다."); - slackService.sendNewApplicationNotification(requestDto); + + eventPublisher.publishEvent(new NotificationEvent(this, ExecutivesAlertType.NEW_APPLICATION, null, + requestDto)); + return registerApplicationPort.save(application).getStudentId(); } } diff --git a/src/main/java/page/clab/api/domain/library/bookLoanRecord/application/service/BookLoanRequestService.java b/src/main/java/page/clab/api/domain/library/bookLoanRecord/application/service/BookLoanRequestService.java index f5c2a01b4..475131df4 100644 --- a/src/main/java/page/clab/api/domain/library/bookLoanRecord/application/service/BookLoanRequestService.java +++ b/src/main/java/page/clab/api/domain/library/bookLoanRecord/application/service/BookLoanRequestService.java @@ -1,6 +1,7 @@ package page.clab.api.domain.library.bookLoanRecord.application.service; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.orm.ObjectOptimisticLockingFailureException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,7 +18,8 @@ import page.clab.api.external.library.book.application.port.ExternalRetrieveBookUseCase; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; import page.clab.api.external.memberManagement.notification.application.port.ExternalSendNotificationUseCase; -import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; +import page.clab.api.global.common.notificationSetting.domain.ExecutivesAlertType; import page.clab.api.global.common.notificationSetting.domain.SlackBookLoanRecordInfo; import page.clab.api.global.exception.CustomOptimisticLockingFailureException; @@ -30,7 +32,7 @@ public class BookLoanRequestService implements RequestBookLoanUseCase { private final ExternalRetrieveBookUseCase externalRetrieveBookUseCase; private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; private final ExternalSendNotificationUseCase externalSendNotificationUseCase; - private final SlackService slackService; + private final ApplicationEventPublisher eventPublisher; /** * 도서 대출 신청을 처리합니다. @@ -62,7 +64,8 @@ public Long requestBookLoan(BookLoanRecordRequestDto requestDto) throws CustomOp "[" + book.getTitle() + "] 도서 대출 신청이 완료되었습니다."); SlackBookLoanRecordInfo bookLoanRecordInfo = SlackBookLoanRecordInfo.create(book, borrowerInfo); - slackService.sendNewBookLoanRequestNotification(bookLoanRecordInfo); + eventPublisher.publishEvent(new NotificationEvent(this, ExecutivesAlertType.NEW_BOOK_LOAN_REQUEST, null, + bookLoanRecordInfo)); return registerBookLoanRecordPort.save(bookLoanRecord).getId(); } catch (ObjectOptimisticLockingFailureException e) { diff --git a/src/main/java/page/clab/api/domain/memberManagement/member/application/service/MemberRoleManagementService.java b/src/main/java/page/clab/api/domain/memberManagement/member/application/service/MemberRoleManagementService.java index 8d656dda3..6ddef51f1 100644 --- a/src/main/java/page/clab/api/domain/memberManagement/member/application/service/MemberRoleManagementService.java +++ b/src/main/java/page/clab/api/domain/memberManagement/member/application/service/MemberRoleManagementService.java @@ -2,6 +2,7 @@ import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.memberManagement.member.application.dto.request.ChangeMemberRoleRequest; @@ -11,7 +12,7 @@ import page.clab.api.domain.memberManagement.member.application.port.out.UpdateMemberPort; import page.clab.api.domain.memberManagement.member.domain.Member; import page.clab.api.domain.memberManagement.member.domain.Role; -import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; @Service @@ -20,7 +21,7 @@ public class MemberRoleManagementService implements ManageMemberRoleUseCase { private final RetrieveMemberPort retrieveMemberPort; private final UpdateMemberPort updateMemberPort; - private final SlackService slackService; + private final ApplicationEventPublisher eventPublisher; @Transactional @Override @@ -35,9 +36,13 @@ public String changeMemberRole(HttpServletRequest httpServletRequest, String mem member.changeRole(newRole); updateMemberPort.update(member); - slackService.sendSecurityAlertNotification(httpServletRequest, SecurityAlertType.MEMBER_ROLE_CHANGED, - String.format("[%s] %s: %s -> %s", - member.getId(), member.getName(), oldRole, newRole)); + + String additionalMessage = String.format("[%s] %s: %s -> %s", member.getId(), member.getName(), oldRole, + newRole); + eventPublisher.publishEvent( + new NotificationEvent(this, SecurityAlertType.MEMBER_ROLE_CHANGED, httpServletRequest, + additionalMessage)); + return memberId; } diff --git a/src/main/java/page/clab/api/domain/members/membershipFee/application/service/MembershipFeeRegisterService.java b/src/main/java/page/clab/api/domain/members/membershipFee/application/service/MembershipFeeRegisterService.java index f3e58d218..bef56e5da 100644 --- a/src/main/java/page/clab/api/domain/members/membershipFee/application/service/MembershipFeeRegisterService.java +++ b/src/main/java/page/clab/api/domain/members/membershipFee/application/service/MembershipFeeRegisterService.java @@ -1,6 +1,7 @@ package page.clab.api.domain.members.membershipFee.application.service; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberBasicInfoDto; @@ -11,7 +12,8 @@ import page.clab.api.domain.members.membershipFee.domain.MembershipFee; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; import page.clab.api.external.memberManagement.notification.application.port.ExternalSendNotificationUseCase; -import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; +import page.clab.api.global.common.notificationSetting.domain.ExecutivesAlertType; import page.clab.api.global.common.notificationSetting.domain.SlackMembershipFeeInfo; @Service @@ -21,7 +23,7 @@ public class MembershipFeeRegisterService implements RegisterMembershipFeeUseCas private final RegisterMembershipFeePort registerMembershipFeePort; private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; private final ExternalSendNotificationUseCase externalSendNotificationUseCase; - private final SlackService slackService; + private final ApplicationEventPublisher eventPublisher; private final MembershipFeeDtoMapper mapper; @Transactional @@ -31,7 +33,8 @@ public Long registerMembershipFee(MembershipFeeRequestDto requestDto) { MembershipFee membershipFee = mapper.fromDto(requestDto, memberInfo.getMemberId()); externalSendNotificationUseCase.sendNotificationToAdmins("새로운 회비 내역이 등록되었습니다."); SlackMembershipFeeInfo membershipFeeInfo = SlackMembershipFeeInfo.create(membershipFee, memberInfo); - slackService.sendNewMembershipFeeNotification(membershipFeeInfo); + eventPublisher.publishEvent(new NotificationEvent(this, ExecutivesAlertType.NEW_MEMBERSHIP_FEE, null, + membershipFeeInfo)); return registerMembershipFeePort.save(membershipFee).getId(); } } diff --git a/src/main/java/page/clab/api/external/auth/accountLockInfo/port/ExternalAccountLockManagementService.java b/src/main/java/page/clab/api/external/auth/accountLockInfo/port/ExternalAccountLockManagementService.java index 428ce931b..01bdf722b 100644 --- a/src/main/java/page/clab/api/external/auth/accountLockInfo/port/ExternalAccountLockManagementService.java +++ b/src/main/java/page/clab/api/external/auth/accountLockInfo/port/ExternalAccountLockManagementService.java @@ -3,6 +3,7 @@ import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.auth.accountLockInfo.application.port.out.RegisterAccountLockInfoPort; @@ -13,7 +14,7 @@ import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberDetailedInfoDto; import page.clab.api.external.auth.accountLockInfo.application.ExternalManageAccountLockUseCase; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; -import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; @Service @@ -23,7 +24,7 @@ public class ExternalAccountLockManagementService implements ExternalManageAccou private final RetrieveAccountLockInfoPort retrieveAccountLockInfoPort; private final RegisterAccountLockInfoPort registerAccountLockInfoPort; private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; - private final SlackService slackService; + private final ApplicationEventPublisher eventPublisher; @Value("${security.login-attempt.max-failures}") private int maxLoginFailures; @@ -99,8 +100,9 @@ private void sendSlackLoginFailureNotification(HttpServletRequest request, Strin String memberName = memberInfo.getMemberName(); if (memberInfo.isAdminRole()) { request.setAttribute("member", memberId + " " + memberName); - slackService.sendSecurityAlertNotification(request, SecurityAlertType.REPEATED_LOGIN_FAILURES, - "로그인 실패 횟수 초과로 계정이 잠겼습니다."); + String additionalMessage = "로그인 실패 횟수 초과로 계정이 잠겼습니다."; + eventPublisher.publishEvent( + new NotificationEvent(this, SecurityAlertType.REPEATED_LOGIN_FAILURES, request, additionalMessage)); } } } diff --git a/src/main/java/page/clab/api/external/auth/redisIpAccessMonitor/application/service/ExternalIpAccessMonitorRegisterService.java b/src/main/java/page/clab/api/external/auth/redisIpAccessMonitor/application/service/ExternalIpAccessMonitorRegisterService.java index e6a63f21e..91e2bfbd7 100644 --- a/src/main/java/page/clab/api/external/auth/redisIpAccessMonitor/application/service/ExternalIpAccessMonitorRegisterService.java +++ b/src/main/java/page/clab/api/external/auth/redisIpAccessMonitor/application/service/ExternalIpAccessMonitorRegisterService.java @@ -4,13 +4,14 @@ import lombok.RequiredArgsConstructor; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.auth.redisIpAccessMonitor.application.port.out.RegisterIpAccessMonitorPort; import page.clab.api.domain.auth.redisIpAccessMonitor.application.port.out.RetrieveIpAccessMonitorPort; import page.clab.api.domain.auth.redisIpAccessMonitor.domain.RedisIpAccessMonitor; import page.clab.api.external.auth.redisIpAccessMonitor.application.port.ExternalRegisterIpAccessMonitorUseCase; -import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; @Service @@ -19,7 +20,7 @@ public class ExternalIpAccessMonitorRegisterService implements ExternalRegisterI private final RegisterIpAccessMonitorPort registerIpAccessMonitorPort; private final RetrieveIpAccessMonitorPort retrieveIpAccessMonitorPort; - private final SlackService slackService; + private final ApplicationEventPublisher eventPublisher; @Value("${security.ip-attempt.max-attempts}") private int maxAttempts; @@ -29,8 +30,10 @@ public class ExternalIpAccessMonitorRegisterService implements ExternalRegisterI public void registerIpAccessMonitor(HttpServletRequest request, String ipAddress) { RedisIpAccessMonitor redisIpAccessMonitor = getOrCreateRedisIpAccessMonitor(ipAddress); if (redisIpAccessMonitor.isBlocked()) { - slackService.sendSecurityAlertNotification(request, SecurityAlertType.ABNORMAL_ACCESS_IP_BLOCKED, - "Blocked IP: " + ipAddress); + String additionalMessage = "Blocked IP: " + ipAddress; + eventPublisher.publishEvent( + new NotificationEvent(this, SecurityAlertType.ABNORMAL_ACCESS_IP_BLOCKED, request, + additionalMessage)); } registerIpAccessMonitorPort.save(redisIpAccessMonitor); } diff --git a/src/main/java/page/clab/api/global/auth/filter/CustomBasicAuthenticationFilter.java b/src/main/java/page/clab/api/global/auth/filter/CustomBasicAuthenticationFilter.java index 100c32390..1f08d5b32 100644 --- a/src/main/java/page/clab/api/global/auth/filter/CustomBasicAuthenticationFilter.java +++ b/src/main/java/page/clab/api/global/auth/filter/CustomBasicAuthenticationFilter.java @@ -8,6 +8,7 @@ import java.util.Base64; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -17,7 +18,7 @@ import page.clab.api.external.auth.blacklistIp.application.port.ExternalRetrieveBlacklistIpUseCase; import page.clab.api.external.auth.redisIpAccessMonitor.application.port.ExternalCheckIpBlockedUseCase; import page.clab.api.global.auth.util.IpWhitelistValidator; -import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; import page.clab.api.global.util.HttpReqResUtil; import page.clab.api.global.util.ResponseUtil; @@ -45,22 +46,22 @@ public class CustomBasicAuthenticationFilter extends BasicAuthenticationFilter { private final IpWhitelistValidator ipWhitelistValidator; - private final SlackService slackService; private final ExternalCheckIpBlockedUseCase externalCheckIpBlockedUseCase; private final ExternalRetrieveBlacklistIpUseCase externalRetrieveBlacklistIpUseCase; + private final ApplicationEventPublisher eventPublisher; public CustomBasicAuthenticationFilter( AuthenticationManager authenticationManager, IpWhitelistValidator ipWhitelistValidator, - SlackService slackService, ExternalCheckIpBlockedUseCase externalCheckIpBlockedUseCase, - ExternalRetrieveBlacklistIpUseCase externalRetrieveBlacklistIpUseCase + ExternalRetrieveBlacklistIpUseCase externalRetrieveBlacklistIpUseCase, + ApplicationEventPublisher eventPublisher ) { super(authenticationManager); this.externalCheckIpBlockedUseCase = externalCheckIpBlockedUseCase; this.externalRetrieveBlacklistIpUseCase = externalRetrieveBlacklistIpUseCase; this.ipWhitelistValidator = ipWhitelistValidator; - this.slackService = slackService; + this.eventPublisher = eventPublisher; } @NotNull @@ -131,22 +132,26 @@ private boolean verifyIpAddressAccess(HttpServletResponse response) throws IOExc private void sendAuthenticationSuccessAlertSlackMessage(HttpServletRequest request) { String path = request.getRequestURI(); if (WhitelistPathMatcher.isSwaggerIndexEndpoint(path)) { - slackService.sendSecurityAlertNotification(request, SecurityAlertType.API_DOCS_ACCESS, - "API 문서에 대한 접근이 허가되었습니다."); + String additionalMessage = "API 문서에 대한 접근이 허가되었습니다."; + eventPublisher.publishEvent( + new NotificationEvent(this, SecurityAlertType.API_DOCS_ACCESS, request, additionalMessage)); } else if (WhitelistPathMatcher.isActuatorRequest(path)) { - slackService.sendSecurityAlertNotification(request, SecurityAlertType.ACTUATOR_ACCESS, - "Actuator에 대한 접근이 허가되었습니다."); + String additionalMessage = "Actuator에 대한 접근이 허가되었습니다."; + eventPublisher.publishEvent( + new NotificationEvent(this, SecurityAlertType.ACTUATOR_ACCESS, request, additionalMessage)); } } private void sendAuthenticationFailureAlertSlackMessage(HttpServletRequest request) { String path = request.getRequestURI(); if (WhitelistPathMatcher.isSwaggerIndexEndpoint(path)) { - slackService.sendSecurityAlertNotification(request, SecurityAlertType.API_DOCS_ACCESS, - "API 문서에 대한 접근이 거부되었습니다."); + String additionalMessage = "API 문서에 대한 접근이 거부되었습니다."; + eventPublisher.publishEvent( + new NotificationEvent(this, SecurityAlertType.API_DOCS_ACCESS, request, additionalMessage)); } else if (WhitelistPathMatcher.isActuatorRequest(path)) { - slackService.sendSecurityAlertNotification(request, SecurityAlertType.ACTUATOR_ACCESS, - "Actuator에 대한 접근이 거부되었습니다."); + String additionalMessage = "Actuator에 대한 접근이 거부되었습니다."; + eventPublisher.publishEvent( + new NotificationEvent(this, SecurityAlertType.ACTUATOR_ACCESS, request, additionalMessage)); } } } diff --git a/src/main/java/page/clab/api/global/auth/filter/InvalidEndpointAccessFilter.java b/src/main/java/page/clab/api/global/auth/filter/InvalidEndpointAccessFilter.java index 4291b37ff..5f7548fd1 100644 --- a/src/main/java/page/clab/api/global/auth/filter/InvalidEndpointAccessFilter.java +++ b/src/main/java/page/clab/api/global/auth/filter/InvalidEndpointAccessFilter.java @@ -9,13 +9,14 @@ import java.io.IOException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.filter.GenericFilterBean; import page.clab.api.domain.auth.blacklistIp.domain.BlacklistIp; import page.clab.api.external.auth.blacklistIp.application.port.ExternalRegisterBlacklistIpUseCase; import page.clab.api.external.auth.blacklistIp.application.port.ExternalRetrieveBlacklistIpUseCase; -import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; import page.clab.api.global.util.HttpReqResUtil; import page.clab.api.global.util.ResponseUtil; @@ -42,10 +43,10 @@ @Slf4j public class InvalidEndpointAccessFilter extends GenericFilterBean { - private final SlackService slackService; private final String fileURL; private final ExternalRegisterBlacklistIpUseCase externalRegisterBlacklistIpUseCase; private final ExternalRetrieveBlacklistIpUseCase externalRetrieveBlacklistIpUseCase; + private final ApplicationEventPublisher eventPublisher; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) @@ -97,8 +98,9 @@ private void sendSecurityAlerts(HttpServletRequest request, String clientIpAddre String abnormalAccessMessage = "서버 내부 파일 및 디렉토리에 대한 접근이 감지되었습니다."; String blacklistAddedMessage = "Added IP: " + clientIpAddress; - slackService.sendSecurityAlertNotification(request, SecurityAlertType.ABNORMAL_ACCESS, abnormalAccessMessage); - slackService.sendSecurityAlertNotification(request, SecurityAlertType.BLACKLISTED_IP_ADDED, - blacklistAddedMessage); + eventPublisher.publishEvent(new NotificationEvent(this, SecurityAlertType.ABNORMAL_ACCESS, request, + abnormalAccessMessage)); + eventPublisher.publishEvent(new NotificationEvent(this, SecurityAlertType.BLACKLISTED_IP_ADDED, request, + blacklistAddedMessage)); } } diff --git a/src/main/java/page/clab/api/global/auth/filter/JwtAuthenticationFilter.java b/src/main/java/page/clab/api/global/auth/filter/JwtAuthenticationFilter.java index 4a61d3682..40c64cd05 100644 --- a/src/main/java/page/clab/api/global/auth/filter/JwtAuthenticationFilter.java +++ b/src/main/java/page/clab/api/global/auth/filter/JwtAuthenticationFilter.java @@ -9,6 +9,7 @@ import java.io.IOException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.filter.GenericFilterBean; @@ -17,7 +18,7 @@ import page.clab.api.external.auth.redisIpAccessMonitor.application.port.ExternalCheckIpBlockedUseCase; import page.clab.api.external.auth.redisToken.application.port.ExternalManageRedisTokenUseCase; import page.clab.api.global.auth.jwt.JwtTokenProvider; -import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; import page.clab.api.global.util.HttpReqResUtil; import page.clab.api.global.util.ResponseUtil; @@ -44,8 +45,8 @@ @Slf4j public class JwtAuthenticationFilter extends GenericFilterBean { - private final SlackService slackService; private final JwtTokenProvider jwtTokenProvider; + private final ApplicationEventPublisher eventPublisher; private final ExternalManageRedisTokenUseCase externalManageRedisTokenUseCase; private final ExternalCheckIpBlockedUseCase externalCheckIpBlockedUseCase; private final ExternalRetrieveBlacklistIpUseCase externalRetrieveBlacklistIpUseCase; @@ -112,8 +113,9 @@ private boolean authenticateToken(HttpServletRequest request, HttpServletRespons private void sendSecurityAlertSlackMessage(HttpServletRequest request, RedisToken redisToken) { if (redisToken.isAdminToken()) { request.setAttribute("member", redisToken.getId()); - slackService.sendSecurityAlertNotification(request, SecurityAlertType.DUPLICATE_LOGIN, - "토큰 발급 IP와 다른 IP에서 접속하여 토큰을 삭제하였습니다."); + String additionalMessage = "토큰 발급 IP와 다른 IP에서 접속하여 토큰을 삭제하였습니다."; + eventPublisher.publishEvent( + new NotificationEvent(this, SecurityAlertType.DUPLICATE_LOGIN, request, additionalMessage)); } } } diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackNotificationSender.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackNotificationSender.java index c7a5ff764..d733d79dc 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackNotificationSender.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackNotificationSender.java @@ -4,6 +4,7 @@ import org.springframework.stereotype.Component; import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; import page.clab.api.global.common.notificationSetting.application.port.out.NotificationSender; +import page.clab.api.global.common.notificationSetting.domain.PlatformType; @Component @RequiredArgsConstructor @@ -12,8 +13,13 @@ public class SlackNotificationSender implements NotificationSender { private final SlackServiceHelper slackServiceHelper; @Override - public void sendNotification(NotificationEvent event) { - slackServiceHelper.sendSlackMessage(event.getWebhookUrl(), event.getAlertType(), event.getRequest(), + public String getPlatformName() { + return PlatformType.SLACK.getName(); + } + + @Override + public void sendNotification(NotificationEvent event, String webhookUrl) { + slackServiceHelper.sendSlackMessage(webhookUrl, event.getAlertType(), event.getRequest(), event.getAdditionalData()); } } diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackService.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackService.java deleted file mode 100644 index d28857897..000000000 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackService.java +++ /dev/null @@ -1,94 +0,0 @@ -package page.clab.api.global.common.notificationSetting.adapter.out.slack; - -import jakarta.servlet.http.HttpServletRequest; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.context.event.EventListener; -import org.springframework.stereotype.Service; -import page.clab.api.domain.hiring.application.application.dto.request.ApplicationRequestDto; -import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberLoginInfoDto; -import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; -import page.clab.api.global.common.notificationSetting.domain.ExecutivesAlertType; -import page.clab.api.global.common.notificationSetting.domain.GeneralAlertType; -import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; -import page.clab.api.global.common.notificationSetting.domain.SlackBoardInfo; -import page.clab.api.global.common.notificationSetting.domain.SlackBookLoanRecordInfo; -import page.clab.api.global.common.notificationSetting.domain.SlackMembershipFeeInfo; -import page.clab.api.global.config.SlackConfig; - -/** - * {@code SlackService}는 다양한 알림 유형에 따라 Slack 알림을 전송하는 서비스입니다. - * - *

        이 서비스는 `NotificationEvent`를 통해 Slack 알림을 발송하며, - * 서버 시작, 서버 오류, 보안 경고, 관리자 로그인 등의 알림 유형을 제공합니다.

        - *

        - * 주요 기능: - *

          - *
        • {@link #sendServerErrorNotification(HttpServletRequest, Exception)} - 서버 오류 발생 시 Slack으로 알림을 전송합니다.
        • - *
        • {@link #sendSecurityAlertNotification(HttpServletRequest, SecurityAlertType, String)} - 보안 경고 발생 시 알림을 전송합니다.
        • - *
        • {@link #sendAdminLoginNotification(HttpServletRequest, MemberLoginInfoDto)} - 관리자 로그인 시 Slack으로 알림을 전송합니다.
        • - *
        • {@link #sendNewApplicationNotification(ApplicationRequestDto)} - 신규 지원 정보가 있을 때 Slack으로 알림을 전송합니다.
        • - *
        • {@link #sendNewBoardNotification(SlackBoardInfo)} - 새 게시글이 등록되었을 때 알림을 전송합니다.
        • - *
        • {@link #sendNewMembershipFeeNotification(SlackMembershipFeeInfo)} - 신규 회비 신청 시 알림을 전송합니다.
        • - *
        • {@link #sendNewBookLoanRequestNotification(SlackBookLoanRecordInfo)} - 도서 대여 신청이 있을 때 알림을 전송합니다.
        • - *
        • {@link #sendServerStartNotification()} - 서버 시작 시 알림을 전송합니다.
        • - *
        - */ -@Service -public class SlackService { - - private final ApplicationEventPublisher eventPublisher; - private final String coreTeamWebhookUrl; - private final String executivesWebhookUrl; - - public SlackService(ApplicationEventPublisher eventPublisher, SlackConfig slackConfig) { - this.eventPublisher = eventPublisher; - this.coreTeamWebhookUrl = slackConfig.getCoreTeamWebhookUrl(); - this.executivesWebhookUrl = slackConfig.getExecutivesWebhookUrl(); - } - - public void sendServerErrorNotification(HttpServletRequest request, Exception e) { - eventPublisher.publishEvent( - new NotificationEvent(this, coreTeamWebhookUrl, GeneralAlertType.SERVER_ERROR, request, e)); - } - - public void sendSecurityAlertNotification(HttpServletRequest request, SecurityAlertType alertType, - String additionalMessage) { - eventPublisher.publishEvent( - new NotificationEvent(this, coreTeamWebhookUrl, alertType, request, additionalMessage)); - } - - public void sendAdminLoginNotification(HttpServletRequest request, MemberLoginInfoDto loginMember) { - eventPublisher.publishEvent( - new NotificationEvent(this, coreTeamWebhookUrl, GeneralAlertType.ADMIN_LOGIN, request, loginMember)); - } - - public void sendNewApplicationNotification(ApplicationRequestDto applicationRequestDto) { - eventPublisher.publishEvent( - new NotificationEvent(this, executivesWebhookUrl, ExecutivesAlertType.NEW_APPLICATION, null, - applicationRequestDto)); - } - - public void sendNewBoardNotification(SlackBoardInfo board) { - eventPublisher.publishEvent( - new NotificationEvent(this, executivesWebhookUrl, ExecutivesAlertType.NEW_BOARD, null, board)); - } - - public void sendNewMembershipFeeNotification(SlackMembershipFeeInfo membershipFee) { - eventPublisher.publishEvent( - new NotificationEvent(this, executivesWebhookUrl, ExecutivesAlertType.NEW_MEMBERSHIP_FEE, null, - membershipFee)); - } - - public void sendNewBookLoanRequestNotification(SlackBookLoanRecordInfo bookLoanRecord) { - eventPublisher.publishEvent( - new NotificationEvent(this, executivesWebhookUrl, ExecutivesAlertType.NEW_BOOK_LOAN_REQUEST, null, - bookLoanRecord)); - } - - @EventListener(ContextRefreshedEvent.class) - public void sendServerStartNotification() { - eventPublisher.publishEvent( - new NotificationEvent(this, coreTeamWebhookUrl, GeneralAlertType.SERVER_START, null, null)); - } -} diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackServiceHelper.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackServiceHelper.java index b46aabbbc..a46d56714 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackServiceHelper.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackServiceHelper.java @@ -35,6 +35,7 @@ import org.springframework.stereotype.Component; import page.clab.api.domain.hiring.application.application.dto.request.ApplicationRequestDto; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberLoginInfoDto; +import page.clab.api.global.common.notificationSetting.config.NotificationConfigProperties; import page.clab.api.global.common.notificationSetting.domain.AlertType; import page.clab.api.global.common.notificationSetting.domain.ExecutivesAlertType; import page.clab.api.global.common.notificationSetting.domain.GeneralAlertType; @@ -42,7 +43,6 @@ import page.clab.api.global.common.notificationSetting.domain.SlackBoardInfo; import page.clab.api.global.common.notificationSetting.domain.SlackBookLoanRecordInfo; import page.clab.api.global.common.notificationSetting.domain.SlackMembershipFeeInfo; -import page.clab.api.global.config.SlackConfig; import page.clab.api.global.util.HttpReqResUtil; /** @@ -69,17 +69,14 @@ public class SlackServiceHelper { private final Slack slack; - private final String webUrl; - private final String apiUrl; - private final String color; + private final NotificationConfigProperties.CommonProperties commonProperties; private final Environment environment; private final AttributeStrategy attributeStrategy; - public SlackServiceHelper(SlackConfig slackConfig, Environment environment, AttributeStrategy attributeStrategy) { - this.slack = slackConfig.slack(); - this.webUrl = slackConfig.getWebUrl(); - this.apiUrl = slackConfig.getApiUrl(); - this.color = slackConfig.getColor(); + public SlackServiceHelper(NotificationConfigProperties notificationConfigProperties, Environment environment, + AttributeStrategy attributeStrategy) { + this.slack = Slack.getInstance(); + this.commonProperties = notificationConfigProperties.getCommon(); this.environment = environment; this.attributeStrategy = attributeStrategy; } @@ -104,7 +101,7 @@ public CompletableFuture sendSlackMessage(String webhookUrl, AlertType .blocks(List.of(blocks.getFirst())) .attachments(Collections.singletonList( Attachment.builder() - .color(color) + .color(commonProperties.getColor()) .blocks(blocks.subList(1, blocks.size())) .build() )).build(); @@ -328,10 +325,10 @@ private List createServerStartBlocks() { ))), actions(actions -> actions.elements(asElements( button(b -> b.text(plainText(pt -> pt.emoji(true).text("Web"))) - .url(webUrl) + .url(commonProperties.getWebUrl()) .value("click_web")), button(b -> b.text(plainText(pt -> pt.emoji(true).text("Swagger"))) - .url(apiUrl) + .url(commonProperties.getWebUrl()) .value("click_swagger")) ))) ); diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/event/ApplicationStartupListener.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/event/ApplicationStartupListener.java new file mode 100644 index 000000000..0f9f08a6d --- /dev/null +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/event/ApplicationStartupListener.java @@ -0,0 +1,23 @@ +package page.clab.api.global.common.notificationSetting.application.event; + +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; +import page.clab.api.global.common.notificationSetting.domain.GeneralAlertType; + +@Component +public class ApplicationStartupListener { + + private final ApplicationEventPublisher eventPublisher; + + public ApplicationStartupListener(ApplicationEventPublisher eventPublisher) { + this.eventPublisher = eventPublisher; + } + + @EventListener(ContextRefreshedEvent.class) + public void onApplicationEvent(ContextRefreshedEvent event) { + eventPublisher.publishEvent( + new NotificationEvent(this, GeneralAlertType.SERVER_START, null, null)); + } +} diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationEvent.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationEvent.java index 0d107d760..4493c8fa6 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationEvent.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationEvent.java @@ -8,15 +8,12 @@ @Getter public class NotificationEvent extends ApplicationEvent { - private final String webhookUrl; private final AlertType alertType; private final HttpServletRequest request; private final Object additionalData; - public NotificationEvent(Object source, String webhookUrl, AlertType alertType, HttpServletRequest request, - Object additionalData) { + public NotificationEvent(Object source, AlertType alertType, HttpServletRequest request, Object additionalData) { super(source); - this.webhookUrl = webhookUrl; this.alertType = alertType; this.request = request; this.additionalData = additionalData; diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java index 1f7be114b..89f49e457 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java @@ -1,19 +1,34 @@ package page.clab.api.global.common.notificationSetting.application.event; -import lombok.RequiredArgsConstructor; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import page.clab.api.global.common.notificationSetting.application.port.in.UpdateNotificationSettingUseCase; import page.clab.api.global.common.notificationSetting.application.port.out.NotificationSender; +import page.clab.api.global.common.notificationSetting.config.NotificationConfigProperties; +import page.clab.api.global.common.notificationSetting.config.NotificationConfigProperties.PlatformMapping; import page.clab.api.global.common.notificationSetting.domain.AlertType; import page.clab.api.global.common.notificationSetting.domain.NotificationSetting; @Component -@RequiredArgsConstructor public class NotificationListener { private final UpdateNotificationSettingUseCase updateNotificationSettingUseCase; - private final NotificationSender notificationSender; + private final Map notificationSenders; + private final NotificationConfigProperties notificationConfigProperties; + + public NotificationListener( + UpdateNotificationSettingUseCase updateNotificationSettingUseCase, + List notificationSenderList, + NotificationConfigProperties notificationConfigProperties) { + this.updateNotificationSettingUseCase = updateNotificationSettingUseCase; + this.notificationConfigProperties = notificationConfigProperties; + this.notificationSenders = notificationSenderList.stream() + .collect(Collectors.toMap(NotificationSender::getPlatformName, Function.identity())); + } @EventListener public void handleNotificationEvent(NotificationEvent event) { @@ -21,7 +36,41 @@ public void handleNotificationEvent(NotificationEvent event) { NotificationSetting setting = updateNotificationSettingUseCase.getOrCreateDefaultSetting(alertType); if (setting.isEnabled()) { - notificationSender.sendNotification(event); + List mappings = getMappingsForAlertType(alertType); + + if (mappings != null) { + for (NotificationConfigProperties.PlatformMapping mapping : mappings) { + NotificationSender sender = notificationSenders.get(mapping.getPlatform()); + if (sender != null) { + String webhookUrl = getWebhookUrl(mapping); + if (webhookUrl != null) { + sender.sendNotification(event, webhookUrl); + } + } + } + } + } + } + + private List getMappingsForAlertType(AlertType alertType) { + String categoryName = alertType.getCategory().name(); + List mappings = + notificationConfigProperties.getCategoryMappings().get(categoryName); + + if (mappings == null || mappings.isEmpty()) { + mappings = notificationConfigProperties.getDefaultMappings(); + } + return mappings; + } + + private String getWebhookUrl(NotificationConfigProperties.PlatformMapping mapping) { + String platform = mapping.getPlatform(); + String webhookKey = mapping.getWebhook(); + NotificationConfigProperties.PlatformConfig platformConfig = notificationConfigProperties.getPlatforms() + .get(platform); + if (platformConfig != null) { + return platformConfig.getWebhooks().get(webhookKey); } + return null; } } diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/NotificationSender.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/NotificationSender.java index c06f60811..23b9f55c3 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/NotificationSender.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/NotificationSender.java @@ -4,5 +4,7 @@ public interface NotificationSender { - void sendNotification(NotificationEvent event); + String getPlatformName(); + + void sendNotification(NotificationEvent event, String webhookUrl); } diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/config/NotificationConfig.java b/src/main/java/page/clab/api/global/common/notificationSetting/config/NotificationConfig.java index ddbfa22fc..b398d8377 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/config/NotificationConfig.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/config/NotificationConfig.java @@ -2,20 +2,12 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackNotificationSender; -import page.clab.api.global.common.notificationSetting.application.port.out.NotificationSender; @Configuration public class NotificationConfig { - private final SlackNotificationSender slackNotificationSender; - - public NotificationConfig(SlackNotificationSender slackNotificationSender) { - this.slackNotificationSender = slackNotificationSender; - } - @Bean - public NotificationSender notificationSender() { - return slackNotificationSender; + public NotificationConfigProperties notificationConfigProperties() { + return new NotificationConfigProperties(); } } diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/config/NotificationConfigProperties.java b/src/main/java/page/clab/api/global/common/notificationSetting/config/NotificationConfigProperties.java new file mode 100644 index 000000000..3cfe86331 --- /dev/null +++ b/src/main/java/page/clab/api/global/common/notificationSetting/config/NotificationConfigProperties.java @@ -0,0 +1,41 @@ +package page.clab.api.global.common.notificationSetting.config; + +import java.util.List; +import java.util.Map; +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "notification") +@Getter +@Setter +public class NotificationConfigProperties { + + private CommonProperties common; + private Map platforms; + private Map> categoryMappings; + private List defaultMappings; + + @Getter + @Setter + public static class CommonProperties { + private String webUrl; + private String apiUrl; + private String color; + } + + @Getter + @Setter + public static class PlatformConfig { + private Map webhooks; + } + + @Getter + @Setter + public static class PlatformMapping { + private String platform; + private String webhook; + } +} diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/domain/AlertCategory.java b/src/main/java/page/clab/api/global/common/notificationSetting/domain/AlertCategory.java new file mode 100644 index 000000000..1bbbb3a72 --- /dev/null +++ b/src/main/java/page/clab/api/global/common/notificationSetting/domain/AlertCategory.java @@ -0,0 +1,8 @@ +package page.clab.api.global.common.notificationSetting.domain; + +public enum AlertCategory { + + GENERAL, + SECURITY, + EXECUTIVES +} diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/domain/AlertType.java b/src/main/java/page/clab/api/global/common/notificationSetting/domain/AlertType.java index 34eac0469..8cb5326ad 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/domain/AlertType.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/domain/AlertType.java @@ -5,4 +5,6 @@ public interface AlertType { String getTitle(); String getDefaultMessage(); + + AlertCategory getCategory(); } diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/domain/ExecutivesAlertType.java b/src/main/java/page/clab/api/global/common/notificationSetting/domain/ExecutivesAlertType.java index 49e4365c4..cba124e18 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/domain/ExecutivesAlertType.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/domain/ExecutivesAlertType.java @@ -7,11 +7,12 @@ @AllArgsConstructor public enum ExecutivesAlertType implements AlertType { - NEW_APPLICATION("새 지원서", "New application has been submitted."), - NEW_BOARD("새 게시글", "New board has been posted."), - NEW_MEMBERSHIP_FEE("새 회비 신청", "New membership fee has been submitted."), - NEW_BOOK_LOAN_REQUEST("도서 대출 신청", "New book loan request has been submitted."); + NEW_APPLICATION("새 지원서", "New application has been submitted.", AlertCategory.EXECUTIVES), + NEW_BOARD("새 게시글", "New board has been posted.", AlertCategory.EXECUTIVES), + NEW_MEMBERSHIP_FEE("새 회비 신청", "New membership fee has been submitted.", AlertCategory.EXECUTIVES), + NEW_BOOK_LOAN_REQUEST("도서 대출 신청", "New book loan request has been submitted.", AlertCategory.EXECUTIVES); private final String title; private final String defaultMessage; + private final AlertCategory category; } diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/domain/GeneralAlertType.java b/src/main/java/page/clab/api/global/common/notificationSetting/domain/GeneralAlertType.java index 3b78b146d..f8854c56e 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/domain/GeneralAlertType.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/domain/GeneralAlertType.java @@ -7,10 +7,11 @@ @AllArgsConstructor public enum GeneralAlertType implements AlertType { - ADMIN_LOGIN("관리자 로그인", "Admin login."), - SERVER_START("서버 시작", "Server has been started."), - SERVER_ERROR("서버 에러", "Server error occurred."); + ADMIN_LOGIN("관리자 로그인", "Admin login.", AlertCategory.GENERAL), + SERVER_START("서버 시작", "Server has been started.", AlertCategory.GENERAL), + SERVER_ERROR("서버 에러", "Server error occurred.", AlertCategory.GENERAL); private final String title; private final String defaultMessage; + private final AlertCategory category; } diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/domain/PlatformType.java b/src/main/java/page/clab/api/global/common/notificationSetting/domain/PlatformType.java new file mode 100644 index 000000000..5ed4c6d32 --- /dev/null +++ b/src/main/java/page/clab/api/global/common/notificationSetting/domain/PlatformType.java @@ -0,0 +1,14 @@ +package page.clab.api.global.common.notificationSetting.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum PlatformType { + + SLACK("slack"), + DISCORD("discord"); + + private final String name; +} diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/domain/SecurityAlertType.java b/src/main/java/page/clab/api/global/common/notificationSetting/domain/SecurityAlertType.java index a8b1f6c7c..5a6fb8800 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/domain/SecurityAlertType.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/domain/SecurityAlertType.java @@ -7,20 +7,21 @@ @AllArgsConstructor public enum SecurityAlertType implements AlertType { - ABNORMAL_ACCESS("비정상적인 접근", "Unexpected access pattern detected."), - REPEATED_LOGIN_FAILURES("지속된 로그인 실패", "Multiple consecutive failed login attempts."), - DUPLICATE_LOGIN("중복 로그인", "Duplicate login attempt."), - API_DOCS_ACCESS("API 문서 접근", "API Documentation access attempt."), - ACTUATOR_ACCESS("Actuator 접근", "Actuator endpoint access attempt."), - UNAUTHORIZED_ACCESS("인가되지 않은 접근", "Unauthorized access attempt."), - BLACKLISTED_IP_ADDED("블랙리스트 IP 등록", "IP address has been added to the blacklist."), - BLACKLISTED_IP_REMOVED("블랙리스트 IP 해제", "IP address has been removed from the blacklist."), - ABNORMAL_ACCESS_IP_BLOCKED("비정상적인 접근 IP 차단", "Abnormal access IP has been blocked."), - ABNORMAL_ACCESS_IP_DELETED("비정상적인 접근 IP 삭제", "Abnormal access IP has been deleted."), - MEMBER_BANNED("멤버 밴 등록", "Member has been banned."), - MEMBER_UNBANNED("멤버 밴 해제", "Member has been unbanned."), - MEMBER_ROLE_CHANGED("멤버 권한 변경", "Member role has been changed."); + ABNORMAL_ACCESS("비정상적인 접근", "Unexpected access pattern detected.", AlertCategory.SECURITY), + REPEATED_LOGIN_FAILURES("지속된 로그인 실패", "Multiple consecutive failed login attempts.", AlertCategory.SECURITY), + DUPLICATE_LOGIN("중복 로그인", "Duplicate login attempt.", AlertCategory.SECURITY), + API_DOCS_ACCESS("API 문서 접근", "API Documentation access attempt.", AlertCategory.SECURITY), + ACTUATOR_ACCESS("Actuator 접근", "Actuator endpoint access attempt.", AlertCategory.SECURITY), + UNAUTHORIZED_ACCESS("인가되지 않은 접근", "Unauthorized access attempt.", AlertCategory.SECURITY), + BLACKLISTED_IP_ADDED("블랙리스트 IP 등록", "IP address has been added to the blacklist.", AlertCategory.SECURITY), + BLACKLISTED_IP_REMOVED("블랙리스트 IP 해제", "IP address has been removed from the blacklist.", AlertCategory.SECURITY), + ABNORMAL_ACCESS_IP_BLOCKED("비정상적인 접근 IP 차단", "Abnormal access IP has been blocked.", AlertCategory.SECURITY), + ABNORMAL_ACCESS_IP_DELETED("비정상적인 접근 IP 삭제", "Abnormal access IP has been deleted.", AlertCategory.SECURITY), + MEMBER_BANNED("멤버 밴 등록", "Member has been banned.", AlertCategory.SECURITY), + MEMBER_UNBANNED("멤버 밴 해제", "Member has been unbanned.", AlertCategory.SECURITY), + MEMBER_ROLE_CHANGED("멤버 권한 변경", "Member role has been changed.", AlertCategory.SECURITY); private final String title; private final String defaultMessage; + private final AlertCategory category; } diff --git a/src/main/java/page/clab/api/global/config/SecurityConfig.java b/src/main/java/page/clab/api/global/config/SecurityConfig.java index 5eee6a35d..d31e1ce21 100644 --- a/src/main/java/page/clab/api/global/config/SecurityConfig.java +++ b/src/main/java/page/clab/api/global/config/SecurityConfig.java @@ -7,6 +7,7 @@ import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; @@ -35,7 +36,6 @@ import page.clab.api.global.auth.jwt.JwtTokenProvider; import page.clab.api.global.auth.util.IpWhitelistValidator; import page.clab.api.global.common.file.application.FileService; -import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; import page.clab.api.global.filter.IPinfoSpringFilter; import page.clab.api.global.util.ApiLogger; import page.clab.api.global.util.HttpReqResUtil; @@ -53,7 +53,7 @@ public class SecurityConfig { private final ExternalCheckIpBlockedUseCase externalCheckIpBlockedUseCase; private final ExternalRegisterBlacklistIpUseCase externalRegisterBlacklistIpUseCase; private final ExternalRetrieveBlacklistIpUseCase externalRetrieveBlacklistIpUseCase; - private final SlackService slackService; + private final ApplicationEventPublisher eventPublisher; private final IpWhitelistValidator ipWhitelistValidator; private final WhitelistAccountProperties whitelistAccountProperties; private final WhitelistPatternsProperties whitelistPatternsProperties; @@ -91,17 +91,17 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { UsernamePasswordAuthenticationFilter.class ) .addFilterBefore( - new InvalidEndpointAccessFilter(slackService, fileURL, externalRegisterBlacklistIpUseCase, - externalRetrieveBlacklistIpUseCase), + new InvalidEndpointAccessFilter(fileURL, externalRegisterBlacklistIpUseCase, + externalRetrieveBlacklistIpUseCase, eventPublisher), UsernamePasswordAuthenticationFilter.class ) .addFilterBefore( - new CustomBasicAuthenticationFilter(authenticationManager, ipWhitelistValidator, slackService, - externalCheckIpBlockedUseCase, externalRetrieveBlacklistIpUseCase), + new CustomBasicAuthenticationFilter(authenticationManager, ipWhitelistValidator, + externalCheckIpBlockedUseCase, externalRetrieveBlacklistIpUseCase, eventPublisher), UsernamePasswordAuthenticationFilter.class ) .addFilterBefore( - new JwtAuthenticationFilter(slackService, jwtTokenProvider, externalManageRedisTokenUseCase, + new JwtAuthenticationFilter(jwtTokenProvider, eventPublisher, externalManageRedisTokenUseCase, externalCheckIpBlockedUseCase, externalRetrieveBlacklistIpUseCase), UsernamePasswordAuthenticationFilter.class ) diff --git a/src/main/java/page/clab/api/global/config/SlackConfig.java b/src/main/java/page/clab/api/global/config/SlackConfig.java deleted file mode 100644 index 31f1f6d63..000000000 --- a/src/main/java/page/clab/api/global/config/SlackConfig.java +++ /dev/null @@ -1,26 +0,0 @@ -package page.clab.api.global.config; - -import com.slack.api.Slack; -import lombok.Getter; -import lombok.Setter; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Setter -@Getter -@Configuration -@ConfigurationProperties(prefix = "slack") -public class SlackConfig { - - private String coreTeamWebhookUrl; - private String executivesWebhookUrl; - private String webUrl; - private String apiUrl; - private String color; - - @Bean - public Slack slack() { - return Slack.getInstance(); - } -} diff --git a/src/main/java/page/clab/api/global/handler/GlobalExceptionHandler.java b/src/main/java/page/clab/api/global/handler/GlobalExceptionHandler.java index 7c3ee1d64..ac269d054 100644 --- a/src/main/java/page/clab/api/global/handler/GlobalExceptionHandler.java +++ b/src/main/java/page/clab/api/global/handler/GlobalExceptionHandler.java @@ -14,6 +14,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.hibernate.query.sqm.UnknownPathException; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.dao.InvalidDataAccessApiUsageException; @@ -79,7 +80,8 @@ import page.clab.api.global.common.file.exception.FileUploadFailException; import page.clab.api.global.common.file.exception.InvalidFileAttributeException; import page.clab.api.global.common.file.exception.InvalidPathVariableException; -import page.clab.api.global.common.notificationSetting.adapter.out.slack.SlackService; +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; +import page.clab.api.global.common.notificationSetting.domain.GeneralAlertType; import page.clab.api.global.exception.CustomOptimisticLockingFailureException; import page.clab.api.global.exception.DecryptionException; import page.clab.api.global.exception.EncryptionException; @@ -96,7 +98,7 @@ @Slf4j public class GlobalExceptionHandler { - private final SlackService slackService; + private final ApplicationEventPublisher eventPublisher; @ExceptionHandler({ InvalidInformationException.class, @@ -230,7 +232,8 @@ public ErrorResponse conflictException(HttpServletResponse response, Exception.class }) public ApiResponse serverException(HttpServletRequest request, HttpServletResponse response, Exception e) { - slackService.sendServerErrorNotification(request, e); + eventPublisher.publishEvent( + new NotificationEvent(this, GeneralAlertType.SERVER_ERROR, request, e)); log.warn(e.getMessage()); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return ApiResponse.failure(); From 0ba7f82f1fffa87da8b24d9c74621b0d79c18492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Thu, 14 Nov 2024 02:38:48 +0900 Subject: [PATCH 07/24] =?UTF-8?q?refactor(Notification):=20SlackServiceHel?= =?UTF-8?q?per=20->=20SlackWebhookClient=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/out/slack/SlackNotificationSender.java | 4 ++-- .../{SlackServiceHelper.java => SlackWebhookClient.java} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/{SlackServiceHelper.java => SlackWebhookClient.java} (99%) diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackNotificationSender.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackNotificationSender.java index d733d79dc..2106b7d3d 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackNotificationSender.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackNotificationSender.java @@ -10,7 +10,7 @@ @RequiredArgsConstructor public class SlackNotificationSender implements NotificationSender { - private final SlackServiceHelper slackServiceHelper; + private final SlackWebhookClient slackWebhookClient; @Override public String getPlatformName() { @@ -19,7 +19,7 @@ public String getPlatformName() { @Override public void sendNotification(NotificationEvent event, String webhookUrl) { - slackServiceHelper.sendSlackMessage(webhookUrl, event.getAlertType(), event.getRequest(), + slackWebhookClient.sendSlackMessage(webhookUrl, event.getAlertType(), event.getRequest(), event.getAdditionalData()); } } diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackServiceHelper.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackWebhookClient.java similarity index 99% rename from src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackServiceHelper.java rename to src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackWebhookClient.java index a46d56714..1bfd9ffc9 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackServiceHelper.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackWebhookClient.java @@ -66,14 +66,14 @@ */ @Component @Slf4j -public class SlackServiceHelper { +public class SlackWebhookClient { private final Slack slack; private final NotificationConfigProperties.CommonProperties commonProperties; private final Environment environment; private final AttributeStrategy attributeStrategy; - public SlackServiceHelper(NotificationConfigProperties notificationConfigProperties, Environment environment, + public SlackWebhookClient(NotificationConfigProperties notificationConfigProperties, Environment environment, AttributeStrategy attributeStrategy) { this.slack = Slack.getInstance(); this.commonProperties = notificationConfigProperties.getCommon(); From 67fc420c47c6449314616214870d7f49e94d9095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Thu, 14 Nov 2024 03:03:32 +0900 Subject: [PATCH 08/24] =?UTF-8?q?refactor(Notification):=20SlackWebhookCli?= =?UTF-8?q?ent=20=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/out/slack/SlackWebhookClient.java | 243 ++++++++++-------- 1 file changed, 131 insertions(+), 112 deletions(-) diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackWebhookClient.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackWebhookClient.java index 1bfd9ffc9..f6149a688 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackWebhookClient.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackWebhookClient.java @@ -30,6 +30,7 @@ import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.core.env.Environment; +import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; @@ -46,7 +47,7 @@ import page.clab.api.global.util.HttpReqResUtil; /** - * {@code SlackServiceHelper}는 다양한 알림 유형에 따라 Slack 메시지를 구성하고 전송하는 클래스입니다. + * {@code SlackWebhookClient}는 다양한 알림 유형에 따라 Slack 메시지를 구성하고 전송하는 클래스입니다. * *

        주요 기능:

        *
          @@ -73,7 +74,8 @@ public class SlackWebhookClient { private final Environment environment; private final AttributeStrategy attributeStrategy; - public SlackWebhookClient(NotificationConfigProperties notificationConfigProperties, Environment environment, + public SlackWebhookClient(NotificationConfigProperties notificationConfigProperties, + Environment environment, AttributeStrategy attributeStrategy) { this.slack = Slack.getInstance(); this.commonProperties = notificationConfigProperties.getCommon(); @@ -96,18 +98,21 @@ public SlackWebhookClient(NotificationConfigProperties notificationConfigPropert public CompletableFuture sendSlackMessage(String webhookUrl, AlertType alertType, HttpServletRequest request, Object additionalData) { List blocks = createBlocks(alertType, request, additionalData); + return CompletableFuture.supplyAsync(() -> { Payload payload = Payload.builder() - .blocks(List.of(blocks.getFirst())) + .blocks(Collections.singletonList(blocks.getFirst())) .attachments(Collections.singletonList( Attachment.builder() .color(commonProperties.getColor()) .blocks(blocks.subList(1, blocks.size())) .build() - )).build(); + )) + .build(); + try { WebhookResponse response = slack.send(webhookUrl, payload); - if (response.getCode() == 200) { + if (response.getCode() == HttpStatus.OK.value()) { return true; } else { log.error("Slack notification failed: {}", response.getMessage()); @@ -134,106 +139,115 @@ public List createBlocks(AlertType alertType, HttpServletRequest re if (alertType instanceof SecurityAlertType) { return createSecurityAlertBlocks(request, alertType, additionalData.toString()); } else if (alertType instanceof GeneralAlertType) { - switch ((GeneralAlertType) alertType) { - case ADMIN_LOGIN: - if (additionalData instanceof MemberLoginInfoDto) { - return createAdminLoginBlocks(request, (MemberLoginInfoDto) additionalData); - } - break; - case SERVER_START: - return createServerStartBlocks(); - case SERVER_ERROR: - if (additionalData instanceof Exception) { - return createErrorBlocks(request, (Exception) additionalData); - } - break; - default: - log.error("Unknown alert type: {}", alertType); - return List.of(); - } + return createGeneralAlertBlocks((GeneralAlertType) alertType, request, additionalData); } else if (alertType instanceof ExecutivesAlertType) { - switch ((ExecutivesAlertType) alertType) { - case NEW_APPLICATION: - if (additionalData instanceof ApplicationRequestDto) { - return createApplicationBlocks((ApplicationRequestDto) additionalData); - } - break; - case NEW_BOARD: - if (additionalData instanceof SlackBoardInfo) { - return createBoardBlocks((SlackBoardInfo) additionalData); - } - break; - case NEW_MEMBERSHIP_FEE: - if (additionalData instanceof SlackMembershipFeeInfo) { - return createMembershipFeeBlocks((SlackMembershipFeeInfo) additionalData); - } - break; - case NEW_BOOK_LOAN_REQUEST: - if (additionalData instanceof SlackBookLoanRecordInfo) { - return createBookLoanRecordBlocks((SlackBookLoanRecordInfo) additionalData); - } - break; - default: - log.error("Unknown alert type: {}", alertType); - return List.of(); - } + return createExecutivesAlertBlocks((ExecutivesAlertType) alertType, additionalData); + } else { + log.error("Unknown alert type: {}", alertType); + return Collections.emptyList(); + } + } + + // 일반 알림 유형에 따른 블록 생성 + private List createGeneralAlertBlocks(GeneralAlertType alertType, HttpServletRequest request, + Object additionalData) { + switch (alertType) { + case ADMIN_LOGIN: + if (additionalData instanceof MemberLoginInfoDto) { + return createAdminLoginBlocks(request, (MemberLoginInfoDto) additionalData); + } + break; + case SERVER_START: + return createServerStartBlocks(); + case SERVER_ERROR: + if (additionalData instanceof Exception) { + return createErrorBlocks(request, (Exception) additionalData); + } + break; + default: + log.error("Unknown general alert type: {}", alertType); } - return List.of(); + return Collections.emptyList(); + } + + // 운영진 알림 유형에 따른 블록 생성 + private List createExecutivesAlertBlocks(ExecutivesAlertType alertType, Object additionalData) { + switch (alertType) { + case NEW_APPLICATION: + if (additionalData instanceof ApplicationRequestDto) { + return createApplicationBlocks((ApplicationRequestDto) additionalData); + } + break; + case NEW_BOARD: + if (additionalData instanceof SlackBoardInfo) { + return createBoardBlocks((SlackBoardInfo) additionalData); + } + break; + case NEW_MEMBERSHIP_FEE: + if (additionalData instanceof SlackMembershipFeeInfo) { + return createMembershipFeeBlocks((SlackMembershipFeeInfo) additionalData); + } + break; + case NEW_BOOK_LOAN_REQUEST: + if (additionalData instanceof SlackBookLoanRecordInfo) { + return createBookLoanRecordBlocks((SlackBookLoanRecordInfo) additionalData); + } + break; + default: + log.error("Unknown executives alert type: {}", alertType); + } + return Collections.emptyList(); } private List createErrorBlocks(HttpServletRequest request, Exception e) { String httpMethod = request.getMethod(); - String requestUrl = request.getRequestURI(); - String queryString = request.getQueryString(); - String fullUrl = queryString == null ? requestUrl : requestUrl + "?" + queryString; + String fullUrl = getFullUrl(request); String username = getUsername(request); - - String errorMessage = e.getMessage() == null ? "No error message provided" : e.getMessage(); + String errorMessage = Optional.ofNullable(e.getMessage()).orElse("No error message provided"); String detailedMessage = extractMessageAfterException(errorMessage); + log.error("Server Error: {}", detailedMessage); + return Arrays.asList( - section(section -> section.text(markdownText(":firecracker: *Server Error*"))), - section(section -> section.fields(Arrays.asList( + section(s -> s.text(markdownText(":firecracker: *Server Error*"))), + section(s -> s.fields(Arrays.asList( markdownText("*User:*\n" + username), markdownText("*Endpoint:*\n[" + httpMethod + "] " + fullUrl) ))), - section(section -> section.text(markdownText("*Error Message:*\n" + detailedMessage))), - section(section -> section.text(markdownText("*Stack Trace:*\n```" + getStackTraceSummary(e) + "```"))) + section(s -> s.text(markdownText("*Error Message:*\n" + detailedMessage))), + section(s -> s.text(markdownText("*Stack Trace:*\n```" + getStackTraceSummary(e) + "```"))) ); } private List createSecurityAlertBlocks(HttpServletRequest request, AlertType alertType, String additionalMessage) { - String clientIpAddress = HttpReqResUtil.getClientIpAddressIfServletRequestExist(); - String requestUrl = request.getRequestURI(); - String queryString = request.getQueryString(); - String fullUrl = queryString == null ? requestUrl : requestUrl + "?" + queryString; + String clientIp = HttpReqResUtil.getClientIpAddressIfServletRequestExist(); + String fullUrl = getFullUrl(request); String username = getUsername(request); String location = getLocation(request); return Arrays.asList( - section(section -> section.text(markdownText(String.format(":imp: *%s*", alertType.getTitle())))), - section(section -> section.fields(Arrays.asList( + section(s -> s.text(markdownText(":imp: *" + alertType.getTitle() + "*"))), + section(s -> s.fields(Arrays.asList( markdownText("*User:*\n" + username), - markdownText("*IP Address:*\n" + clientIpAddress), + markdownText("*IP Address:*\n" + clientIp), markdownText("*Location:*\n" + location), markdownText("*Endpoint:*\n" + fullUrl) ))), - section(section -> section.text( + section(s -> s.text( markdownText("*Details:*\n" + alertType.getDefaultMessage() + "\n" + additionalMessage))) ); } private List createAdminLoginBlocks(HttpServletRequest request, MemberLoginInfoDto loginMember) { - String clientIpAddress = HttpReqResUtil.getClientIpAddressIfServletRequestExist(); + String clientIp = HttpReqResUtil.getClientIpAddressIfServletRequestExist(); String location = getLocation(request); return Arrays.asList( - section(section -> section.text( - markdownText(String.format(":mechanic: *%s Login*", loginMember.getRole().getDescription())))), - section(section -> section.fields(Arrays.asList( + section(s -> s.text(markdownText(":mechanic: *" + loginMember.getRole().getDescription() + " Login*"))), + section(s -> s.fields(Arrays.asList( markdownText("*User:*\n" + loginMember.getMemberId() + " " + loginMember.getMemberName()), - markdownText("*IP Address:*\n" + clientIpAddress), + markdownText("*IP Address:*\n" + clientIp), markdownText("*Location:*\n" + location) ))) ); @@ -242,8 +256,8 @@ private List createAdminLoginBlocks(HttpServletRequest request, Mem private List createApplicationBlocks(ApplicationRequestDto requestDto) { List blocks = new ArrayList<>(); - blocks.add(section(section -> section.text(markdownText(":sparkles: *동아리 지원*")))); - blocks.add(section(section -> section.fields(Arrays.asList( + blocks.add(section(s -> s.text(markdownText(":sparkles: *동아리 지원*")))); + blocks.add(section(s -> s.fields(Arrays.asList( markdownText("*구분:*\n" + requestDto.getApplicationType().getDescription()), markdownText("*학번:*\n" + requestDto.getStudentId()), markdownText("*이름:*\n" + requestDto.getName()), @@ -252,51 +266,51 @@ private List createApplicationBlocks(ApplicationRequestDto requestD )))); if (requestDto.getGithubUrl() != null && !requestDto.getGithubUrl().isEmpty()) { - blocks.add(actions(actions -> actions.elements(asElements( + blocks.add(actions(a -> a.elements(asElements( button(b -> b.text(plainText(pt -> pt.emoji(true).text("Github"))) .url(requestDto.getGithubUrl()) .actionId("click_github")) )))); } + return blocks; } private List createBoardBlocks(SlackBoardInfo board) { - List blocks = new ArrayList<>(); - - blocks.add(section(section -> section.text(markdownText(":writing_hand: *새 게시글*")))); - blocks.add(section(section -> section.fields(Arrays.asList( - markdownText("*제목:*\n" + board.getTitle()), - markdownText("*분류:*\n" + board.getCategory()), - markdownText("*작성자:*\n" + board.getUsername()) - )))); - return blocks; + return Arrays.asList( + section(s -> s.text(markdownText(":writing_hand: *새 게시글*"))), + section(s -> s.fields(Arrays.asList( + markdownText("*제목:*\n" + board.getTitle()), + markdownText("*분류:*\n" + board.getCategory()), + markdownText("*작성자:*\n" + board.getUsername()) + ))) + ); } - private List createMembershipFeeBlocks(SlackMembershipFeeInfo additionalData) { - String username = additionalData.getMemberId() + " " + additionalData.getMemberName(); + private List createMembershipFeeBlocks(SlackMembershipFeeInfo data) { + String username = data.getMemberId() + " " + data.getMemberName(); return Arrays.asList( - section(section -> section.text(markdownText(":dollar: *회비 신청*"))), - section(section -> section.fields(Arrays.asList( + section(s -> s.text(markdownText(":dollar: *회비 신청*"))), + section(s -> s.fields(Arrays.asList( markdownText("*신청자:*\n" + username), - markdownText("*분류:*\n" + additionalData.getCategory()), - markdownText("*금액:*\n" + additionalData.getAmount() + "원") + markdownText("*분류:*\n" + data.getCategory()), + markdownText("*금액:*\n" + data.getAmount() + "원") ))), - section(section -> section.text(markdownText("*Content:*\n" + additionalData.getContent()))) + section(s -> s.text(markdownText("*Content:*\n" + data.getContent()))) ); } - private List createBookLoanRecordBlocks(SlackBookLoanRecordInfo additionalData) { - String username = additionalData.getMemberId() + " " + additionalData.getMemberName(); + private List createBookLoanRecordBlocks(SlackBookLoanRecordInfo data) { + String username = data.getMemberId() + " " + data.getMemberName(); return Arrays.asList( - section(section -> section.text(markdownText(":books: *도서 대여 신청*"))), - section(section -> section.fields(Arrays.asList( - markdownText("*도서명:*\n" + additionalData.getBookTitle()), - markdownText("*분류:*\n" + additionalData.getCategory()), + section(s -> s.text(markdownText(":books: *도서 대여 신청*"))), + section(s -> s.fields(Arrays.asList( + markdownText("*도서명:*\n" + data.getBookTitle()), + markdownText("*분류:*\n" + data.getCategory()), markdownText("*신청자:*\n" + username), - markdownText("*상태:*\n" + (additionalData.isAvailable() ? "대여 가능" : "대여 중")) + markdownText("*상태:*\n" + (data.isAvailable() ? "대여 가능" : "대여 중")) ))) ); } @@ -306,38 +320,43 @@ private List createServerStartBlocks() { String jdkVersion = System.getProperty("java.version"); OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean(); - int availableProcessors = osBean.getAvailableProcessors(); + int processors = osBean.getAvailableProcessors(); double systemLoadAverage = osBean.getSystemLoadAverage(); - double cpuUsage = ((systemLoadAverage / availableProcessors) * 100); + double cpuUsage = (systemLoadAverage / processors) * 100; MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); - MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage(); - String memoryInfo = formatMemoryUsage(heapMemoryUsage); + String memoryInfo = formatMemoryUsage(memoryMXBean.getHeapMemoryUsage()); return Arrays.asList( - section(section -> section.text(markdownText("*:battery: Server Started*"))), - section(section -> section.fields(Arrays.asList( + section(s -> s.text(markdownText(":battery: *Server Started*"))), + section(s -> s.fields(Arrays.asList( markdownText("*Environment:* \n" + environment.getProperty("spring.profiles.active")), markdownText("*OS:* \n" + osInfo), markdownText("*JDK Version:* \n" + jdkVersion), markdownText("*CPU Usage:* \n" + String.format("%.2f%%", cpuUsage)), markdownText("*Memory Usage:* \n" + memoryInfo) ))), - actions(actions -> actions.elements(asElements( + actions(a -> a.elements(asElements( button(b -> b.text(plainText(pt -> pt.emoji(true).text("Web"))) .url(commonProperties.getWebUrl()) .value("click_web")), button(b -> b.text(plainText(pt -> pt.emoji(true).text("Swagger"))) - .url(commonProperties.getWebUrl()) + .url(commonProperties.getApiUrl()) .value("click_swagger")) ))) ); } + private String getFullUrl(HttpServletRequest request) { + String requestUrl = request.getRequestURI(); + String queryString = request.getQueryString(); + return queryString == null ? requestUrl : requestUrl + "?" + queryString; + } + private String extractMessageAfterException(String message) { String exceptionIndicator = "Exception:"; - int exceptionIndex = message.indexOf(exceptionIndicator); - return exceptionIndex == -1 ? message : message.substring(exceptionIndex + exceptionIndicator.length()).trim(); + int index = message.indexOf(exceptionIndicator); + return index == -1 ? message : message.substring(index + exceptionIndicator.length()).trim(); } private String getStackTraceSummary(Exception e) { @@ -347,17 +366,17 @@ private String getStackTraceSummary(Exception e) { .collect(Collectors.joining("\n")); } - private String formatMemoryUsage(MemoryUsage memoryUsage) { - long usedMemory = memoryUsage.getUsed() / (1024 * 1024); - long maxMemory = memoryUsage.getMax() / (1024 * 1024); - return String.format("%dMB / %dMB (%.2f%%)", usedMemory, maxMemory, ((double) usedMemory / maxMemory) * 100); + private String formatMemoryUsage(MemoryUsage usage) { + long used = usage.getUsed() / (1024 * 1024); + long max = usage.getMax() / (1024 * 1024); + return String.format("%dMB / %dMB (%.2f%%)", used, max, ((double) used / max) * 100); } private @NotNull String getUsername(HttpServletRequest request) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); return Optional.ofNullable(request.getAttribute("member")) .map(Object::toString) - .orElseGet(() -> Optional.ofNullable(authentication) + .orElseGet(() -> Optional.ofNullable(auth) .map(Authentication::getName) .orElse("anonymous")); } From dd95f801774269dc60448406217a6a71b9b1f775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Thu, 14 Nov 2024 14:30:37 +0900 Subject: [PATCH 09/24] =?UTF-8?q?feat(Notification):=20Discord=20=EC=9B=B9?= =?UTF-8?q?=ED=9B=85=20=EB=B0=8F=20=EC=9B=B9=ED=9B=85=20=ED=81=B4=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EC=96=B8=ED=8A=B8=20=EC=9D=B8=ED=84=B0=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../discord/DiscordNotificationSender.java | 25 ++ .../out/discord/DiscordWebhookClient.java | 420 ++++++++++++++++++ .../out/slack/SlackNotificationSender.java | 2 +- .../adapter/out/slack/SlackWebhookClient.java | 32 +- .../application/port/out/WebhookClient.java | 11 + .../domain/DiscordMessage.java | 21 - 6 files changed, 476 insertions(+), 35 deletions(-) create mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordNotificationSender.java create mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordWebhookClient.java create mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/WebhookClient.java delete mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/domain/DiscordMessage.java diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordNotificationSender.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordNotificationSender.java new file mode 100644 index 000000000..0ba9d815a --- /dev/null +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordNotificationSender.java @@ -0,0 +1,25 @@ +package page.clab.api.global.common.notificationSetting.adapter.out.discord; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; +import page.clab.api.global.common.notificationSetting.application.port.out.NotificationSender; +import page.clab.api.global.common.notificationSetting.domain.PlatformType; + +@Component +@RequiredArgsConstructor +public class DiscordNotificationSender implements NotificationSender { + + private final DiscordWebhookClient discordWebhookClient; + + @Override + public String getPlatformName() { + return PlatformType.DISCORD.getName(); + } + + @Override + public void sendNotification(NotificationEvent event, String webhookUrl) { + discordWebhookClient.sendMessage(webhookUrl, event.getAlertType(), event.getRequest(), + event.getAdditionalData()); + } +} diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordWebhookClient.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordWebhookClient.java new file mode 100644 index 000000000..4918454af --- /dev/null +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordWebhookClient.java @@ -0,0 +1,420 @@ +package page.clab.api.global.common.notificationSetting.adapter.out.discord; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.ipinfo.api.model.IPResponse; +import io.ipinfo.spring.strategies.attribute.AttributeStrategy; +import jakarta.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryUsage; +import java.lang.management.OperatingSystemMXBean; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.core.env.Environment; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import page.clab.api.domain.hiring.application.application.dto.request.ApplicationRequestDto; +import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberLoginInfoDto; +import page.clab.api.global.common.notificationSetting.application.port.out.WebhookClient; +import page.clab.api.global.common.notificationSetting.config.NotificationConfigProperties; +import page.clab.api.global.common.notificationSetting.domain.AlertType; +import page.clab.api.global.common.notificationSetting.domain.ExecutivesAlertType; +import page.clab.api.global.common.notificationSetting.domain.GeneralAlertType; +import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; +import page.clab.api.global.common.notificationSetting.domain.SlackBoardInfo; +import page.clab.api.global.common.notificationSetting.domain.SlackBookLoanRecordInfo; +import page.clab.api.global.common.notificationSetting.domain.SlackMembershipFeeInfo; +import page.clab.api.global.util.HttpReqResUtil; + +/** + * {@code DiscordWebhookClient}는 다양한 알림 유형에 따라 Discord 메시지를 구성하고 전송하는 클래스입니다. + * + *

          주요 기능:

          + *
            + *
          • {@link #sendMessage(String, AlertType, HttpServletRequest, Object)}: Discord에 알림 메시지를 비동기적으로 전송
          • + *
          • {@link #createEmbeds(AlertType, HttpServletRequest, Object)}: 알림 유형에 따라 Discord 메시지 임베드 생성
          • + *
          • 다양한 알림 유형에 맞는 메시지 형식을 생성하는 전용 메서드
          • + *
          + * + *

          Discord Webhook API를 사용하여 웹훅 URL을 통해 메시지를 전송하며, 메시지 전송 실패 시 로그에 오류를 기록합니다.

          + * + *

          AlertType을 기반으로 여러 도메인에서 발생하는 이벤트를 Discord를 통해 모니터링할 수 있도록 지원하며, + * Discord 알림은 주로 서버 이벤트, 보안 경고, 신규 신청, 관리자 로그인 등의 이벤트를 다룹니다.

          + * + * @see HttpClient + * @see HttpRequest + * @see HttpResponse + */ +@Component +@Slf4j +public class DiscordWebhookClient implements WebhookClient { + + private final HttpClient httpClient; + private final ObjectMapper objectMapper; + private final NotificationConfigProperties.CommonProperties commonProperties; + private final Environment environment; + private final AttributeStrategy attributeStrategy; + + public DiscordWebhookClient(NotificationConfigProperties notificationConfigProperties, + Environment environment, + AttributeStrategy attributeStrategy) { + this.httpClient = HttpClient.newHttpClient(); + this.objectMapper = new ObjectMapper(); + this.commonProperties = notificationConfigProperties.getCommon(); + this.environment = environment; + this.attributeStrategy = attributeStrategy; + } + + /** + * Discord에 알림 메시지를 비동기적으로 전송합니다. + * + * @param webhookUrl 메시지를 보낼 Discord 웹훅 URL + * @param alertType 알림 유형을 나타내는 {@link AlertType} + * @param request HttpServletRequest 객체, 클라이언트 요청 정보 + * @param additionalData 추가 데이터 + * @return 메시지 전송 성공 여부를 나타내는 CompletableFuture + */ + public CompletableFuture sendMessage(String webhookUrl, AlertType alertType, + HttpServletRequest request, Object additionalData) { + Map payload = createPayload(alertType, request, additionalData); + + return CompletableFuture.supplyAsync(() -> { + try { + String jsonPayload = objectMapper.writeValueAsString(payload); + + HttpRequest httpRequest = HttpRequest.newBuilder() + .uri(URI.create(webhookUrl)) + .header("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .POST(HttpRequest.BodyPublishers.ofString(jsonPayload)) + .build(); + + HttpResponse response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() == HttpStatus.NO_CONTENT.value()) { + return true; + } else { + log.error("Discord notification failed: {}", response.body()); + return false; + } + } catch (IOException | InterruptedException e) { + log.error("Failed to send Discord message: {}", e.getMessage(), e); + return false; + } + }); + } + + /** + * 알림 유형과 요청 정보, 추가 데이터를 사용하여 Discord 메시지 페이로드를 생성합니다. + * + * @param alertType 알림 유형 + * @param request 클라이언트 요청 정보 + * @param additionalData 추가 데이터 + * @return 생성된 페이로드 맵 + */ + public Map createPayload(AlertType alertType, HttpServletRequest request, Object additionalData) { + List> embeds = createEmbeds(alertType, request, additionalData); + + Map payload = new HashMap<>(); + payload.put("embeds", embeds); + + return payload; + } + + /** + * 특정 알림 유형과 요청 정보 및 추가 데이터를 사용하여 Discord 메시지의 임베드를 생성합니다. + * + * @param alertType 알림 유형 + * @param request HttpServletRequest 객체 + * @param additionalData 추가 데이터 + * @return 생성된 임베드 목록 + */ + public List> createEmbeds(AlertType alertType, HttpServletRequest request, + Object additionalData) { + switch (alertType) { + case SecurityAlertType securityAlertType -> { + return createSecurityAlertEmbeds(request, alertType, additionalData.toString()); + } + case GeneralAlertType generalAlertType -> { + return createGeneralAlertEmbeds(generalAlertType, request, additionalData); + } + case ExecutivesAlertType executivesAlertType -> { + return createExecutivesAlertEmbeds(executivesAlertType, additionalData); + } + case null, default -> { + log.error("Unknown alert type: {}", alertType); + return Collections.emptyList(); + } + } + } + + // 일반 알림 유형에 따른 임베드 생성 + private List> createGeneralAlertEmbeds(GeneralAlertType alertType, HttpServletRequest request, + Object additionalData) { + switch (alertType) { + case ADMIN_LOGIN: + if (additionalData instanceof MemberLoginInfoDto) { + return createAdminLoginEmbeds(request, (MemberLoginInfoDto) additionalData); + } + break; + case SERVER_START: + return createServerStartEmbeds(); + case SERVER_ERROR: + if (additionalData instanceof Exception) { + return createErrorEmbeds(request, (Exception) additionalData); + } + break; + default: + log.error("Unknown general alert type: {}", alertType); + } + return Collections.emptyList(); + } + + // 운영진 알림 유형에 따른 임베드 생성 + private List> createExecutivesAlertEmbeds(ExecutivesAlertType alertType, + Object additionalData) { + switch (alertType) { + case NEW_APPLICATION: + if (additionalData instanceof ApplicationRequestDto) { + return createApplicationEmbeds((ApplicationRequestDto) additionalData); + } + break; + case NEW_BOARD: + if (additionalData instanceof SlackBoardInfo) { + return createBoardEmbeds((SlackBoardInfo) additionalData); + } + break; + case NEW_MEMBERSHIP_FEE: + if (additionalData instanceof SlackMembershipFeeInfo) { + return createMembershipFeeEmbeds((SlackMembershipFeeInfo) additionalData); + } + break; + case NEW_BOOK_LOAN_REQUEST: + if (additionalData instanceof SlackBookLoanRecordInfo) { + return createBookLoanRecordEmbeds((SlackBookLoanRecordInfo) additionalData); + } + break; + default: + log.error("Unknown executives alert type: {}", alertType); + } + return Collections.emptyList(); + } + + private List> createErrorEmbeds(HttpServletRequest request, Exception e) { + String httpMethod = request.getMethod(); + String fullUrl = getFullUrl(request); + String username = getUsername(request); + String errorMessage = Optional.ofNullable(e.getMessage()).orElse("No error message provided"); + String detailedMessage = extractMessageAfterException(errorMessage); + + log.error("Server Error: {}", detailedMessage); + + Map embed = new HashMap<>(); + embed.put("title", ":firecracker: Server Error"); + embed.put("color", 0xFF0000); + embed.put("fields", Arrays.asList( + createField("User", username, true), + createField("Endpoint", "[" + httpMethod + "] " + fullUrl, true), + createField("Error Message", detailedMessage, false), + createField("Stack Trace", "```" + getStackTraceSummary(e) + "```", false) + )); + + return Collections.singletonList(embed); + } + + private List> createSecurityAlertEmbeds(HttpServletRequest request, AlertType alertType, + String additionalMessage) { + String clientIp = HttpReqResUtil.getClientIpAddressIfServletRequestExist(); + String fullUrl = getFullUrl(request); + String username = getUsername(request); + String location = getLocation(request); + + Map embed = new HashMap<>(); + embed.put("title", ":imp: " + alertType.getTitle()); + embed.put("color", 0xFFA500); + embed.put("fields", Arrays.asList( + createField("User", username, true), + createField("IP Address", clientIp, true), + createField("Location", location, true), + createField("Endpoint", fullUrl, true), + createField("Details", alertType.getDefaultMessage() + "\n" + additionalMessage, false) + )); + + return Collections.singletonList(embed); + } + + private List> createAdminLoginEmbeds(HttpServletRequest request, + MemberLoginInfoDto loginMember) { + String clientIp = HttpReqResUtil.getClientIpAddressIfServletRequestExist(); + String location = getLocation(request); + + Map embed = new HashMap<>(); + embed.put("title", ":mechanic: " + loginMember.getRole().getDescription() + " Login"); + embed.put("color", 0x00FF00); + embed.put("fields", Arrays.asList( + createField("User", loginMember.getMemberId() + " " + loginMember.getMemberName(), true), + createField("IP Address", clientIp, true), + createField("Location", location, true) + )); + + return Collections.singletonList(embed); + } + + private List> createApplicationEmbeds(ApplicationRequestDto requestDto) { + Map embed = new HashMap<>(); + embed.put("title", ":sparkles: 동아리 지원"); + embed.put("color", 0x00BFFF); + embed.put("fields", Arrays.asList( + createField("구분", requestDto.getApplicationType().getDescription(), true), + createField("학번", requestDto.getStudentId(), true), + createField("이름", requestDto.getName(), true), + createField("학년", requestDto.getGrade() + "학년", true), + createField("관심 분야", requestDto.getInterests(), false) + )); + + if (requestDto.getGithubUrl() != null && !requestDto.getGithubUrl().isEmpty()) { + embed.put("description", "[Github](" + requestDto.getGithubUrl() + ")"); + } + + return Collections.singletonList(embed); + } + + private List> createBoardEmbeds(SlackBoardInfo board) { + Map embed = new HashMap<>(); + embed.put("title", ":writing_hand: 새 게시글"); + embed.put("color", 0x008080); + embed.put("fields", Arrays.asList( + createField("제목", board.getTitle(), true), + createField("분류", board.getCategory(), true), + createField("작성자", board.getUsername(), true) + )); + + return Collections.singletonList(embed); + } + + private List> createMembershipFeeEmbeds(SlackMembershipFeeInfo data) { + String username = data.getMemberId() + " " + data.getMemberName(); + + Map embed = new HashMap<>(); + embed.put("title", ":dollar: 회비 신청"); + embed.put("color", 0xFFFF00); + embed.put("fields", Arrays.asList( + createField("신청자", username, true), + createField("분류", data.getCategory(), true), + createField("금액", data.getAmount() + "원", true), + createField("Content", data.getContent(), false) + )); + + return Collections.singletonList(embed); + } + + private List> createBookLoanRecordEmbeds(SlackBookLoanRecordInfo data) { + String username = data.getMemberId() + " " + data.getMemberName(); + + Map embed = new HashMap<>(); + embed.put("title", ":books: 도서 대여 신청"); + embed.put("color", 0x800080); + embed.put("fields", Arrays.asList( + createField("도서명", data.getBookTitle(), true), + createField("분류", data.getCategory(), true), + createField("신청자", username, true), + createField("상태", data.isAvailable() ? "대여 가능" : "대여 중", true) + )); + + return Collections.singletonList(embed); + } + + private List> createServerStartEmbeds() { + String osInfo = System.getProperty("os.name") + " " + System.getProperty("os.version"); + String jdkVersion = System.getProperty("java.version"); + + OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean(); + int processors = osBean.getAvailableProcessors(); + double systemLoadAverage = osBean.getSystemLoadAverage(); + double cpuUsage = (systemLoadAverage / processors) * 100; + + MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + String memoryInfo = formatMemoryUsage(memoryMXBean.getHeapMemoryUsage()); + + Map embed = new HashMap<>(); + embed.put("title", ":battery: Server Started"); + embed.put("color", 0x00FF7F); + embed.put("fields", Arrays.asList( + createField("Environment", environment.getProperty("spring.profiles.active"), true), + createField("OS", osInfo, true), + createField("JDK Version", jdkVersion, true), + createField("CPU Usage", String.format("%.2f%%", cpuUsage), true), + createField("Memory Usage", memoryInfo, true) + )); + + // 버튼 대신 링크를 설명에 추가 + embed.put("description", + "[Web](" + commonProperties.getWebUrl() + ") | [Swagger](" + commonProperties.getApiUrl() + ")"); + + return Collections.singletonList(embed); + } + + // 유틸리티 메서드들 + + private Map createField(String name, String value, boolean inline) { + Map field = new HashMap<>(); + field.put("name", name); + field.put("value", value); + field.put("inline", inline); + return field; + } + + private String getFullUrl(HttpServletRequest request) { + String requestUrl = request.getRequestURI(); + String queryString = request.getQueryString(); + return queryString == null ? requestUrl : requestUrl + "?" + queryString; + } + + private String extractMessageAfterException(String message) { + String exceptionIndicator = "Exception:"; + int index = message.indexOf(exceptionIndicator); + return index == -1 ? message : message.substring(index + exceptionIndicator.length()).trim(); + } + + private String getStackTraceSummary(Exception e) { + return Arrays.stream(e.getStackTrace()) + .limit(10) + .map(StackTraceElement::toString) + .collect(Collectors.joining("\n")); + } + + private String formatMemoryUsage(MemoryUsage usage) { + long used = usage.getUsed() / (1024 * 1024); + long max = usage.getMax() / (1024 * 1024); + return String.format("%dMB / %dMB (%.2f%%)", used, max, ((double) used / max) * 100); + } + + private @NotNull String getUsername(HttpServletRequest request) { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + return Optional.ofNullable(request.getAttribute("member")) + .map(Object::toString) + .orElseGet(() -> Optional.ofNullable(auth) + .map(Authentication::getName) + .orElse("anonymous")); + } + + private @NotNull String getLocation(HttpServletRequest request) { + IPResponse ipResponse = attributeStrategy.getAttribute(request); + return ipResponse == null ? "Unknown" : ipResponse.getCountryName() + ", " + ipResponse.getCity(); + } +} diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackNotificationSender.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackNotificationSender.java index 2106b7d3d..8e983bdf3 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackNotificationSender.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackNotificationSender.java @@ -19,7 +19,7 @@ public String getPlatformName() { @Override public void sendNotification(NotificationEvent event, String webhookUrl) { - slackWebhookClient.sendSlackMessage(webhookUrl, event.getAlertType(), event.getRequest(), + slackWebhookClient.sendMessage(webhookUrl, event.getAlertType(), event.getRequest(), event.getAdditionalData()); } } diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackWebhookClient.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackWebhookClient.java index f6149a688..abb666743 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackWebhookClient.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackWebhookClient.java @@ -36,6 +36,7 @@ import org.springframework.stereotype.Component; import page.clab.api.domain.hiring.application.application.dto.request.ApplicationRequestDto; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberLoginInfoDto; +import page.clab.api.global.common.notificationSetting.application.port.out.WebhookClient; import page.clab.api.global.common.notificationSetting.config.NotificationConfigProperties; import page.clab.api.global.common.notificationSetting.domain.AlertType; import page.clab.api.global.common.notificationSetting.domain.ExecutivesAlertType; @@ -51,7 +52,7 @@ * *

          주요 기능:

          *
            - *
          • {@link #sendSlackMessage(String, AlertType, HttpServletRequest, Object)}: Slack에 알림 메시지를 비동기적으로 전송
          • + *
          • {@link #sendMessage(String, AlertType, HttpServletRequest, Object)}: Slack에 알림 메시지를 비동기적으로 전송
          • *
          • {@link #createBlocks(AlertType, HttpServletRequest, Object)}: 알림 유형에 따라 Slack 메시지 블록 생성
          • *
          • 다양한 알림 유형에 맞는 메시지 형식을 생성하는 전용 메서드
          • *
          @@ -67,7 +68,7 @@ */ @Component @Slf4j -public class SlackWebhookClient { +public class SlackWebhookClient implements WebhookClient { private final Slack slack; private final NotificationConfigProperties.CommonProperties commonProperties; @@ -95,8 +96,8 @@ public SlackWebhookClient(NotificationConfigProperties notificationConfigPropert * @param additionalData 추가 데이터 * @return 메시지 전송 성공 여부를 나타내는 CompletableFuture */ - public CompletableFuture sendSlackMessage(String webhookUrl, AlertType alertType, - HttpServletRequest request, Object additionalData) { + public CompletableFuture sendMessage(String webhookUrl, AlertType alertType, + HttpServletRequest request, Object additionalData) { List blocks = createBlocks(alertType, request, additionalData); return CompletableFuture.supplyAsync(() -> { @@ -136,15 +137,20 @@ public CompletableFuture sendSlackMessage(String webhookUrl, AlertType * @return 생성된 LayoutBlock 목록 */ public List createBlocks(AlertType alertType, HttpServletRequest request, Object additionalData) { - if (alertType instanceof SecurityAlertType) { - return createSecurityAlertBlocks(request, alertType, additionalData.toString()); - } else if (alertType instanceof GeneralAlertType) { - return createGeneralAlertBlocks((GeneralAlertType) alertType, request, additionalData); - } else if (alertType instanceof ExecutivesAlertType) { - return createExecutivesAlertBlocks((ExecutivesAlertType) alertType, additionalData); - } else { - log.error("Unknown alert type: {}", alertType); - return Collections.emptyList(); + switch (alertType) { + case SecurityAlertType securityAlertType -> { + return createSecurityAlertBlocks(request, alertType, additionalData.toString()); + } + case GeneralAlertType generalAlertType -> { + return createGeneralAlertBlocks(generalAlertType, request, additionalData); + } + case ExecutivesAlertType executivesAlertType -> { + return createExecutivesAlertBlocks(executivesAlertType, additionalData); + } + case null, default -> { + log.error("Unknown alert type: {}", alertType); + return Collections.emptyList(); + } } } diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/WebhookClient.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/WebhookClient.java new file mode 100644 index 000000000..0bc95a24b --- /dev/null +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/WebhookClient.java @@ -0,0 +1,11 @@ +package page.clab.api.global.common.notificationSetting.application.port.out; + +import jakarta.servlet.http.HttpServletRequest; +import java.util.concurrent.CompletableFuture; +import page.clab.api.global.common.notificationSetting.domain.AlertType; + +public interface WebhookClient { + + CompletableFuture sendMessage(String webhookUrl, AlertType alertType, HttpServletRequest request, + Object additionalData); +} diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/domain/DiscordMessage.java b/src/main/java/page/clab/api/global/common/notificationSetting/domain/DiscordMessage.java deleted file mode 100644 index 36059bcf6..000000000 --- a/src/main/java/page/clab/api/global/common/notificationSetting/domain/DiscordMessage.java +++ /dev/null @@ -1,21 +0,0 @@ -package page.clab.api.global.common.notificationSetting.domain; - -import java.util.List; -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class DiscordMessage { - - private String content; - private List embeds; - - @Getter - @Builder - public static class Embed { - - private String title; - private String description; - } -} From 4c737e3a4ea5c3f021bfcc94c0494ced3bfb51e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Thu, 14 Nov 2024 14:42:16 +0900 Subject: [PATCH 10/24] =?UTF-8?q?refactor(Notification):=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EA=B4=80=EB=A0=A8=20DTO=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EB=AA=85=20=EB=B0=8F=20=ED=8C=A8=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/BoardRegisterService.java | 4 ++-- .../service/BookLoanRequestService.java | 5 ++-- .../service/MembershipFeeRegisterService.java | 5 ++-- .../out/discord/DiscordWebhookClient.java | 24 +++++++++---------- .../adapter/out/slack/SlackWebhookClient.java | 24 +++++++++---------- .../notification/BoardNotificationInfo.java} | 11 +++++---- .../BookLoanRecordNotificationInfo.java} | 8 +++---- .../MembershipFeeNotificationInfo.java} | 8 +++---- 8 files changed, 46 insertions(+), 43 deletions(-) rename src/main/java/page/clab/api/global/common/notificationSetting/{domain/SlackBoardInfo.java => application/dto/notification/BoardNotificationInfo.java} (58%) rename src/main/java/page/clab/api/global/common/notificationSetting/{domain/SlackBookLoanRecordInfo.java => application/dto/notification/BookLoanRecordNotificationInfo.java} (69%) rename src/main/java/page/clab/api/global/common/notificationSetting/{domain/SlackMembershipFeeInfo.java => application/dto/notification/MembershipFeeNotificationInfo.java} (69%) diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java b/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java index 7493fa1cb..cdcd2dee8 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java @@ -15,9 +15,9 @@ import page.clab.api.external.memberManagement.notification.application.port.ExternalSendNotificationUseCase; import page.clab.api.global.common.file.application.UploadedFileService; import page.clab.api.global.common.file.domain.UploadedFile; +import page.clab.api.global.common.notificationSetting.application.dto.notification.BoardNotificationInfo; import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; import page.clab.api.global.common.notificationSetting.domain.ExecutivesAlertType; -import page.clab.api.global.common.notificationSetting.domain.SlackBoardInfo; import page.clab.api.global.exception.PermissionDeniedException; @Service @@ -53,7 +53,7 @@ public String registerBoard(BoardRequestDto requestDto) throws PermissionDeniedE "[" + board.getTitle() + "] 새로운 공지사항이 등록되었습니다."); } - SlackBoardInfo boardInfo = SlackBoardInfo.create(board, currentMemberInfo); + BoardNotificationInfo boardInfo = BoardNotificationInfo.create(board, currentMemberInfo); eventPublisher.publishEvent(new NotificationEvent(this, ExecutivesAlertType.NEW_BOARD, null, boardInfo)); diff --git a/src/main/java/page/clab/api/domain/library/bookLoanRecord/application/service/BookLoanRequestService.java b/src/main/java/page/clab/api/domain/library/bookLoanRecord/application/service/BookLoanRequestService.java index 475131df4..9f32d5adb 100644 --- a/src/main/java/page/clab/api/domain/library/bookLoanRecord/application/service/BookLoanRequestService.java +++ b/src/main/java/page/clab/api/domain/library/bookLoanRecord/application/service/BookLoanRequestService.java @@ -18,9 +18,9 @@ import page.clab.api.external.library.book.application.port.ExternalRetrieveBookUseCase; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; import page.clab.api.external.memberManagement.notification.application.port.ExternalSendNotificationUseCase; +import page.clab.api.global.common.notificationSetting.application.dto.notification.BookLoanRecordNotificationInfo; import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; import page.clab.api.global.common.notificationSetting.domain.ExecutivesAlertType; -import page.clab.api.global.common.notificationSetting.domain.SlackBookLoanRecordInfo; import page.clab.api.global.exception.CustomOptimisticLockingFailureException; @Service @@ -63,7 +63,8 @@ public Long requestBookLoan(BookLoanRecordRequestDto requestDto) throws CustomOp externalSendNotificationUseCase.sendNotificationToMember(borrowerInfo.getMemberId(), "[" + book.getTitle() + "] 도서 대출 신청이 완료되었습니다."); - SlackBookLoanRecordInfo bookLoanRecordInfo = SlackBookLoanRecordInfo.create(book, borrowerInfo); + BookLoanRecordNotificationInfo bookLoanRecordInfo = BookLoanRecordNotificationInfo.create(book, + borrowerInfo); eventPublisher.publishEvent(new NotificationEvent(this, ExecutivesAlertType.NEW_BOOK_LOAN_REQUEST, null, bookLoanRecordInfo)); diff --git a/src/main/java/page/clab/api/domain/members/membershipFee/application/service/MembershipFeeRegisterService.java b/src/main/java/page/clab/api/domain/members/membershipFee/application/service/MembershipFeeRegisterService.java index bef56e5da..533b7d879 100644 --- a/src/main/java/page/clab/api/domain/members/membershipFee/application/service/MembershipFeeRegisterService.java +++ b/src/main/java/page/clab/api/domain/members/membershipFee/application/service/MembershipFeeRegisterService.java @@ -12,9 +12,9 @@ import page.clab.api.domain.members.membershipFee.domain.MembershipFee; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; import page.clab.api.external.memberManagement.notification.application.port.ExternalSendNotificationUseCase; +import page.clab.api.global.common.notificationSetting.application.dto.notification.MembershipFeeNotificationInfo; import page.clab.api.global.common.notificationSetting.application.event.NotificationEvent; import page.clab.api.global.common.notificationSetting.domain.ExecutivesAlertType; -import page.clab.api.global.common.notificationSetting.domain.SlackMembershipFeeInfo; @Service @RequiredArgsConstructor @@ -32,7 +32,8 @@ public Long registerMembershipFee(MembershipFeeRequestDto requestDto) { MemberBasicInfoDto memberInfo = externalRetrieveMemberUseCase.getCurrentMemberBasicInfo(); MembershipFee membershipFee = mapper.fromDto(requestDto, memberInfo.getMemberId()); externalSendNotificationUseCase.sendNotificationToAdmins("새로운 회비 내역이 등록되었습니다."); - SlackMembershipFeeInfo membershipFeeInfo = SlackMembershipFeeInfo.create(membershipFee, memberInfo); + MembershipFeeNotificationInfo membershipFeeInfo = MembershipFeeNotificationInfo.create(membershipFee, + memberInfo); eventPublisher.publishEvent(new NotificationEvent(this, ExecutivesAlertType.NEW_MEMBERSHIP_FEE, null, membershipFeeInfo)); return registerMembershipFeePort.save(membershipFee).getId(); diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordWebhookClient.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordWebhookClient.java index 4918454af..df9491cae 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordWebhookClient.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordWebhookClient.java @@ -31,15 +31,15 @@ import org.springframework.stereotype.Component; import page.clab.api.domain.hiring.application.application.dto.request.ApplicationRequestDto; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberLoginInfoDto; +import page.clab.api.global.common.notificationSetting.application.dto.notification.BoardNotificationInfo; +import page.clab.api.global.common.notificationSetting.application.dto.notification.BookLoanRecordNotificationInfo; +import page.clab.api.global.common.notificationSetting.application.dto.notification.MembershipFeeNotificationInfo; import page.clab.api.global.common.notificationSetting.application.port.out.WebhookClient; import page.clab.api.global.common.notificationSetting.config.NotificationConfigProperties; import page.clab.api.global.common.notificationSetting.domain.AlertType; import page.clab.api.global.common.notificationSetting.domain.ExecutivesAlertType; import page.clab.api.global.common.notificationSetting.domain.GeneralAlertType; import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; -import page.clab.api.global.common.notificationSetting.domain.SlackBoardInfo; -import page.clab.api.global.common.notificationSetting.domain.SlackBookLoanRecordInfo; -import page.clab.api.global.common.notificationSetting.domain.SlackMembershipFeeInfo; import page.clab.api.global.util.HttpReqResUtil; /** @@ -195,18 +195,18 @@ private List> createExecutivesAlertEmbeds(ExecutivesAlertTyp } break; case NEW_BOARD: - if (additionalData instanceof SlackBoardInfo) { - return createBoardEmbeds((SlackBoardInfo) additionalData); + if (additionalData instanceof BoardNotificationInfo) { + return createBoardEmbeds((BoardNotificationInfo) additionalData); } break; case NEW_MEMBERSHIP_FEE: - if (additionalData instanceof SlackMembershipFeeInfo) { - return createMembershipFeeEmbeds((SlackMembershipFeeInfo) additionalData); + if (additionalData instanceof MembershipFeeNotificationInfo) { + return createMembershipFeeEmbeds((MembershipFeeNotificationInfo) additionalData); } break; case NEW_BOOK_LOAN_REQUEST: - if (additionalData instanceof SlackBookLoanRecordInfo) { - return createBookLoanRecordEmbeds((SlackBookLoanRecordInfo) additionalData); + if (additionalData instanceof BookLoanRecordNotificationInfo) { + return createBookLoanRecordEmbeds((BookLoanRecordNotificationInfo) additionalData); } break; default: @@ -294,7 +294,7 @@ private List> createApplicationEmbeds(ApplicationRequestDto return Collections.singletonList(embed); } - private List> createBoardEmbeds(SlackBoardInfo board) { + private List> createBoardEmbeds(BoardNotificationInfo board) { Map embed = new HashMap<>(); embed.put("title", ":writing_hand: 새 게시글"); embed.put("color", 0x008080); @@ -307,7 +307,7 @@ private List> createBoardEmbeds(SlackBoardInfo board) { return Collections.singletonList(embed); } - private List> createMembershipFeeEmbeds(SlackMembershipFeeInfo data) { + private List> createMembershipFeeEmbeds(MembershipFeeNotificationInfo data) { String username = data.getMemberId() + " " + data.getMemberName(); Map embed = new HashMap<>(); @@ -323,7 +323,7 @@ private List> createMembershipFeeEmbeds(SlackMembershipFeeIn return Collections.singletonList(embed); } - private List> createBookLoanRecordEmbeds(SlackBookLoanRecordInfo data) { + private List> createBookLoanRecordEmbeds(BookLoanRecordNotificationInfo data) { String username = data.getMemberId() + " " + data.getMemberName(); Map embed = new HashMap<>(); diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackWebhookClient.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackWebhookClient.java index abb666743..a4432bc65 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackWebhookClient.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackWebhookClient.java @@ -36,15 +36,15 @@ import org.springframework.stereotype.Component; import page.clab.api.domain.hiring.application.application.dto.request.ApplicationRequestDto; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberLoginInfoDto; +import page.clab.api.global.common.notificationSetting.application.dto.notification.BoardNotificationInfo; +import page.clab.api.global.common.notificationSetting.application.dto.notification.BookLoanRecordNotificationInfo; +import page.clab.api.global.common.notificationSetting.application.dto.notification.MembershipFeeNotificationInfo; import page.clab.api.global.common.notificationSetting.application.port.out.WebhookClient; import page.clab.api.global.common.notificationSetting.config.NotificationConfigProperties; import page.clab.api.global.common.notificationSetting.domain.AlertType; import page.clab.api.global.common.notificationSetting.domain.ExecutivesAlertType; import page.clab.api.global.common.notificationSetting.domain.GeneralAlertType; import page.clab.api.global.common.notificationSetting.domain.SecurityAlertType; -import page.clab.api.global.common.notificationSetting.domain.SlackBoardInfo; -import page.clab.api.global.common.notificationSetting.domain.SlackBookLoanRecordInfo; -import page.clab.api.global.common.notificationSetting.domain.SlackMembershipFeeInfo; import page.clab.api.global.util.HttpReqResUtil; /** @@ -185,18 +185,18 @@ private List createExecutivesAlertBlocks(ExecutivesAlertType alertT } break; case NEW_BOARD: - if (additionalData instanceof SlackBoardInfo) { - return createBoardBlocks((SlackBoardInfo) additionalData); + if (additionalData instanceof BoardNotificationInfo) { + return createBoardBlocks((BoardNotificationInfo) additionalData); } break; case NEW_MEMBERSHIP_FEE: - if (additionalData instanceof SlackMembershipFeeInfo) { - return createMembershipFeeBlocks((SlackMembershipFeeInfo) additionalData); + if (additionalData instanceof MembershipFeeNotificationInfo) { + return createMembershipFeeBlocks((MembershipFeeNotificationInfo) additionalData); } break; case NEW_BOOK_LOAN_REQUEST: - if (additionalData instanceof SlackBookLoanRecordInfo) { - return createBookLoanRecordBlocks((SlackBookLoanRecordInfo) additionalData); + if (additionalData instanceof BookLoanRecordNotificationInfo) { + return createBookLoanRecordBlocks((BookLoanRecordNotificationInfo) additionalData); } break; default: @@ -282,7 +282,7 @@ private List createApplicationBlocks(ApplicationRequestDto requestD return blocks; } - private List createBoardBlocks(SlackBoardInfo board) { + private List createBoardBlocks(BoardNotificationInfo board) { return Arrays.asList( section(s -> s.text(markdownText(":writing_hand: *새 게시글*"))), section(s -> s.fields(Arrays.asList( @@ -293,7 +293,7 @@ private List createBoardBlocks(SlackBoardInfo board) { ); } - private List createMembershipFeeBlocks(SlackMembershipFeeInfo data) { + private List createMembershipFeeBlocks(MembershipFeeNotificationInfo data) { String username = data.getMemberId() + " " + data.getMemberName(); return Arrays.asList( @@ -307,7 +307,7 @@ private List createMembershipFeeBlocks(SlackMembershipFeeInfo data) ); } - private List createBookLoanRecordBlocks(SlackBookLoanRecordInfo data) { + private List createBookLoanRecordBlocks(BookLoanRecordNotificationInfo data) { String username = data.getMemberId() + " " + data.getMemberName(); return Arrays.asList( diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/domain/SlackBoardInfo.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/dto/notification/BoardNotificationInfo.java similarity index 58% rename from src/main/java/page/clab/api/global/common/notificationSetting/domain/SlackBoardInfo.java rename to src/main/java/page/clab/api/global/common/notificationSetting/application/dto/notification/BoardNotificationInfo.java index 63c0be9d6..4cf0a3442 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/domain/SlackBoardInfo.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/dto/notification/BoardNotificationInfo.java @@ -1,4 +1,4 @@ -package page.clab.api.global.common.notificationSetting.domain; +package page.clab.api.global.common.notificationSetting.application.dto.notification; import lombok.Builder; import lombok.Getter; @@ -7,17 +7,18 @@ @Getter @Builder -public class SlackBoardInfo { +public class BoardNotificationInfo { private String title; private String category; private String username; - public static SlackBoardInfo create(Board board, MemberDetailedInfoDto memberInfo) { - return SlackBoardInfo.builder() + public static BoardNotificationInfo create(Board board, MemberDetailedInfoDto memberInfo) { + return BoardNotificationInfo.builder() .title(board.getTitle()) .category(board.getCategory().getDescription()) - .username(board.isWantAnonymous() ? board.getNickname() : memberInfo.getMemberId() + " " + memberInfo.getMemberName()) + .username(board.isWantAnonymous() ? board.getNickname() + : memberInfo.getMemberId() + " " + memberInfo.getMemberName()) .build(); } } diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/domain/SlackBookLoanRecordInfo.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/dto/notification/BookLoanRecordNotificationInfo.java similarity index 69% rename from src/main/java/page/clab/api/global/common/notificationSetting/domain/SlackBookLoanRecordInfo.java rename to src/main/java/page/clab/api/global/common/notificationSetting/application/dto/notification/BookLoanRecordNotificationInfo.java index fcb02d2ce..036f1de32 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/domain/SlackBookLoanRecordInfo.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/dto/notification/BookLoanRecordNotificationInfo.java @@ -1,4 +1,4 @@ -package page.clab.api.global.common.notificationSetting.domain; +package page.clab.api.global.common.notificationSetting.application.dto.notification; import lombok.Builder; import lombok.Getter; @@ -7,7 +7,7 @@ @Getter @Builder -public class SlackBookLoanRecordInfo { +public class BookLoanRecordNotificationInfo { private String memberId; private String memberName; @@ -15,8 +15,8 @@ public class SlackBookLoanRecordInfo { private String category; private boolean isAvailable; - public static SlackBookLoanRecordInfo create(Book book, MemberBorrowerInfoDto borrowerInfo) { - return SlackBookLoanRecordInfo.builder() + public static BookLoanRecordNotificationInfo create(Book book, MemberBorrowerInfoDto borrowerInfo) { + return BookLoanRecordNotificationInfo.builder() .memberId(borrowerInfo.getMemberId()) .memberName(borrowerInfo.getMemberName()) .bookTitle(book.getTitle()) diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/domain/SlackMembershipFeeInfo.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/dto/notification/MembershipFeeNotificationInfo.java similarity index 69% rename from src/main/java/page/clab/api/global/common/notificationSetting/domain/SlackMembershipFeeInfo.java rename to src/main/java/page/clab/api/global/common/notificationSetting/application/dto/notification/MembershipFeeNotificationInfo.java index 55518ec0e..65b6ed8f3 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/domain/SlackMembershipFeeInfo.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/dto/notification/MembershipFeeNotificationInfo.java @@ -1,4 +1,4 @@ -package page.clab.api.global.common.notificationSetting.domain; +package page.clab.api.global.common.notificationSetting.application.dto.notification; import lombok.Builder; import lombok.Getter; @@ -7,7 +7,7 @@ @Getter @Builder -public class SlackMembershipFeeInfo { +public class MembershipFeeNotificationInfo { private String memberId; private String memberName; @@ -15,8 +15,8 @@ public class SlackMembershipFeeInfo { private Long amount; private String content; - public static SlackMembershipFeeInfo create(MembershipFee membershipFee, MemberBasicInfoDto memberInfo) { - return SlackMembershipFeeInfo.builder() + public static MembershipFeeNotificationInfo create(MembershipFee membershipFee, MemberBasicInfoDto memberInfo) { + return MembershipFeeNotificationInfo.builder() .memberId(memberInfo.getMemberId()) .memberName(memberInfo.getMemberName()) .category(membershipFee.getCategory()) From 54b9b9928632ef89c46420ec3b5d425c890fc064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Thu, 14 Nov 2024 14:44:47 +0900 Subject: [PATCH 11/24] =?UTF-8?q?refactor(Dependency):=20OpenFeign=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/build.gradle b/build.gradle index 626c601d6..d45de1ee3 100644 --- a/build.gradle +++ b/build.gradle @@ -23,28 +23,17 @@ java { } } -ext { - springCloudVersion = '2023.0.3' -} - repositories { mavenCentral() maven { url 'https://jitpack.io' } } -dependencyManagement { - imports { - mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" - } -} - dependencies { // Spring Project implementation 'org.springframework.boot:spring-boot-starter-web' // 웹 MVC implementation 'org.springframework.boot:spring-boot-starter-validation' // 유효성 검사 implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' // 템플릿 엔진 implementation 'org.springframework.boot:spring-boot-starter-webflux' // WebFlux - implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' // OpenFeign // Security implementation 'org.springframework.boot:spring-boot-starter-security' // Spring Security From a1fda4923943fdb9d410ba67c5ec4fb9fb4b8ead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Thu, 14 Nov 2024 14:45:55 +0900 Subject: [PATCH 12/24] =?UTF-8?q?refactor(Dependency):=20OpenFeign=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/page/clab/api/ApiApplication.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/page/clab/api/ApiApplication.java b/src/main/java/page/clab/api/ApiApplication.java index 32356719d..9e890f824 100644 --- a/src/main/java/page/clab/api/ApiApplication.java +++ b/src/main/java/page/clab/api/ApiApplication.java @@ -2,9 +2,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.openfeign.EnableFeignClients; -@EnableFeignClients @SpringBootApplication public class ApiApplication { From 258a5be37a2247f36abb5748baa34b85f7de1430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Thu, 14 Nov 2024 15:34:16 +0900 Subject: [PATCH 13/24] =?UTF-8?q?refactor(Notification):=20Discord=20?= =?UTF-8?q?=EC=9B=B9=ED=9B=85=20=EC=9D=B8=EB=9D=BC=EC=9D=B8=20=EC=BB=AC?= =?UTF-8?q?=EB=9F=AC=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../out/discord/DiscordWebhookClient.java | 16 ++++++++-------- .../config/NotificationConfigProperties.java | 4 ++++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordWebhookClient.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordWebhookClient.java index df9491cae..a81c173ab 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordWebhookClient.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordWebhookClient.java @@ -226,7 +226,7 @@ private List> createErrorEmbeds(HttpServletRequest request, Map embed = new HashMap<>(); embed.put("title", ":firecracker: Server Error"); - embed.put("color", 0xFF0000); + embed.put("color", commonProperties.getColorAsInt()); embed.put("fields", Arrays.asList( createField("User", username, true), createField("Endpoint", "[" + httpMethod + "] " + fullUrl, true), @@ -246,7 +246,7 @@ private List> createSecurityAlertEmbeds(HttpServletRequest r Map embed = new HashMap<>(); embed.put("title", ":imp: " + alertType.getTitle()); - embed.put("color", 0xFFA500); + embed.put("color", commonProperties.getColorAsInt()); embed.put("fields", Arrays.asList( createField("User", username, true), createField("IP Address", clientIp, true), @@ -265,7 +265,7 @@ private List> createAdminLoginEmbeds(HttpServletRequest requ Map embed = new HashMap<>(); embed.put("title", ":mechanic: " + loginMember.getRole().getDescription() + " Login"); - embed.put("color", 0x00FF00); + embed.put("color", commonProperties.getColorAsInt()); embed.put("fields", Arrays.asList( createField("User", loginMember.getMemberId() + " " + loginMember.getMemberName(), true), createField("IP Address", clientIp, true), @@ -278,7 +278,7 @@ private List> createAdminLoginEmbeds(HttpServletRequest requ private List> createApplicationEmbeds(ApplicationRequestDto requestDto) { Map embed = new HashMap<>(); embed.put("title", ":sparkles: 동아리 지원"); - embed.put("color", 0x00BFFF); + embed.put("color", commonProperties.getColorAsInt()); embed.put("fields", Arrays.asList( createField("구분", requestDto.getApplicationType().getDescription(), true), createField("학번", requestDto.getStudentId(), true), @@ -297,7 +297,7 @@ private List> createApplicationEmbeds(ApplicationRequestDto private List> createBoardEmbeds(BoardNotificationInfo board) { Map embed = new HashMap<>(); embed.put("title", ":writing_hand: 새 게시글"); - embed.put("color", 0x008080); + embed.put("color", commonProperties.getColorAsInt()); embed.put("fields", Arrays.asList( createField("제목", board.getTitle(), true), createField("분류", board.getCategory(), true), @@ -312,7 +312,7 @@ private List> createMembershipFeeEmbeds(MembershipFeeNotific Map embed = new HashMap<>(); embed.put("title", ":dollar: 회비 신청"); - embed.put("color", 0xFFFF00); + embed.put("color", commonProperties.getColorAsInt()); embed.put("fields", Arrays.asList( createField("신청자", username, true), createField("분류", data.getCategory(), true), @@ -328,7 +328,7 @@ private List> createBookLoanRecordEmbeds(BookLoanRecordNotif Map embed = new HashMap<>(); embed.put("title", ":books: 도서 대여 신청"); - embed.put("color", 0x800080); + embed.put("color", commonProperties.getColorAsInt()); embed.put("fields", Arrays.asList( createField("도서명", data.getBookTitle(), true), createField("분류", data.getCategory(), true), @@ -353,7 +353,7 @@ private List> createServerStartEmbeds() { Map embed = new HashMap<>(); embed.put("title", ":battery: Server Started"); - embed.put("color", 0x00FF7F); + embed.put("color", commonProperties.getColorAsInt()); embed.put("fields", Arrays.asList( createField("Environment", environment.getProperty("spring.profiles.active"), true), createField("OS", osInfo, true), diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/config/NotificationConfigProperties.java b/src/main/java/page/clab/api/global/common/notificationSetting/config/NotificationConfigProperties.java index 3cfe86331..a052ac17e 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/config/NotificationConfigProperties.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/config/NotificationConfigProperties.java @@ -24,6 +24,10 @@ public static class CommonProperties { private String webUrl; private String apiUrl; private String color; + + public int getColorAsInt() { + return Integer.parseInt(color.replaceFirst("^#", ""), 16); + } } @Getter From 092092333be17d4adead8c6b1f8fc51e97d2b0e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Thu, 14 Nov 2024 17:51:19 +0900 Subject: [PATCH 14/24] =?UTF-8?q?refactor(Notification):=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=EC=97=90=20=EB=94=B0=EB=A5=B8=20YAML=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 49 +++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 5ae527332..22f873204 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -195,13 +195,48 @@ resource: ipinfo: access-token: ${IPINFO_ACCESS_TOKEN} # Register at https://ipinfo.io/ -# Slack webhook configuration -slack: - core-team-webhook-url: ${SLACK_WEBHOOK_URL} # Create a Slack channel and get a webhook URL - executives-webhook-url: ${SLACK_WEBHOOK_URL} # Create a Slack channel and get a webhook URL - web-url: ${WEB_URL} # Your web URL - api-url: ${API_URL} # Your API docs URL - color: "#FF968A" # Slack message color +notification: + common: + web-url: "${WEB_URL}" # Your web URL + api-url: "${API_URL}" # Your API documentation URL + color: "#FF968A" # Message color used in notifications + platforms: + slack: + webhooks: + # Replace the placeholders with your actual Slack webhook URLs + core-team: "${SLACK_CORE_TEAM_WEBHOOK_URL}" # Slack webhook URL for core team notifications + executives: "${SLACK_EXECUTIVES_WEBHOOK_URL}" # Slack webhook URL for executive team notifications + discord: + webhooks: + # Replace the placeholders with your actual Discord webhook URLs + release: "${DISCORD_RELEASE_WEBHOOK_URL}" # Discord webhook URL for release notifications + notifications: "${DISCORD_NOTIFICATIONS_WEBHOOK_URL}" # Discord webhook URL for general notifications + executives: "${DISCORD_EXECUTIVES_WEBHOOK_URL}" # Discord webhook URL for executive team notifications + # The category-mappings section defines how notifications are routed based on their category. + # The category names should match those specified in "page.clab.api.global.common.notificationSetting.domain.AlertCategory". + # By specifying multiple platforms and webhooks under each category, you can configure messages to be sent to multiple platforms and multiple webhooks simultaneously. + category-mappings: + GENERAL: + - platform: slack + webhook: core-team + - platform: discord + webhook: notifications + SECURITY: + - platform: slack + webhook: core-team + - platform: discord + webhook: notifications + EXECUTIVES: + - platform: slack + webhook: executives + - platform: discord + webhook: executives + # If a notification category is not explicitly mapped in category-mappings, it will use the default-mappings to determine where to send messages. + default-mappings: + - platform: slack + webhook: core-team + - platform: discord + webhook: notifications # Configure Swagger UI and generate OpenAPI documentation springdoc: From f4d881347e639277ecd8d1471ed4dcd3202a7f58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Thu, 14 Nov 2024 17:55:07 +0900 Subject: [PATCH 15/24] =?UTF-8?q?refactor(Notification):=20YAML=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 22f873204..68c4e082c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -195,6 +195,7 @@ resource: ipinfo: access-token: ${IPINFO_ACCESS_TOKEN} # Register at https://ipinfo.io/ +# Messaging configuration notification: common: web-url: "${WEB_URL}" # Your web URL @@ -209,7 +210,7 @@ notification: discord: webhooks: # Replace the placeholders with your actual Discord webhook URLs - release: "${DISCORD_RELEASE_WEBHOOK_URL}" # Discord webhook URL for release notifications + release: "${DISCORD_RELEASE_WEBHOOK_URL}" # Discord webhook URL for release notifications notifications: "${DISCORD_NOTIFICATIONS_WEBHOOK_URL}" # Discord webhook URL for general notifications executives: "${DISCORD_EXECUTIVES_WEBHOOK_URL}" # Discord webhook URL for executive team notifications # The category-mappings section defines how notifications are routed based on their category. From 1a9f704f937a7b39601f5a5a66cc09ed5cc1e0a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Thu, 14 Nov 2024 17:58:59 +0900 Subject: [PATCH 16/24] =?UTF-8?q?refactor(Notification):=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/out/discord/DiscordWebhookClient.java | 5 ----- .../adapter/out/slack/SlackWebhookClient.java | 2 -- 2 files changed, 7 deletions(-) diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordWebhookClient.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordWebhookClient.java index a81c173ab..31d48053e 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordWebhookClient.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordWebhookClient.java @@ -163,7 +163,6 @@ public List> createEmbeds(AlertType alertType, HttpServletRe } } - // 일반 알림 유형에 따른 임베드 생성 private List> createGeneralAlertEmbeds(GeneralAlertType alertType, HttpServletRequest request, Object additionalData) { switch (alertType) { @@ -185,7 +184,6 @@ private List> createGeneralAlertEmbeds(GeneralAlertType aler return Collections.emptyList(); } - // 운영진 알림 유형에 따른 임베드 생성 private List> createExecutivesAlertEmbeds(ExecutivesAlertType alertType, Object additionalData) { switch (alertType) { @@ -362,15 +360,12 @@ private List> createServerStartEmbeds() { createField("Memory Usage", memoryInfo, true) )); - // 버튼 대신 링크를 설명에 추가 embed.put("description", "[Web](" + commonProperties.getWebUrl() + ") | [Swagger](" + commonProperties.getApiUrl() + ")"); return Collections.singletonList(embed); } - // 유틸리티 메서드들 - private Map createField(String name, String value, boolean inline) { Map field = new HashMap<>(); field.put("name", name); diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackWebhookClient.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackWebhookClient.java index a4432bc65..3c4a3ff13 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackWebhookClient.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackWebhookClient.java @@ -154,7 +154,6 @@ public List createBlocks(AlertType alertType, HttpServletRequest re } } - // 일반 알림 유형에 따른 블록 생성 private List createGeneralAlertBlocks(GeneralAlertType alertType, HttpServletRequest request, Object additionalData) { switch (alertType) { @@ -176,7 +175,6 @@ private List createGeneralAlertBlocks(GeneralAlertType alertType, H return Collections.emptyList(); } - // 운영진 알림 유형에 따른 블록 생성 private List createExecutivesAlertBlocks(ExecutivesAlertType alertType, Object additionalData) { switch (alertType) { case NEW_APPLICATION: From 149cad994389ca41649fef37d29c02e8e9d91e26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Fri, 15 Nov 2024 03:01:26 +0900 Subject: [PATCH 17/24] =?UTF-8?q?refactor(Notification):=20=EC=9B=B9?= =?UTF-8?q?=ED=9B=85=20=EC=B6=94=EC=83=81=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EA=B3=B5=ED=86=B5=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DiscordNotificationSender.java | 2 +- .../DiscordWebhookClient.java | 112 +++++------------- .../SlackNotificationSender.java | 2 +- .../SlackWebhookClient.java | 107 +++++------------ .../webhook/common/AbstractWebhookClient.java | 17 +++ .../application/port/out/WebhookClient.java | 12 ++ .../service/WebhookCommonService.java | 87 ++++++++++++++ 7 files changed, 175 insertions(+), 164 deletions(-) rename src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/{discord => webhook}/DiscordNotificationSender.java (98%) rename src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/{discord => webhook}/DiscordWebhookClient.java (79%) rename src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/{slack => webhook}/SlackNotificationSender.java (98%) rename src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/{slack => webhook}/SlackWebhookClient.java (79%) create mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/common/AbstractWebhookClient.java create mode 100644 src/main/java/page/clab/api/global/common/notificationSetting/application/service/WebhookCommonService.java diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordNotificationSender.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/DiscordNotificationSender.java similarity index 98% rename from src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordNotificationSender.java rename to src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/DiscordNotificationSender.java index 0ba9d815a..b6bfedf51 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordNotificationSender.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/DiscordNotificationSender.java @@ -1,4 +1,4 @@ -package page.clab.api.global.common.notificationSetting.adapter.out.discord; +package page.clab.api.global.common.notificationSetting.adapter.out.webhook; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordWebhookClient.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/DiscordWebhookClient.java similarity index 79% rename from src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordWebhookClient.java rename to src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/DiscordWebhookClient.java index 31d48053e..857d60158 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/discord/DiscordWebhookClient.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/DiscordWebhookClient.java @@ -1,14 +1,8 @@ -package page.clab.api.global.common.notificationSetting.adapter.out.discord; +package page.clab.api.global.common.notificationSetting.adapter.out.webhook; import com.fasterxml.jackson.databind.ObjectMapper; -import io.ipinfo.api.model.IPResponse; -import io.ipinfo.spring.strategies.attribute.AttributeStrategy; import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.lang.management.MemoryMXBean; -import java.lang.management.MemoryUsage; -import java.lang.management.OperatingSystemMXBean; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; @@ -18,23 +12,19 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; import org.springframework.core.env.Environment; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import page.clab.api.domain.hiring.application.application.dto.request.ApplicationRequestDto; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberLoginInfoDto; +import page.clab.api.global.common.notificationSetting.adapter.out.webhook.common.AbstractWebhookClient; import page.clab.api.global.common.notificationSetting.application.dto.notification.BoardNotificationInfo; import page.clab.api.global.common.notificationSetting.application.dto.notification.BookLoanRecordNotificationInfo; import page.clab.api.global.common.notificationSetting.application.dto.notification.MembershipFeeNotificationInfo; -import page.clab.api.global.common.notificationSetting.application.port.out.WebhookClient; +import page.clab.api.global.common.notificationSetting.application.service.WebhookCommonService; import page.clab.api.global.common.notificationSetting.config.NotificationConfigProperties; import page.clab.api.global.common.notificationSetting.domain.AlertType; import page.clab.api.global.common.notificationSetting.domain.ExecutivesAlertType; @@ -63,22 +53,25 @@ */ @Component @Slf4j -public class DiscordWebhookClient implements WebhookClient { +public class DiscordWebhookClient extends AbstractWebhookClient { private final HttpClient httpClient; private final ObjectMapper objectMapper; private final NotificationConfigProperties.CommonProperties commonProperties; private final Environment environment; - private final AttributeStrategy attributeStrategy; - - public DiscordWebhookClient(NotificationConfigProperties notificationConfigProperties, - Environment environment, - AttributeStrategy attributeStrategy) { + private final WebhookCommonService webhookCommonService; + + public DiscordWebhookClient( + NotificationConfigProperties notificationConfigProperties, + ObjectMapper objectMapper, + Environment environment, + WebhookCommonService webhookCommonService + ) { this.httpClient = HttpClient.newHttpClient(); - this.objectMapper = new ObjectMapper(); + this.objectMapper = objectMapper; this.commonProperties = notificationConfigProperties.getCommon(); this.environment = environment; - this.attributeStrategy = attributeStrategy; + this.webhookCommonService = webhookCommonService; } /** @@ -215,12 +208,12 @@ private List> createExecutivesAlertEmbeds(ExecutivesAlertTyp private List> createErrorEmbeds(HttpServletRequest request, Exception e) { String httpMethod = request.getMethod(); - String fullUrl = getFullUrl(request); - String username = getUsername(request); - String errorMessage = Optional.ofNullable(e.getMessage()).orElse("No error message provided"); - String detailedMessage = extractMessageAfterException(errorMessage); + String fullUrl = webhookCommonService.getFullUrl(request); + String username = webhookCommonService.getUsername(request); + String errorMessage = webhookCommonService.extractMessageAfterException(e); + String stackTrace = webhookCommonService.getStackTraceSummary(e); - log.error("Server Error: {}", detailedMessage); + log.error("Server Error: {}", errorMessage); Map embed = new HashMap<>(); embed.put("title", ":firecracker: Server Error"); @@ -228,8 +221,8 @@ private List> createErrorEmbeds(HttpServletRequest request, embed.put("fields", Arrays.asList( createField("User", username, true), createField("Endpoint", "[" + httpMethod + "] " + fullUrl, true), - createField("Error Message", detailedMessage, false), - createField("Stack Trace", "```" + getStackTraceSummary(e) + "```", false) + createField("Error Message", errorMessage, false), + createField("Stack Trace", "```" + stackTrace + "```", false) )); return Collections.singletonList(embed); @@ -238,9 +231,9 @@ private List> createErrorEmbeds(HttpServletRequest request, private List> createSecurityAlertEmbeds(HttpServletRequest request, AlertType alertType, String additionalMessage) { String clientIp = HttpReqResUtil.getClientIpAddressIfServletRequestExist(); - String fullUrl = getFullUrl(request); - String username = getUsername(request); - String location = getLocation(request); + String fullUrl = webhookCommonService.getFullUrl(request); + String username = webhookCommonService.getUsername(request); + String location = webhookCommonService.getLocation(request); Map embed = new HashMap<>(); embed.put("title", ":imp: " + alertType.getTitle()); @@ -259,7 +252,7 @@ private List> createSecurityAlertEmbeds(HttpServletRequest r private List> createAdminLoginEmbeds(HttpServletRequest request, MemberLoginInfoDto loginMember) { String clientIp = HttpReqResUtil.getClientIpAddressIfServletRequestExist(); - String location = getLocation(request); + String location = webhookCommonService.getLocation(request); Map embed = new HashMap<>(); embed.put("title", ":mechanic: " + loginMember.getRole().getDescription() + " Login"); @@ -338,16 +331,10 @@ private List> createBookLoanRecordEmbeds(BookLoanRecordNotif } private List> createServerStartEmbeds() { - String osInfo = System.getProperty("os.name") + " " + System.getProperty("os.version"); - String jdkVersion = System.getProperty("java.version"); - - OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean(); - int processors = osBean.getAvailableProcessors(); - double systemLoadAverage = osBean.getSystemLoadAverage(); - double cpuUsage = (systemLoadAverage / processors) * 100; - - MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); - String memoryInfo = formatMemoryUsage(memoryMXBean.getHeapMemoryUsage()); + String osInfo = webhookCommonService.getOperatingSystemInfo(); + String jdkVersion = webhookCommonService.getJavaRuntimeVersion(); + double cpuUsage = webhookCommonService.getCpuUsage(); + String memoryUsage = webhookCommonService.getMemoryUsage(); Map embed = new HashMap<>(); embed.put("title", ":battery: Server Started"); @@ -357,7 +344,7 @@ private List> createServerStartEmbeds() { createField("OS", osInfo, true), createField("JDK Version", jdkVersion, true), createField("CPU Usage", String.format("%.2f%%", cpuUsage), true), - createField("Memory Usage", memoryInfo, true) + createField("Memory Usage", memoryUsage, true) )); embed.put("description", @@ -373,43 +360,4 @@ private Map createField(String name, String value, boolean inlin field.put("inline", inline); return field; } - - private String getFullUrl(HttpServletRequest request) { - String requestUrl = request.getRequestURI(); - String queryString = request.getQueryString(); - return queryString == null ? requestUrl : requestUrl + "?" + queryString; - } - - private String extractMessageAfterException(String message) { - String exceptionIndicator = "Exception:"; - int index = message.indexOf(exceptionIndicator); - return index == -1 ? message : message.substring(index + exceptionIndicator.length()).trim(); - } - - private String getStackTraceSummary(Exception e) { - return Arrays.stream(e.getStackTrace()) - .limit(10) - .map(StackTraceElement::toString) - .collect(Collectors.joining("\n")); - } - - private String formatMemoryUsage(MemoryUsage usage) { - long used = usage.getUsed() / (1024 * 1024); - long max = usage.getMax() / (1024 * 1024); - return String.format("%dMB / %dMB (%.2f%%)", used, max, ((double) used / max) * 100); - } - - private @NotNull String getUsername(HttpServletRequest request) { - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - return Optional.ofNullable(request.getAttribute("member")) - .map(Object::toString) - .orElseGet(() -> Optional.ofNullable(auth) - .map(Authentication::getName) - .orElse("anonymous")); - } - - private @NotNull String getLocation(HttpServletRequest request) { - IPResponse ipResponse = attributeStrategy.getAttribute(request); - return ipResponse == null ? "Unknown" : ipResponse.getCountryName() + ", " + ipResponse.getCity(); - } } diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackNotificationSender.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/SlackNotificationSender.java similarity index 98% rename from src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackNotificationSender.java rename to src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/SlackNotificationSender.java index 8e983bdf3..3a993dc57 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackNotificationSender.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/SlackNotificationSender.java @@ -1,4 +1,4 @@ -package page.clab.api.global.common.notificationSetting.adapter.out.slack; +package page.clab.api.global.common.notificationSetting.adapter.out.webhook; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackWebhookClient.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/SlackWebhookClient.java similarity index 79% rename from src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackWebhookClient.java rename to src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/SlackWebhookClient.java index 3c4a3ff13..f4fc2cdaf 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/slack/SlackWebhookClient.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/SlackWebhookClient.java @@ -1,4 +1,4 @@ -package page.clab.api.global.common.notificationSetting.adapter.out.slack; +package page.clab.api.global.common.notificationSetting.adapter.out.webhook; import static com.slack.api.model.block.Blocks.actions; import static com.slack.api.model.block.Blocks.section; @@ -12,34 +12,24 @@ import com.slack.api.model.block.LayoutBlock; import com.slack.api.webhook.Payload; import com.slack.api.webhook.WebhookResponse; -import io.ipinfo.api.model.IPResponse; -import io.ipinfo.spring.strategies.attribute.AttributeStrategy; import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.lang.management.MemoryMXBean; -import java.lang.management.MemoryUsage; -import java.lang.management.OperatingSystemMXBean; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Optional; import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; import org.springframework.core.env.Environment; import org.springframework.http.HttpStatus; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import page.clab.api.domain.hiring.application.application.dto.request.ApplicationRequestDto; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberLoginInfoDto; +import page.clab.api.global.common.notificationSetting.adapter.out.webhook.common.AbstractWebhookClient; import page.clab.api.global.common.notificationSetting.application.dto.notification.BoardNotificationInfo; import page.clab.api.global.common.notificationSetting.application.dto.notification.BookLoanRecordNotificationInfo; import page.clab.api.global.common.notificationSetting.application.dto.notification.MembershipFeeNotificationInfo; -import page.clab.api.global.common.notificationSetting.application.port.out.WebhookClient; +import page.clab.api.global.common.notificationSetting.application.service.WebhookCommonService; import page.clab.api.global.common.notificationSetting.config.NotificationConfigProperties; import page.clab.api.global.common.notificationSetting.domain.AlertType; import page.clab.api.global.common.notificationSetting.domain.ExecutivesAlertType; @@ -68,20 +58,22 @@ */ @Component @Slf4j -public class SlackWebhookClient implements WebhookClient { +public class SlackWebhookClient extends AbstractWebhookClient { private final Slack slack; private final NotificationConfigProperties.CommonProperties commonProperties; private final Environment environment; - private final AttributeStrategy attributeStrategy; + private final WebhookCommonService webhookCommonService; - public SlackWebhookClient(NotificationConfigProperties notificationConfigProperties, - Environment environment, - AttributeStrategy attributeStrategy) { + public SlackWebhookClient( + NotificationConfigProperties notificationConfigProperties, + Environment environment, + WebhookCommonService webhookCommonService + ) { this.slack = Slack.getInstance(); this.commonProperties = notificationConfigProperties.getCommon(); this.environment = environment; - this.attributeStrategy = attributeStrategy; + this.webhookCommonService = webhookCommonService; } /** @@ -205,12 +197,12 @@ private List createExecutivesAlertBlocks(ExecutivesAlertType alertT private List createErrorBlocks(HttpServletRequest request, Exception e) { String httpMethod = request.getMethod(); - String fullUrl = getFullUrl(request); - String username = getUsername(request); - String errorMessage = Optional.ofNullable(e.getMessage()).orElse("No error message provided"); - String detailedMessage = extractMessageAfterException(errorMessage); + String fullUrl = webhookCommonService.getFullUrl(request); + String username = webhookCommonService.getUsername(request); + String errorMessage = webhookCommonService.extractMessageAfterException(e); + String stackTrace = webhookCommonService.getStackTraceSummary(e); - log.error("Server Error: {}", detailedMessage); + log.error("Server Error: {}", errorMessage); return Arrays.asList( section(s -> s.text(markdownText(":firecracker: *Server Error*"))), @@ -218,17 +210,17 @@ private List createErrorBlocks(HttpServletRequest request, Exceptio markdownText("*User:*\n" + username), markdownText("*Endpoint:*\n[" + httpMethod + "] " + fullUrl) ))), - section(s -> s.text(markdownText("*Error Message:*\n" + detailedMessage))), - section(s -> s.text(markdownText("*Stack Trace:*\n```" + getStackTraceSummary(e) + "```"))) + section(s -> s.text(markdownText("*Error Message:*\n" + errorMessage))), + section(s -> s.text(markdownText("*Stack Trace:*\n```" + stackTrace + "```"))) ); } private List createSecurityAlertBlocks(HttpServletRequest request, AlertType alertType, String additionalMessage) { String clientIp = HttpReqResUtil.getClientIpAddressIfServletRequestExist(); - String fullUrl = getFullUrl(request); - String username = getUsername(request); - String location = getLocation(request); + String fullUrl = webhookCommonService.getFullUrl(request); + String username = webhookCommonService.getUsername(request); + String location = webhookCommonService.getLocation(request); return Arrays.asList( section(s -> s.text(markdownText(":imp: *" + alertType.getTitle() + "*"))), @@ -245,7 +237,7 @@ private List createSecurityAlertBlocks(HttpServletRequest request, private List createAdminLoginBlocks(HttpServletRequest request, MemberLoginInfoDto loginMember) { String clientIp = HttpReqResUtil.getClientIpAddressIfServletRequestExist(); - String location = getLocation(request); + String location = webhookCommonService.getLocation(request); return Arrays.asList( section(s -> s.text(markdownText(":mechanic: *" + loginMember.getRole().getDescription() + " Login*"))), @@ -320,16 +312,10 @@ private List createBookLoanRecordBlocks(BookLoanRecordNotificationI } private List createServerStartBlocks() { - String osInfo = System.getProperty("os.name") + " " + System.getProperty("os.version"); - String jdkVersion = System.getProperty("java.version"); - - OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean(); - int processors = osBean.getAvailableProcessors(); - double systemLoadAverage = osBean.getSystemLoadAverage(); - double cpuUsage = (systemLoadAverage / processors) * 100; - - MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); - String memoryInfo = formatMemoryUsage(memoryMXBean.getHeapMemoryUsage()); + String osInfo = webhookCommonService.getOperatingSystemInfo(); + String jdkVersion = webhookCommonService.getJavaRuntimeVersion(); + double cpuUsage = webhookCommonService.getCpuUsage(); + String memoryUsage = webhookCommonService.getMemoryUsage(); return Arrays.asList( section(s -> s.text(markdownText(":battery: *Server Started*"))), @@ -338,7 +324,7 @@ private List createServerStartBlocks() { markdownText("*OS:* \n" + osInfo), markdownText("*JDK Version:* \n" + jdkVersion), markdownText("*CPU Usage:* \n" + String.format("%.2f%%", cpuUsage)), - markdownText("*Memory Usage:* \n" + memoryInfo) + markdownText("*Memory Usage:* \n" + memoryUsage) ))), actions(a -> a.elements(asElements( button(b -> b.text(plainText(pt -> pt.emoji(true).text("Web"))) @@ -350,43 +336,4 @@ private List createServerStartBlocks() { ))) ); } - - private String getFullUrl(HttpServletRequest request) { - String requestUrl = request.getRequestURI(); - String queryString = request.getQueryString(); - return queryString == null ? requestUrl : requestUrl + "?" + queryString; - } - - private String extractMessageAfterException(String message) { - String exceptionIndicator = "Exception:"; - int index = message.indexOf(exceptionIndicator); - return index == -1 ? message : message.substring(index + exceptionIndicator.length()).trim(); - } - - private String getStackTraceSummary(Exception e) { - return Arrays.stream(e.getStackTrace()) - .limit(10) - .map(StackTraceElement::toString) - .collect(Collectors.joining("\n")); - } - - private String formatMemoryUsage(MemoryUsage usage) { - long used = usage.getUsed() / (1024 * 1024); - long max = usage.getMax() / (1024 * 1024); - return String.format("%dMB / %dMB (%.2f%%)", used, max, ((double) used / max) * 100); - } - - private @NotNull String getUsername(HttpServletRequest request) { - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - return Optional.ofNullable(request.getAttribute("member")) - .map(Object::toString) - .orElseGet(() -> Optional.ofNullable(auth) - .map(Authentication::getName) - .orElse("anonymous")); - } - - private @NotNull String getLocation(HttpServletRequest request) { - IPResponse ipResponse = attributeStrategy.getAttribute(request); - return ipResponse == null ? "Unknown" : ipResponse.getCountryName() + ", " + ipResponse.getCity(); - } } diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/common/AbstractWebhookClient.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/common/AbstractWebhookClient.java new file mode 100644 index 000000000..1ec92a961 --- /dev/null +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/common/AbstractWebhookClient.java @@ -0,0 +1,17 @@ +package page.clab.api.global.common.notificationSetting.adapter.out.webhook.common; + +import jakarta.servlet.http.HttpServletRequest; +import java.util.concurrent.CompletableFuture; +import page.clab.api.global.common.notificationSetting.application.port.out.WebhookClient; +import page.clab.api.global.common.notificationSetting.domain.AlertType; + +/** + * {@code AbstractWebhookClient}는 Discord 및 Slack Webhook 클라이언트의 공통 인터페이스를 정의하는 추상 클래스입니다. + */ +public abstract class AbstractWebhookClient implements WebhookClient { + + @Override + public abstract CompletableFuture sendMessage(String webhookUrl, AlertType alertType, + HttpServletRequest request, + Object additionalData); +} diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/WebhookClient.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/WebhookClient.java index 0bc95a24b..c85e5776d 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/WebhookClient.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/port/out/WebhookClient.java @@ -4,8 +4,20 @@ import java.util.concurrent.CompletableFuture; import page.clab.api.global.common.notificationSetting.domain.AlertType; +/** + * WebhookClient는 외부 시스템(Discord, Slack 등)과 통신하기 위한 포트 인터페이스입니다. + */ public interface WebhookClient { + /** + * 특정 알림 유형과 요청 정보, 추가 데이터를 사용하여 메시지를 비동기적으로 전송합니다. + * + * @param webhookUrl 메시지를 보낼 Webhook URL + * @param alertType 알림 유형 + * @param request 클라이언트 요청 정보 + * @param additionalData 추가 데이터 + * @return 메시지 전송 성공 여부를 나타내는 CompletableFuture + */ CompletableFuture sendMessage(String webhookUrl, AlertType alertType, HttpServletRequest request, Object additionalData); } diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/service/WebhookCommonService.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/service/WebhookCommonService.java new file mode 100644 index 000000000..0cb574aa0 --- /dev/null +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/service/WebhookCommonService.java @@ -0,0 +1,87 @@ +package page.clab.api.global.common.notificationSetting.application.service; + +import io.ipinfo.api.model.IPResponse; +import io.ipinfo.spring.strategies.attribute.AttributeStrategy; +import jakarta.servlet.http.HttpServletRequest; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryUsage; +import java.lang.management.OperatingSystemMXBean; +import java.util.Arrays; +import java.util.Optional; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +/** + * {@code WebhookCommonService}는 Webhook 클라이언트에서 공통으로 사용되는 로직을 제공합니다. + */ +@Service +@RequiredArgsConstructor +public class WebhookCommonService { + + private final AttributeStrategy attributeStrategy; + + public String getFullUrl(HttpServletRequest request) { + String requestUrl = request.getRequestURI(); + String queryString = request.getQueryString(); + return queryString == null ? requestUrl : requestUrl + "?" + queryString; + } + + public String extractMessageAfterException(Exception e) { + String errorMessage = Optional.ofNullable(e.getMessage()).orElse("No error message provided"); + String exceptionIndicator = "Exception:"; + int index = errorMessage.indexOf(exceptionIndicator); + return index == -1 ? errorMessage : errorMessage.substring(index + exceptionIndicator.length()).trim(); + } + + public String getStackTraceSummary(Exception e) { + return Arrays.stream(e.getStackTrace()) + .limit(10) + .map(StackTraceElement::toString) + .collect(Collectors.joining("\n")); + } + + public String getOperatingSystemInfo() { + String osName = System.getProperty("os.name"); + String osVersion = System.getProperty("os.version"); + return osName + " " + osVersion; + } + + public String getJavaRuntimeVersion() { + return System.getProperty("java.version"); + } + + public double getCpuUsage() { + OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean(); + int processors = osBean.getAvailableProcessors(); + double systemLoadAverage = osBean.getSystemLoadAverage(); + return (systemLoadAverage / processors) * 100; + } + + public String getMemoryUsage() { + MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + MemoryUsage memoryUsage = memoryMXBean.getHeapMemoryUsage(); + + long used = memoryUsage.getUsed() / (1024 * 1024); + long max = memoryUsage.getMax() / (1024 * 1024); + + return String.format("%dMB / %dMB (%.2f%%)", used, max, ((double) used / max) * 100); + } + + public String getUsername(HttpServletRequest request) { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + return Optional.ofNullable(request.getAttribute("member")) + .map(Object::toString) + .orElseGet(() -> Optional.ofNullable(auth) + .map(Authentication::getName) + .orElse("anonymous")); + } + + public String getLocation(HttpServletRequest request) { + IPResponse ipResponse = attributeStrategy.getAttribute(request); + return ipResponse == null ? "Unknown" : ipResponse.getCountryName() + ", " + ipResponse.getCity(); + } +} From 734988259eb57a467566b3087e043955e490de48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Fri, 15 Nov 2024 03:04:19 +0900 Subject: [PATCH 18/24] =?UTF-8?q?refactor(Notification):=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20Swagger=EB=A5=BC=20API=20Docs=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/out/webhook/DiscordWebhookClient.java | 2 +- .../adapter/out/webhook/SlackWebhookClient.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/DiscordWebhookClient.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/DiscordWebhookClient.java index 857d60158..9945ba1ac 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/DiscordWebhookClient.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/DiscordWebhookClient.java @@ -348,7 +348,7 @@ private List> createServerStartEmbeds() { )); embed.put("description", - "[Web](" + commonProperties.getWebUrl() + ") | [Swagger](" + commonProperties.getApiUrl() + ")"); + "[Web](" + commonProperties.getWebUrl() + ") | [API Docs](" + commonProperties.getApiUrl() + ")"); return Collections.singletonList(embed); } diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/SlackWebhookClient.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/SlackWebhookClient.java index f4fc2cdaf..ea5a5cf4e 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/SlackWebhookClient.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/SlackWebhookClient.java @@ -330,9 +330,9 @@ private List createServerStartBlocks() { button(b -> b.text(plainText(pt -> pt.emoji(true).text("Web"))) .url(commonProperties.getWebUrl()) .value("click_web")), - button(b -> b.text(plainText(pt -> pt.emoji(true).text("Swagger"))) + button(b -> b.text(plainText(pt -> pt.emoji(true).text("API Docs"))) .url(commonProperties.getApiUrl()) - .value("click_swagger")) + .value("click_apiDocs")) ))) ); } From 7b2b987045e933e872fbf569c6ee0302fbb16311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Fri, 15 Nov 2024 04:48:52 +0900 Subject: [PATCH 19/24] =?UTF-8?q?refactor(Notification):=20NotificationLis?= =?UTF-8?q?tener=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../event/NotificationListener.java | 73 ++++++++++++------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java index 89f49e457..2d2116822 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java @@ -2,18 +2,22 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import page.clab.api.global.common.notificationSetting.application.port.in.UpdateNotificationSettingUseCase; import page.clab.api.global.common.notificationSetting.application.port.out.NotificationSender; import page.clab.api.global.common.notificationSetting.config.NotificationConfigProperties; +import page.clab.api.global.common.notificationSetting.config.NotificationConfigProperties.PlatformConfig; import page.clab.api.global.common.notificationSetting.config.NotificationConfigProperties.PlatformMapping; import page.clab.api.global.common.notificationSetting.domain.AlertType; import page.clab.api.global.common.notificationSetting.domain.NotificationSetting; @Component +@Slf4j public class NotificationListener { private final UpdateNotificationSettingUseCase updateNotificationSettingUseCase; @@ -33,44 +37,59 @@ public NotificationListener( @EventListener public void handleNotificationEvent(NotificationEvent event) { AlertType alertType = event.getAlertType(); - NotificationSetting setting = updateNotificationSettingUseCase.getOrCreateDefaultSetting(alertType); - if (setting.isEnabled()) { - List mappings = getMappingsForAlertType(alertType); + NotificationSetting setting = updateNotificationSettingUseCase.getOrCreateDefaultSetting(alertType); + if (!setting.isEnabled()) { + return; + } - if (mappings != null) { - for (NotificationConfigProperties.PlatformMapping mapping : mappings) { - NotificationSender sender = notificationSenders.get(mapping.getPlatform()); - if (sender != null) { - String webhookUrl = getWebhookUrl(mapping); - if (webhookUrl != null) { - sender.sendNotification(event, webhookUrl); - } - } - } - } + List mappings = getMappingsForAlertType(alertType); + if (mappings.isEmpty()) { + return; } + + mappings.forEach(mapping -> getWebhookUrl(mapping) + .ifPresent(webhookUrl -> sendNotification(mapping.getPlatform(), event, webhookUrl))); } - private List getMappingsForAlertType(AlertType alertType) { + private List getMappingsForAlertType(AlertType alertType) { String categoryName = alertType.getCategory().name(); - List mappings = - notificationConfigProperties.getCategoryMappings().get(categoryName); + Map> categoryMappings = notificationConfigProperties.getCategoryMappings(); - if (mappings == null || mappings.isEmpty()) { - mappings = notificationConfigProperties.getDefaultMappings(); - } - return mappings; + return Optional.ofNullable(categoryMappings.get(categoryName)) + .filter(list -> !list.isEmpty()) + .orElseGet(notificationConfigProperties::getDefaultMappings); } - private String getWebhookUrl(NotificationConfigProperties.PlatformMapping mapping) { + private Optional getWebhookUrl(PlatformMapping mapping) { String platform = mapping.getPlatform(); String webhookKey = mapping.getWebhook(); - NotificationConfigProperties.PlatformConfig platformConfig = notificationConfigProperties.getPlatforms() - .get(platform); - if (platformConfig != null) { - return platformConfig.getWebhooks().get(webhookKey); + Map platforms = notificationConfigProperties.getPlatforms(); + + return Optional.ofNullable(platforms.get(platform)) + .map(platformConfig -> platformConfig.getWebhooks().get(webhookKey)) + .map(url -> { + log.debug("Found webhook URL for platform '{}', key '{}': {}", platform, webhookKey, url); + return url; + }) + .or(() -> { + log.warn("No webhook URL found for platform '{}', key '{}'", platform, webhookKey); + return Optional.empty(); + }); + } + + private void sendNotification(String platform, NotificationEvent event, String webhookUrl) { + NotificationSender sender = notificationSenders.get(platform); + if (sender == null) { + log.warn("No NotificationSender found for platform: {}", platform); + return; + } + + try { + sender.sendNotification(event, webhookUrl); + log.debug("Notification sent via platform: {}", platform); + } catch (Exception e) { + log.error("Failed to send notification via platform: {}", platform, e); } - return null; } } From aa64979b784216544e427bbe985b23eb0c73692a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Fri, 15 Nov 2024 15:37:58 +0900 Subject: [PATCH 20/24] =?UTF-8?q?refactor(Notification):=20AbstractWebhook?= =?UTF-8?q?Client=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/out/webhook/{common => }/AbstractWebhookClient.java | 2 +- .../adapter/out/webhook/DiscordWebhookClient.java | 1 - .../adapter/out/webhook/SlackWebhookClient.java | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) rename src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/{common => }/AbstractWebhookClient.java (97%) diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/common/AbstractWebhookClient.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/AbstractWebhookClient.java similarity index 97% rename from src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/common/AbstractWebhookClient.java rename to src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/AbstractWebhookClient.java index 1ec92a961..d67f0a17f 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/common/AbstractWebhookClient.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/AbstractWebhookClient.java @@ -1,4 +1,4 @@ -package page.clab.api.global.common.notificationSetting.adapter.out.webhook.common; +package page.clab.api.global.common.notificationSetting.adapter.out.webhook; import jakarta.servlet.http.HttpServletRequest; import java.util.concurrent.CompletableFuture; diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/DiscordWebhookClient.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/DiscordWebhookClient.java index 9945ba1ac..fb1c443d3 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/DiscordWebhookClient.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/DiscordWebhookClient.java @@ -20,7 +20,6 @@ import org.springframework.stereotype.Component; import page.clab.api.domain.hiring.application.application.dto.request.ApplicationRequestDto; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberLoginInfoDto; -import page.clab.api.global.common.notificationSetting.adapter.out.webhook.common.AbstractWebhookClient; import page.clab.api.global.common.notificationSetting.application.dto.notification.BoardNotificationInfo; import page.clab.api.global.common.notificationSetting.application.dto.notification.BookLoanRecordNotificationInfo; import page.clab.api.global.common.notificationSetting.application.dto.notification.MembershipFeeNotificationInfo; diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/SlackWebhookClient.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/SlackWebhookClient.java index ea5a5cf4e..291bfd047 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/SlackWebhookClient.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/out/webhook/SlackWebhookClient.java @@ -25,7 +25,6 @@ import org.springframework.stereotype.Component; import page.clab.api.domain.hiring.application.application.dto.request.ApplicationRequestDto; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberLoginInfoDto; -import page.clab.api.global.common.notificationSetting.adapter.out.webhook.common.AbstractWebhookClient; import page.clab.api.global.common.notificationSetting.application.dto.notification.BoardNotificationInfo; import page.clab.api.global.common.notificationSetting.application.dto.notification.BookLoanRecordNotificationInfo; import page.clab.api.global.common.notificationSetting.application.dto.notification.MembershipFeeNotificationInfo; From 060fa83f5586f8fc4c69f90a4085db0c70d34c90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Sun, 17 Nov 2024 13:15:27 +0900 Subject: [PATCH 21/24] =?UTF-8?q?refactor(Notification):=20API=20Docs=20?= =?UTF-8?q?=EC=84=A4=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/NotificationSettingRetrieveController.java | 4 ++-- .../adapter/in/web/NotificationSettingUpdateController.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingRetrieveController.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingRetrieveController.java index 673db1696..00117991b 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingRetrieveController.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingRetrieveController.java @@ -15,12 +15,12 @@ @RestController @RequestMapping("/api/v1/notification-settings") @RequiredArgsConstructor -@Tag(name = "Notification Setting", description = "알림 설정") +@Tag(name = "Notification Setting", description = "웹훅 알림 설정") public class NotificationSettingRetrieveController { private final RetrieveNotificationSettingUseCase retrieveNotificationSettingUseCase; - @Operation(summary = "[S] 슬랙 알림 조회", description = "ROLE_SUPER 이상의 권한이 필요함") + @Operation(summary = "[S] 웹훅 알림 조회", description = "ROLE_SUPER 이상의 권한이 필요함") @PreAuthorize("hasRole('SUPER')") @GetMapping("") public ApiResponse> getNotificationSettings() { diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingUpdateController.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingUpdateController.java index bbe0b3342..488284d6a 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingUpdateController.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingUpdateController.java @@ -16,12 +16,12 @@ @RestController @RequestMapping("/api/v1/notification-settings") @RequiredArgsConstructor -@Tag(name = "Notification Setting", description = "알림 설정") +@Tag(name = "Notification Setting", description = "웹훅 알림 설정") public class NotificationSettingUpdateController { private final UpdateNotificationSettingUseCase updateNotificationSettingUseCase; - @Operation(summary = "[S] 슬랙 알림 설정 변경", description = "ROLE_SUPER 이상의 권한이 필요함") + @Operation(summary = "[S] 웹훅 알림 설정 변경", description = "ROLE_SUPER 이상의 권한이 필요함") @PreAuthorize("hasRole('SUPER')") @PutMapping("") public ApiResponse updateNotificationSetting( From 0345e73a4195f1391cb907edc21c5b3dd354d721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Sun, 17 Nov 2024 15:08:34 +0900 Subject: [PATCH 22/24] =?UTF-8?q?refactor(message):=20additionalMessage=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/service/MemberBanService.java | 4 ++-- .../application/service/MemberUnbanService.java | 4 ++-- .../service/BlacklistIpRegisterService.java | 4 ++-- .../service/BlacklistIpRemoveService.java | 5 +++-- .../service/BlacklistIpResetService.java | 5 +++-- .../service/AbnormalAccessIpRemoveService.java | 5 +++-- .../service/AbnormalAccessIpsClearService.java | 5 +++-- .../service/MemberRoleManagementService.java | 4 ++-- .../ExternalAccountLockManagementService.java | 5 +++-- .../ExternalIpAccessMonitorRegisterService.java | 4 ++-- .../filter/CustomBasicAuthenticationFilter.java | 16 ++++++++-------- .../auth/filter/JwtAuthenticationFilter.java | 4 ++-- 12 files changed, 35 insertions(+), 30 deletions(-) diff --git a/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberBanService.java b/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberBanService.java index ce2cac1c2..a7ca3a616 100644 --- a/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberBanService.java +++ b/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberBanService.java @@ -59,8 +59,8 @@ private AccountLockInfo createAccountLockInfo(String memberId) { private void sendSlackBanNotification(HttpServletRequest request, String memberId) { String memberName = externalRetrieveMemberUseCase.getMemberBasicInfoById(memberId).getMemberName(); - String additionalMessage = "ID: " + memberId + ", Name: " + memberName; + String memberBannedMessage = "ID: " + memberId + ", Name: " + memberName; eventPublisher.publishEvent( - new NotificationEvent(this, SecurityAlertType.MEMBER_BANNED, request, additionalMessage)); + new NotificationEvent(this, SecurityAlertType.MEMBER_BANNED, request, memberBannedMessage)); } } diff --git a/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberUnbanService.java b/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberUnbanService.java index a205dd1a6..1aaf67e4d 100644 --- a/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberUnbanService.java +++ b/src/main/java/page/clab/api/domain/auth/accountLockInfo/application/service/MemberUnbanService.java @@ -56,8 +56,8 @@ private AccountLockInfo createAccountLockInfo(String memberId) { private void sendSlackUnbanNotification(HttpServletRequest request, String memberId) { String memberName = externalRetrieveMemberUseCase.getMemberBasicInfoById(memberId).getMemberName(); - String additionalMessage = "ID: " + memberId + ", Name: " + memberName; + String memberUnbannedMessage = "ID: " + memberId + ", Name: " + memberName; eventPublisher.publishEvent( - new NotificationEvent(this, SecurityAlertType.MEMBER_UNBANNED, request, additionalMessage)); + new NotificationEvent(this, SecurityAlertType.MEMBER_UNBANNED, request, memberUnbannedMessage)); } } diff --git a/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRegisterService.java b/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRegisterService.java index 1a6f614c0..42bc014d8 100644 --- a/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRegisterService.java +++ b/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRegisterService.java @@ -43,10 +43,10 @@ public String registerBlacklistIp(HttpServletRequest request, BlacklistIpRequest BlacklistIp blacklistIp = mapper.fromDto(requestDto); registerBlacklistIpPort.save(blacklistIp); - String additionalMessage = "Added IP: " + ipAddress; + String blacklistAddedMessage = "Added IP: " + ipAddress; eventPublisher.publishEvent( new NotificationEvent(this, SecurityAlertType.BLACKLISTED_IP_ADDED, request, - additionalMessage)); + blacklistAddedMessage)); return ipAddress; }); diff --git a/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRemoveService.java b/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRemoveService.java index fe1304e83..26b558adc 100644 --- a/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRemoveService.java +++ b/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpRemoveService.java @@ -36,9 +36,10 @@ public String removeBlacklistIp(HttpServletRequest request, String ipAddress) { BlacklistIp blacklistIp = retrieveBlacklistIpPort.getByIpAddress(ipAddress); removeBlacklistIpPort.delete(blacklistIp); - String additionalMessage = "Deleted IP: " + ipAddress; + String blacklistRemovedMessage = "Deleted IP: " + ipAddress; eventPublisher.publishEvent( - new NotificationEvent(this, SecurityAlertType.BLACKLISTED_IP_REMOVED, request, additionalMessage)); + new NotificationEvent(this, SecurityAlertType.BLACKLISTED_IP_REMOVED, request, + blacklistRemovedMessage)); return blacklistIp.getIpAddress(); } diff --git a/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpResetService.java b/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpResetService.java index e3f8e6178..cdcf60021 100644 --- a/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpResetService.java +++ b/src/main/java/page/clab/api/domain/auth/blacklistIp/application/service/BlacklistIpResetService.java @@ -39,9 +39,10 @@ public List resetBlacklistIps(HttpServletRequest request) { .toList(); removeBlacklistIpPort.deleteAll(); - String additionalMessage = "Deleted IP: ALL"; + String blacklistRemovedMessage = "Deleted IP: ALL"; eventPublisher.publishEvent( - new NotificationEvent(this, SecurityAlertType.BLACKLISTED_IP_REMOVED, request, additionalMessage)); + new NotificationEvent(this, SecurityAlertType.BLACKLISTED_IP_REMOVED, request, + blacklistRemovedMessage)); return blacklistedIps; } diff --git a/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpRemoveService.java b/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpRemoveService.java index 394145421..a31e7ee8a 100644 --- a/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpRemoveService.java +++ b/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpRemoveService.java @@ -21,9 +21,10 @@ public class AbnormalAccessIpRemoveService implements RemoveAbnormalAccessIpUseC @Transactional public String removeAbnormalAccessIp(HttpServletRequest request, String ipAddress) { removeIpAccessMonitorPort.deleteById(ipAddress); - String additionalMessage = "Deleted IP: " + ipAddress; + String abnormalAccessIpDeletedMessage = "Deleted IP: " + ipAddress; eventPublisher.publishEvent( - new NotificationEvent(this, SecurityAlertType.ABNORMAL_ACCESS_IP_DELETED, request, additionalMessage)); + new NotificationEvent(this, SecurityAlertType.ABNORMAL_ACCESS_IP_DELETED, request, + abnormalAccessIpDeletedMessage)); return ipAddress; } } diff --git a/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpsClearService.java b/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpsClearService.java index 713bf41b7..f347f2644 100644 --- a/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpsClearService.java +++ b/src/main/java/page/clab/api/domain/auth/redisIpAccessMonitor/application/service/AbnormalAccessIpsClearService.java @@ -27,9 +27,10 @@ public List clearAbnormalAccessIps(HttpServletRequest requ List ipAccessMonitors = retrieveIpAccessMonitorPort.findAll(); clearIpAccessMonitorPort.deleteAll(); - String additionalMessage = "Deleted IP: ALL"; + String abnormalAccessIpClearedMessage = "Deleted IP: ALL"; eventPublisher.publishEvent( - new NotificationEvent(this, SecurityAlertType.ABNORMAL_ACCESS_IP_DELETED, request, additionalMessage)); + new NotificationEvent(this, SecurityAlertType.ABNORMAL_ACCESS_IP_DELETED, request, + abnormalAccessIpClearedMessage)); return ipAccessMonitors; } diff --git a/src/main/java/page/clab/api/domain/memberManagement/member/application/service/MemberRoleManagementService.java b/src/main/java/page/clab/api/domain/memberManagement/member/application/service/MemberRoleManagementService.java index 6ddef51f1..ceed5fb73 100644 --- a/src/main/java/page/clab/api/domain/memberManagement/member/application/service/MemberRoleManagementService.java +++ b/src/main/java/page/clab/api/domain/memberManagement/member/application/service/MemberRoleManagementService.java @@ -37,11 +37,11 @@ public String changeMemberRole(HttpServletRequest httpServletRequest, String mem updateMemberPort.update(member); - String additionalMessage = String.format("[%s] %s: %s -> %s", member.getId(), member.getName(), oldRole, + String memberRoleChangedMessage = String.format("[%s] %s: %s -> %s", member.getId(), member.getName(), oldRole, newRole); eventPublisher.publishEvent( new NotificationEvent(this, SecurityAlertType.MEMBER_ROLE_CHANGED, httpServletRequest, - additionalMessage)); + memberRoleChangedMessage)); return memberId; } diff --git a/src/main/java/page/clab/api/external/auth/accountLockInfo/port/ExternalAccountLockManagementService.java b/src/main/java/page/clab/api/external/auth/accountLockInfo/port/ExternalAccountLockManagementService.java index 01bdf722b..436c68313 100644 --- a/src/main/java/page/clab/api/external/auth/accountLockInfo/port/ExternalAccountLockManagementService.java +++ b/src/main/java/page/clab/api/external/auth/accountLockInfo/port/ExternalAccountLockManagementService.java @@ -100,9 +100,10 @@ private void sendSlackLoginFailureNotification(HttpServletRequest request, Strin String memberName = memberInfo.getMemberName(); if (memberInfo.isAdminRole()) { request.setAttribute("member", memberId + " " + memberName); - String additionalMessage = "로그인 실패 횟수 초과로 계정이 잠겼습니다."; + String repeatedLoginFailuresMessage = "로그인 실패 횟수 초과로 계정이 잠겼습니다."; eventPublisher.publishEvent( - new NotificationEvent(this, SecurityAlertType.REPEATED_LOGIN_FAILURES, request, additionalMessage)); + new NotificationEvent(this, SecurityAlertType.REPEATED_LOGIN_FAILURES, request, + repeatedLoginFailuresMessage)); } } } diff --git a/src/main/java/page/clab/api/external/auth/redisIpAccessMonitor/application/service/ExternalIpAccessMonitorRegisterService.java b/src/main/java/page/clab/api/external/auth/redisIpAccessMonitor/application/service/ExternalIpAccessMonitorRegisterService.java index 91e2bfbd7..61ac9a980 100644 --- a/src/main/java/page/clab/api/external/auth/redisIpAccessMonitor/application/service/ExternalIpAccessMonitorRegisterService.java +++ b/src/main/java/page/clab/api/external/auth/redisIpAccessMonitor/application/service/ExternalIpAccessMonitorRegisterService.java @@ -30,10 +30,10 @@ public class ExternalIpAccessMonitorRegisterService implements ExternalRegisterI public void registerIpAccessMonitor(HttpServletRequest request, String ipAddress) { RedisIpAccessMonitor redisIpAccessMonitor = getOrCreateRedisIpAccessMonitor(ipAddress); if (redisIpAccessMonitor.isBlocked()) { - String additionalMessage = "Blocked IP: " + ipAddress; + String abnormalAccessIpBlockedMessage = "Blocked IP: " + ipAddress; eventPublisher.publishEvent( new NotificationEvent(this, SecurityAlertType.ABNORMAL_ACCESS_IP_BLOCKED, request, - additionalMessage)); + abnormalAccessIpBlockedMessage)); } registerIpAccessMonitorPort.save(redisIpAccessMonitor); } diff --git a/src/main/java/page/clab/api/global/auth/filter/CustomBasicAuthenticationFilter.java b/src/main/java/page/clab/api/global/auth/filter/CustomBasicAuthenticationFilter.java index 1f08d5b32..ed2287c8e 100644 --- a/src/main/java/page/clab/api/global/auth/filter/CustomBasicAuthenticationFilter.java +++ b/src/main/java/page/clab/api/global/auth/filter/CustomBasicAuthenticationFilter.java @@ -132,26 +132,26 @@ private boolean verifyIpAddressAccess(HttpServletResponse response) throws IOExc private void sendAuthenticationSuccessAlertSlackMessage(HttpServletRequest request) { String path = request.getRequestURI(); if (WhitelistPathMatcher.isSwaggerIndexEndpoint(path)) { - String additionalMessage = "API 문서에 대한 접근이 허가되었습니다."; + String apiDocsAccessMessage = "API 문서에 대한 접근이 허가되었습니다."; eventPublisher.publishEvent( - new NotificationEvent(this, SecurityAlertType.API_DOCS_ACCESS, request, additionalMessage)); + new NotificationEvent(this, SecurityAlertType.API_DOCS_ACCESS, request, apiDocsAccessMessage)); } else if (WhitelistPathMatcher.isActuatorRequest(path)) { - String additionalMessage = "Actuator에 대한 접근이 허가되었습니다."; + String actuatorAccessMessage = "Actuator에 대한 접근이 허가되었습니다."; eventPublisher.publishEvent( - new NotificationEvent(this, SecurityAlertType.ACTUATOR_ACCESS, request, additionalMessage)); + new NotificationEvent(this, SecurityAlertType.ACTUATOR_ACCESS, request, actuatorAccessMessage)); } } private void sendAuthenticationFailureAlertSlackMessage(HttpServletRequest request) { String path = request.getRequestURI(); if (WhitelistPathMatcher.isSwaggerIndexEndpoint(path)) { - String additionalMessage = "API 문서에 대한 접근이 거부되었습니다."; + String apiDocsAccessMessage = "API 문서에 대한 접근이 거부되었습니다."; eventPublisher.publishEvent( - new NotificationEvent(this, SecurityAlertType.API_DOCS_ACCESS, request, additionalMessage)); + new NotificationEvent(this, SecurityAlertType.API_DOCS_ACCESS, request, apiDocsAccessMessage)); } else if (WhitelistPathMatcher.isActuatorRequest(path)) { - String additionalMessage = "Actuator에 대한 접근이 거부되었습니다."; + String actuatorAccessMessage = "Actuator에 대한 접근이 거부되었습니다."; eventPublisher.publishEvent( - new NotificationEvent(this, SecurityAlertType.ACTUATOR_ACCESS, request, additionalMessage)); + new NotificationEvent(this, SecurityAlertType.ACTUATOR_ACCESS, request, actuatorAccessMessage)); } } } diff --git a/src/main/java/page/clab/api/global/auth/filter/JwtAuthenticationFilter.java b/src/main/java/page/clab/api/global/auth/filter/JwtAuthenticationFilter.java index 40c64cd05..fba999b53 100644 --- a/src/main/java/page/clab/api/global/auth/filter/JwtAuthenticationFilter.java +++ b/src/main/java/page/clab/api/global/auth/filter/JwtAuthenticationFilter.java @@ -113,9 +113,9 @@ private boolean authenticateToken(HttpServletRequest request, HttpServletRespons private void sendSecurityAlertSlackMessage(HttpServletRequest request, RedisToken redisToken) { if (redisToken.isAdminToken()) { request.setAttribute("member", redisToken.getId()); - String additionalMessage = "토큰 발급 IP와 다른 IP에서 접속하여 토큰을 삭제하였습니다."; + String duplicateLoginMessage = "토큰 발급 IP와 다른 IP에서 접속하여 토큰을 삭제하였습니다."; eventPublisher.publishEvent( - new NotificationEvent(this, SecurityAlertType.DUPLICATE_LOGIN, request, additionalMessage)); + new NotificationEvent(this, SecurityAlertType.DUPLICATE_LOGIN, request, duplicateLoginMessage)); } } } From 3e78f20d961388bb1c9530e47feb1cb34c98171b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Sun, 17 Nov 2024 15:14:17 +0900 Subject: [PATCH 23/24] =?UTF-8?q?refactor(NotificationSetting):=20?= =?UTF-8?q?=EC=9B=B9=ED=9B=85=20=EC=95=8C=EB=A6=BC=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EA=B4=80=EB=A0=A8=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=EC=9D=84=20update=20->=20toggle=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...va => NotificationSettingToggleController.java} | 14 +++++++------- ...va => NotificationSettingToggleRequestDto.java} | 2 +- .../application/event/NotificationListener.java | 10 +++++----- ....java => ToggleNotificationSettingUseCase.java} | 4 ++-- ....java => ToggleNotificationSettingService.java} | 8 ++++---- 5 files changed, 19 insertions(+), 19 deletions(-) rename src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/{NotificationSettingUpdateController.java => NotificationSettingToggleController.java} (75%) rename src/main/java/page/clab/api/global/common/notificationSetting/application/dto/request/{NotificationSettingUpdateRequestDto.java => NotificationSettingToggleRequestDto.java} (91%) rename src/main/java/page/clab/api/global/common/notificationSetting/application/port/in/{UpdateNotificationSettingUseCase.java => ToggleNotificationSettingUseCase.java} (73%) rename src/main/java/page/clab/api/global/common/notificationSetting/application/service/{UpdateNotificationSettingService.java => ToggleNotificationSettingService.java} (91%) diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingUpdateController.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingToggleController.java similarity index 75% rename from src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingUpdateController.java rename to src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingToggleController.java index 488284d6a..9245d714c 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingUpdateController.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingToggleController.java @@ -10,24 +10,24 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import page.clab.api.global.common.dto.ApiResponse; -import page.clab.api.global.common.notificationSetting.application.dto.request.NotificationSettingUpdateRequestDto; -import page.clab.api.global.common.notificationSetting.application.port.in.UpdateNotificationSettingUseCase; +import page.clab.api.global.common.notificationSetting.application.dto.request.NotificationSettingToggleRequestDto; +import page.clab.api.global.common.notificationSetting.application.port.in.ToggleNotificationSettingUseCase; @RestController @RequestMapping("/api/v1/notification-settings") @RequiredArgsConstructor @Tag(name = "Notification Setting", description = "웹훅 알림 설정") -public class NotificationSettingUpdateController { +public class NotificationSettingToggleController { - private final UpdateNotificationSettingUseCase updateNotificationSettingUseCase; + private final ToggleNotificationSettingUseCase toggleNotificationSettingUseCase; @Operation(summary = "[S] 웹훅 알림 설정 변경", description = "ROLE_SUPER 이상의 권한이 필요함") @PreAuthorize("hasRole('SUPER')") @PutMapping("") - public ApiResponse updateNotificationSetting( - @Valid @RequestBody NotificationSettingUpdateRequestDto requestDto + public ApiResponse toggleNotificationSetting( + @Valid @RequestBody NotificationSettingToggleRequestDto requestDto ) { - updateNotificationSettingUseCase.updateNotificationSetting(requestDto.getAlertType(), requestDto.isEnabled()); + toggleNotificationSettingUseCase.toggleNotificationSetting(requestDto.getAlertType(), requestDto.isEnabled()); return ApiResponse.success(); } } diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/dto/request/NotificationSettingUpdateRequestDto.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/dto/request/NotificationSettingToggleRequestDto.java similarity index 91% rename from src/main/java/page/clab/api/global/common/notificationSetting/application/dto/request/NotificationSettingUpdateRequestDto.java rename to src/main/java/page/clab/api/global/common/notificationSetting/application/dto/request/NotificationSettingToggleRequestDto.java index b0db80a2e..172cc146d 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/application/dto/request/NotificationSettingUpdateRequestDto.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/dto/request/NotificationSettingToggleRequestDto.java @@ -7,7 +7,7 @@ @Getter @Setter -public class NotificationSettingUpdateRequestDto { +public class NotificationSettingToggleRequestDto { @NotNull(message = "{notNull.notificationSetting.alertType}") @Schema(description = "알림 타입", example = "서버 시작") diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java index 2d2116822..dbcdc7b6f 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java @@ -8,7 +8,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; -import page.clab.api.global.common.notificationSetting.application.port.in.UpdateNotificationSettingUseCase; +import page.clab.api.global.common.notificationSetting.application.port.in.ToggleNotificationSettingUseCase; import page.clab.api.global.common.notificationSetting.application.port.out.NotificationSender; import page.clab.api.global.common.notificationSetting.config.NotificationConfigProperties; import page.clab.api.global.common.notificationSetting.config.NotificationConfigProperties.PlatformConfig; @@ -20,15 +20,15 @@ @Slf4j public class NotificationListener { - private final UpdateNotificationSettingUseCase updateNotificationSettingUseCase; + private final ToggleNotificationSettingUseCase toggleNotificationSettingUseCase; private final Map notificationSenders; private final NotificationConfigProperties notificationConfigProperties; public NotificationListener( - UpdateNotificationSettingUseCase updateNotificationSettingUseCase, + ToggleNotificationSettingUseCase toggleNotificationSettingUseCase, List notificationSenderList, NotificationConfigProperties notificationConfigProperties) { - this.updateNotificationSettingUseCase = updateNotificationSettingUseCase; + this.toggleNotificationSettingUseCase = toggleNotificationSettingUseCase; this.notificationConfigProperties = notificationConfigProperties; this.notificationSenders = notificationSenderList.stream() .collect(Collectors.toMap(NotificationSender::getPlatformName, Function.identity())); @@ -38,7 +38,7 @@ public NotificationListener( public void handleNotificationEvent(NotificationEvent event) { AlertType alertType = event.getAlertType(); - NotificationSetting setting = updateNotificationSettingUseCase.getOrCreateDefaultSetting(alertType); + NotificationSetting setting = toggleNotificationSettingUseCase.getOrCreateDefaultSetting(alertType); if (!setting.isEnabled()) { return; } diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/port/in/UpdateNotificationSettingUseCase.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/port/in/ToggleNotificationSettingUseCase.java similarity index 73% rename from src/main/java/page/clab/api/global/common/notificationSetting/application/port/in/UpdateNotificationSettingUseCase.java rename to src/main/java/page/clab/api/global/common/notificationSetting/application/port/in/ToggleNotificationSettingUseCase.java index 3825f9246..2c7533dce 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/application/port/in/UpdateNotificationSettingUseCase.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/port/in/ToggleNotificationSettingUseCase.java @@ -3,9 +3,9 @@ import page.clab.api.global.common.notificationSetting.domain.AlertType; import page.clab.api.global.common.notificationSetting.domain.NotificationSetting; -public interface UpdateNotificationSettingUseCase { +public interface ToggleNotificationSettingUseCase { - void updateNotificationSetting(String alertTypeName, boolean enabled); + void toggleNotificationSetting(String alertTypeName, boolean enabled); NotificationSetting getOrCreateDefaultSetting(AlertType alertType); } diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/service/UpdateNotificationSettingService.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/service/ToggleNotificationSettingService.java similarity index 91% rename from src/main/java/page/clab/api/global/common/notificationSetting/application/service/UpdateNotificationSettingService.java rename to src/main/java/page/clab/api/global/common/notificationSetting/application/service/ToggleNotificationSettingService.java index c57706d75..1ed0353f9 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/application/service/UpdateNotificationSettingService.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/service/ToggleNotificationSettingService.java @@ -3,7 +3,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import page.clab.api.global.common.notificationSetting.application.port.in.UpdateNotificationSettingUseCase; +import page.clab.api.global.common.notificationSetting.application.port.in.ToggleNotificationSettingUseCase; import page.clab.api.global.common.notificationSetting.application.port.out.RetrieveNotificationSettingPort; import page.clab.api.global.common.notificationSetting.application.port.out.UpdateNotificationSettingPort; import page.clab.api.global.common.notificationSetting.domain.AlertType; @@ -18,13 +18,13 @@ *

          * 주요 기능: *

            - *
          • {@link #updateNotificationSetting(String, boolean)} - 주어진 알림 유형에 대해 알림 설정을 업데이트합니다.
          • + *
          • {@link #toggleNotificationSetting(String, boolean)} - 주어진 알림 유형에 대해 알림 설정을 업데이트합니다.
          • *
          • {@link #getOrCreateDefaultSetting(AlertType)} - 주어진 알림 유형에 대한 기본 알림 설정을 조회하거나, 존재하지 않으면 생성합니다.
          • *
          */ @Service @RequiredArgsConstructor -public class UpdateNotificationSettingService implements UpdateNotificationSettingUseCase { +public class ToggleNotificationSettingService implements ToggleNotificationSettingUseCase { private final AlertTypeResolver alertTypeResolver; private final RetrieveNotificationSettingPort retrieveNotificationSettingPort; @@ -32,7 +32,7 @@ public class UpdateNotificationSettingService implements UpdateNotificationSetti @Transactional @Override - public void updateNotificationSetting(String alertTypeName, boolean enabled) { + public void toggleNotificationSetting(String alertTypeName, boolean enabled) { AlertType alertType = alertTypeResolver.resolve(alertTypeName); NotificationSetting setting = getOrCreateDefaultSetting(alertType); setting.updateEnabled(enabled); From 02c0ca97fcc74135d16c3f6c4b1a66df22e8133a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=EA=B4=80=ED=9D=AC?= Date: Sun, 17 Nov 2024 15:16:37 +0900 Subject: [PATCH 24/24] =?UTF-8?q?refactor(NotificationSetting):=20ToggleNo?= =?UTF-8?q?tificationSettingUseCase=20->=20ManageNotificationSettingUseCas?= =?UTF-8?q?e=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/NotificationSettingToggleController.java | 6 +++--- .../application/event/NotificationListener.java | 10 +++++----- ...Case.java => ManageNotificationSettingUseCase.java} | 2 +- ...vice.java => ManageNotificationSettingService.java} | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) rename src/main/java/page/clab/api/global/common/notificationSetting/application/port/in/{ToggleNotificationSettingUseCase.java => ManageNotificationSettingUseCase.java} (88%) rename src/main/java/page/clab/api/global/common/notificationSetting/application/service/{ToggleNotificationSettingService.java => ManageNotificationSettingService.java} (95%) diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingToggleController.java b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingToggleController.java index 9245d714c..74023a6bc 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingToggleController.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/adapter/in/web/NotificationSettingToggleController.java @@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.RestController; import page.clab.api.global.common.dto.ApiResponse; import page.clab.api.global.common.notificationSetting.application.dto.request.NotificationSettingToggleRequestDto; -import page.clab.api.global.common.notificationSetting.application.port.in.ToggleNotificationSettingUseCase; +import page.clab.api.global.common.notificationSetting.application.port.in.ManageNotificationSettingUseCase; @RestController @RequestMapping("/api/v1/notification-settings") @@ -19,7 +19,7 @@ @Tag(name = "Notification Setting", description = "웹훅 알림 설정") public class NotificationSettingToggleController { - private final ToggleNotificationSettingUseCase toggleNotificationSettingUseCase; + private final ManageNotificationSettingUseCase manageNotificationSettingUseCase; @Operation(summary = "[S] 웹훅 알림 설정 변경", description = "ROLE_SUPER 이상의 권한이 필요함") @PreAuthorize("hasRole('SUPER')") @@ -27,7 +27,7 @@ public class NotificationSettingToggleController { public ApiResponse toggleNotificationSetting( @Valid @RequestBody NotificationSettingToggleRequestDto requestDto ) { - toggleNotificationSettingUseCase.toggleNotificationSetting(requestDto.getAlertType(), requestDto.isEnabled()); + manageNotificationSettingUseCase.toggleNotificationSetting(requestDto.getAlertType(), requestDto.isEnabled()); return ApiResponse.success(); } } diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java index dbcdc7b6f..56c2eda22 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/event/NotificationListener.java @@ -8,7 +8,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; -import page.clab.api.global.common.notificationSetting.application.port.in.ToggleNotificationSettingUseCase; +import page.clab.api.global.common.notificationSetting.application.port.in.ManageNotificationSettingUseCase; import page.clab.api.global.common.notificationSetting.application.port.out.NotificationSender; import page.clab.api.global.common.notificationSetting.config.NotificationConfigProperties; import page.clab.api.global.common.notificationSetting.config.NotificationConfigProperties.PlatformConfig; @@ -20,15 +20,15 @@ @Slf4j public class NotificationListener { - private final ToggleNotificationSettingUseCase toggleNotificationSettingUseCase; + private final ManageNotificationSettingUseCase manageNotificationSettingUseCase; private final Map notificationSenders; private final NotificationConfigProperties notificationConfigProperties; public NotificationListener( - ToggleNotificationSettingUseCase toggleNotificationSettingUseCase, + ManageNotificationSettingUseCase manageNotificationSettingUseCase, List notificationSenderList, NotificationConfigProperties notificationConfigProperties) { - this.toggleNotificationSettingUseCase = toggleNotificationSettingUseCase; + this.manageNotificationSettingUseCase = manageNotificationSettingUseCase; this.notificationConfigProperties = notificationConfigProperties; this.notificationSenders = notificationSenderList.stream() .collect(Collectors.toMap(NotificationSender::getPlatformName, Function.identity())); @@ -38,7 +38,7 @@ public NotificationListener( public void handleNotificationEvent(NotificationEvent event) { AlertType alertType = event.getAlertType(); - NotificationSetting setting = toggleNotificationSettingUseCase.getOrCreateDefaultSetting(alertType); + NotificationSetting setting = manageNotificationSettingUseCase.getOrCreateDefaultSetting(alertType); if (!setting.isEnabled()) { return; } diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/port/in/ToggleNotificationSettingUseCase.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/port/in/ManageNotificationSettingUseCase.java similarity index 88% rename from src/main/java/page/clab/api/global/common/notificationSetting/application/port/in/ToggleNotificationSettingUseCase.java rename to src/main/java/page/clab/api/global/common/notificationSetting/application/port/in/ManageNotificationSettingUseCase.java index 2c7533dce..338de0f31 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/application/port/in/ToggleNotificationSettingUseCase.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/port/in/ManageNotificationSettingUseCase.java @@ -3,7 +3,7 @@ import page.clab.api.global.common.notificationSetting.domain.AlertType; import page.clab.api.global.common.notificationSetting.domain.NotificationSetting; -public interface ToggleNotificationSettingUseCase { +public interface ManageNotificationSettingUseCase { void toggleNotificationSetting(String alertTypeName, boolean enabled); diff --git a/src/main/java/page/clab/api/global/common/notificationSetting/application/service/ToggleNotificationSettingService.java b/src/main/java/page/clab/api/global/common/notificationSetting/application/service/ManageNotificationSettingService.java similarity index 95% rename from src/main/java/page/clab/api/global/common/notificationSetting/application/service/ToggleNotificationSettingService.java rename to src/main/java/page/clab/api/global/common/notificationSetting/application/service/ManageNotificationSettingService.java index 1ed0353f9..d42337ddf 100644 --- a/src/main/java/page/clab/api/global/common/notificationSetting/application/service/ToggleNotificationSettingService.java +++ b/src/main/java/page/clab/api/global/common/notificationSetting/application/service/ManageNotificationSettingService.java @@ -3,7 +3,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import page.clab.api.global.common.notificationSetting.application.port.in.ToggleNotificationSettingUseCase; +import page.clab.api.global.common.notificationSetting.application.port.in.ManageNotificationSettingUseCase; import page.clab.api.global.common.notificationSetting.application.port.out.RetrieveNotificationSettingPort; import page.clab.api.global.common.notificationSetting.application.port.out.UpdateNotificationSettingPort; import page.clab.api.global.common.notificationSetting.domain.AlertType; @@ -24,7 +24,7 @@ */ @Service @RequiredArgsConstructor -public class ToggleNotificationSettingService implements ToggleNotificationSettingUseCase { +public class ManageNotificationSettingService implements ManageNotificationSettingUseCase { private final AlertTypeResolver alertTypeResolver; private final RetrieveNotificationSettingPort retrieveNotificationSettingPort;