커미션 신청 - (4) 내게 카톡 보내기 (신청 알림)

jinvicky·2023년 12월 22일
0
post-thumbnail

0. 시작하기 전

신청자가 커미션을 신청했으면 나에게 알림이 왔으면 좋겠다.
전에 페이팔 테스트할때 텔레그램을 했으니, 이번엔 카톡 내게 보내기 기능을 시도해 보았다.

솔직히 레퍼런스가 훌륭해서 기능은 쉬운데 자잘한 app 설정과 리팩토링 시도에서 시간이 조금 걸렸다.

참고 https://github.com/asd9211/kakao_api_demo

이 포스팅을 본다면 먼저 참고 git과 아래 작성자 블로그를 여러번 반복한 후 보는 게 도움이 된다.

1. 카카오톡 Developer App 설정

먼저 App을 만들어 놓아야 한다. 카카오 애플리케이션 만들기는 쉬우니까 인터넷 참고.

중요 부분

1. talk_message 항목 선택 동의

이게 인증시 scope 파라미터가 있는데 talk_message 항목을 url에 추가해도 여기서 선택 동의가 안되어 있으면 곤란하다;;


또한 위의 항목들도 상태 설정에 따라서 scope에 추가를 해야 할 수 있으니 설정 시 고려하길 바란다.

2. clientId, clientSecret, redirectUrl 등의 정보 수집

매우 중요한 파트.

참고 https://foot-develop.tistory.com/21
Git url 작성자 블로그인데 clientId, clientSecret등등 정보가 뭐 필요한지 잘 나와있다.

앞서 말하자면 application.yml에 넣기 위해서 아래와 같이 필요하다.

kakao:
  authUrl: https://kauth.kakao.com/oauth/token
  clientId: (개인마다 달라요)
  redirectUrl: (개인마다 달라요)
  clientSecret: (개인마다 달라요)
  send:
    msgUrl: https://kapi.kakao.com/v2/api/talk/memo/default/send

이제 코드 짜러 가자.

3. 코딩

참고 github을 따라하되, 개인정보값만 @Value를 사용해서 application.yml 파일로 분리하는 등의 리팩토링을 조금했다.

프로젝트 구조

HttpCallservice.java
참고 git과 동일

KakaoAuthService.java

import com.cms.world.domain.bo.KakaoBo;
import com.cms.world.utils.GlobalStatus;
import com.cms.world.utils.StringUtil;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

@Service
public class KakaoAuthService extends HttpCallService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    private KakaoBo kakaoBo;

    private String authToken;

    public KakaoAuthService(KakaoBo kakaoBo) {
        this.kakaoBo = kakaoBo;
    }

    public String getAuthToken(){
        return authToken;
    }

    public boolean getKakaoAuthToken(String code) {
        HttpHeaders header = new HttpHeaders();
        String accessToken = "";
        String refreshToken = "";
        MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();

        header.set("Content-Type", APP_TYPE_URL_ENCODED);


        parameters.add("code", code);
        parameters.add("grant_type", "authorization_code");
        parameters.add("client_id", kakaoBo.getClientId());
        parameters.add("redirect_url", kakaoBo.getRedirectUrl());
        parameters.add("client_secret", kakaoBo.getClientSecret());

        HttpEntity<?> requestEntity = httpClientEntity(header, parameters);
        ResponseEntity<String> response = httpRequest(kakaoBo.getAuthUrl(), HttpMethod.POST, requestEntity);
        JSONObject jsonData = new JSONObject(response.getBody());
        accessToken = jsonData.get("access_token").toString();
        refreshToken = jsonData.get("refresh_token").toString();
        if(StringUtil.isEmpty(accessToken) || StringUtil.isEmpty(refreshToken)) {
            logger.debug(GlobalStatus.KAK_CRT_TOKEN_FAILED.getMsg());
            return false;
        } else {
            authToken = accessToken;
            return true;
        }
    }

KakaoMsgService.java

import com.cms.world.domain.bo.KakaoBo;
import com.cms.world.domain.bo.KakaoMsgBo;
import com.cms.world.utils.GlobalStatus;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import org.springframework.http.HttpHeaders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

@Service
public class KakaoMsgService extends HttpCallService {

    private KakaoBo kakaoBo;

    private KakaoAuthService kakaoAuthService;

    public KakaoMsgService(KakaoBo kakaoBo, KakaoAuthService kakaoAuthService) {
        this.kakaoBo = kakaoBo;
        this.kakaoAuthService = kakaoAuthService;
    }

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    public boolean sendMsg(KakaoMsgBo msgBo) {
        String accessToken = kakaoAuthService.getAuthToken();
        return process(accessToken, msgBo);
    }

    public boolean process(String accessToken, KakaoMsgBo msgBo) {
        JSONObject linkObj = new JSONObject();
        linkObj.put("web_url", msgBo.getWebUrl());
        linkObj.put("mobile_web_url", msgBo.getMobileUrl());

        JSONObject templateObj = new JSONObject();
        templateObj.put("object_type", msgBo.getObjType());
        templateObj.put("text", msgBo.getText());
        templateObj.put("link", linkObj);
        templateObj.put("button_title", msgBo.getBtnTitle());

        HttpHeaders header = new HttpHeaders();
        header.set("Content-Type", APP_TYPE_URL_ENCODED);
        header.set("Authorization", "Bearer " + accessToken);

        MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
        parameters.add("template_object", templateObj.toString());

        HttpEntity<?> messageRequestEntity = httpClientEntity(header, parameters);

        String resultCode = "";
        ResponseEntity<String> response = httpRequest(kakaoBo.getSendMsgUrl(), HttpMethod.POST, messageRequestEntity);
        JSONObject jsonData = new JSONObject(response.getBody());
        resultCode = jsonData.get("result_code").toString();

        if (resultCode.equals(String.valueOf(GlobalStatus.KAK_SEND_MSG_SUCCESS.getStatus()))) {
            logger.info(GlobalStatus.KAK_SEND_MSG_SUCCESS.getMsg());
            return true;
        }
        logger.debug(GlobalStatus.KAK_SEND_MSG_FAILED.getMsg());
        return false;
    }

}

KakaoMsgBo.java

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
@Builder
public class KakaoMsgBo {

    private String objType; // 메세지 타입 예: text
    private String text;
    private String webUrl;
    private String mobileUrl;
    private String btnTitle; //링크 버튼 텍스트

}

*참고 깃과 @Builder 제외 동일

GlobalStatus.java
카카오 인증 관련 메세지 추가

 ... 
 	//카카오
    KAK_CRT_TOKEN_FAILED( 510, "카카오 토큰 발급 실패"),
    KAK_SEND_MSG_SUCCESS(0, "카카오 메세지 전송 성공"),
    KAK_SEND_MSG_FAILED(511, "카카오 메세지 전송 실패"),

KakaoBo.java
문제의 @Value 삽질.... 이 담긴


import lombok.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;


@Component
//@NoArgsConstructor //이거 있으면 안된다;;
@Getter
@Setter
public class KakaoBo {

    public KakaoBo(@Value("${kakao.clientId}") String clientId,
                   @Value("${kakao.redirectUrl}") String redirectUrl,
                   @Value("${kakao.clientSecret}") String clientSecret,
                   @Value("${kakao.authUrl}") String authUrl,
                   @Value("${kakao.send.msgUrl}") String sendMsgUrl) {
        this.clientId = clientId;
        this.clientSecret = clientSecret;
        this.redirectUrl = redirectUrl;
        this.authUrl = authUrl;
        this.sendMsgUrl = sendMsgUrl;
    }

    private String clientId;
    private String redirectUrl;
    private String clientSecret;
    private String authUrl;
    private String sendMsgUrl;

}
  1. @NoArgsConstructor를 추가하면 값이 안 들어간다.
  2. @Value는 필드뿐만이 아니라 생성자에도 저런식으로 주입할 수 있다.
  3. 문법을 제대로 알자. (${} 없으면 값 안 들어감)
@Value("${ ~ 값 ~ }")
  1. @PropertySource 설정은 필요없다. (지금 기준)

4. 에러

1. @Value 문법 오류

URI is not absolute;
해석 : url이 절대 경로가 아니다.

 parameters.add("grant_type", "authorization_code");
 parameters.add("client_id", kakaoBo.getClientId());
 parameters.add("redirect_url", kakaoBo.getRedirectUrl());
 parameters.add("client_secret", kakaoBo.getClientSecret());

위 코드처럼 값을 하드코딩하지 않고 비즈니스 객체(?)를 만들어서 get() 받아오려고 @Value를 썼다.

401 Unauthorized: [no body]
org.springframework.web.client.HttpClientErrorException$Unauthorized: 401 Unauthorized: [no body]

해석 : body가 없다.

결론 : yml에 설정한 값들이 제대로 안 들어와 있어서 그렇다.


이렇게 제대로 나와야지~

5. 테스트

테스트 endpoint

https://kauth.kakao.com/oauth/authorize
?client_id={개인 client_id}
&redirect_uri={개인 redirect_url}
&response_type=code
&scope=talk_message(,를 기준으로 추가 )
  1. response_type=code가 없으면 에러난다.
  2. 아까 위에서 scope 이야기를 했는데, 선택 동의 지정한만큼
    개수를 파라미터로 붙여주어야 한다.

성공하면 다음과 같은 화면이 뜬다.

고민

텔레그램은 메세지를 보낼때 인증이 필요없는데 카톡은 인증을 요구해서 생각했던 알림 기능과 맞지 않는다고 생각한다.
결과적으로 신청 때마다 나에게 메세지 보내기는 실패했다;; 신청하는 POST 요청을 했을 때, 신청자 이름을 MsgBo에 담아서 인증을 태울 방법을 도저히 못 찾겠다;; 이번 시도는 공부는 되었어도 결과는 실패다.

Git https://github.com/CM-WORLD

profile
Front-End와 Back-End 경험, 지식을 공유합니다.

0개의 댓글