Spring Boot와 Discord 웹훅으로 실시간 신고 알림 시스템 구현하기

이채은·2024년 11월 12일
2
post-thumbnail

현재 개발 중인 "웹소설 유저를 위한 기록 & 커뮤니티 서비스 웹소소"에는 신고 서비스가 존재한다.
다른 사용자가 작성한 피드 글 또는 댓글이 부적절한 표현 또는 스포일러가 포함되어 있다고 생각했을 때 두 가지 유형의 신고가 가능하다.
우리 팀은 신고가 발생했을 때 이유와 글의 본문 내용 등을 바로바로 확인할 수 있어야 할 것 같다고 판단하여, 현재 사용 중인 협업 툴인 디스코드로 신고 알림을 받기로 결정했다.

디스코드 설정

웹훅(webhook)은 Discord 채널에 메시지를 게시하는 간편한 방법입니다.
- Discord webhook 공식 문서

먼저 사용 중인 팀 디스코드 서버에 웹훅을 생성해 줘야 한다.

별다른 과정 없이 바로 생성이 가능하다.
생성 후 사진처럼 이름과 알림을 받을 채널을 등록해 줘야 한다.
그리고 저 웹후크 URL을 복사해두면 된다!


Spring 프로젝트에 웹훅 연동하기

Discord가 제공하는 웹훅 공식 문서에 따르면 다양한 옵션들을 사용할 수 있는데, 우리는 간단한 알림만 받아보면 됐었기에 내용(content) 파라미터만 사용했다.


public record DiscordWebhookMessage(
        String content
) {
    public static DiscordWebhookMessage of(String content) {
        if (content.length() >= 2000) {
            content = content.substring(0, 1993) + "\n...```";
        }
        return new DiscordWebhookMessage(content);
    }
}

메세지를 담을 클래스를 만들어줬다.
신고 내용이 너무 길면 가독성이 떨어질 수 있어서, 메시지 길이를 2000자 이하로 제한하는 코드를 추가해주었다.


import static org.websoso.WSSServer.domain.common.ReportedType.IMPERTINENCE;
import static org.websoso.WSSServer.domain.common.ReportedType.SPOILER;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.websoso.WSSServer.domain.Comment;
import org.websoso.WSSServer.domain.Feed;
import org.websoso.WSSServer.domain.User;
import org.websoso.WSSServer.domain.common.ReportedType;

public class MessageFormatter {

    private static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";

    private static final String FEED_REPORT_MESSAGE =
            "```[%s] 피드 %s 신고가 접수되었습니다.\n\n" +
                    "[신고된 피드 작성자]\n" +
                    "유저 아이디 : %d\n" +
                    "유저 닉네임 : %s\n\n" +
                    "[신고된 피드 내용]\n%s\n\n" +
                    "[신고 횟수]\n총 신고 횟수 %d회.\n" +
                    "%s\n```";

    private static final String COMMENT_REPORT_MESSAGE =
            "```[%s] 피드 댓글 %s 신고가 접수되었습니다.\n\n" +
                    "[신고된 댓글 작성자]\n" +
                    "유저 아이디 : %d\n" +
                    "유저 닉네임 : %s\n\n" +
                    "[신고된 댓글 내용]\n%s\n\n" +
                    "[신고 횟수]\n총 신고 횟수 %d회.\n" +
                    "%s\n```";

    public static String formatFeedReportMessage(Feed feed, ReportedType reportedType, int reportedCount,
                                                 boolean isHidden) {
        String hiddenMessage = isHidden ? "해당 피드는 숨김 처리되었습니다." : "해당 피드는 숨김 처리되지 않았습니다.";

        return String.format(
                FEED_REPORT_MESSAGE,
                LocalDateTime.now().format(DateTimeFormatter.ofPattern(DATE_TIME_FORMAT)),
                reportedType.getDescription(),
                feed.getUser().getUserId(),
                feed.getUser().getNickname(),
                feed.getFeedContent(),
                reportedCount,
                hiddenMessage
        );
    }

    public static String formatCommentReportMessage(Comment comment, ReportedType reportedType, User user,
                                                    int reportedCount, boolean isHidden) {
        String hiddenMessage = "해당 댓글은 현재 숨김 처리되지 않은 상태입니다.";
        if (isHidden) {
            if (reportedType.equals(SPOILER)) {
                hiddenMessage = "해당 댓글은 스포일러 댓글로 지정되었습니다.";
            } else if (reportedType.equals(IMPERTINENCE)) {
                hiddenMessage = "해당 댓글은 부적절한 내용으로 인해 숨김 처리되었습니다.";
            }
        }

        return String.format(
                COMMENT_REPORT_MESSAGE,
                LocalDateTime.now().format(DateTimeFormatter.ofPattern(DATE_TIME_FORMAT)),
                reportedType.getDescription(),
                user.getUserId(),
                user.getNickname(),
                comment.getCommentContent(),
                reportedCount,
                hiddenMessage
        );
    }
}

그리고 알림의 내용 포맷을 정의할 클래스를 만들어줬다.
피드와 댓글 신고 알림 메시지는 서로 다른 양식을 사용하기 때문에 신고된 콘텐츠의 종류와 상태에 따라 자동으로 알맞은 메시지를 포맷팅하도록 했다.


import static org.springframework.http.HttpMethod.POST;
import static org.springframework.http.HttpStatus.NO_CONTENT;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.websoso.WSSServer.domain.common.DiscordWebhookMessage;

@Service
@Slf4j
public class MessageService {

    @Value("${logging.discord.webhook-url}")
    String discordWebhookUrl;

    public void sendDiscordWebhookMessage(DiscordWebhookMessage message) {
        try {
            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.add("Content-Type", "application/json; utf-8");
            HttpEntity<DiscordWebhookMessage> messageEntity = new HttpEntity<>(message, httpHeaders);

            RestTemplate template = new RestTemplate();
            ResponseEntity<String> response = template.exchange(
                    discordWebhookUrl,
                    POST,
                    messageEntity,
                    String.class
            );

            if (response.getStatusCode().value() != NO_CONTENT.value()) {
                log.error("메시지 전송 이후 에러 발생");
            }

        } catch (Exception e) {
            log.error("에러 발생 :: " + e);
        }
    }
}

이제 마지막으로 실제로 디스코드와 통신해 메세지를 전송할 서비스 클래스를 만들어준다.
디스코드 웹훅 URL은 복사해두었던 것을 그대로 사용하되, 주소가 공개되면 안되기 때문에 yml 파일에 등록해주었다.
RestTemplate를 이용하여 POST로 디스코드 웹훅에게 알림을 요청하고 있는 것을 확인할 수 있다.


실제로 신고가 발생하면 알림이 잘 발송되었다 🙌



이렇게 디스코드 웹훅을 사용해서 신고 발생 시 디스코드 채널에 실시간으로 알림을 전송해, 팀원들이 바로 신고 내용을 확인할 수 있도록 구현했다.
비교적 간편하게 구현이 가능해서 간단한 로그를 확인하는 데 유용하게 사용이 가능할 것 같다!

0개의 댓글

관련 채용 정보