ZoomClientImpl.java 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. package com.malk.shunfeng.service.impl;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.JSONObject;
  4. import com.malk.server.common.McException;
  5. import com.malk.shunfeng.server.zoom.ZoomConf;
  6. import com.malk.shunfeng.server.zoom.ZoomR;
  7. import com.malk.shunfeng.service.ZoomClient;
  8. import com.malk.utils.UtilHttp;
  9. import com.malk.utils.UtilMap;
  10. import com.malk.utils.UtilToken;
  11. import lombok.extern.slf4j.Slf4j;
  12. import org.apache.commons.lang3.StringUtils;
  13. import org.springframework.beans.factory.annotation.Autowired;
  14. import org.springframework.stereotype.Service;
  15. import java.util.Map;
  16. /**
  17. * Zoom 会议 API 客户端实现
  18. * -
  19. * 使用 OAuth 2.0 Account Credentials,Token 通过 UtilToken 缓存复用(key="sf-zoom-token")
  20. */
  21. @Slf4j
  22. @Service
  23. public class ZoomClientImpl implements ZoomClient {
  24. @Autowired
  25. private ZoomConf zoomConf;
  26. /** UtilToken 缓存 key */
  27. private static final String TOKEN_KEY = "sf-zoom-token";
  28. /** Zoom 会议接口路径前缀 */
  29. private static final String PATH_USERS_ME_MEETINGS = "/users/me/meetings";
  30. /**
  31. * 创建会议
  32. * POST /users/me/meetings
  33. */
  34. @Override
  35. public ZoomR createMeeting(Map body) {
  36. String url = zoomConf.getApiHost() + PATH_USERS_ME_MEETINGS;
  37. Map header = buildAuthHeader();
  38. String rsp = UtilHttp.doPost(url, header, null, body);
  39. return parseAndAssert(rsp, "创建 Zoom 会议失败");
  40. }
  41. /**
  42. * 更新会议(PATCH,成功响应 204 No Content)
  43. * PATCH /meetings/{meetingId}
  44. * fixme: 204 响应无 body,parseAndAssert 不做调用
  45. */
  46. @Override
  47. public void updateMeeting(String meetingId, Map body) {
  48. String url = zoomConf.getApiHost() + "/meetings/" + meetingId;
  49. Map header = buildAuthHeader();
  50. String rsp = UtilHttp.doPatch(url, header, null, body);
  51. // fixme: PATCH 成功返回 204 空响应,只打印日志,不解析
  52. log.debug("[Zoom] updateMeeting 响应: {}", rsp);
  53. if (StringUtils.isNotBlank(rsp)) {
  54. ZoomR r = JSON.parseObject(rsp, ZoomR.class);
  55. r.assertSuccess();
  56. }
  57. }
  58. /**
  59. * 删除会议
  60. * DELETE /meetings/{meetingId}
  61. */
  62. @Override
  63. public void deleteMeeting(String meetingId) {
  64. String url = zoomConf.getApiHost() + "/meetings/" + meetingId;
  65. Map header = buildAuthHeader();
  66. String rsp = UtilHttp.doDelete(url, header, null, (Map) null);
  67. log.debug("[Zoom] deleteMeeting 响应: {}", rsp);
  68. if (StringUtils.isNotBlank(rsp)) {
  69. ZoomR r = JSON.parseObject(rsp, ZoomR.class);
  70. r.assertSuccess();
  71. }
  72. }
  73. /**
  74. * 查询会议详情
  75. * GET /meetings/{meetingId}
  76. */
  77. @Override
  78. public ZoomR getMeeting(String meetingId) {
  79. String url = zoomConf.getApiHost() + "/meetings/" + meetingId;
  80. Map header = buildAuthHeader();
  81. String rsp = UtilHttp.doGet(url, header, null);
  82. return parseAndAssert(rsp, "查询 Zoom 会议失败");
  83. }
  84. /**
  85. * 获取 Access Token(缓存复用,过期自动刷新)
  86. * POST {oauthUrl}?grant_type=account_credentials&account_id={accountId}
  87. * Basic Auth: clientId:clientSecret
  88. */
  89. private String getAccessToken() {
  90. // ppExt: 先从缓存取,命中则直接返回,避免重复申请
  91. String cached = UtilToken.get(TOKEN_KEY);
  92. if (StringUtils.isNotBlank(cached)) {
  93. return cached;
  94. }
  95. Map param = UtilMap.map("grant_type, account_id", "account_credentials", zoomConf.getAccountId());
  96. String rsp = UtilHttp.doRequest(
  97. UtilHttp.METHOD.POST,
  98. zoomConf.getOauthUrl(),
  99. null, param, null, null,
  100. zoomConf.getClientId(), zoomConf.getClientSecret()
  101. );
  102. McException.assertException(StringUtils.isBlank(rsp), "ZOOM_TOKEN_NULL", "Zoom Token 获取失败");
  103. JSONObject tokenJson = JSON.parseObject(rsp);
  104. String accessToken = tokenJson.getString("access_token");
  105. Long expiresIn = tokenJson.getLong("expires_in");
  106. McException.assertException(StringUtils.isBlank(accessToken), "ZOOM_TOKEN_EMPTY", "Zoom Token 为空");
  107. // fixme: expiresIn 单位为秒,UtilToken.put 单位为毫秒,内部冗余 5s 容错
  108. UtilToken.put(TOKEN_KEY, accessToken, expiresIn * 1000);
  109. return accessToken;
  110. }
  111. /**
  112. * 构建 Zoom Bearer Token 请求 Header
  113. */
  114. private Map buildAuthHeader() {
  115. return UtilMap.map("Authorization", "Bearer " + getAccessToken());
  116. }
  117. private ZoomR parseAndAssert(String rsp, String errMsg) {
  118. log.debug("[Zoom] 响应: {}", rsp);
  119. McException.assertException(StringUtils.isBlank(rsp), "ZOOM_RSP_NULL", errMsg);
  120. ZoomR r = JSON.parseObject(rsp, ZoomR.class);
  121. r.assertSuccess();
  122. return r;
  123. }
  124. }