해커톤 프로젝트 - open api : 카카오 메시지 보내기

Chooooo·2023년 9월 2일
0

TIL

목록 보기
10/28
post-thumbnail
post-custom-banner

😎 KakaoMessageService

@Service
@RequiredArgsConstructor
@Slf4j
public class KakaoMessageService {

    @Value("${kakao.api}")
    private String kakaoApiKey;

    private final WebClient webClient;
    private final String BASE_URL = "https://kapi.kakao.com/v2/api/talk/memo/default/send";


    public String sendTextMessage(String accessToken, KakaoMessageTextRequest request) {

        /**
         * 메시지 보낼 때 필요한 정보들
         *
         */
//        Map<String, Object> linkData = new HashMap<>();
//        linkData.put("web_url", request.getLink().get("web_url"));
//        linkData.put("mobile_web_url", request.getLink().get("mobile_web_url"));
//
//        Map<String, Object> requestBody = new HashMap<>();
//        requestBody.put("object_type", "text");  //text로 고정
//        requestBody.put("text", request.getText());
//        requestBody.put("link", linkData);
//        requestBody.put("button_title", request.getButton_title());

        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
        try {
            formData.add("template_object", new ObjectMapper().writeValueAsString(request));
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }

        String res = webClient.mutate()
                .baseUrl(BASE_URL)
                .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                .build()
                .post()
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                .body(BodyInserters.fromFormData(formData))
                .retrieve()
                .bodyToMono(String.class)
                .block();

        return res;
    }

    /**
     * 피드 메시지 보내기.
     */
    public String sendFeedMessage(String accessToken, KakaoMessageFeedRequest request) {
        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
        try {
            formData.add("template_object", new ObjectMapper().writeValueAsString(request));
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }

        String res = webClient.mutate()
                .baseUrl(BASE_URL)
                .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                .build()
                .post()
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                .body(BodyInserters.fromFormData(formData))
                .retrieve()
                .bodyToMono(String.class)
                .block();

        return res;
    }

    /**
     * 만약 커스텀을 해야하는 경우에.
     */
    public String sendFeedCustomMessage(String accessToken, String con) {
        KakaoMessageFeedRequest request = new KakaoMessageFeedRequest();
        request.setObject_type("feed"); //feed 메시지라 여기는 고정으로 넣어야함.

        KakaoMessageFeedRequest.Content content = new KakaoMessageFeedRequest.Content();
        content.setTitle(con);
        // 여기서 나머지 정보들을 내가 직접 구인 구직 정보 레포지토리에서 꺼내와서 설정해주면 됨.

        KakaoMessageFeedRequest.Button webButton = new KakaoMessageFeedRequest.Button();
        webButton.setTitle("웹으로 이동");
        KakaoMessageFeedRequest.Link webLink = new KakaoMessageFeedRequest.Link();
        webLink.setWeb_url("http://www.daum.net");
        webLink.setMobile_web_url("http://m.daum.net");
        webButton.setLink(webLink);

        KakaoMessageFeedRequest.Button appButton = new KakaoMessageFeedRequest.Button();
        appButton.setTitle("앱으로 이동");
        KakaoMessageFeedRequest.Link appLink = new KakaoMessageFeedRequest.Link();
        appLink.setAndroid_execution_params("contentId=100");
        appLink.setIos_execution_params("contentId=100");
        appButton.setLink(appLink);

        List<KakaoMessageFeedRequest.Button> buttons = new ArrayList<>();
        buttons.add(webButton);
        buttons.add(appButton);

        request.setContent(content);
        request.setButtons(buttons);

        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
        try {
            formData.add("template_object", new ObjectMapper().writeValueAsString(request));
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }

        String res = webClient.mutate()
                .baseUrl(BASE_URL)
                .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                .build()
                .post()
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                .body(BodyInserters.fromFormData(formData))
                .retrieve()
                .bodyToMono(String.class)
                .block();

        return res;
    }

}

👻 요청 방법

  • 나에게 보내기

👻 요청

🖤 텍스트 메시지 보내기

위와 같이 메시지 구성 요소를 담은 객체(Object) template_object를 Object 타입으로 요청 보내야 해 !!

public String sendTextMessage(String accessToken, KakaoMessageTextRequest request) {

        /**
         * 메시지 보낼 때 필요한 정보들
         *
         */
//        Map<String, Object> linkData = new HashMap<>();
//        linkData.put("web_url", request.getLink().get("web_url"));
//        linkData.put("mobile_web_url", request.getLink().get("mobile_web_url"));
//
//        Map<String, Object> requestBody = new HashMap<>();
//        requestBody.put("object_type", "text");  //text로 고정
//        requestBody.put("text", request.getText());
//        requestBody.put("link", linkData);
//        requestBody.put("button_title", request.getButton_title());

        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
        try {
            formData.add("template_object", new ObjectMapper().writeValueAsString(request));
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }

        String res = webClient.mutate()
                .baseUrl(BASE_URL)
                .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                .build()
                .post()
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                .body(BodyInserters.fromFormData(formData))
                .retrieve()
                .bodyToMono(String.class)
                .block();

        return res;
    }

이 코드는 KakaoTalk 플랫폼에서 텍스트 메시지를 보내는 메서드이다. 메시지 내용과 다양한 설정을 포함하는 KakaoMessageTextRequest 객체를 사용하여 메시지를 작성하고, 이를 KakaoTalk API로 보내기 위해 필요한 작업을 수행한다.

🖤 formData 변수를 초기화한다. 이 변수는 KakaoTalk API로 전송할 데이터를 저장하는 데 사용될 것.

  • KakaoMessageTextRequest 객체를 JSON 형식으로 직렬화하여 template_object라는 필드에 추가한다.

🖤 webClient를 사용하여 KakaoTalk API와 통신을 설정한다.

🖤 HTTP 요청을 빌드하고, 요청 헤더에 KakaoTalk API에 대한 인증 정보를 추가한다. accessToken은 이 API를 호출하기 위한 액세스 토큰 !

🖤 POST 요청을 구성하고, 요청 본문에는 formData의 내용을 추가한다. 요청 본문 형식은 MediaType.APPLICATION_FORM_URLENCODED로 설정되어 있다.

🖤 retrieve() 메서드를 호출하여 요청을 실행하고, KakaoTalk API로부터의 응답을 가져온다. 응답 내용은 JSON 형식의 문자열 !

🖤 bodyToMono(String.class)를 사용하여 응답 본문을 문자열로 변환하고, block() 메서드를 호출하여 Mono를 동기적으로 처리하여 최종 응답을 문자열로 반환한다.

세부 코드 분석

MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
        try {
            formData.add("template_object", new ObjectMapper().writeValueAsString(request));
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }

MultivalueMap을 사용하여 HTTP POST 요청의 본문을 구성하는데, 해당 자료구조는 키와 값의 여러 쌍을 저장할 수 있는 자료구조이다.

LinkedMultiValueMap는 하나의 키에 여러 값을 저장할 수 있다. (다중 값 저장)

  • 또한 입력된 순서를 보존한다. 즉 데이터를 추가한 순서대로 값들이 유지된다 !

new ObjectMapper().writeValueAsString(request) : ObjectMapper는 Jackson 라이브러리의 일부이며, Java 객체를 JSON 문자열로 직렬화(변환)하는 데 사용된다. request 객체는 KakaoMessageTextRequest 타입으로, 이 객체의 내용을 JSON 문자열로 변환한다. writeValueAsString() 메서드는 객체를 JSON 문자열로 변환하는 역할을 합니다.

formData.add("template_object", ...) : 이제 formData에 template_object라는 키로 JSON 문자열을 추가된다. 이렇게 하면 요청 본문에 template_object 키 아래에 JSON 문자열이 들어가게 된다.

  • 이러한 과정을 통해 formData에는 template_object라는 키로 KakaoMessageTextRequest 객체의 내용이 JSON 문자열로 저장되어 있다. 이후 HTTP POST 요청에서 이 데이터를 본문으로 전송하여 KakaoTalk API에 메시지를 전송하게 된다.

세부코드 분석2

String res = webClient.mutate()
                .baseUrl(BASE_URL)
                .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                .build()
                .post()
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                .body(BodyInserters.fromFormData(formData))
                .retrieve()
                .bodyToMono(String.class)
                .block();

        return res;

이 코드는 Spring WebClient를 사용하여 HTTP POST 요청을 만들고, 해당 요청을 KakaoTalk API로 보내고 응답을 처리하는 부분.

webClient.mutate(): WebClient의 mutate() 메서드를 호출하여 새로운 WebClient.Builder 인스턴스를 생성한다. WebClient.Builder는 웹 클라이언트를 구성하고 사용자 정의할 때 사용된다.

.baseUrl(BASE_URL): baseUrl(BASE_URL) 메서드를 사용하여 기본 URL을 설정, BASE_URL은 KakaoTalk API의 기본 URL이며, 이 URL에 다른 경로 및 매개변수가 추가될 것.

.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken): 기본 요청 헤더에 Authorization 헤더를 추가한다. 이 헤더에는 Bearer 토큰을 사용하여 KakaoTalk API에 대한 인증이 포함되어 있다. accessToken은 이전에 전달된 액세스 토큰이다.

.build(): 이전 설정을 기반으로하여 WebClient.Builder를 사용하여 실제 WebClient 인스턴스를 생성한다.

.post(): HTTP POST 요청을 설정하고 이 메서드를 호출하면 POST 메서드를 사용하는 요청이 설정된다.

.contentType(MediaType.APPLICATION_FORM_URLENCODED): 요청 본문의 콘텐츠 타입을 MediaType.APPLICATION_FORM_URLENCODED로 설정한다. 이것은 HTTP 요청의 본문이 URL-encoded 폼 데이터임을 나타낸다.

.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken): 요청 헤더에 Authorization 헤더를 추가한다. 이번에도 accessToken을 사용하여 API에 대한 인증을 제공한다. (굳이 추가 안했어도 됐는데..)

.body(BodyInserters.fromFormData(formData)): 요청의 본문 데이터를 설정한다. 앞에서 구성한 formData를 사용하여 URL-encoded 폼 데이터를 요청에 추가한다.

  • 이 부분은 요청 본문을 url-encoded 폼 데이터 형식으로 설정한다. formData라는 MultiValue 객체를 사용하여 URL-encoded 폼 데이터를 생성하고 요청 본문에 추가.

🖤 여기서 헷갈리면 안되는게 .contentType(MediaType.APPLICATION_FORM_URLENCODED)은 요청의 콘텐츠 타입을 설정하고, .body(BodyInserters.fromFormData(formData))는 해당 콘텐츠 타입에 맞는 데이터를 요청 본문에 추가한다.

  • 두 부분은 서로 다른 역할을 한다!!!

.retrieve(): 설정된 요청을 실행하고, KakaoTalk API로부터의 응답을 가져오기 위해 retrieve() 메서드를 호출

.bodyToMono(String.class): 응답 본문을 문자열로 변환하기 위해 bodyToMono(String.class)를 호출한다. 이 메서드는 응답 데이터를 Mono 형식으로 가져오고, 이 Mono를 문자열로 변환

.block(): Mono를 동기적으로 처리하여 최종 응답을 얻기 위해 block() 메서드를 호출합니다. 이것은 비동기 프로그래밍에서 결과를 기다리는 용도로 사용

  • 카카오 API에서 --data-urlencoded 를 사영하여 데이터를 요청하기 때문에 데이터를 URL-encoded 형식으로 제공해야 했다. 그러므로
    .body(BodyInserters.fromFormData(formData)) 부분은 필수적.

--data-urlencode를 사용하는 것은 데이터를 URL-encoded 형식으로 전송하겠다는 의미이며, Java의 BodyInserters.fromFormData(formData)는 이를 수행하는 데 사용됩니다. 이 부분이 없으면 데이터가 제대로 URL-encoded 되지 않고 전송될 수 있으며, API 서버에서 요청을 처리하지 못할 수 있습니다.

요약하면, Kakao API에서 --data-urlencode와 같은 방식으로 데이터를 요청할 때는 요청 본문을 URL-encoded 폼 데이터로 제공해야 하므로, .body(BodyInserters.fromFormData(formData)) 부분은 필수적입니다. 이 부분을 통해 요청 데이터가 올바른 형식으로 API에 전달됩니다.

  • 여기까지 카카오 메시지 (나에게 보내기) 중에 텍스트 메시지에 대해 알아보았다 !!

🖤 피드 메시지 보내기

  • 요청 양식
curl -v -X POST "https://kapi.kakao.com/v2/api/talk/memo/default/send" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -H "Authorization: Bearer ${ACCESS_TOKEN}" \
    --data-urlencode 'template_object={
        "object_type": "feed",
        "content": {
            "title": "오늘의 디저트",
            "description": "아메리카노, 빵, 케익",
            "image_url": "https://mud-kage.kakao.com/dn/NTmhS/btqfEUdFAUf/FjKzkZsnoeE4o19klTOVI1/openlink_640x640s.jpg",
            "image_width": 640,
            "image_height": 640,
            "link": {
                "web_url": "http://www.daum.net",
                "mobile_web_url": "http://m.daum.net",
                "android_execution_params": "contentId=100",
                "ios_execution_params": "contentId=100"
            }
        },
        "item_content" : {
            "profile_text" :"Kakao",
            "profile_image_url" :"https://mud-kage.kakao.com/dn/Q2iNx/btqgeRgV54P/VLdBs9cvyn8BJXB3o7N8UK/kakaolink40_original.png",
            "title_image_url" : "https://mud-kage.kakao.com/dn/Q2iNx/btqgeRgV54P/VLdBs9cvyn8BJXB3o7N8UK/kakaolink40_original.png",
            "title_image_text" :"Cheese cake",
            "title_image_category" : "Cake",
            "items" : [
                {
                    "item" :"Cake1",
                    "item_op" : "1000원"
                },
                {
                    "item" :"Cake2",
                    "item_op" : "2000원"
                },
                {
                    "item" :"Cake3",
                    "item_op" : "3000원"
                },
                {
                    "item" :"Cake4",
                    "item_op" : "4000원"
                },
                {
                    "item" :"Cake5",
                    "item_op" : "5000원"
                }
            ],
            "sum" :"Total",
            "sum_op" : "15000원"
        },
        "social": {
            "like_count": 100,
            "comment_count": 200,
            "shared_count": 300,
            "view_count": 400,
            "subscriber_count": 500
        },
        "buttons": [
            {
                "title": "웹으로 이동",
                "link": {
                    "web_url": "http://www.daum.net",
                    "mobile_web_url": "http://m.daum.net"
                }
            },
            {
                "title": "앱으로 이동",
                "link": {
                    "android_execution_params": "contentId=100",
                    "ios_execution_params": "contentId=100"
                }
            }
        ]
    }

내용은 많지만 결국 비슷하다.

  • 똑같이 데이터를 url-encoded 형식으로 요청 보내면 된다.
/**
     * 피드 메시지 보내기.
     */
    public String sendFeedMessage(String accessToken, KakaoMessageFeedRequest request) {
        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
        try {
            formData.add("template_object", new ObjectMapper().writeValueAsString(request));
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }

        String res = webClient.mutate()
                .baseUrl(BASE_URL)
                .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                .build()
                .post()
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                .body(BodyInserters.fromFormData(formData))
                .retrieve()
                .bodyToMono(String.class)
                .block();

        return res;
    }

💨 만약 메시지를 커스텀해서 보내야 할 경우에는

/**
     * 만약 커스텀을 해야하는 경우에.
     */
    public String sendFeedCustomMessage(String accessToken, String con) {
        KakaoMessageFeedRequest request = new KakaoMessageFeedRequest();
        request.setObject_type("feed"); //feed 메시지라 여기는 고정으로 넣어야함.

        KakaoMessageFeedRequest.Content content = new KakaoMessageFeedRequest.Content();
        content.setTitle(con);
        // 여기서 나머지 정보들을 내가 직접 구인 구직 정보 레포지토리에서 꺼내와서 설정해주면 됨.

        KakaoMessageFeedRequest.Button webButton = new KakaoMessageFeedRequest.Button();
        webButton.setTitle("웹으로 이동");
        KakaoMessageFeedRequest.Link webLink = new KakaoMessageFeedRequest.Link();
        webLink.setWeb_url("http://www.daum.net");
        webLink.setMobile_web_url("http://m.daum.net");
        webButton.setLink(webLink);

        KakaoMessageFeedRequest.Button appButton = new KakaoMessageFeedRequest.Button();
        appButton.setTitle("앱으로 이동");
        KakaoMessageFeedRequest.Link appLink = new KakaoMessageFeedRequest.Link();
        appLink.setAndroid_execution_params("contentId=100");
        appLink.setIos_execution_params("contentId=100");
        appButton.setLink(appLink);

        List<KakaoMessageFeedRequest.Button> buttons = new ArrayList<>();
        buttons.add(webButton);
        buttons.add(appButton);

        request.setContent(content);
        request.setButtons(buttons);

        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
        try {
            formData.add("template_object", new ObjectMapper().writeValueAsString(request));
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }

        String res = webClient.mutate()
                .baseUrl(BASE_URL)
                .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                .build()
                .post()
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                .body(BodyInserters.fromFormData(formData))
                .retrieve()
                .bodyToMono(String.class)
                .block();

        return res;
    }

위처럼 따로 세팅을 해주면 된다!

😎 KakaoController

/**
     * 카카오톡 피드 메시지 보내기
     */
    @PostMapping("/kakao/send_message/feed")
    public String returnFeedMessage(@RequestBody KakaoMessageFeedRequest request) {
        log.info("access_token is {}", access_token);
        String res = kakaoMessageService.sendFeedMessage(access_token, request);
        return res;
    }

    /**
     * 노인 구직 정보에서 하트 클릭 시 해당 구인 구직 정보 카카오톡 메시지로 보내기
     * 프론트에서 양식 다 맞춰서 보냄.
     */
    @PostMapping("/kakao/send_message/want")
    public String returnJobSearchMessage(@RequestBody KakaoMessageFeedRequest request, @RequestParam String accessToken) {
        String res = kakaoMessageService.sendFeedMessage(accessToken, request);
        log.info("res = {}", res);
        log.info("accessToken = {}", accessToken);
        return res;

    }

위처럼 컨트롤러에서 진행하면 됐다.

profile
back-end, 지속 성장 가능한 개발자를 향하여
post-custom-banner

0개의 댓글