신청자가 커미션을 신청했으면 나에게 알림이 왔으면 좋겠다.
전에 페이팔 테스트할때 텔레그램을 했으니, 이번엔 카톡 내게 보내기 기능을 시도해 보았다.
솔직히 레퍼런스가 훌륭해서 기능은 쉬운데 자잘한 app 설정과 리팩토링 시도에서 시간이 조금 걸렸다.
참고 https://github.com/asd9211/kakao_api_demo
이 포스팅을 본다면 먼저 참고 git과 아래 작성자 블로그를 여러번 반복한 후 보는 게 도움이 된다.
먼저 App을 만들어 놓아야 한다. 카카오 애플리케이션 만들기는 쉬우니까 인터넷 참고.
이게 인증시 scope 파라미터가 있는데 talk_message 항목을 url에 추가해도 여기서 선택 동의가 안되어 있으면 곤란하다;;
또한 위의 항목들도 상태 설정에 따라서 scope에 추가를 해야 할 수 있으니 설정 시 고려하길 바란다.
매우 중요한 파트.
참고 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
이제 코드 짜러 가자.
참고 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;
}
@NoArgsConstructor
를 추가하면 값이 안 들어간다. @Value
는 필드뿐만이 아니라 생성자에도 저런식으로 주입할 수 있다. @Value("${ ~ 값 ~ }")
@PropertySource
설정은 필요없다. (지금 기준)@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에 설정한 값들이 제대로 안 들어와 있어서 그렇다.
이렇게 제대로 나와야지~
테스트 endpoint
https://kauth.kakao.com/oauth/authorize
?client_id={개인 client_id}
&redirect_uri={개인 redirect_url}
&response_type=code
&scope=talk_message(,를 기준으로 추가 )
response_type=code
가 없으면 에러난다. scope
이야기를 했는데, 선택 동의 지정한만큼성공하면 다음과 같은 화면이 뜬다.
텔레그램은 메세지를 보낼때 인증이 필요없는데 카톡은 인증을 요구해서 생각했던 알림 기능과 맞지 않는다고 생각한다.
결과적으로 신청 때마다 나에게 메세지 보내기는 실패했다;; 신청하는 POST 요청을 했을 때, 신청자 이름을 MsgBo에 담아서 인증을 태울 방법을 도저히 못 찾겠다;; 이번 시도는 공부는 되었어도 결과는 실패다.