diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..26684d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/vendor/ +/build/* +*.swp +**.*.swp \ No newline at end of file diff --git a/README.md b/README.md index e8fa32c..a52adc8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,231 @@ -# kakao-api -Kakao API for Laravel 5 +# KakaoApi +> 라라벨에서 사용할 라라벨용 Kakao API 가 없길래 간단하게 몇몇 API 만 만들어 봤습니다. + +- Kakao API for Laravel 5 +- 카카오 사용자 정보, 카카오 스토리 포스팅 몇개만 api를 지원합니다. + +#require + +```` php +"guzzlehttp/guzzle": "^6.0", +"php": ">=5.5.0" +```` + +#Installation +프로젝트에 있는 composer.json에 다음을 추가 하시거나, + +```` php +{ + "require": { + "pouu69/kakao-api": "^1.0" + } +} +```` + +composer 를 이용하여 설치 할 수 있습니다. + +```` php +composer require pouu69/kakao-api +```` + +#ServiceProvider +`config/app.php`에 아래 와 같이 providers에 등록을 합니다. + +```` php +'providers' => [ + pouu69\KakaoApi\KakaoApiServiceProvider::class, +] +```` + +#Facade +Facade 등록을 통해 alias를 등록 하는 경우 다음과 같이 추가 하시면 됩니다. + +```` php +'aliases' => [ + 'Kakao' => pouu69\KakaoApi\KakaoApiFacade::class, +]; +```` + +#config +`config` 폴더에 config 파일을 생성해야합니다. + +다음과 같은 내용을 가진 `kakao.php` 파일을 생성합니다. + +```` php + 'https://kauth.kakao.com', + 'API_URL' => 'https://kapi.kakao.com', + 'API_VERSION' => '/v1', + + 'REDIRECT_URL' => env('KAKAO_URL',''), + + 'API_KEY' => env('KAKAO_KEY', ''), + 'ADMIN_KEY' => env('KAKAO_ADMIN_KEY', ''), +]; +```` + +`.env` 에 kakao에서 발급받은 *KEY* 와 *REDIRECT_URL*을 등록 해놓습니다. + + +# 제공하는 Kakao API +- 기본 카카오 사용자 API + - 로그인 + - 로그아웃 + - 사용자 토큰 발급 + - 사용자 토큰 유효성 검사 및 정보 얻기 + - 사용자 토큰 갱신 + - 사용자 정보 요청 +- 카카오 스토리 API + - 사용자 확인 + - 사진(photo) 포스팅 + - 사진 업로드 + - 퍼블리싱(포스팅) + +# API 사용 +## 기본 설정 + +```` php +// 사용하는 곳에다가 등록 +use Kakao; +```` + +## 카카오 로그인 / 사용자 토큰 발급 / 사용자 정보 요청 +쿼리파라미터를 추가한 배열을 전달합니다. + +```` php +try{ + $url = Kakao::getLogin(); + return redirect()->to($url); +}catch(Exception $e){ + var_dump('kakaoLogin error : ',$e->getMessage()); +} +```` + +- 콜백으로 실행되는 메서드 + +```` php +$code = $request->input()['code']; // $code 는 콜백 URL에 쿼리로 들어온 authorize_code 이다. + +// 카카오 로그인 이후 발급 받은 `authorize_code` 로 수행한다. +$result = Kakao::postAccessToken($code); + +if(($result['code'] < 200 || $result['code'] > 206)){ + // 에러 발생 + // $result['body']['error_description'] 에러메세지 +} + +if(!empty($result['contents']->access_token)){ + $accessToken = $result['contents']->access_token; + $refreshToken = $result['contents']->refresh_token; + + // 사용자 정보 가져오기 + $credentials = Kakao::getCredential($accessToken); + + //token 세션 저장( 본인에 맞게 수행 ) + Session::put('kakao_access_token', $accessToken); + Session::put('kakao_refresh_token',$refreshToken); + + // 여기서 로그인 작업이나, 사용자 DB 작업 수행 +} +```` + +##로그아웃 +- 세션을 현재 해당 기기만 해제 해줍니다. + +```` php +$result = Kakao::postLogout($accessToken); +```` + +##사용자 토큰 유효성 검사 및 정보 얻기 / 사용자 토큰 갱신 +- access_token 유효성 검사 (12~24시간이 대부분 만료시간이기 때문에) +- refresh_token 이 있어야 합니다. +- 그리고 토큰을 갱신합니다. + +```` php +if(session()->has('kakao_access_token') && session()->has('kakao_refresh_token')){ + try{ + $kakaoAccessToken = session()->get('kakao_access_token'); + + // 요걸로 token 유효성 검사 합니다. + $accessTokenInfo = Kakao::getInfoAccessToken($kakaoAccessToken); + + if($accessTokenInfo['code'] !== 200){ + // 사용자 토큰 만료됬으므로, 다시 재발급 하는 곳입니다. + $tokens = Kakao::postRefreshToken(session()->get('kakao_refresh_token')); + + Session::put('kakao_access_token', $tokens['contents']->access_token); + Session::put('kakao_refresh_token',$tokens['contents']->refresh_token); + } + }catch(Exception $e){ + // 다음과 같이 에러를 받습니다. + $error = json_decode($e->getMessage()); + } +} +```` + +##카카오 스토리 API - 사용자 확인 +- 카카오 스토리 사용자 인지 확인 + +```` php + try{ + // 카카오스토리 사용자 인지 확인합니다. + $result = Kakao::isStoryUser(session()->get('kakao_access_token')); + return $result; + }catch(Exception $e){ + $error = json_decode($e->getMessage()); + } +```` + +##카카오 스토리 API - 사진 포스팅 +### 사진 업로드 +> 항상 사진을 업로드 한 이후에 포스팅 해야합니다. + +```` php +// 올릴 사진들을 guzzle이 원하는 포맷에 맞게 만듭니다. + +$imageUrl = [] +$i = 0; +foreach($images as $imagePath){ + $fileDir = // 이미지가 저장되어 있는 로컬 절대 경로 + $fileName = // 이미지 이름 + $file = [ + 'name' => 'file['.$i++.']', + 'contents' => fopen($fileDir,'r'), + 'filename' => $fileName + ]; + $imageUrl[] = $file; +} + +try{ + // 위에서 작업한 이미지를 넘기며, 이미지를 카카오에 업로드합니다. + $result = Kakao::postImageUpload($imageUrl, session()->get('kakao_access_token')); + $imageUrlInfos = $result['contents']; // 여기에 실제 업로드 할때 필요한 정보가 담겨있습니다. +}catch(Exception $e){ + throw new Exception(json_decode($e->getMessage())); +} +```` + +###퍼블리싱(포스팅) +- 위 사진 업로드 작업 이어서 진행 + +```` php +try{ + // request data 형식 + $data = [ + 'image_url_list' => $imageUrlInfos, + 'content' => '메세지 작성하기', + 'permission' => // 포스팅 할 스토리를 전체 공개할지 친구 공개할지 여부. F : 친구에게만 공개, A : 전체 공개, M : 나만 보기 , 기본값은 A. + ]; + + // 이제 최종 퍼블리싱 하기 + $result = Kakao::postPhoto($data, session()->get('kakao_access_token')); + return $this->response($result); +}catch(Exception $e){ + throw new Exception(json_decode($e->getMessage())); +} +```` + +#License +The MIT License (MIT). \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..2b42520 --- /dev/null +++ b/composer.json @@ -0,0 +1,24 @@ +{ + "name": "pouu69/kakao-api", + "description": "kakao api for laravel 5", + "require": { + "guzzlehttp/guzzle": "^6.0", + "php": ">=5.5.0" + }, + "license": "MIT", + "authors": [ + { + "name": "KwanUng", + "email": "pouu69@gmail.com" + } + ], + "autoload": { + "psr-4": { + "pouu69\\KakaoApi\\": "src" + } + }, + "branch-alias": { + "dev-master": "1.x-dev" + } + +} diff --git a/src/Facade/KakaoFacade.php b/src/Facade/KakaoFacade.php new file mode 100644 index 0000000..9f3bf37 --- /dev/null +++ b/src/Facade/KakaoFacade.php @@ -0,0 +1,19 @@ +_API_KEY = config('kakao.API_KEY'); + $this->_ADMIN_KEY = config('kakak.ADMIN_KEY'); + $this->REDIRECT_URL = config('kakao.REDIRECT_URL'); + + $this->API_VERSION = config('kakao.API_VERSION'); + + $this->AUTH_URL = config('kakao.AUTH_URL'); + $this->API_URL = config('kakao.API_URL'); + } + + /** + * http request 하기 + * @param string $method request type(get|post) + * @param string $requestAPI request API 주소 + * @param string $host base URL + * @param array $options 전송 데이터 또는 헤더 등등 옵션 + * @return array|object response + */ + public function query($method, $requestAPI, $host, $options){ + $client = new \GuzzleHttp\Client([ + 'base_uri' => $host + ]); + + try{ + $response = $client->request($method, $requestAPI, $options); + return $this->makeResponse($response); + }catch (RequestException $e) { + throw new Exception($this->makeErrorResponse($e)); + } + } + + /** + * response 결과 정제 + * @param object $response response 결과 + * @return array respoonse 를 정제한 결과 + */ + protected function makeResponse($response){ + $result = [ + 'code' => $response->getStatusCode(), // 200 + 'reason' => $response->getReasonPhrase(), // OK + 'body' => $response->getBody(), + 'contents' => json_decode($response->getBody()->getContents()) + ]; + + return $result; + } + + /** + * error response 정제 + * @param object $e error object + * @return object json + */ + protected function makeErrorResponse($e){ + $errorResponse = $e->getResponse(); + $errorCode = $errorResponse->getStatusCode(); + $errorBody = json_decode($errorResponse->getBody(), true, 512, JSON_BIGINT_AS_STRING); + + return json_encode([ + 'code' => $errorCode, + 'body' => $errorBody + ]); + } + + /** + * request type (api|auth) + * @param string $type api|auth 인지 request type 체크 + * @return string request type에 맞는 URL + */ + public function getRequestType($type){ + if($type === 'AUTH'){ + return $this->AUTH_URL; + }else if($type === 'API'){ + return $this->API_URL; + } + } +} \ No newline at end of file diff --git a/src/KakaoRequest.php b/src/KakaoRequest.php new file mode 100644 index 0000000..ff5c591 --- /dev/null +++ b/src/KakaoRequest.php @@ -0,0 +1,35 @@ +getRequestType($type); + return $this->query('get', $requestAPI, $host, $options); + } + + /** + * post type HTTP request + * @param string $type request가 auth|api 인지 체크 + * @param string $requestAPI 요청 API Url + * @param array $options 옵션(데이터) + * @return array|object response + */ + public function post($type, $requestAPI, $options=[]){ + $host = $this->getRequestType($type); + return $this->query('post', $requestAPI, $host, $options); + } + + /** + * request type (api|auth) + * @param string $type api|auth 인지 request type 체크 + * @return string request type에 맞는 URL + */ + abstract public function getRequestType($type); +} \ No newline at end of file diff --git a/src/KakaoServiceProvider.php b/src/KakaoServiceProvider.php new file mode 100644 index 0000000..75c7aad --- /dev/null +++ b/src/KakaoServiceProvider.php @@ -0,0 +1,62 @@ +app ?: app(); + + $this->mergeConfigFrom(__DIR__.'/config/config.php', 'kakao'); + + $this->publishes([ + __DIR__.'/config/config.php' => config_path('kakao.php'), + ]); + + + $this->app[Kakao::class] = $this->app->share(function($app) + { + return new Kakao(); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return ['kakao']; + } + +} \ No newline at end of file diff --git a/src/Traits/AuthTrait.php b/src/Traits/AuthTrait.php new file mode 100644 index 0000000..f76a093 --- /dev/null +++ b/src/Traits/AuthTrait.php @@ -0,0 +1,100 @@ +_API_KEY.'&redirect_uri='.$this->REDIRECT_URL.'&response_type=code'; + return $this->AUTH_URL.$requestAPI.$query; + } + + /** + * 카카오 로그아웃 (세션 해제) + * @param string $accessToken 사용자 토큰 + * @return array response + */ + public function postLogout($accessToken){ + $requestAPI = $this->API_VERSION.'/user/logout'; + + $headers = [ + 'Authorization' => "Bearer {$accessToken}" + ]; + + $options = [ + 'headers' => $headers + ]; + + return $this->post($this->API_TYPE, $requestAPI, $options); + } + + /** + * token 유효성 검사 + * @param string $accessToken 사용자 엑세스 토큰 + * @return array response + */ + public function getInfoAccessToken($accessToken){ + $requestAPI = $this->API_VERSION.'/user/access_token_info'; + + $headers = [ + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Authorization' => "Bearer {$accessToken}" + ]; + + $options = [ + 'headers' => $headers + ]; + + return $this->get($this->API_TYPE, $requestAPI, $options); + } + + /** + * access token 발급 + * @param string $code authrize code + * @return array response + */ + public function postAccessToken($code){ + $requestAPI = '/oauth/token'; + $data = [ + 'grant_type' => 'authorization_code', + 'client_id' => $this->_API_KEY, + 'redirect_uri' => $this->REDIRECT_URL, + 'code' => $code + ]; + + $headers = [ + 'Content-Type' => 'application/x-www-form-urlencoded' + ]; + + $options = [ + 'headers' => $headers, + 'form_params' => $data + ]; + + return $this->post($this->AUTH_TYPE, $requestAPI, $options); + } + + /** + * toekn 새로 발급 + * @param string $refreshToken 사용자 refresh token + * @return array response + */ + public function postRefreshToken($refreshToken){ + $requestAPI = '/oauth/token'; + + $data = [ + 'grant_type' => 'refresh_token', + 'client_id' => $this->_API_KEY, + 'refresh_token' => $refreshToken + ]; + + $options = [ + 'form_params' => $data + ]; + + return $this->post($this->AUTH_TYPE, $requestAPI, $options); + } +} \ No newline at end of file diff --git a/src/Traits/KakaoStoryTrait.php b/src/Traits/KakaoStoryTrait.php new file mode 100644 index 0000000..211e421 --- /dev/null +++ b/src/Traits/KakaoStoryTrait.php @@ -0,0 +1,79 @@ +API_VERSION.'/api/story/isstoryuser'; + + $header = [ + 'Accept' => 'application/json', + 'Content-Type' => "application/x-www-form-urlencoded", + 'Authorization' => "Bearer {$accessToken}" + ]; + + $options = [ + 'headers' => $header + ]; + + return $this->get($this->API_TYPE, $requestAPI, $options); + } + + public function postNote(){ + + } + + /** + * image upload + * @param array $files upload할 이미지 + * @param string $accessToken 사용자 엑세스 토큰 + * @return object|array response + */ + public function postImageUpload($files, $accessToken){ + $requestAPI = $this->API_VERSION.'/api/story/upload/multi'; + + $header = [ + 'Authorization' => "Bearer {$accessToken}" + ]; + + $options = [ + 'headers' => $header, + 'multipart' => $files + ]; + + return $this->post($this->API_TYPE, $requestAPI, $options); + } + + /** + * 사진포함 포스팅 하기 + * @param array $data 전송할 데이터 + * @param string $accessToken 사용자 엑세스토큰 + * @return object|array response + */ + public function postPhoto($data, $accessToken){ + $requestAPI = $this->API_VERSION.'/api/story/post/photo'; + + $header = [ + 'Content-Type' => "application/x-www-form-urlencoded;charset=utf-8", + 'Authorization' => "Bearer {$accessToken}" + ]; + + $data = [ + 'permission' => $data['permission'], + 'enable_share' => 'true', + 'content' => $data['content'], + 'image_url_list' => json_encode($data['image_url_list']) + ]; + + $options = [ + 'headers' => $header, + 'form_params' => $data + ]; + + return $this->post($this->API_TYPE, $requestAPI, $options); + } +} \ No newline at end of file diff --git a/src/Traits/UserTrait.php b/src/Traits/UserTrait.php new file mode 100644 index 0000000..ebcb975 --- /dev/null +++ b/src/Traits/UserTrait.php @@ -0,0 +1,40 @@ +API_VERSION."/user/me"; + + $header = [ + 'Accept' => 'application/json', + 'Content-Type' => "application/x-www-form-urlencoded", + 'Authorization' => "Bearer {$accessToken}" + ]; + + $options = [ + 'headers' => $header + ]; + + return $this->get($this->API_TYPE, $requestAPI, $options); + } + + public function updateProfile(string $accessToken){ + $requestAPI = $this->API_VERSION."/user/update_profile"; + $header = [ + 'Accept' => 'application/json', + 'Content-Type' => "application/x-www-form-urlencoded", + 'Authorization' => "Bearer {$accessToken}" + ]; + $options = [ + 'headers' => $header + ]; + return $this->post($this->API_TYPE, $requestAPI, $options); + + } +} \ No newline at end of file diff --git a/src/config/config.php b/src/config/config.php new file mode 100644 index 0000000..b7bed74 --- /dev/null +++ b/src/config/config.php @@ -0,0 +1,14 @@ + 'https://kauth.kakao.com', + 'API_URL' => 'https://kapi.kakao.com', + 'API_VERSION' => '/v1', + + 'REDIRECT_URL' => env('KAKAO_URL',''), + + 'API_KEY' => env('KAKAO_KEY', ''), + 'ADMIN_KEY' => env('KAKAO_ADMIN_KEY', ''), +]; \ No newline at end of file