国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

我發現了大廠OpenApi接口的bug!你發現了嗎?

開發 項目管理
在與火山云旗下云游戲產品的 OpenApi 接口對接過程中,我總共踩了三個坑。一是文檔版本不是最新,二是官方提供的 OpenApi 示例 demo 過于簡單,三是官方提供的驗簽代碼沒有考慮到 POST JSON 請求場景下的 contentType 設置問題。

本文記錄我在對接字節旗下產品火山云游戲OpenApi 接口文檔時遇到的坑,希望能幫助大家(火山云旗下云游戲產品的文檔坑很多,我算是從零到一都踩了一遍,特此記錄,希望大家引以為鑒)。

1. 文檔問題

很經典的開局一張圖,對接全靠問。

產品給的圖產品給的圖

這里給大家強調下,當要跟第三方產品對接時,一定要確認拿到的文檔是不是最新版本。

比如我在這次對接中,第一次拿到的文檔是產品給的,在業務中需要用到一個用戶主動退出游戲的接口,于是我在第一份文檔里面找到一個用戶退出游戲的接口 RomoveUser。

RemoveUserRemoveUser

但是當我在控制臺調用此接口報錯后,去群里一問才發現,對方建議我使用官網公布的最新接口文檔。

官網最新文檔:https://www.volcengine.com/docs/6512/143674

進入官網發現 RemoveUser 這個接口已經是歷史接口了,官方建議換到 BanRoomUser 接口。

BanRoomUserBanRoomUser

OK,這里算是踩到了第一個坑,文檔版本不是最新。

ps:還要說一下,火山云旗下云游戲的這個 OpenApi 接口文檔需要在群里聯系他們開白才能看到,說實話給我的感覺很奇怪,懷疑產品是否有趕鴨子上架問題,暫且懷疑他們的目的是防止不明攻擊吧。

2. OpenApi 示例 demo

第三方接口的接入一般都需要做鑒權。火山云旗下云游戲產品的 OpenApi 接口接入當然也不例外。于是我開始了第二個踩坑之旅,那就是他們給出的 OpenApi 示例 demo 的使用過于簡單。

圖片圖片

火山云旗下云游戲產品的 OpenApi 示例 demo 寫的很簡單,只提供了一個 GET 請求示例。

OpenApi 示例 demo 地址:https://github.com/volcengine/veGame

但是在我司的業務場景還是上個問題,需要一個用戶主動退出游戲的接口,在火山云官網的 OpenApi 文檔中我也找到了這個接口,就是上文提到的 BanRoomUser 接口。

但是在官方文檔中 BanRoomUser 接口是一個 POST JSON 格式的請求。官方給出的 OpenApi 示例 demo 中并沒有關于 POST JSON 請求的示例代碼,所以只能靠我一個人查看他們提供的 SDK 依賴源碼硬猜來寫...,這就很讓人頭痛了。

好在我翻閱他們 SDK 源碼中找到一個靠譜的 json(...) 請求方法,來完成這個 POST JSON 請求。

圖片圖片

OK,說干就干,直接寫好示例代碼,開始發送 POST JSON 請求。

錯誤返回錯誤返回

what f**k?什么鬼,返回了我一個 null,此時我的內心中充滿了一個大大的問號。

我開始懷疑我的代碼是不是寫錯了。但是當我經歷過數次源碼 debug 以及調用其他 OpenApi 接口測試并得到正確返回后,我堅定的認為我沒錯,這就是火山云 OpenApi 的 bug!

正常返回正常返回

OK,說干就干,直接反饋給火山那邊。

接著火山那邊的人就聯系說下午兩點開會一起遠程共享我的屏幕看看,OK 欣然接收,讓他們見證下他們寫的 bug!

...

時間來到下午兩點,當我共享屏幕給字節工程師演示這個 bug 時,我的控制臺打印如下:

圖片圖片

woca,竟然不是 null!好在我腦袋靈活,思路清晰,瞬間想到我改了一個參數 GameId,之前返回 null 時,我傳的 GameId 是一個假數據,現在我傳的是一個真數據。造成了返回不一致。

OK,找到了返回正常的原因,當我把 GameId 改成假數據時,如我所愿,返回了一個 null。

圖片圖片

自此,我也就在字節工程師的圍觀下,復現了他們的 OpenApi 接口的線上 bug。大功告成。

3. 鑒權失敗

字節提供的 OpenApi 示例 demo 現在算是跑通了,但是由于我司項目一些依賴限制問題,我們不能直接引入火山云旗下云游戲產品的 SDK 依賴。所以我還得手動編寫生成簽名的代碼。于是我開始了第三個踩坑之旅,那就是 GET 請求驗簽成功 POST 請求驗簽失敗的問題。

這里先說一下,火山云提供了手動生成簽名的示例代碼:

圖片圖片

Java 生成簽名的代碼:https://github.com/volcengine/volc-openapi-demos/blob/main/signature/java/Sign.java

這里我也是直接把簽名代碼拿來即用就行,一開始接入生成簽名代碼非常順利,GET 請求的 OpenApi 接口都是可以順利調通的,但是當我調用 BanRoomUser 接口時(沒錯,又是這個接口,踩的三個坑都與這個接口有關),直接提示驗簽失敗!

驗簽失敗驗簽失敗

OK,開始排查為什么簽名失敗。

圖片圖片

查看源碼發現,POST JSON 請求時的 contentType 還是 application/x-www-form-urlencoded,直覺告訴我這里不對,所以改成 application/json 試試,看看控制臺返回,

圖片圖片

很好,還是驗簽失敗!!!

我盡力了兄弟們,這個坑踩的我是無話可說。直接聯系直接字節開發人員看下我的請求內容是哪里有問題。

在與字節開發人員一起觀摩我寫的代碼以及生成的簽名之后,大家都沒找到問題所在。那沒辦法了,只能上服務器看接口請求日志了。

圖片圖片

大家可以看出問題在哪里嗎?沒錯我剛剛不是把 contentType 改成了 application/json 嗎,為什么日志顯示的 contentType 是 application/json; charset=utf-8!。

OK,到這里問題也找到了,原因是我這個項目用的 http 請求工具是 okhttp3。他自動給我拼接上去的!

那么怎么解決嘞,替換 http3 工具的話,改造成本比較大,所以我就順勢把代碼的 contentType 也改成application/json; charset=utf-8。

在測試一遍,看看控制臺打印。

圖片圖片

OK,拿到成功響應,自此也就解決了第三個坑,POST JSON 請求時的驗簽不匹配問題。

最后給大家貼出手動生成驗簽的代碼,有需要自取。

@Slf4j
public class Sign {
    private static final BitSet URLENCODER = new BitSet(256);
    private static final String CONST_ENCODE = "0123456789ABCDEF";
    public static final Charset UTF_8 = StandardCharsets.UTF_8;
    private final String region;
    private final String service;
    private final String host;
    private final String path;
    private final String ak;
    private final String sk;
    static {
        int i;
        for (i = 97; i <= 122; ++i) {
            URLENCODER.set(i);
        }

        for (i = 65; i <= 90; ++i) {
            URLENCODER.set(i);
        }

        for (i = 48; i <= 57; ++i) {
            URLENCODER.set(i);
        }
        URLENCODER.set('-');
        URLENCODER.set('_');
        URLENCODER.set('.');
        URLENCODER.set('~');
    }

    public Sign(String region, String service, String host, String path, String ak, String sk) {
        this.region = region;
        this.service = service;
        this.host = host;
        this.path = path;
        this.ak = ak;
        this.sk = sk;
    }

    public Headers calcAuthorization(String method, Map<String, String> queryList, byte[] body,
                                     Date date, String action, String version) throws Exception {
        // 請求頭
        Map<String, String> headerMap = new HashMap<>();
        String contentType = "application/x-www-form-urlencoded; charset=utf-8";
        if (body == null) {
            body = new byte[0];
        } else {
            contentType = "application/json; charset=utf-8";
        }
        String xContentSha256 = hashSHA256(body);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
        sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
        // String xDate = "20240515T061353Z";
        String xDate = sdf.format(date);
        String shortXDate = xDate.substring(0, 8);
        String signHeader = "content-type;host;x-content-sha256;x-date";

        SortedMap<String, String> realQueryList = new TreeMap<>(queryList);
        realQueryList.put("Action", action);
        realQueryList.put("Version", version);
        StringBuilder querySB = new StringBuilder();
        for (String key : realQueryList.keySet()) {
            querySB.append(signStringEncoder(key)).append("=").append(signStringEncoder(realQueryList.get(key))).append("&");
        }
        querySB.deleteCharAt(querySB.length() - 1);
        String canonicalStringBuilder = method + "\n" + path + "\n" + querySB + "\n" +
                "content-type:" + contentType + "\n" +
                "host:" + host + "\n" +
                "x-content-sha256:" + xContentSha256 + "\n" +
                "x-date:" + xDate + "\n" +
                "\n" +
                signHeader + "\n" +
                xContentSha256;

        // log.info("canonicalStringBuilder is {}", canonicalStringBuilder);
        String hashcanonicalString = hashSHA256(canonicalStringBuilder.getBytes());
        String credentialScope = shortXDate + "/" + region + "/" + service + "/request";
        String signString = "HMAC-SHA256" + "\n" + xDate + "\n" + credentialScope + "\n" + hashcanonicalString;
        // log.info("signString is {}", signString);

        byte[] signKey = genSigningSecretKeyV4(sk, shortXDate, region, service);
        String signature = HexUtil.encodeHexStr(hmacSHA256(signKey, signString));
        String auth = "HMAC-SHA256" +
                " Credential=" + ak + "/" + credentialScope +
                ", SignedHeaders=" + signHeader +
                ", Signature=" + signature;
        headerMap.put("Authorization", auth);
        headerMap.put("X-Date", xDate);
        headerMap.put("X-Content-Sha256", xContentSha256);
        headerMap.put("Host", host);
        headerMap.put("Content-Type", contentType);
        headerMap.put("User-Agent", "volc-sdk-java/v");
        headerMap.put("Accept", "application/json");
        return Headers.of(headerMap);
    }

    private static String signStringEncoder(String source) {
        if (source == null) {
            return null;
        }
        StringBuilder buf = new StringBuilder(source.length());
        ByteBuffer bb = UTF_8.encode(source);
        while (bb.hasRemaining()) {
            int b = bb.get() & 255;
            if (URLENCODER.get(b)) {
                buf.append((char) b);
            } else if (b == 32) {
                buf.append("%20");
            } else {
                buf.append("%");
                char hex1 = CONST_ENCODE.charAt(b >> 4);
                char hex2 = CONST_ENCODE.charAt(b & 15);
                buf.append(hex1);
                buf.append(hex2);
            }
        }

        return buf.toString();
    }

    public static String hashSHA256(byte[] content) throws Exception {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            // return HexFormat.of().formatHex(md.digest(content));
            return HexUtil.encodeHexStr(md.digest(content));
        } catch (Exception e) {
            throw new Exception(
                    "Unable to compute hash while signing request: "
                            + e.getMessage(), e);
        }
    }

    public static byte[] hmacSHA256(byte[] key, String content) throws Exception {
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(key, "HmacSHA256"));
            return mac.doFinal(content.getBytes());
        } catch (Exception e) {
            throw new Exception(
                    "Unable to calculate a request signature: "
                            + e.getMessage(), e);
        }
    }

    private byte[] genSigningSecretKeyV4(String secretKey, String date, String region, String service) throws Exception {
        byte[] kDate = hmacSHA256((secretKey).getBytes(), date);
        byte[] kRegion = hmacSHA256(kDate, region);
        byte[] kService = hmacSHA256(kRegion, service);
        return hmacSHA256(kService, "request");
    }
}

總結

在與火山云旗下云游戲產品的 OpenApi 接口對接過程中,我總共踩了三個坑。一是文檔版本不是最新,二是官方提供的 OpenApi 示例 demo 過于簡單,三是官方提供的驗簽代碼沒有考慮到 POST JSON 請求場景下的 contentType 設置問題。

責任編輯:武曉燕 來源: 程序員wayn
相關推薦

2022-04-26 06:43:12

文檔TCPLinux

2020-04-14 15:30:00

微信群管理朋友圈

2023-06-24 23:11:07

2024-11-05 09:47:08

VGG網絡模型

2022-11-30 09:18:51

JavaMyBatisMQ

2022-04-18 07:42:31

配置機制Spring

2021-04-22 07:47:47

JavaJDKMYSQL

2020-04-01 08:40:44

Vue.jsweb開發

2024-06-03 11:43:55

2020-09-01 10:32:52

iOS微信新功能

2020-05-18 08:42:23

CSS背景圖像前端開發

2023-06-20 08:01:09

RoseDB存儲數據

2014-08-21 14:49:32

MIUI 6

2021-07-10 07:40:27

Excel數據分析大數據

2021-08-19 15:05:08

微信功能技巧

2018-07-12 14:03:33

區塊鏈新零售電子商務

2021-10-29 11:45:26

Python代碼Python 3.

2025-06-04 08:10:59

2024-11-08 14:18:38

點贊
收藏

51CTO技術棧公眾號

欧美肥婆姓交大片| 人人妻人人澡人人爽欧美一区双 | 97高清免费视频| 一个人www视频在线免费观看| 亚洲国产精品久久久男人的天堂| 青青青青草视频| 蜜臀av一级做a爰片久久| 国产精品一区二区三区免费观看 | 免费看男男www网站入口在线| 国产精品久久久久久久久图文区| 国产一级爱c视频| 成人av在线网站| 免费看欧美一级片| 国产呦萝稀缺另类资源| 亚洲毛片aa| 狠狠色综合色综合网络| 奇米777四色影视在线看| 国产成人精品www牛牛影视| av一区二区三区免费观看| 国产精品综合网| 亚洲一区二区三区av无码| 成人免费毛片片v| 很污的网站在线观看| 粉嫩久久99精品久久久久久夜| 青青青在线观看视频| 97aⅴ精品视频一二三区| 欧美性久久久久| 国产精品美女久久久久久2018| 天堂一区在线观看| 亚洲女人的天堂| 夜色资源站国产www在线视频| 欧美日韩午夜视频在线观看| 国产乱视频在线观看| 日韩一级大片在线| 欧美不卡高清一区二区三区| 欧美多人乱p欧美4p久久| 亚洲区小说区| 国产精品手机视频| 免费成人性网站| 草草草在线视频| 亚洲地区一二三色| 蜜桃视频在线观看免费视频网站www| 精品乱人伦小说| 精品国产伦一区二区三区观看说明 | 国产精品扒开腿做爽爽爽软件| 精品国产一区二区三区四区精华 | caoporn国产精品免费公开| 国产手机视频一区二区 | 欧美精品生活片| 欧美午夜精品一区二区三区电影| 高清国语自产拍免费一区二区三区| 久久欧美肥婆一二区| 嫩草影院中文字幕| 国产精品女同互慰在线看| 色哟哟在线观看| 亚洲成人激情在线| 成人动漫视频| 精品一区二区三区自拍图片区| 寂寞少妇一区二区三区| 免费看a级黄色片| 色拍拍在线精品视频8848| 日本黄色免费在线| 奇米4444一区二区三区| 国产日韩欧美高清免费| 精品无码国模私拍视频| 午夜视频一区在线观看| 美女av在线免费看| 国产精品日韩精品| 精品一二三四区| 亚洲夫妻av| 色狠狠av一区二区三区香蕉蜜桃| 国产精品一区二区av日韩在线 | 这里只有精品在线| 国产免费一区二区视频| 高潮白浆女日韩av免费看| 欧美xxxhd| 国产一区私人高清影院| 国产成人亚洲精品青草天美 | 成人动漫在线观看视频| 成人av免费在线播放| 色天堂在线视频| 久久夜精品va视频免费观看| 一本一本久久| 色综合97天天综合网| 精品粉嫩超白一线天av| 欧美精品色图| 国产av麻豆mag剧集| 制服丝袜国产精品| 乱中年女人伦av一区二区| 一本一道久久a久久精品综合 | 久久精品日韩一区二区三区| 一区二区三区四区在线免费视频| 亚洲欧洲免费视频| 在线一区电影| 天天干在线影院| 欧美成人精品福利| 成人系列视频| 少妇激情一区二区三区| 亚洲老头同性xxxxx| 亚洲午夜视频| 日本亚洲天堂| 午夜精品www| 99久久夜色精品国产网站| 毛片在线导航| 国产精品视频500部| 亚洲成人免费在线| 亚洲一区av| 国产一区二区三区不卡在线观看| 青青国产在线| 日韩av手机在线看| 久久久高清一区二区三区| 小视频免费在线观看| 日本高清一区| 婷婷综合五月天| 国产一区二区精品福利地址| 国产精品久久久久9999小说| 在线日韩精品视频| 日韩av网站在线观看| 在线免费黄色| 波多野结衣成人在线| 亚洲综合色视频| 欧美精美视频| 免费三级欧美电影| 国产成人福利网站| 亚洲一区二区三区自拍| 日韩三级av| 婷婷福利视频导航| 日韩免费在线看| 一区二区三区美女| 米奇精品关键词| av天天在线| 国产精品精品视频一区二区三区| 亚洲人成影院在线观看| 国产探花在线精品一区二区| 人人在线97| 91在线观看免费观看| 色噜噜久久综合| 亚洲性感美女99在线| 韩国av网站在线| 国产精品xxxx| 91精品国产综合久久香蕉麻豆| 好看的av在线不卡观看| 日本黄色片在线观看| 欧美久久在线| 亚洲精品国产精品乱码不99按摩 | 992tv国产精品成人影院| 青青在线视频免费观看| 国产一区二区三区中文| 91亚洲男人天堂| 丁香五月缴情综合网| 国产乱xxⅹxx国语对白| 国产美女精品视频| 欧美色区777第一页| 国产九九精品| 国产调教视频在线观看| 国产日韩视频在线播放| 中文字幕视频在线免费欧美日韩综合在线看 | 国产精品久久久久久久久搜平片| 精品国产一区二区三区2021| av丝袜天堂网| 国产精品va在线播放| 在线精品亚洲一区二区不卡| 亚洲欧美成人| 深夜视频一区二区| 五月综合网站| 成人日韩av在线| 91麻豆精品91久久久久同性| 九九久久精品视频| 澳门久久精品| 嫩草研究院在线| 水蜜桃在线免费观看| 久久99精品视频一区97| 黄色成人在线免费| 天堂一区二区在线| 婷婷精品久久久久久久久久不卡| 嫩草嫩草嫩草| 亚洲国产精品一区二区第四页av| 日韩一区av在线| 天天综合网天天综合色| 久久精品国产精品青草| 精品视频在线你懂得| а天堂8中文最新版在线官网| 欧美日韩中文字幕在线播放| 国产97在线|日韩| 精品国产乱码久久久久久免费 | 99re在线观看视频| 亚洲天堂第二页| 亚洲免费在线观看| 97成人超碰| 91精品久久久久久9s密挑| 欧洲一区二区在线观看| 久久av资源网站| 91久久奴性调教| 国产91高潮流白浆在线麻豆| 亚洲自拍电影| av电影在线地址| 男女午夜视频在线观看| 一本色道久久综合亚洲二区三区| 57pao国产成人免费| 日韩视频免费观看高清完整版 |