๋‚ด ์ด์•ผ๊ธฐ(TMI)

[TIL] Slack ์•Œ๋ฆผ ์—ฐ๋™ ์ž‘์—… + ์ •์ฒ˜๊ธฐ ๊ณต๋ถ€

newkr 2022. 4. 13. 01:37
728x90

๐Ÿคฆ‍โ™€๏ธ Slack ์•Œ๋ฆผ ์—ฐ๋™ ์ž‘์—…

 ๊ธฐ์กด์— ์ง„ํ–‰ํ–ˆ๋˜ ํ”„๋กœ์ ํŠธ์— Slack ์•Œ๋ฆผ ์—ฐ๋™ ์ž‘์—…์„ ์ง„ํ–‰ํ•˜๋Š” ์ค‘์ด๋‹ค. ์›น ํŽ˜์ด์ง€์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜๋ฉด ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ Slack ์•Œ๋ฆผ์„ ํ†ตํ•ด ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ ์ž ํ•˜๋Š” ์ž‘์—…์ด๋‹ค. ๋‹ค๋งŒ, ์ˆœํƒ„์น˜ ์•Š๋‹ค. ํ˜„์žฌ๊นŒ์ง€์˜ ์ž‘์—…์„ ํ•œ ๋ฒˆ ์ •๋ฆฌํ•ด๋ณด๋ฉด ์•Œ ์ˆ˜ ์žˆ์„๊นŒ ์‹ถ์–ด ์ •๋ฆฌํ•ด๋ณธ๋‹ค.


 Spring Boot ์„ค์ •

 - build.gradle

implementation 'com.github.maricn:logback-slack-appender:1.4.0'

 - application.properties

logging.slack.webhook-uri: ${SLACK_WEBHOOK_URI}
logging.slack.config: classpath:logback-slack.xml

 - logback-spring.xml

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <springProperty name="SLACK_WEBHOOK_URI" source="logging.slack.webhook-uri"/>
    <appender name="SLACK" class="com.github.maricn.logback.SlackAppender">
        <webhookUri>${SLACK_WEBHOOK_URI}</webhookUri>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %msg %n</pattern>
        </layout>
        <username>Cake-Server-log</username>
        <iconEmoji>:stuck_out_tongue_winking_eye:</iconEmoji>
        <colorCoding>true</colorCoding>
    </appender>


    <!-- Consol appender ์„ค์ • -->
    <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %green(%-5level) %logger{35}  %cyan(%logger{15}) -  %msg  %n</pattern>
        </encoder>
    </appender>

    <appender name="ASYNC_SLACK" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="SLACK"/>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
    </appender>

    <root level="INFO">
        <appender-ref ref="Console" />
        <appender-ref ref="ASYNC_SLACK"/>
    </root>
</configuration>

 ์†Œ์Šค์ฝ”๋“œ ์„ค์ •

 - StatusAlarm

package com.example.thebestmeal_test.domain;

import com.example.thebestmeal_test.dto.StatusAlarmDto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Entity
public class StatusAlarm extends Timestamped{

    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
    private long id;

    @Column(nullable = false)
    private String title;

    @Column(nullable = false)
    private String content;

    @Column
    private String imageUrl;

    public StatusAlarm(StatusAlarmDto.Request request, String imageUrl) {
        this.title = request.getTitle();
        this.content = request.getContent();
        this.imageUrl = imageUrl;
    }
}

 - StatusAlarmDto

package com.example.thebestmeal_test.dto;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.web.multipart.MultipartFile;

@NoArgsConstructor
public class StatusAlarmDto {

    // Slack ์ƒํƒœ ๋ฉ”์„ธ์ง€ ์ „์†ก์„ ์œ„ํ•œ ๋‚ด๋ถ€ ํด๋ž˜์Šค
    @Getter
    @Setter
    public static class Request {
        private String title;
        private String content;
        private MultipartFile image;
    }

    @Getter
    @Setter
    public static class Response {
        private long id;
        private String title;
        private String imageUrl;
    }
}

 - StatusAlarmService

package com.example.thebestmeal_test.service;

import com.example.thebestmeal_test.domain.StatusAlarm;
import com.example.thebestmeal_test.dto.StatusAlarmDto;
import com.example.thebestmeal_test.repository.StatusAlarmRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.io.IOException;
import java.util.List;

@RequiredArgsConstructor
@Service
public class StatusAlarmService {

    private final StatusAlarmRepository statusAlarmRepository;
    private final AwsService awsService;

    @Transactional
    public StatusAlarm setStatusAlarm(StatusAlarmDto.Request request) throws IOException {
        String url = null;
        if(request.getImage() != null) url = awsService.upload(request.getImage());
        StatusAlarm statusAlarm = new StatusAlarm(request, url);
        statusAlarmRepository.save(statusAlarm);

        return statusAlarm;
    }

    public StatusAlarm getStatusAlarm(Long id) {
        return statusAlarmRepository.findById(id).orElseThrow(
                () -> new NullPointerException("ํ•ด๋‹น ์•„์ด๋””๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.")
        );
    }

    public List<StatusAlarm> getStatusAlarms() {
        return statusAlarmRepository.findAll();
    }
}

 - StatusAlarmController

package com.example.thebestmeal_test.controller;

import com.example.thebestmeal_test.domain.StatusAlarm;
import com.example.thebestmeal_test.dto.StatusAlarmDto;
import com.example.thebestmeal_test.service.StatusAlarmService;
import lombok.RequiredArgsConstructor;
import org.modelmapper.ModelMapper;
import org.modelmapper.TypeToken;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.util.List;

@RequiredArgsConstructor
@RestController
public class StatusAlarmController {

    private final StatusAlarmService statusAlarmService;
    private final ModelMapper modelMapper;

    @PostMapping("/statusAlarm")
    public StatusAlarm setStatusAlarm(StatusAlarmDto.Request request) throws IOException {
        return statusAlarmService.setStatusAlarm(request);
    }

    @GetMapping("/statusAlarms")
    public List<StatusAlarmDto.Response> getStatusAlarms() {
        int i = 1/0; // (์˜๋„์ ์ธ ์—๋Ÿฌ ๋ฐœ์ƒ์„ ์œ„ํ•œ ์ž„์‹œ ์ฝ”๋“œ)
        List<StatusAlarm> statusAlarms = statusAlarmService.getStatusAlarms();
        List<StatusAlarmDto.Response> response = modelMapper.map(statusAlarms, new TypeToken<List<StatusAlarmDto.Response>>() {}.getType());
        return response;
    }
}

 ์†Œ์Šค์ฝ”๋“œ ์ž์ฒด์—๋Š” ์ด์ƒ์ด ์—†์—ˆ๋‹ค. Build ์‹คํ–‰๋„ ์ž˜ ๋˜์—ˆ๊ณ , Postman์„ ํ†ตํ•œ ํ…Œ์ŠคํŠธ ๋‚ด์šฉ์€ ํ„ฐ๋ฏธ๋„์— ๋กœ๊ทธ๋„ ์ž˜ ์ฐํ˜”๋‹ค. ์˜ค๋Š˜ ๊ณ ๋ฏผ์„ ๊ฝค๋‚˜ ํ–ˆ์ง€๋งŒ ์›์ธ์€ ์ฐพ์ง€ ๋ชปํ–ˆ๋‹ค. ์–ด์ฉŒ๋ฉด ๋‚ด๊ฐ€ ์•„์ฃผ ๋ฐ”๋ณด ๊ฐ™์€ ์‹ค์ˆ˜๋ฅผ ํ•˜๊ณ  ์žˆ์„์ง€๋„ ๋ชจ๋ฅธ๋‹ค. ๊ทธ๊ฒŒ ์กฐ๊ธˆ ๋ฌด์„ญ๋‹ค. ์ง€๊ธˆ์œผ๋กœ์„œ๋Š” ์Šฌ๋ž™ api ์„œ๋น„์Šค ๋‚ด์—์„œ ์–ด๋– ํ•œ ์„ค์ •๊ฐ’์„ ์ž˜๋ชปํ•ด์ฃผ์—ˆ๋‚˜๋ผ๋Š” ์ƒ๊ฐ์„ ํ•˜๊ณ  ์žˆ๋‹ค. ๋‚ด์ผ ์ค‘์œผ๋กœ ๋งˆ๋ฌด๋ฆฌํ•˜๊ณ  ์‹ถ๋‹ค.... ๋š€๋ฅต ใ… 


 ๐Ÿ“ ์ •๋ณด์ฒ˜๋ฆฌ๊ธฐ์‚ฌ ์ค€๋น„์ค‘!

 ์ •์ฒ˜๊ธฐ ์ค€๋น„๋Š” ์•„์ง๊นŒ์ง€๋Š” ์ˆœํ•ญ์ด๋‹ค. ํ•„๊ธฐ๊ฐ€ 4์›” 24์ผ์— ์˜ˆ์ •์ธ๋ฐ, ์‚ฌ์‹ค ํ•„๊ธฐ๋Š” ๋ฌธ์ œ์€ํ–‰๊ณผ ๊ฐ๊ด€์‹์ด๋ผ ํฐ ๋ถ€๋‹ด์ด ์—†๋‹ค. ์ง€๊ธˆ๊นŒ์ง€ ๋ชจ์˜๊ณ ์‚ฌ ํ‘ผ ๊ฒƒ๋„ ๋‹ค ํ•ฉ๊ฒฉ์„ ์ด๋‹ˆ๊นŒ.. ๋ฌธ์ œ๋Š” ์‹ค๊ธฐ๋‹ค.
 ์‹ค๊ธฐ ๋ฌธ์ œ๋Š” ์† ์ฝ”๋”ฉ, ์†Œํ”„ํŠธ์›จ์–ด ๋ฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ด€๋ จ ์ฃผ๊ด€์‹ ๋ฌธ์ œ ๋“ฑ ์ƒ๋‹นํžˆ ๊ตฌ์ฒด์ ์ธ ๋‚ด์šฉ๋“ค์ด ๋‚˜์™€์„œ ๋ฒŒ์จ๋ถ€ํ„ฐ ๊ฒ์ด ๋‚œ๋‹ค. ํŠนํžˆ, C์–ธ์–ด ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์†์ฝ”๋”ฉ ๋ฌธ์ œ๊ฐ€ ๋‚˜์˜ค๋ฉด.. ์•”์šธํ•  ๊ฒƒ ๊ฐ™๋‹ค. C์–ธ์–ด๋Š” ๋‹ค๋ค„๋ณธ ์ ์ด ์—†์œผ๋‹ˆ๊นŒ.. ํ•„๊ธฐ๋ฅผ ์ค€๋น„ํ•˜๋ฉด์„œ ๋ณธ ๋‚ด์šฉ๋“ค์ด ์ „๋ถ€๋ผ์„œ ์•ž์ด ์บ„์บ„ํ•˜๋‹ค. ๊ทธ๋ž˜๋„ ๋ถ€๋”ชํ˜€๋ด์•ผ ์•Œ๊ณ , ๋‹ค๋ฅธ ๋ฌธ์ œ๋“ค์—์„œ ๋“์ ํ•˜๋ฉด ๊ฐ€๋Šฅ์„ฑ์ด ์—†์ง€๋Š” ์•Š์œผ๋‹ˆ๊นŒ ์ผ๋‹จ ๋‹ฅ๋Œ์ด๋‹ค.
 ์—ด์‹ฌํžˆ ํ•ด์„œ ์ •๋ณด์ฒ˜๋ฆฌ๊ธฐ์‚ฌ ๋™์ฐจ ํ•ฉ๊ฒฉ์„ ์ด๋ฃจ์ž.

728x90