成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

我發(fā)現(xiàn)了大廠OpenApi接口的bug!你發(fā)現(xiàn)了嗎?

開發(fā) 項(xiàng)目管理
在與火山云旗下云游戲產(chǎn)品的 OpenApi 接口對(duì)接過程中,我總共踩了三個(gè)坑。一是文檔版本不是最新,二是官方提供的 OpenApi 示例 demo 過于簡(jiǎn)單,三是官方提供的驗(yàn)簽代碼沒有考慮到 POST JSON 請(qǐng)求場(chǎng)景下的 contentType 設(shè)置問題。

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

1. 文檔問題

很經(jīng)典的開局一張圖,對(duì)接全靠問。

產(chǎn)品給的圖產(chǎn)品給的圖

這里給大家強(qiáng)調(diào)下,當(dāng)要跟第三方產(chǎn)品對(duì)接時(shí),一定要確認(rèn)拿到的文檔是不是最新版本。

比如我在這次對(duì)接中,第一次拿到的文檔是產(chǎn)品給的,在業(yè)務(wù)中需要用到一個(gè)用戶主動(dòng)退出游戲的接口,于是我在第一份文檔里面找到一個(gè)用戶退出游戲的接口 RomoveUser。

RemoveUserRemoveUser

但是當(dāng)我在控制臺(tái)調(diào)用此接口報(bào)錯(cuò)后,去群里一問才發(fā)現(xiàn),對(duì)方建議我使用官網(wǎng)公布的最新接口文檔。

官網(wǎng)最新文檔:https://www.volcengine.com/docs/6512/143674

進(jìn)入官網(wǎng)發(fā)現(xiàn) RemoveUser 這個(gè)接口已經(jīng)是歷史接口了,官方建議換到 BanRoomUser 接口。

BanRoomUserBanRoomUser

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

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

2. OpenApi 示例 demo

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

圖片圖片

火山云旗下云游戲產(chǎn)品的 OpenApi 示例 demo 寫的很簡(jiǎn)單,只提供了一個(gè) GET 請(qǐng)求示例。

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

但是在我司的業(yè)務(wù)場(chǎng)景還是上個(gè)問題,需要一個(gè)用戶主動(dòng)退出游戲的接口,在火山云官網(wǎng)的 OpenApi 文檔中我也找到了這個(gè)接口,就是上文提到的 BanRoomUser 接口。

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

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

圖片圖片

OK,說(shuō)干就干,直接寫好示例代碼,開始發(fā)送 POST JSON 請(qǐng)求。

錯(cuò)誤返回錯(cuò)誤返回

what f**k?什么鬼,返回了我一個(gè) null,此時(shí)我的內(nèi)心中充滿了一個(gè)大大的問號(hào)。

我開始懷疑我的代碼是不是寫錯(cuò)了。但是當(dāng)我經(jīng)歷過數(shù)次源碼 debug 以及調(diào)用其他 OpenApi 接口測(cè)試并得到正確返回后,我堅(jiān)定的認(rèn)為我沒錯(cuò),這就是火山云 OpenApi 的 bug!

正常返回正常返回

OK,說(shuō)干就干,直接反饋給火山那邊。

接著火山那邊的人就聯(lián)系說(shuō)下午兩點(diǎn)開會(huì)一起遠(yuǎn)程共享我的屏幕看看,OK 欣然接收,讓他們見證下他們寫的 bug!

...

時(shí)間來(lái)到下午兩點(diǎn),當(dāng)我共享屏幕給字節(jié)工程師演示這個(gè) bug 時(shí),我的控制臺(tái)打印如下:

圖片圖片

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

OK,找到了返回正常的原因,當(dāng)我把 GameId 改成假數(shù)據(jù)時(shí),如我所愿,返回了一個(gè) null。

圖片圖片

自此,我也就在字節(jié)工程師的圍觀下,復(fù)現(xiàn)了他們的 OpenApi 接口的線上 bug。大功告成。

3. 鑒權(quán)失敗

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

這里先說(shuō)一下,火山云提供了手動(dòng)生成簽名的示例代碼:

圖片圖片

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

這里我也是直接把簽名代碼拿來(lái)即用就行,一開始接入生成簽名代碼非常順利,GET 請(qǐng)求的 OpenApi 接口都是可以順利調(diào)通的,但是當(dāng)我調(diào)用 BanRoomUser 接口時(shí)(沒錯(cuò),又是這個(gè)接口,踩的三個(gè)坑都與這個(gè)接口有關(guān)),直接提示驗(yàn)簽失??!

驗(yàn)簽失敗驗(yàn)簽失敗

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

圖片圖片

查看源碼發(fā)現(xiàn),POST JSON 請(qǐng)求時(shí)的 contentType 還是 application/x-www-form-urlencoded,直覺告訴我這里不對(duì),所以改成 application/json 試試,看看控制臺(tái)返回,

圖片圖片

很好,還是驗(yàn)簽失敗?。?!

我盡力了兄弟們,這個(gè)坑踩的我是無(wú)話可說(shuō)。直接聯(lián)系直接字節(jié)開發(fā)人員看下我的請(qǐng)求內(nèi)容是哪里有問題。

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

圖片圖片

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

OK,到這里問題也找到了,原因是我這個(gè)項(xiàng)目用的 http 請(qǐng)求工具是 okhttp3。他自動(dòng)給我拼接上去的!

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

在測(cè)試一遍,看看控制臺(tái)打印。

圖片圖片

OK,拿到成功響應(yīng),自此也就解決了第三個(gè)坑,POST JSON 請(qǐng)求時(shí)的驗(yàn)簽不匹配問題。

最后給大家貼出手動(dòng)生成驗(yàn)簽的代碼,有需要自取。

@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 {
        // 請(qǐng)求頭
        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");
    }
}

總結(jié)

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

責(zé)任編輯:武曉燕 來(lái)源: 程序員wayn
相關(guān)推薦

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網(wǎng)絡(luò)模型

2022-11-30 09:18:51

JavaMyBatisMQ

2022-04-18 07:42:31

配置機(jī)制Spring

2021-04-22 07:47:47

JavaJDKMYSQL

2020-04-01 08:40:44

Vue.jsweb開發(fā)

2024-06-03 11:43:55

2020-09-01 10:32:52

iOS微信新功能

2020-05-18 08:42:23

CSS背景圖像前端開發(fā)

2023-06-20 08:01:09

RoseDB存儲(chǔ)數(shù)據(jù)

2014-08-21 14:49:32

MIUI 6

2021-07-10 07:40:27

Excel數(shù)據(jù)分析大數(shù)據(jù)

2021-08-19 15:05:08

微信功能技巧

2018-07-12 14:03:33

區(qū)塊鏈新零售電子商務(wù)

2021-10-29 11:45:26

Python代碼Python 3.

2025-06-04 08:10:59

2024-11-08 14:18:38

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 亚洲一区二区三区免费在线 | 亚洲欧美日韩电影 | 欧美一区二区网站 | 欧美韩一区二区 | 伊人久久精品一区二区三区 | 亚洲成人一区二区三区 | 日韩 欧美 二区 | 日韩精品在线看 | 欧美日韩国产综合在线 | 日韩成人一区二区 | 亚洲一区二区在线播放 | 午夜一区二区三区在线观看 | 色综合av | 国产99在线 | 欧美 | 午夜国产在线 | 国产在线观看一区二区三区 | 欧美一二三 | 麻豆av一区二区三区久久 | 国产精品视频免费观看 | 日韩一区二区视频 | 国产99视频精品免费播放照片 | 欧洲性生活视频 | 国产一区| 亚洲三区视频 | 国产精品国产三级国产aⅴ无密码 | 国产亚洲精品成人av久久ww | 亚洲第一视频网 | 久久久精品一区 | 欧美一区二区三区久久精品视 | 成人超碰 | 日韩成人一区 | 欧美精品欧美精品系列 | 久草.com | 国产精品久久久爽爽爽麻豆色哟哟 | 精品国产一区二区三区四区在线 | 久久一区二区三区电影 | 搞av.com | 操久久| 精品一二三区 | 久久国产精品视频免费看 | 成人免费在线视频 |