SpringBoot로 Zoom Create Meeting API 사용방법에 대해 알아보자 !

두의 개발 고민 블로그·2023년 9월 26일

CarryAWay 프로젝트

목록 보기
4/7
이번 포스팅엣서는 SpringBoot로 Zoom API를 사용해서 회의(Meeting)을 생성하는 코드를 알아보겠습니다. 
제가 열심히 삽질한 내용이니 더 좋은 방법이 있으시면 공유나 따끔한 한마디 부탁드리겠습니다.

이 글은 Zoom App MarketPlace에서 OAuth app 을 생성한 이후 포스팅을 봐주시면 감사하겠습니다.

Zoom App MarketPlace - 앱 생성 URL

OAuth for user authorized apps - 권한 API 문서

위 사이트는 ZOOM Developers의 OAuth 관련 문서입니다. 
여기서 제가 자유롭게 해석해서 제 맛대로 사용할 수 있습니다. 
아래 글은 제 멋대로 사용한 코드임으로 무단으로 사용하셔도 좋습니다 ^^

API endpoint

Zoom's OAuth 2.0 endpoints are:

  • https://zoom.us/oauth/authorize for user authorization
  • https://zoom.us/oauth/devicecode for device code authorization
  • https://zoom.us/oauth/token for access tokens
  • https://zoom.us/oauth_device or https://zm.me/in for device code verification
해당 요청으로 권한, 엑세스 토큰을 얻을 수 있다고 합니다. 저는 웹사이트를 구축하기 때문에 device에 대한 endpoint는 무시해도 됩니다.  

Getting an access token

  1. Request user authorization. When you create your app, you set scopes for the level of access that your app needs from the user. Zoom presents information about these scopes and access requests to the user. The user will Authorize or Decline these access permissions for your app.
  2. Request an access token. If the user authorizes permissions for your app to use the scopes you chose, your app can request an access token. To retain this access level, your app can request refresh tokens.
결국 제가 원하는 회의 생성, 삭제, 조회 등 다양한 api를 사용하기 위한 
access token 발급 절차를 알려주는 부분입니다.

간단하게 이야기하면,  https://zoom.us/oauth/authorize 로 권한을 얻고, 
권한을 얻은 상태에서 https://zoom.us/oauth/token 로 token을 얻을 수 있습니다. 
결국 2번의 api 조회 끝에, 원하는 api까지 도달 할 수 있는 준비가 완료된것이죠. 

Step 1: Request user authorization

Query ParameterDescription
response_typeAccess response type being requested. The supported authorization workflow requires the value code.
redirect_uriURI to handle successful user authorization. Must match with Development or Production Redirect URI in your OAuth app settings.
client_idOAuth application's Development or Production Client ID.
state(Optional) An opaque value that you can use to maintain state between the request and callback. The authorization server appends the state value to the redirect URI. This is also useful to prevent https://datatracker.ietf.org/doc/html/rfc6749#section-10.12.
code_challenge(Optional, but required if using PKCE A challenge derived from the code verifier sent in the authorization request to verify against the code_verifier later.
code_challenge_method(Optional) A method that was used to derive the code challenge. Defaults to "plain" if not present in the request. Code verifier transformation method is "S256" or "plain".
해당 엔드포인트로 위의 parameter들과 함께 전송을 하면 됩니다. 뭔말인지 모르겠다구요 ?

예시를 들어보겠습니다.

예시코드

https://zoom.us/oauth/authorize?response_type=code&client_id=7lstjK9NTyett_oeXtFiEQ&redirect_uri=https://example.com

  • client_id : zoom 회원의 id 값 (zoom apps 나 zoom 이메일 가능)
  • redirect_uri : 현재 웹페이지를 구성하고 있는 리디렉션 주소
    • springboot 일 경우 : redirect_uri의 주소에 queryString 으로 code 값이 옵니다.

    • redirect_uri = http://localhost:8080/_new/support/reservation/zoomApi
      - 해당 uri는 자신의 프로젝트에 따라 다르게 설정하셔도 됩니다 !!

      @RequestMapping(value="_new/support/reservation/zoomApi" , method = {RequestMethod.GET, RequestMethod.POST})
          public String googleAsync(HttpServletRequest req, @RequestParam String code)  {
              System.out.println("code: "+ code);
      }

      이렇게 구성하면 됩니다.

예시결과

https://example.com/?code=obBEe8ewaL_KdyNjniT4KPd8ffDWt9fGB

  • code : 토큰을 얻는데 사용되는 권한값

이렇게 해당 code값이 옵니다.

Step 2: Request access token

Request headers

KeyValue description
AuthorizationThe string Basic with your Client ID and Client Secret separated with colon (:), https://www.base64encode.org/. For example, Client_ID:Client_Secret Base64-encoded is Q2xpZW50X0lEOkNsaWVudF9TZWNyZXQ=.
Content-Typeapplication/x-www-form-urlencoded

Request body

Send the appropriate request body parameters:

KeyValue description
codeThe authorization code supplied to the callback by Zoom. User authorization only, not device.
grant_typeOne of the following:- authorization_code for user authorization- urn:ietf:params:oauth:grant-type:device_code for device authorization
redirect_uriYour application's redirect URI. User authorization only, not device.
code_verifier_Optional, but required if using PKCE. A cryptographically random string used to correlate the authorization request to the token request. User authorization only.
device_codeThe device code supplied to the callback by Zoom. Device authorization only.
이쯤 되면 슬슬 해석이 되기 시작합니다. 해당 endpoint로 Request 요청을 보내야하는데 

header
Authorization 에 {client_id}:{client_secret} 값을 Base64로 인코딩한 값을 넣어줍니다.
Content-Type 에 application/x-www-form-urlencoded 넣어줍니다.

body
code : Step1에서 얻었던 code 값
grant_type 에 authorization_code 
redirect_uri: [REDIRECT URI]
code_verifier: [CODE VERIFIER]
이렇게 넣어줍니다. 

SpringBoot 코드

String zoomUrl = "https://zoom.us/oauth/token";
        //통신을 위한 okhttp 사용 maven 추가 필요a
        OkHttpClient client = new OkHttpClient();
        ObjectMapper mapper = new ObjectMapper();

        FormBody formBody = new FormBody.Builder()
                .add("code", code) // 1단계에서 받은 code 값
                .add("redirect_uri", "http://localhost:8080/_new/support/reservation/zoomApi") //등록 된 uri
                .add("grant_type", "authorization_code") // 문서에 명시 된 grant_type
                .add("code_verifier", EncodeUtil.encode(code)) // code를 SHA-256 방식으로 암호화하여 전달
                .build();
        Request zoomRequest = new Request.Builder()
                .url(zoomUrl) // 호출 url
                .addHeader("Content-Type", "application/x-www-form-urlencoded") // 공식 문서에 명시 된 type
                .addHeader("Authorization","Basic " + secretKey) // Client_ID:Client_Secret 을  Base64-encoded 한 값
                .post(formBody)
                .build();

        Response zoomResponse = client.newCall(zoomRequest).execute();
        String zoomText = zoomResponse.body().string();

        mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

        Map<String, String> list = mapper.readValue(zoomText, new TypeReference<>() {});

        ZoomToken zoomToken = new ZoomToken();
        zoomToken.setId(0L);
        zoomToken.setAccessToken(list.get("access_token"));
        zoomToken.setRefreshToken(list.get("refresh_token"));

이렇게 Response 데이터로 받아오면

{
  "access_token": "eyJhbGciOiJIUzUxMiIsInYiOiIyLjAiLCJraWQiOiI8S0lEPiJ9.eyJ2ZXIiOiI2IiwiY2xpZW50SWQiOiI8Q2xpZW50X0lEPiIsImNvZGUiOiI8Q29kZT4iLCJpc3MiOiJ1cm46em9vbTpjb25uZWN0OmNsaWVudGlkOjxDbGllbnRfSUQ-IiwiYXV0aGVudGljYXRpb25JZCI6IjxBdXRoZW50aWNhdGlvbl9JRD4iLCJ1c2VySWQiOiI8VXNlcl9JRD4iLCJncm91cE51bWJlciI6MCwiYXVkIjoiaHR0cHM6Ly9vYXV0aC56b29tLnVzIiwiYWNjb3VudElkIjoiPEFjY291bnRfSUQ-IiwibmJmIjoxNTgwMTQ2OTkzLCJleHAiOjE1ODAxNTA1OTMsInRva2VuVHlwZSI6ImFjY2Vzc190b2tlbiIsImlhdCI6MTU4MDE0Njk5MywianRpIjoiPEpUST4iLCJ0b2xlcmFuY2VJZCI6MjV9.F9o_w7_lde4Jlmk_yspIlDc-6QGmVrCbe_6El-xrZehnMx7qyoZPUzyuNAKUKcHfbdZa6Q4QBSvpd6eIFXvjHw",
  "token_type": "bearer",
  "refresh_token": "eyJhbGciOiJIUzUxMiIsInYiOiIyLjAiLCJraWQiOiI8S0lEPiJ9.eyJ2ZXIiOiI2IiwiY2xpZW50SWQiOiI8Q2xpZW50X0lEPiIsImNvZGUiOiI8Q29kZT4iLCJpc3MiOiJ1cm46em9vbTpjb25uZWN0OmNsaWVudGlkOjxDbGllbnRfSUQ-IiwiYXV0aGVudGljYXRpb25JZCI6IjxBdXRoZW50aWNhdGlvbl9JRD4iLCJ1c2VySWQiOiI8VXNlcl9JRD4iLCJncm91cE51bWJlciI6MCwiYXVkIjoiaHR0cHM6Ly9vYXV0aC56b29tLnVzIiwiYWNjb3VudElkIjoiPEFjY291bnRfSUQ-IiwibmJmIjoxNTgwMTQ2OTkzLCJleHAiOjIwNTMxODY5OTMsInRva2VuVHlwZSI6InJlZnJlc2hfdG9rZW4iLCJpYXQiOjE1ODAxNDY5OTMsImp0aSI6IjxKVEk-IiwidG9sZXJhbmNlSWQiOjI1fQ.Xcn_1i_tE6n-wy6_-3JZArIEbiP4AS3paSD0hzb0OZwvYSf-iebQBr0Nucupe57HUDB5NfR9VuyvQ3b74qZAfA",
  "expires_in": 3599,
  "scope": "user:read:admin"
}

이렇게 access_token과 관련된 데이터들이 옵니다.

그런데 여기서 주목 !!!!!!

refresh_token과 expires_in 은 왜 있는지 궁금하지 않습니까? 바로바로

access_token의 유효시간은 60분

맞습니다.. 60분이 지나면 access_code를 사용할 수 없습니다. 그래서 refresh_token이 있는데요. refresh_token을 사용하는 방법도 문서에 있습니다 !

Refreshing an access token

Request headers

KeyValue description
AuthorizationThe string Basic with your Client ID and Client Secret separated with colon (:), https://www.base64encode.org/. For example, Client_ID:Client_Secret Base64-encoded is Q2xpZW50X0lEOkNsaWVudF9TZWNyZXQ=.
Content-Typeapplication/x-www-form-urlencoded

Request body

KeyValue description
grant_typerefresh_token
refresh_tokenYour refresh token.
이제 혹시 어떻게 사용해야하는지 이제 보이시나요 ?

이해하셨다면 당신은,, 대단한 사람,,
해당 요청이 성공적으로 마쳤다면 아래의 데이터가 옵니다.

spring code

public String refreshToken() throws IOException {
        String zoomUrl = "https://zoom.us/oauth/token";
        //통신을 위한 okhttp 사용 maven 추가 필요
        OkHttpClient client = new OkHttpClient();
        ObjectMapper mapper = new ObjectMapper();

        FormBody formBody = new FormBody.Builder()
                .add("grant_type", "refresh_token") // 문서에 명시 된 grant_type
                .add("refresh_token", zoomTokenRepository.findById(0L).get().getRefreshToken()) //
                .build();
        Request zoomRequest = new Request.Builder()
                .url(zoomUrl) // 호출 url
                .addHeader("Content-Type", "application/x-www-form-urlencoded") // 공식 문서에 명시 된 type
                .addHeader("Authorization","Basic " + secretKey) // Client_ID:Client_Secret 을  Base64-encoded 한 값
                .post(formBody)
                .build();

        Response zoomResponse = client.newCall(zoomRequest).execute();
        String zoomText = zoomResponse.body().string();

        mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

        Map<String, String> list = mapper.readValue(zoomText, new TypeReference<>() {});
        String accessToken = list.get("access_token");
        String refreshToken = list.get("refresh_token");
}
{
  "access_token": "eyJhbGciOiJIUzUxMiIsInYiOiIyLjAiLCJraWQiOiI8S0lEPiJ9.eyJ2ZXIiOiI2IiwiY2xpZW50SWQiOiI8Q2xpZW50X0lEPiIsImNvZGUiOiI8Q29kZT4iLCJpc3MiOiJ1cm46em9vbTpjb25uZWN0OmNsaWVudGlkOjxDbGllbnRfSUQ-IiwiYXV0aGVudGljYXRpb25JZCI6IjxBdXRoZW50aWNhdGlvbl9JRD4iLCJ1c2VySWQiOiI8VXNlcl9JRD4iLCJncm91cE51bWJlciI6MCwiYXVkIjoiaHR0cHM6Ly9vYXV0aC56b29tLnVzIiwiYWNjb3VudElkIjoiPEFjY291bnRfSUQ-IiwibmJmIjoxNTgwMTQ3Mzk0LCJleHAiOjE1ODAxNTA5OTQsInRva2VuVHlwZSI6ImFjY2Vzc190b2tlbiIsImlhdCI6MTU4MDE0NzM5NCwianRpIjoiPEpUST4iLCJ0b2xlcmFuY2VJZCI6MjZ9.5c58p0PflZJdlz4Y7PgMIVCrQpHDnbM565iCKlrtajZ5HHmy00P5FCcoMwHb9LxjsUgbJ7653EfdeX5NEm6RoA",
  "token_type": "bearer",
  "refresh_token": "eyJhbGciOiJIUzUxMiIsInYiOiIyLjAiLCJraWQiOiI8S0lEPiJ9.eyJ2ZXIiOiI2IiwiY2xpZW50SWQiOiI8Q2xpZW50X0lEPiIsImNvZGUiOiI8Q29kZT4iLCJpc3MiOiJ1cm46em9vbTpjb25uZWN0OmNsaWVudGlkOjxDbGllbnRfSUQ-IiwiYXV0aGVudGljYXRpb25JZCI6IjxBdXRoZW50aWNhdGlvbl9JRD4iLCJ1c2VySWQiOiI8VXNlcl9JRD4iLCJncm91cE51bWJlciI6MCwiYXVkIjoiaHR0cHM6Ly9vYXV0aC56b29tLnVzIiwiYWNjb3VudElkIjoiPEFjY291bnRfSUQ-IiwibmJmIjoxNTgwMTQ3Mzk0LCJleHAiOjIwNTMxODczOTQsInRva2VuVHlwZSI6InJlZnJlc2hfdG9rZW4iLCJpYXQiOjE1ODAxNDczOTQsImp0aSI6IjxKVEk-IiwidG9sZXJhbmNlSWQiOjI2fQ.DwuqOzywRrQO2a6yp0K_6V-hR_i_mOB62flkr0_NfFdYsSqahIRRGk1GlUTQnFzHd896XDKf_FnSSvoJg_tzuQ",
  "expires_in": 3599,
  "scope": "user:read"
}

제가 refresh_token 이렇게 테스트 해봤는데 access_token을 비교 해봤습니다.

엑세스 코드가 수정되서 날아옵니다.

access_token을 받아오는데까지 (ㅠㅠ) 알아봤습니다. 그럼 이제 회의를 생성해야겠죠 ?

ZOOM Meeting API

Zoom Meeting API

이제 Zoom을 생성하는 API를 사용해 보겠습니다. 

PATH PARAMETERS

userIdThe user's user ID or email address. For user-level apps, pass the me value.

REQUEST BODY : application/json

Meeting object

  • 상당히 길기 때문에 본문 참조부탁드립니다. 🙏🏻
    • Request Body Example
      {
        "agenda": "My Meeting",
        "default_password": false,
        "duration": 60,
        "password": "123456",
        "pre_schedule": false,
        "recurrence": {
          "end_date_time": "2022-04-02T15:59:00Z",
          "end_times": 7,
          "monthly_day": 1,
          "monthly_week": 1,
          "monthly_week_day": 1,
          "repeat_interval": 1,
          "type": 1,
          "weekly_days": "1"
        },
        "schedule_for": "jchill@example.com",
        "settings": {
          "additional_data_center_regions": [
            "TY"
          ],
          "allow_multiple_devices": true,
          "alternative_hosts": "jchill@example.com;thill@example.com",
          "alternative_hosts_email_notification": true,
          "approval_type": 2,
          "approved_or_denied_countries_or_regions": {
            "approved_list": [
              "CX"
            ],
            "denied_list": [
              "CA"
            ],
            "enable": true,
            "method": "approve"
          },
          "audio": "telephony",
          "audio_conference_info": "test",
          "authentication_domains": "example.com",
          "authentication_exception": [
            {
              "email": "jchill@example.com",
              "name": "Jill Chill"
            }
          ],
          "authentication_option": "signIn_D8cJuqWVQ623CI4Q8yQK0Q",
          "auto_recording": "cloud",
          "breakout_room": {
            "enable": true,
            "rooms": [
              {
                "name": "room1",
                "participants": [
                  "jchill@example.com"
                ]
              }
            ]
          },
          "calendar_type": 1,
          "close_registration": false,
          "contact_email": "jchill@example.com",
          "contact_name": "Jill Chill",
          "email_notification": true,
          "encryption_type": "enhanced_encryption",
          "focus_mode": true,
          "global_dial_in_countries": [
            "US"
          ],
          "host_video": true,
          "jbh_time": 0,
          "join_before_host": false,
          "language_interpretation": {
            "enable": true,
            "interpreters": [
              {
                "email": "interpreter@example.com",
                "languages": "US,FR"
              }
            ]
          },
          "sign_language_interpretation": {
            "enable": true,
            "interpreters": [
              {
                "email": "interpreter@example.com",
                "sign_language": "American"
              }
            ]
          },
          "meeting_authentication": true,
          "meeting_invitees": [
            {
              "email": "jchill@example.com"
            }
          ],
          "mute_upon_entry": false,
          "participant_video": false,
          "private_meeting": false,
          "registrants_confirmation_email": true,
          "registrants_email_notification": true,
          "registration_type": 1,
          "show_share_button": true,
          "use_pmi": false,
          "waiting_room": false,
          "watermark": false,
          "host_save_video_order": true,
          "alternative_host_update_polls": true,
          "internal_meeting": false,
          "continuous_meeting_chat": {
            "enable": true,
            "auto_add_invited_external_users": true
          },
          "participant_focused_meeting": false,
          "push_change_to_calendar": false
        },
        "start_time": "2022-03-25T07:32:55Z",
        "template_id": "Dv4YdINdTk+Z5RToadh5ug==",
        "timezone": "America/Los_Angeles",
        "topic": "My Meeting",
        "tracking_fields": [
          {
            "field": "field1",
            "value": "value1"
          }
        ],
        "type": 2
      }
Request Body를 통해 데이터를 보낼 수 있는데 굉장히 많은 데이터를 보낼 수 있습니다. 제목부터 시간, 비밀번호, 진행시간, 시작시간, 날짜, 다양한 Settings 등을 요청값으로 보낼 수 있습니다. 위는 예시 JSON 코드입니다.

이렇게 API 요청을 보내게 되면, 회의가 생성되고 다양한 Response 값들이 전달됩니다.

  • Response Example
    {
      "assistant_id": "kFFvsJc-Q1OSxaJQLvaa_A",
      "host_email": "jchill@example.com",
      "id": 92674392836,
      "registration_url": "https://example.com/meeting/register/7ksAkRCoEpt1Jm0wa-E6lICLur9e7Lde5oW6",
      "agenda": "My Meeting",
      "created_at": "2022-03-25T07:29:29Z",
      "duration": 60,
      "h323_password": "123456",
      "join_url": "https://example.com/j/11111",
      "chat_join_url": "https://example.com/launch/jc/11111",
      "occurrences": [
        {
          "duration": 60,
          "occurrence_id": "1648194360000",
          "start_time": "2022-03-25T07:46:00Z",
          "status": "available"
        }
      ],
      "password": "123456",
      "pmi": "97891943927",
      "pre_schedule": false,
      "recurrence": {
        "end_date_time": "2022-04-02T15:59:00Z",
        "end_times": 7,
        "monthly_day": 1,
        "monthly_week": 1,
        "monthly_week_day": 1,
        "repeat_interval": 1,
        "type": 1,
        "weekly_days": "1"
      },
      "settings": {
        "allow_multiple_devices": true,
        "alternative_hosts": "jchill@example.com;thill@example.com",
        "alternative_hosts_email_notification": true,
        "alternative_host_update_polls": true,
        "approval_type": 0,
        "approved_or_denied_countries_or_regions": {
          "approved_list": [
            "CX"
          ],
          "denied_list": [
            "CA"
          ],
          "enable": true,
          "method": "approve"
        },
        "audio": "telephony",
        "audio_conference_info": "test",
        "authentication_domains": "example.com",
        "authentication_exception": [
          {
            "email": "jchill@example.com",
            "name": "Jill Chill",
            "join_url": "https://example.com/s/11111"
          }
        ],
        "authentication_name": "Sign in to Zoom",
        "authentication_option": "signIn_D8cJuqWVQ623CI4Q8yQK0Q",
        "auto_recording": "cloud",
        "breakout_room": {
          "enable": true,
          "rooms": [
            {
              "name": "room1",
              "participants": [
                "jchill@example.com"
              ]
            }
          ]
        },
        "calendar_type": 1,
        "close_registration": false,
        "contact_email": "jchill@example.com",
        "contact_name": "Jill Chill",
        "custom_keys": [
          {
            "key": "key1",
            "value": "value1"
          }
        ],
        "email_notification": true,
        "encryption_type": "enhanced_encryption",
        "focus_mode": true,
        "global_dial_in_countries": [
          "US"
        ],
        "global_dial_in_numbers": [
          {
            "city": "New York",
            "country": "US",
            "country_name": "US",
            "number": "+1 1000200200",
            "type": "toll"
          }
        ],
        "host_video": true,
        "jbh_time": 0,
        "join_before_host": true,
        "language_interpretation": {
          "enable": true,
          "interpreters": [
            {
              "email": "interpreter@example.com",
              "languages": "US,FR"
            }
          ]
        },
        "sign_language_interpretation": {
          "enable": true,
          "interpreters": [
            {
              "email": "interpreter@example.com",
              "sign_language": "American"
            }
          ]
        },
        "meeting_authentication": true,
        "mute_upon_entry": false,
        "participant_video": false,
        "private_meeting": false,
        "registrants_confirmation_email": true,
        "registrants_email_notification": true,
        "registration_type": 1,
        "show_share_button": true,
        "use_pmi": false,
        "waiting_room": false,
        "watermark": false,
        "host_save_video_order": true,
        "internal_meeting": false,
        "continuous_meeting_chat": {
          "enable": true,
          "auto_add_invited_external_users": true
        },
        "participant_focused_meeting": false,
        "push_change_to_calendar": false
      },
      "start_time": "2022-03-25T07:29:29Z",
      "start_url": "https://example.com/s/11111",
      "timezone": "America/Los_Angeles",
      "topic": "My Meeting",
      "tracking_fields": [
        {
          "field": "field1",
          "value": "value1",
          "visible": true
        }
      ],
      "type": 2
    }

spring code

public ZoomMeetingObjectEntity createMeeting(ZoomMeetingObjectDTO zoomMeetingObjectDTO) throws IOException {
        // refresh 검
        isExpired();

        System.out.println("Request to create a Zoom meeting");
        // replace zoomUserId with your user ID
        String apiUrl = "https://api.zoom.us/v2/users/" + "jongminshin373@gmail.com" + "/meetings";

        // replace with your password or method
        // replace email with your email
        zoomMeetingObjectDTO.setHost_email("jongminshin373@gmail.com");

        // Optional Settings for host and participant related options
        ZoomMeetingSettingsDTO settingsDTO = new ZoomMeetingSettingsDTO();
        settingsDTO.setJoin_before_host(false);
        settingsDTO.setParticipant_video(true);
        settingsDTO.setHost_video(false);
        settingsDTO.setAuto_recording("cloud");
        settingsDTO.setMute_upon_entry(true);
        zoomMeetingObjectDTO.setSettings(settingsDTO);

        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "Bearer " + zoomTokenRepository.findById(0L).get().getAccessToken());
        headers.add("content-type", "application/json");
        HttpEntity<ZoomMeetingObjectDTO> httpEntity = new HttpEntity<ZoomMeetingObjectDTO>(zoomMeetingObjectDTO, headers);
        ResponseEntity<ZoomMeetingObjectDTO> zEntity = restTemplate.exchange(apiUrl, HttpMethod.POST, httpEntity, ZoomMeetingObjectDTO.class);
        if(zEntity.getStatusCodeValue() == 201) {
            System.out.println("Zooom meeeting response {}" + zEntity);
            return zoomMeetingRepository.save(zEntity.getBody().toEntity());
        } else {
            System.out.println("Error while creating zoom meeting {}" + zEntity.getStatusCode());
        }
        zoomMeetingObjectDTO.setSettings(null);
        return null;
    }

그럼 해당 회의 Dto를 생성해서 원하는 데이터를 담고 데이터베이스에 저장하면 ~~~ 끝이납니다.


매번 토큰 받기 귀찮을 때;;

이렇게 하면, Access_Token을 발급받을 때 굉장히 복잡하게 됩니다. 
매번 API 요청을 보내야하고, 계속해서 Code 발급받고, 
access_token을 발급받아야하는데요  그래서 제가 자동화 코드를 짰습니다 !! 

access_token, refresh_token, updatedAt 데이터 세개를 DB에 저장하고,

updatedAt 이 1시간이 지났으면, refresh_token으로 토큰을 재생성하고 
다시 DB에 넣어두고 사용하는 방식입니다. 
비효율적인거 같으면 말해주세요 ! 좋은 방법있으면 수정하겠습니다.

유효성 확인 메서드 : isExpired()

  • 토큰 만료 : 위의 refreshToken() 메서드 호출
public void isExpired() throws IOException {
        // 현재 시간 가져오기
        LocalDateTime now = LocalDateTime.now();

        // updatedAt과 현재 시간의 차이 계산 (단위: 분)
        long minutesSinceUpdate = ChronoUnit.MINUTES.between(zoomTokenRepository.findById(0L).get().getUpdatedAt(), now);

        // 만료 시간을 60분으로 설정
        long expirationTimeInMinutes = 60;

        // 현재 시간과 updatedAt의 차이가 expirationTimeInMinutes 이상인 경우 만료되었다고 판단
        // updatedAt 시간이 현재 시간으로부터 60분 이상 경과했다면 true를 반환하며, 그렇지 않으면 false를 반환합니다.
        boolean checkExpired = minutesSinceUpdate >= expirationTimeInMinutes;
        // 토큰 재발급
        if(checkExpired) {
            refreshToken();
        }
}

⚠️ 핵심만 전달하기 위해 생략한 부분이 많음을 알려드립니다 !

해당부분이 핵심이긴 하지만, yml, Base64 코드,수많은 Dto, 다양한 파일들을 생성해야 위의 메서드를 사용할 수 있습니다. 여러분들이 필요하시면 제가 사용한 java 코드를 공유하도록 하겠습니다. Dto도 위의 Request, Response 보고 GPT 보고 만들어줘 하면 10초만에 만들어 줄껍니다 하하 !! 여러분은 저처럼 삽질하지 마시고, 똑똑하게 코딩하세요

profile
고민이 많은 개발자

2개의 댓글

comment-user-thumbnail
2023년 11월 20일

잘 읽었습니다.

답글 달기
comment-user-thumbnail
2024년 1월 9일

안녕하세요 zoom third party app 만들고 있는 개발자 입니다.
관련 소스 코드 확인하고 싶은데 깃허브 레포에 올라와있나요?

답글 달기