본문 바로가기
Spring

내 코인이 떡락이라고??! Slack으로 알림 경보를 받아보자!

by zkdlu 2021. 4. 20.

최근에 생각 없이 산 도지 코인이 엄청난 폭등을 하는 경험을 겪고나니, 코알못 (코인 잘 모른다는 뜻~)인 저도 코인에 관심을 갖게 되었습니다. 하지만 급락과 급등하는 살얼음판 핀 코인 시장에서 저같이 지식이 부족한 사람이 발 붙이기 쉽지 않은거 같아요.

그래서 어떻게 하면 이 미쳐버린 불지옥에 발 한자국 들일 수 있을까 생각해봤습니다.

바로 Slack을 이용한 알림 경보 시스템!!

Slack

슬랙은 일반 메신저의 기능뿐만 아니라 슬랙에서 제공하는 Webhook을 이용한 메시지 전송이 가능하고,  Slack에서 제공하는 Block Kit 을 사용해 개발자가 원하는 형태로 메시지 레이아웃 작성이 가능합니다.

 

Slack에서 제공하는 API를 사용해 메시지를 보내는 방법은 제 개발 메모장 블로그에 작성해두었습니다.

코인

그러면 이제 코인 업비트에서 코인 정보를 조회해야 하는데 업비트 개발자 센터에 들어가면 Open API로 여러 기능을 제공해주고 있습니다.

curl --request GET \
  --url 'https://api.upbit.com/v1/candles/minutes/1?market=KRW-BTC&count=1'

코인가격 조회 할 때는 별도로 API 키가 필요하진 않지만, API 사용 제한이 있습니다.

  • 초당 30회, 분당 900회

API를 호출하면 아래와 같은 응답 결과를 확인할 수 있습니다.

[
   {
      "market":"KRW-BTC",
      "candle_date_time_utc":"2021-04-20T01:50:00",
      "candle_date_time_kst":"2021-04-20T10:50:00",
      "opening_price":67661000.00000000,
      "high_price":67689000.00000000,
      "low_price":67551000.00000000,
      "trade_price":67555000.00000000,
      "timestamp":1618883433600,
      "candle_acc_trade_price":912653078.38012000,
      "candle_acc_trade_volume":13.50362761,
      "unit":1
   }
]

실습

우리의 코인 알람 서비스는 1분에 한번 지정해둔 코인 정보를 가져와서, 코인 정보로 이벤트를 발행 합니다. 이벤트를 발행하면 코인 이벤트를 수신하는 이벤트 핸들러에서는 슬랙 메시지를 조합해 메시지를 전송합니다.

 

1. 코인 정보를 가져오기 전 Coin 모델을 만들어 줍니다.

json 데이터가 스네이크 표기법으로 되어있으니 @JsonNaming 어노테이션을 사용해 옳바르게 변경 될 수 있게 설정해줍니다.

@NoArgsConstructor
@Getter
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class Coin {
    private String market;
    private LocalDateTime candleDateTimeUtc;
    private LocalDateTime candleDateTimeKst;
    private double openingPrice;
    private double highPrice;
    private double lowPrice;
    private double tradePrice;
    private long timeStamp;
    private double candleAccTradePrice;
    private double candleAccTradeVolume;
    private long unit;
}

 

2. 스케줄러를 사용해 코인 정보를 조회 하는 서비스를 만들어 줍니다.

cron식을 이용해 정해진 주기마다 스케줄러가 실행 될 수 있도록 만들어줍니다. 아래의 표현식은 "1분마다" 를 뜻하며 Spring Scheduler 사용법에 대한 추가적인 내용은 이 글을 확인해주세요. API 조회 결과는 array형태로 내려오며, count 설정을 변경하면 여러 데이터를 조회 가능하며 아래 예제에서는 1개씩 조회하기 때문에 코인 정보는 0번째 인덱스에 있습니다. 

한번에 여러개 조인하는거는 API 콜 횟수에 카운팅이 안되려나???
@EnableScheduling
@SpringBootApplication
public class AlertApplication {
....
}

@Slf4j
@RequiredArgsConstructor
@Service
public class CoinScheduler {
    private final RestTemplate restTemplate;

    @Scheduled(cron = "0 0/1 * * * ?")
    public void getCoin() {
        var coins = restTemplate.getForObject("https://api.upbit.com/v1/candles/minutes/1?market=KRW-BTC&count=1",
                Coin[].class);

        if (coins.length > 0) {
            log.info("{}", coins[0].getMarket());
        }
    }
}

 

3. 조회한 코인 데이터를 이벤트 모델로  만들어 주고 이벤트 핸들러를 추가해줍니다.

수신한 코인 이벤트를 슬랙 봇에 보내주기 위한 메시지로 조합해줍니다. Spring에서 Application Event 사용법은 이 글에서 확인 가능합니다.

@Getter
public class CoinEvent extends ApplicationEvent {
    private Coin coin;

    public CoinEvent(Object source, Coin coin) {
        super(source);
        this.coin = coin;
    }
}

@RequiredArgsConstructor
@Service
public class AlertService {
    private final SlackBot slackBot;
    private final NumberFormat numberFormat = NumberFormat.getCurrencyInstance( Locale.KOREA );

    @EventListener
    public void alert(CoinEvent coinEvent) throws IOException {
        Coin coin = coinEvent.getCoin();

        var fields = getCoinFields(coin);

        List<LayoutBlock> layoutBlocks = Blocks.asBlocks(
                getHeader("코인 정보가 도착했어요. " + coin.getMarket()),
                Blocks.divider(),
                getFieldSection(fields)
        );

        slackBot.send(layoutBlocks);
    }

    private LayoutBlock getFieldSection(List<TextObject> fields) {
        return Blocks.section(s -> s.fields(fields));
    }

    private List<TextObject> getCoinFields(Coin coin) {
        return Arrays.asList(
                getField("고가", numberFormat.format(coin.getHighPrice())),
                getField("저가", numberFormat.format(coin.getLowPrice())),
                getField("현재 가격", numberFormat.format(coin.getTradePrice()))
        );
    }

    private LayoutBlock getSection(String message) {
        return Blocks.section(s -> s.text(
                BlockCompositions.markdownText(message)));
    }

    private LayoutBlock getHeader(String text) {
        return Blocks.header(h -> h.text(
                BlockCompositions.plainText(pt -> pt
                        .emoji(true)
                        .text(text))));
    }

    private TextObject getField(String title, String content) {
        return BlockCompositions.markdownText(
                "*" + title + "*\n" + content);
    }
}

 

4. 슬랙봇에서는 이벤트 핸들러가 수신한 이벤트를 슬랙 메시지로 조합해주면, 이를 슬랙에 전송합니다.

추가로 스케줄러에 이벤트를 발행할 수 있도록 ApplicationEventPublisher를 주입받아 코인 이벤트를 발행합니다.

@Component
public class SlackBot {
    @Value("${slack}")
    private String webhook;

    public String send(List<LayoutBlock> slackMessage) throws IOException {
        return Slack.getInstance().send(webhook, WebhookPayloads
                .payload(p -> p
                        .text(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
                        .blocks(slackMessage)
                )).getMessage();
    }
}

@RequiredArgsConstructor
@Service
public class CoinScheduler {
    private final RestTemplate restTemplate;
    private final ApplicationEventPublisher eventPublisher;

    @Scheduled(cron = "0 0/1 * * * ?")
    public void getCoin() {
        var coins = restTemplate.getForObject("https://api.upbit.com/v1/candles/minutes/1?market=KRW-ONT&count=1",
                Coin[].class);

        if (coins.length > 0) {
            log.info("{}", coins[0].getMarket());

            // 이벤트 발행
            eventPublisher.publishEvent(new CoinEvent(this, coins[0]));
        }
    }
}

 

마무리

프로젝트를 빌드하고 실행하면 설정한 스케줄러의 실행간격마다 코인 정보를 슬랙으로 보내는걸 확인할 수 있습니다.

이를 활용하여 개인마다 특정 조건에 따라 알림을 보낼 수 있도록 AlertService를 변경하면 더욱 안전(?)한 코인 활동이 가능할 것 같습니다.

 

슬랙에 도착한 메시지

왜 온톨로지로 테스트 했을까요? ^^....