Infra

<Infra> Spring Slack ๋ฉ”์„ธ์ง€ ์ „์†ก (Incoming Webhook)

wjdtkdgns 2023. 3. 27. 16:56

๐Ÿ“Œ ์Šฌ๋ž™ ์—ฐ๊ฒฐ ๋ฐฉ๋ฒ• ์„ ํƒ

๊ตฌํ˜„ํ•  ๊ธฐ๋Šฅ์ด ๋ฉ”์„ธ์ง€ ์ „์†ก์ด๊ธฐ ๋•Œ๋ฌธ์— ์œ„ ๋ฐฉ๋ฒ• ์ค‘์—์„œ, Web API ํ˜น์€ Webhook์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

  • Slack API: Slack์—์„œ ์ œ๊ณตํ•˜๋Š” ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ(๋ฉ”์„ธ์ง€, ์ฑ„๋„ ๊ด€๋ฆฌ, ํŒŒ์ผ ์—…๋กœ๋“œ, ์œ ์ € ์ •๋ณด)์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค. Slack API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ API ํ† ํฐ์ด ํ•„์š”ํ•˜๋ฉฐ, ํ•ด๋‹น ํ† ํฐ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๋ถ€์—ฌ๋ฅผ ๋ฐ›์•„์•ผ ํ•œ๋‹ค.
  • Slack Incoming Webhook: Slack Incoming Webhook์€ ์ƒˆ๋กœ์šด ๋ฉ”์‹œ์ง€๋ฅผ ํŠน์ • ์ฑ„๋„์— ์ž๋™์œผ๋กœ ๊ฒŒ์‹œํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. Incoming Webhook์„ ์ƒ์„ฑํ•œ ํ›„ Webhook URL์„ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ๋กœ์šด ๋ฉ”์‹œ์ง€๋ฅผ ๊ฒŒ์‹œํ•  ์ˆ˜ ์žˆ๋‹ค. Webhook URL์€ ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๋ถ€์—ฌ๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์œผ๋ฉฐ, ๋ˆ„๊ตฌ๋‚˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

์œ„์™€ ๊ฐ™์€ ํŠน์ง•์„ ์ง€๋‹ˆ๋Š”๋ฐ, ๋ฉ”์„ธ์ง€ ์ „์†ก๋งŒ์„ ํ•˜๊ธฐ์—” webhook์œผ๋กœ ํ•˜๋Š” ๊ฒƒ์ด ๋” ์œ ๋ฆฌํ•˜๋‹ค ์ƒ๊ฐํ•˜์—ฌ webhook์„ ์‚ฌ์šฉํ•  ์˜ˆ์ •์ด๋‹ค.

 

๐Ÿ“Œ ์›Œํฌ ์ŠคํŽ˜์ด์Šค ๋ฐ APP ์ƒ์„ฑ

๐Ÿ”บ ์›Œํฌ ์ŠคํŽ˜์ด์Šค ์ƒ์„ฑ

์ขŒ์ธก ๋ฐ”์—์„œ + ๋ˆ„๋ฅด๊ณ  ์ƒ์„ฑํ•˜๋ฉด ๋จ

๐Ÿ”บ APP ์ƒ์„ฑ

Slack api ํŽ˜์ด์ง€์ด๋‹ค.

์—ฌ๊ธฐ์„œ ์ง„ํ–‰ํ•˜๋ฉด ๋œ๋‹ค.

SlackApi

 

Slack API: Applications | Slack

Your Apps Don't see an app you're looking for? Sign in to another workspace.

api.slack.com

์—ฌ๊ธฐ์„œ Create App ๋ˆ„๋ฅด๊ณ  from scratch

์ด๋ฆ„ ์ ๊ณ , ์‚ฌ์šฉํ•  ์›Œํฌ ์ŠคํŽ˜์ด์Šค ์ •ํ•˜๊ณ  create ์ง„ํ–‰

๐Ÿ”บ APP ๊ถŒํ•œ ์„ค์ •

Basic Infomation ๋ฉ”๋‰ด๋กœ ๋“ค์–ด์™€์„œ Permission์œผ๋กœ ์ด๋™

\

๋‚ด๋ ค๋ณด๋ฉด Scope ๋‚˜์˜ค๋Š”๋ฐ,

์—ฌ๊ธฐ์„œ Bot Token Scope ์ˆ˜์ •

์ฑ„๋„์— ๊ธ€์„ ์ž‘์„ฑํ•ด์•ผํ•˜๋ฏ€๋กœ chat:write ์ถ”๊ฐ€

๐Ÿ”บ APP ํ† ํฐ ๋ฐœํ–‰ ๋ฐ ์›Œํฌ ์ŠคํŽ˜์ด์Šค์— ๋ด‡ ์ถ”๊ฐ€

ํ† ํฐ ๋ฐœํ–‰ ๋จผ์ € ํ•œ๋‹ค.

slack ์•ฑ์œผ๋กœ ๋Œ์•„๊ฐ€์„œ ์ฑ„๋„ ์„ธ๋ถ€์ •๋ณด ๋ณด๊ธฐ ํด๋ฆญ

์•ฑ ์ถ”๊ฐ€ → app ์ฐพ์€ ํ›„ ์ถ”๊ฐ€

๊ทธ๋Ÿฌ๋ฉด ์™„๋ฃŒ


๐Ÿ“Œ ์Šคํ”„๋ง ํ™˜๊ฒฝ ์…‹ํŒ…

๐Ÿ”บ dependency ์ถ”๊ฐ€

implementation("com.slack.api:slack-api-client:1.28.0")

slack api ์‚ฌ์šฉ์œ„ํ•ด ์„ค์น˜

๐Ÿ”บ application.yml ์„ค์ •

slack:
  token: 'ํ† ํฐ'
  channel:
    monitor: '์ฑ„๋„ ๋ช…'

์›Œํฌ ์ŠคํŽ˜์ด์Šค ์ด๋ฆ„ ์•„๋‹˜ ์ฃผ์˜


๐Ÿ“Œ ์Šฌ๋ž™ ์—ฐ๊ฒฐ

๐Ÿ”บ Incoming Webhook ์„ค์ •

์ฑ„๋„ ์„ธ๋ถ€์ •๋ณด ๋ณด๊ธฐ → ํ†ตํ•ฉ → ์•ฑ ์ถ”๊ฐ€

incoming webhook ์ฐพ์•„์„œ ์„ค์น˜

์ฑ„๋„ ์ฐพ์•„์„œ ์•ฑ ์ถ”๊ฐ€

๊ทธ๋Ÿฌ๋ฉด Webhook url ๋ณด์ž„

๊ทธ๋ฆฌ๊ณ  ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

curl -X POST --data-urlencode "payload={\\"channel\\": \\"#test\\", \\"username\\": \\"webhookbot\\", \\"text\\": \\"์ด ํ•ญ๋ชฉ์€ #๊ฐœ์˜ test์— ํฌ์ŠคํŠธ๋˜๋ฉฐ webhookbot์ด๋ผ๋Š” ๋ด‡์—์„œ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.\\", \\"icon_emoji\\": \\":ghost:\\"}" <https://hooks.slack.com/services/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>

์ด๊ฑธ ํ…Œ์ŠคํŠธ ํ•ด๋ณด๋ฉด

์ •์ƒ์ ์œผ๋กœ ์ „์†ก๋œ ๊ฒฝ์šฐ ok๋ผ๋Š” ์‘๋‹ต์„ ๋ณด์ด๋ฉฐ

๋ฉ”์„ธ์ง€๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

 


๐Ÿ“Œ ์Šคํ”„๋ง ์ฝ”๋“œ ๊ตฌํ˜„

๐Ÿ”บ Slack ์•Œ๋ฆผ ๊ตฌํ˜„

eventlistener๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ–ˆ๋‹ค.

// Event.java

public class Event {
    private static ApplicationEventPublisher publisher;

    static void setPublisher(ApplicationEventPublisher publisher) {
        Event.publisher = publisher;
    }

    public static void raise(Object event) {
        if (publisher != null) {
            publisher.publishEvent(event);
        }
    }
}

์ด๋ฒคํŠธ ๋ฐœ์ƒ๊ณผ ์ถœํŒ์„ ์œ„ํ•ด ApplicationEventPublisher ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

Event ๋ผ๋Š” ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด์„œ ์ด๋ฒคํŠธ๋ฅผ ํŽธํ•˜๊ฒŒ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ๋‹ค.

// EventConfig.java

@Configuration
public class EventConfig {
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    @Bean
    public InitializingBean eventsInitializer() {
        return () -> Event.setPublisher(applicationEventPublisher);
    }
}

Event ํด๋ž˜์Šค๋ฅผ ์ดˆ๊ธฐํ™”ํ•ด์ค€๋‹ค.

// SlackService.java

@Service
@RequiredArgsConstructor
public class SlackService {
    public void SlackTest() {
        Event.raise(new TestEvent(100));
    }
}

Event.raise() ๋ฅผ ํ†ตํ•ด ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค

raise ํ•จ์ˆ˜์˜ ์ธ์ž๋กœ TestEvent ๋ผ๋Š” ๊ฐ์ฒด๊ฐ€ ์ „๋‹ฌ๋˜๋Š”๋ฐ, ์ด๋Š” ์ด๋ฒคํŠธ์— ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์ „๋‹ฌํ•ด์ฃผ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

// TestEvent.java

@AllArgsConstructor
@Getter
public class TestEvent {
    private int number;
}

TestEvent ๋Š” ์ผ๋ฐ˜์ ์ธ ๊ฐ์ฒด์ด๋‹ค.

// SlackSendMessageHandler.java

@Component
public class SlackSendMessageHandler {
    @Value("${slack.webhook.url}")
    String url;

    @Async
    @EventListener(TestEvent.class)
    public void handle(TestEvent testEvent) {
        final Slack slack = Slack.getInstance();
        final Payload payload =
                Payload.builder().text(Integer.toString(testEvent.getNumber())).build();

        String responseBody = null;
        try {
            responseBody = slack.send(url, payload).getBody();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

raise ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด publishEvent ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜๋ฉด, EventListener ๋ฅผ ํ†ตํ•ด handle ํ•จ์ˆ˜๊ฐ€ ์ž‘๋™ํ•œ๋‹ค.

payload ์— ๋ณด๋‚ผ ๋‚ด์šฉ์„ ์ ์€ ํ›„, send ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์ „์†กํ•˜๋ฉด ๋œ๋‹ค.

์„ฑ๊ณต!!

๐Ÿ”บ ์œ ์ € ์ •๋ณด ๋ฐ Markdown ํŽธ์ง‘

๋ฉ”์„ธ์ง€๋ฅผ ๋ณด๋‚ด๋Š”๋ฐ ์„ฑ๊ณตํ–ˆ์ง€๋งŒ, ์œ ์ € ์ •๋ณด์™€ ๋‚ด์šฉ์„ ํŽธ์ง‘ํ•˜๊ณ ์‹ถ๋‹ค.

ํ•ด๋ณด์ž!

  1. ์œ ์ € ์ •๋ณด ํŽธ์ง‘
final Payload payload = Payload.builder()
	                        .text(Integer.toString(testEvent.getNumber()))
	                        .username("์œ ์ €")
	                        .iconEmoji(":dog:")
	                        .build();

Payload ์— ์„ค์ •์„ ํ•˜๋ฉด ๊ฐ€๋Šฅํ•˜๋‹ค.

  1. markdown ํŽธ์ง‘
final Payload payload = Payload.builder()
	                        .text(Integer.toString(testEvent.getNumber()))
	                        .username("์œ ์ €")
	                        .iconEmoji(":dog:")
	                        .blocks(blocks)
	                        .build();
private List<LayoutBlock> SlackMessageConverter(TestEvent testEvent) {
    List<LayoutBlock> layoutBlocks = new ArrayList<>();
    // ์ œ๋ชฉ
    layoutBlocks.add(HeaderBlock.builder().text(plainText("์ œ๋ชฉ")).build());
    // ๊ตฌ๋ถ„์„ 
    layoutBlocks.add(new DividerBlock());
    // text
    MarkdownTextObject messageText = MarkdownTextObject.builder()
            .text(Integer.toString(testEvent.getNumber()))
            .build();
    layoutBlocks.add(SectionBlock.builder().text(messageText).build());
    return layoutBlocks;
}

๋งˆํฌ๋‹ค์šด ์˜ค๋ธŒ์ ํŠธ๊ฐ€ Slack Api์— ํฌํ•จ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์—,

์ด๋“ค์„ ์กฐํ•ฉํ•˜๋ฉด ๋œ๋‹ค.

์•„๋ž˜ ์‚ฌ์ดํŠธ๋ฅผ ์ฐธ๊ณ ํ•˜์ž!

Slack Block Builder

 

Slack

nav.top { position: relative; } #page_contents > h1 { width: 920px; margin-right: auto; margin-left: auto; } h2, .align_margin { padding-left: 50px; } .card { width: 920px; margin: 0 auto; .card { width: 880px; } } .linux_col { display: none; } .platform_i

app.slack.com