<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      抖音開放平臺接入

      抖音開放平臺接入

      準備工作

      注冊抖音開放平臺 https://developer.open-douyin.com/ 并且進行企業(yè)認證。內(nèi)網(wǎng)穿透工具(需支持https),推薦ngrok

      相關(guān)網(wǎng)站

      需要資料

      • 應用對應:Client Key 和 Client Secret
      • 白名單管理:增加白名單抖音賬號

      注意事項

      1. 抖音權(quán)登錄抖音號需要加入白名單 控制臺-->設(shè)置-->白名單管理
      2. 授權(quán)登錄地址權(quán)限scope需要配置已有權(quán)限,就是抖音對應的能力管理
      3. 授權(quán)回調(diào)訂閱事件都需要在抖音配置才可以生效,控制臺-->設(shè)置-->開發(fā)配置

      授權(quán)登錄

      第一步 授權(quán)登錄地址拼接

      授權(quán)登錄和微信授權(quán)登錄類似,拼接授權(quán)登錄地址。包含回調(diào)地址(必須https)攜帶code以及自定義參數(shù) state。回調(diào)地址可以是到指定前端頁面可以是接口,我這里是通過接口去接收。

      public String access(String uid, String redirectUri) {
          List<String> scope = Lists.newArrayList();
          scope.add("data.external.item");//視頻數(shù)據(jù)
          scope.add("user_info");// 用戶信息
          scope.add("h5.share");// 支持H5場景的內(nèi)容可以分享發(fā)布到抖音,且可攜帶指定話題、小程序等內(nèi)容
          scope.add("open.get.ticket");// 用于h5鏈接拉起抖音發(fā)布器分享視頻時對開發(fā)者身份進行驗簽
          scope.add("trial.whitelist");// 測試應用白名單權(quán)限
          scope.add("poi.cps.common");// CPS傭金設(shè)置與查詢
          scope.add("open.get.ticket");// 獲取openTicket
      
          URLBuildUtil url = new URLBuildUtil("https://open.douyin.com/platform/oauth/connect/");
          url.putParams("client_key", '抖音應用信息 client key');
          url.putParams("response_type", "code");
          url.putParams("redirect_uri", "回調(diào)地址");
          url.putParams("scope", String.join(",", scope));
          url.putParams("state", uid);
          String build = url.build();
          log.debug("url:{}", build);
          return build;
      }
      

      最終訪問地址

      https://open.douyin.com/platform/oauth/connect?client_key=1234563&redirect_uri=https://d2c2-120-238-70-9.ngrok-free.app/tiktok/callBack&response_type=code&scope=data.external.item,user_info,h5.share,open.get.ticket,trial.whitelist,poi.cps.common,open.get.ticket&state=1662039377758420993
      

      直接瀏覽器訪問

      注意

      1、生成的地址可以直接通過瀏覽器打開,抖音進行掃碼授權(quán)。

      2、將上面生成的地址再次作為二維碼內(nèi)容生成一個二維碼,用戶直接抖音掃碼也是可以完成授權(quán)登錄。

      第二步 配置授權(quán)回調(diào)

      如果是開發(fā)環(huán)境請使用內(nèi)網(wǎng)穿透地址,可以配置多個回調(diào)地址。

      第三步 回調(diào)接口處理

      授權(quán)成功抖音會重定向到回調(diào)地址,GET請求,并且攜帶 codestate 參數(shù)如果有

      @ApiOperation(value = "授權(quán)回調(diào)")
      @GetMapping(value = "callBack")
      public Result callback(String code, String state) {
         	  // 獲取 token 和 open id
            Map<String, Object> params = new HashMap<>();
              params.put("client_secret","client_secret");
              params.put("client_key", "client_key");
              params.put("code", code);
              params.put("grant_type", "authorization_code");
              Map map = HttpClientUtils.getInstance()
                      .setContentType("application/json")
                      .putParams(params)
                      .doPost("https://open.douyin.com/oauth/access_token/").toMap();
            //... 自己的業(yè)務邏輯
            return Result.success("授權(quán)成功");
      }
      

      訂閱事件

      第一步 配置Webhooks

      抖音是以POST請求進行驗證配置的,后續(xù)所有事件都會發(fā)送到改接口上。和微信公眾號開發(fā)服務器驗證類似。

      第二步 訂閱接口處理

      該接口接收抖音驗證消息,驗證通過才能配置成功。請求頭類型是 application/json 內(nèi)容是在body中所以我的接口用 map 來接收,消息體有返回openid 抖音用戶唯一標識

      抖音事件如下

      • create_video 視頻(通過h5分享發(fā)布視頻觸發(fā)該事件)
      • unauthorize 取消授權(quán)(設(shè)置->賬號與安全->授權(quán)管理-解綁觸發(fā))
      • authorize 授權(quán)(掃碼授權(quán)登錄觸發(fā))
      • verify_webhook 服務器驗證(抖音開發(fā)配置添加URL觸發(fā))

      注意

      抖音簽名是headers里面獲取,簽名驗證是 應用秘鑰+body字符 進行sha1 加密結(jié)果比對相對表示沒問題。返回給抖音的是content 內(nèi)容,響應頭必須是 application/json

      @ApiOperation("訂閱消息")
      @PostMapping(value = "subscribe")
      public void subscribeEvent(@RequestBody Map<String, Object> map, HttpServletRequest request, HttpServletResponse response) {
          log.info("訂閱消息:{}", JSONUtil.objToStr(map));
          // 簽名字符串
          String signature = request.getHeader("x-douyin-signature");
          log.info("signature:{}", signature);
          // 消息id
          String msgId = request.getHeader("msg-id");
          log.info("msgId:{}", msgId);
          // 消息內(nèi)容
          Map content = (Map) map.get("content");
          // 事件
          String event = map.get("event").toString();
          // verify_webhook 服務器驗證
          if (event.equals("verify_webhook")) {
              // 秘鑰+body字符串
              String data = "client secret " + JSONUtil.objToStr(map);
              // sha1 簽名
              String sign = DigestUtils.sha1Hex(data);
              log.info("sign:{}", sign);
              if (Objects.equals(sign, signature)) {
                  this.responseText(JSONUtil.objToStr(content), response);
                  return; // 提前結(jié)束
              }
          }
          // ... 其他業(yè)務邏輯
      }
      // 處理響應結(jié)果
      private void responseText(String text, HttpServletResponse response) {
          response.setCharacterEncoding("UTF-8");
          response.setContentType("application/json");
          PrintWriter writer = null;
          try {
              writer = response.getWriter();
          } catch (IOException e) {
              e.printStackTrace();
          }
          assert writer != null;
          writer.write(text);
          writer.flush();
          writer.close();
      }
      

      官網(wǎng)代碼示例

      import org.apache.commons.codec.digest.DigestUtils; // sha1算法庫
      // 獲取消息中body
      String str, wholeStr = "";
      try{
          BufferedReader br = re.getReader();
          while((str = br.readLine()) != null){
              wholeStr += str;
          }
      } catch (Exception e){
          log.warn("獲取請求內(nèi)容失敗");
      }
      // 獲取請求頭中的加簽信息
      String  signature = re.getHeader("X-Douyin-Signature");
      String data = appSecret + wholeStr;
      String sign = DigestUtils.sha1Hex(data);
      if(!sign.equals(signature)){
          log.error("驗簽失敗");
      }
      

      第三步 事件消息體

      消息內(nèi)容做了脫敏處理,實際返回內(nèi)容以接口為準

      h5發(fā)布視頻

      {
          "event": "create_video",
          "client_key": "11111",
          "from_user_id": "234234234ldoQ2UFeflKR33333eLYTNVTs",
          "content": {
              "share_id": "111111",
              "item_id": "@9VxX1111111zoA+lLFUWbfL+60z333333EBbHec9qLXdMCWaQQYTUnzwg==",
              "has_default_hashtag": null,
              "video_id": "111111"
          },
          "log_id": "2023060117095922440FA7ABD95228B8D0",
          "event_id": ""
      }
      

      取消授權(quán)

      {
          "event": "unauthorize",
          "client_key": "11111",
          "from_user_id": "11111dU7BKTbvqPiLPts8KxFUDKS",
          "content": {
              "scopes": [
                  "user_info",
                  "trial.whitelist",
                  "data.external.item"
              ],
              "code": 1,
              "description": "用戶取消授權(quán)"
          },
          "log_id": "20230601165505000000000000596F91E",
          "event_id": ""
      }
      

      授權(quán)登錄

      {
          "event": "authorize",
          "client_key": "11111",
          "from_user_id": "11111BKTbvqPiLPts8KxFUDKS",
          "content": {
              "scopes": [
                  "user_info",
                  "trial.whitelist",
                  "data.external.item"
              ]
          },
          "log_id": "20230601165602172018000003663DCE3",
          "event_id": ""
      }
      

      服務器驗證

      {
          "event": "verify_webhook",
          "client_key": "11111",
          "from_user_id": "",
          "content": {
              "challenge": 19655498
          },
          "log_id": "2023060116524902BF3D205E5EBE86AAE4",
          "event_id": ""
      }
      

      H5 發(fā)布 Schema 生成示例

      https://developer.open-douyin.com/docs/resource/zh-CN/dop/develop/sdk/web-app/h5/share-to-h5

      H5 分享是指第三方應用通過接入該功能,讓用戶可以從網(wǎng)頁或者外部應用分享在線視頻或圖片等信息到抖音。

      說白就是通過h5可以做視頻批量分發(fā),平臺可以檢測這些視頻數(shù)據(jù)。

      步驟:

      1. 首先獲取 client_token
      2. 其次通過 client_token 獲取 ticket
      3. 最后 票據(jù) ticket 、隨機字符串nonce_str、 時間戳timestamp進行簽名
      4. share id 這個參數(shù)可選,用來跟蹤用戶發(fā)布h5視頻狀態(tài),是否發(fā)布成功,以及后續(xù)業(yè)務處理。

      第一步 client_token

      Map<String, Object> params = new HashMap<>();
      params.put("client_secret", "client_secret");
      params.put("client_key", "client_key");
      params.put("grant_type", "client_credential"); 
      Map map = HttpClientUtils.getInstance()
              .putParams(params)
              .doPost("https://open.douyin.com/oauth/client_token/").toMap();
      log.debug("clientToken:{}", map);
      Map data = (Map) map.get("data");
      if (!Objects.equals(data.get("error_code"), 0)) {
          throw new RuntimeException(map.get("message").toString());
      }
      String access_token = data.get("access_token").toString();
      

      第二步 ticket

      票據(jù)有效期 2 小時,自行緩存

      Map map = HttpClientUtils.getInstance()
              .putHeader("access-token", this.clientToken())
              .doGet("https://open.douyin.com/open/getticket/").toMap();
      log.debug("clientToken:{}", map);
      Map data = (Map) map.get("data");
      if (!Objects.equals(data.get("error_code"), 0)) {
          throw new RuntimeException(data.get("description").toString());
      }
      String ticket = data.get("ticket").toString();
      

      第三步 簽名

      這里使用了 TreeMap 因為官網(wǎng)要求要 ASCII 碼從小到大排序(字典序) 注意時間戳字段是秒不是毫秒

      long timestamp = System.currentTimeMillis() / 1000; // 秒
      
      TreeMap<String, String> map = new TreeMap<>();
      map.put("nonce_str", '隨機字符串');
      map.put("ticket", '票據(jù)');
      map.put("timestamp", '時間戳' + "");
      log.debug("參數(shù):{}", JSONUtil.objToStr(map));
      List<String> list = new ArrayList<>();
      for (Map.Entry<String, String> entry : map.entrySet()) {
          String key = entry.getKey();
          String value = entry.getValue();
          list.add(String.format("%s=%s", key, value));
      }
      String join = String.join("&", list);
      String signature = Md5Util.md5Encode(join, "UTF-8");
      log.debug("簽名:{}", signature);
      

      第四步 share id(可選)

      public String shareId() {
          Map<String, Object> params = new HashMap<>();
          params.put("need_callback", true); //
          Map map = HttpClientUtils.getInstance()
                  .putParams(params)
                  .putHeader("access-token", 'client_token')
                  .setContentType("application/json")
                  .doGet("https://open.douyin.com/share-id/").toMap();
          log.debug("shareId:{}", map);
          Map data = (Map) map.get("data");
          if (!Objects.equals(data.get("error_code"), 0)) {
              throw new RuntimeException(data.get("description").toString());
          }
          return data.get("share_id").toString();
      }
      

      第五步 schema

      這里需要注意的是我們生成并不是一個 URL 地址,他是一個 schema 地址,特殊URL地址和蘋果URL Schemes差不多意思。就是可以通過瀏覽器訪問這個鏈接喚起對應APP。
      具體參數(shù)參考官網(wǎng):https://developer.open-douyin.com/docs/resource/zh-CN/dop/develop/sdk/web-app/h5/share-to-h5

      注意

      • 提供的素材必須是http地址的可以下載的素材,像有些資源有防盜鏈等就是不行的
      • 生成 schema 地址可以復制在瀏覽器直接打開,會自動跳轉(zhuǎn)抖音
      • 也可以生成二維碼使用抖音掃碼,原則上這個分享鏈接有效期2小時
      • 蘋果手機請使用 Safari 瀏覽器打開,其他瀏覽器可能有問題
      // 構(gòu)建 schema
      URLBuildUtil url = new URLBuildUtil("snssdk1128://openplatform/share");
      url.putParams("share_type", "h5");
      url.putParams("client_key",'client_key');
      url.putParams("title", 'title');
      url.putParams("nonce_str", 'nonce_str');
      url.putParams("timestamp", 'timestamp' );
      url.putParams("signature", 'signature');
      url.putParams("image_path",URLEncoder.encode("https://js.ibaotu.com/act/23/04/20/6441198db0ef0.jpg", "UTF-8"););
      // 分享 id
      url.putParams("state", "shareId");
      

      schema 地址

      snssdk1128://openplatform/share?client_key=1111111&image_path=https%3A%2F%2Fjs.ibaotu.com%2Fact%2F23%2F04%2F20%2F6441198db0ef0.jpg&nonce_str=29649247100043823967999435052604&share_type=h5&signature=efb371d33b0deefb6e471ded1724faef&state=1767391659575207&timestamp=1685516015&title=哈哈哈
      

      完整代碼示例

      import com.github.chenlijia1111.utils.core.RandomUtil;
      import com.github.chenlijia1111.utils.http.HttpClientUtils;
      import com.github.chenlijia1111.utils.http.URLBuildUtil;
      
      import java.io.UnsupportedEncodingException;
      import java.net.URLEncoder;
      import java.util.*;
      
      class Tiktok {
      
          /**
           * description: 客戶端 token
           * create by: Mr.Fang
           *
           * @return: java.lang.String
           * @date:  2023/6/2 16:11
           */
          public static String clientToken() {
      
              Map<String, Object> params = new HashMap<>();
              params.put("client_secret", Constants.TIKTOK_CLIENT_SECRET); //
              params.put("client_key", Constants.TIKTOK_CLIENT_KEY); //
              params.put("grant_type", "client_credential"); // 回調(diào)地址
              Map map = HttpClientUtils.getInstance()
                      .putParams(params)
                      .doPost("https://open.douyin.com/oauth/client_token/").toMap();
              Map data = (Map) map.get("data");
              if (!Objects.equals(data.get("error_code"), 0)) {
                  throw new RuntimeException(map.get("message").toString());
              }
              String access_token = data.get("access_token").toString();
              return access_token;
          }
      
          /**
           * description: 票據(jù)
           * create by: Mr.Fang
           *
           * @return: java.lang.String
           * @date:  2023/6/2 16:11
           */
          public static String ticket() {
              Map map = HttpClientUtils.getInstance()
                      .putHeader("access-token", clientToken())
                      .doGet("https://open.douyin.com/open/getticket/").toMap();
              Map data = (Map) map.get("data");
              if (!Objects.equals(data.get("error_code"), 0)) {
                  throw new RuntimeException(data.get("description").toString());
              }
              String ticket = data.get("ticket").toString();
              return ticket;
          }
      
          /**
           * description: 分享 id
           * create by: Mr.Fang
           *
           * @return: java.lang.String
           * @date:  2023/6/2 16:11
           */
          public static String shareId() {
              Map<String, Object> params = new HashMap<>();
              params.put("need_callback", true); //
              Map map = HttpClientUtils.getInstance()
                      .putParams(params)
                      .putHeader("access-token", clientToken())
                      .setContentType("application/json")
                      .doGet("https://open.douyin.com/share-id/").toMap();
              Map data = (Map) map.get("data");
              if (!Objects.equals(data.get("error_code"), 0)) {
                  throw new RuntimeException(data.get("description").toString());
              }
              return data.get("share_id").toString();
          }
      
      
          /**
           * description: schema
           * create by: Mr.Fang
           *
           * @return: java.lang.String
           * @date:  2023/6/2 16:11
           */
          public static String schemaH5() {
              String randomCode = RandomUtil.createRandomCode(32); // 隨機字符
              long timestamp = System.currentTimeMillis() / 1000; // 秒
              String ticket = ticket(); // 票據(jù)
      
              TreeMap<String, String> map = new TreeMap<>();
              map.put("nonce_str", randomCode);
              map.put("ticket", ticket);
              map.put("timestamp", timestamp + "");
      
              List<String> list = new ArrayList<>();
              for (Map.Entry<String, String> entry : map.entrySet()) {
                  String key = entry.getKey();
                  String value = entry.getValue();
                  list.add(String.format("%s=%s", key, value));
              }
              String join = String.join("&", list);
              String signature = Md5Util.md5Encode(join, "UTF-8");
      
              // 構(gòu)建 URL
              URLBuildUtil url = new URLBuildUtil("snssdk1128://openplatform/share");
              url.putParams("share_type", "h5");
              url.putParams("client_key", Constants.TIKTOK_CLIENT_KEY);
              url.putParams("title", "標題");
              url.putParams("nonce_str", randomCode);
              url.putParams("timestamp", timestamp + "");
              url.putParams("signature", signature);
              url.putParams("state", shareId());
              url.putParams("image_path", urlEncode("https://js.ibaotu.com/act/23/04/20/6441198db0ef0.jpg"));
              return url.build();
          }
      
          private static String urlEncode(String url) {
              try {
                  return URLEncoder.encode(url, "UTF-8");
              } catch (UnsupportedEncodingException e) {
                  e.printStackTrace();
                  throw new RuntimeException(url);
              }
          }
      }
      

      API請求工具類型使用了第三方工具包,在此鳴謝 chenlijia1111 該作者

       <dependency>
          <groupId>com.github.chenlijia1111</groupId>
          <artifactId>utils</artifactId>
          <version>1.2.0-RELEASE</version>
      </dependency>
      
      posted @ 2023-06-02 17:14  天葬  閱讀(11338)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 安塞县| 少妇人妻真实偷人精品| 日韩有码中文字幕国产| 国产精品一区二区三区日韩 | 无码伊人66久久大杳蕉网站谷歌| 国产毛片精品一区二区色| 樱花草在线社区www| 伊人久久大香线蕉AV网禁呦| 一个色综合亚洲热色综合| 一区二区丝袜美腿视频| 国产人妻人伦精品婷婷| 亚洲av综合色区在线观看| 久久日产一线二线三线| 国产精品毛片在线看不卡| 国内精品久久久久影院网站| AV最新高清无码专区| 亚洲熟女综合色一区二区三区| 亚洲免费成人av一区| 中文字幕无线码中文字幕| 好吊妞无缓冲视频观看| 毛片av在线尤物一区二区 | 国产精品自在线拍国产手青青机版 | 亚洲免费观看在线视频| 蜜臀精品视频一区二区三区| 亚洲男女羞羞无遮挡久久丫| 滕州市| 吴桥县| 国精偷拍一区二区三区| 国产毛片精品一区二区色| 亚洲第一视频区| 久久亚洲女同第一区综合| 美女自卫慰黄网站| 99久久精品费精品国产一区二| 精品亚洲国产成人| 韩国精品久久久久久无码| 老司机亚洲精品一区二区| 国产日韩一区二区在线看| 亚洲精品成人区在线观看| 国产午夜精品久久精品电影| 亚洲中文字幕国产精品| 日本久久精品一区二区三区|