Todos os produtos
Search
Central de documentação

Web Application Firewall:Integrar o SDK para Android

Última atualização: Jul 03, 2026

Para configurar regras baseadas em cenários anticrawler no gerenciamento de bots pelo console, integre o SDK de Proteção de Aplicativo do WAF. Este tópico explica como integrar o SDK a aplicativos Android.

Contexto

O SDK de Proteção de Aplicativo assina requisições originadas nos clientes do aplicativo. O servidor do Web Application Firewall (WAF) verifica essas assinaturas para identificar riscos, bloquear requisições maliciosas e proteger o aplicativo.

Limitações

  • No Android, o SDK suporta as arquiteturas arm64-v8a e armeabi-v7a.

  • O nível da API do Android deve ser 16 ou superior.

  • O método init pode demorar para concluir. Para garantir proteção completa, aguarde pelo menos 2 segundos entre a chamada do método init e do método vmpSign. Esse atraso é recomendado para otimizar a proteção, mas não é obrigatório. Ajuste-o conforme suas necessidades de negócio. Um intervalo menor pode impedir que os recursos de segurança funcionem com plena eficácia.

  • Ao usar o ProGuard para ofuscação de código, utilize a opção -keep para preservar os métodos do SDK. Exemplo:

    -keep class com.aliyun.TigerTally.** {*;}
    -keep class com.aliyun.captcha.* {*;}
    -keepclassmembers,allowobfuscation class * {
         @com.alibaba.fastjson.annotation.JSONField <fields>;
    }
    -keep class com.alibaba.fastjson.** {*;}

Pré-requisitos

  • Obtenha o SDK para seu aplicativo Android.

    Envie um ticket aos nossos especialistas técnicos de produto para obter o SDK.

    Nota

    O SDK para Android inclui dois arquivos AAR: AliTigerTally_X.Y.Z.aar e AliCaptcha_X.Y.Z.aar, onde X.Y.Z representa o número da versão.

  • Obtenha a chave de autenticação do SDK (também conhecida como appkey).

    Após ativar o Gerenciamento de Bots, acesse a página Bot Management > App Protection. Na lista de aplicativos, clique em Obtain and Copy AppKey para obter a chave de autenticação do SDK. Essa chave é necessária para a inicialização do SDK e deve constar no código de integração.

    image

    Nota

    Cada conta Alibaba Cloud possui uma appkey exclusiva para todos os nomes de domínio protegidos pelo WAF. Use essa appkey na integração do SDK em aplicativos Android, iOS e HarmonyOS.

    Exemplo de chave de autenticação: ****OpKLvM6zliu6KopyHIhmneb_****u4ekci2W8i6F9vrgpEezqAzEzj2ANrVUhvAXMwYzgY_****vc51aEQlRovkRoUhRlVsf4IzO9dZp6nN_****Wz8pk2TDLuMo4pVIQvGaxH3vrsnSQiK****.

Etapa 1: Crie um projeto

No Android Studio, siga o assistente de configuração para criar um novo projeto Android. A figura abaixo mostra o diretório do projeto.

image.png

Etapa 2: Integrar o AAR

  1. Extraia o arquivo do SDK tigertally-X.Y.Z-xxxxxx-android.tgz e copie todos os arquivos AAR da pasta extraída para o diretório libs do módulo principal (o caminho exato depende da configuração do projeto).image.png

  2. Abra o arquivo build.gradle do aplicativo e adicione dependências para AliTigerTally_X.Y.Z.aar e AliCaptcha_X.Y.Z.aar no diretório libs.

    Importante

    Substitua X.Y.Z nos nomes dos arquivos AliTigerTally_X.Y.Z.aar e AliCaptcha_X.Y.Z.aar pelo número de versão dos arquivos AAR.

    A configuração segue este formato:

    dependencies {
        // ...
        implementation files('libs/AliTigerTally_X.Y.Z.aar')
        implementation files('libs/AliCaptcha_X.Y.Z.aar')
      
        // third-party library dependencies
        implementation 'com.alibaba:fastjson:1.2.83_noneautotype'
        implementation 'com.squareup.okhttp3:okhttp:3.11.0'
        implementation 'com.squareup.okio:okio:1.14.0'
    }

Etapa 3: Filtrar arquiteturas de CPU SO

Se o projeto ainda não utilizar arquivos SO, adicione a seguinte configuração ao arquivo build.gradle.

android {
    defaultConfig {
        ndk {
            abiFilters 'arm64-v8a', 'armeabi-v7a'
        }
    }
}

Etapa 4: Solicitar permissões

  • Permissão obrigatória

    <uses-permission android:name="android.permission.INTERNET"/>
  • Permissões opcionais

    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Nota

No Android 6.0 e versões posteriores, solicite dinamicamente as permissões android.permission.READ_EXTERNAL_STORAGE e android.permission.WRITE_EXTERNAL_STORAGE.

Etapa 5: Adicionar código de integração

1. Adicionar arquivos de cabeçalho

import com.alibaba.fastjson.*;
import com.aliyun.tigertally.*;

2. Configure a assinatura de dados

  1. Defina um ID personalizado para cada usuário final. Isso permite configurar políticas de mitigação do WAF com maior flexibilidade.

    /**
    * Sets the user account.
    *
    * @param account The user account.
    * @return An error code.
    */
    public static int setAccount(String account)
    • Parâmetros:

      • account: String. Identificador do usuário. Recomendamos o uso de um formato anonimizado.

    • Valor de retorno: int. Retorna 0 em caso de sucesso ou -1 em caso de falha.

    • Código de exemplo:

      // For a guest, you can skip setAccount and directly initialize the SDK. After the user logs in, call setAccount and reinitialize.
      String account = "user001";
      TigerTallyAPI.setAccount(account);
  2. Inicialize o SDK e execute uma coleta inicial de dados.

    A coleta inicial reúne informações do dispositivo uma única vez. Chame a função init novamente para realizar nova coleta conforme as necessidades do negócio. Existem três modos de coleta: completa, personalizada sem dados de privacidade e sem dados de privacidade. O modo sem dados de privacidade ignora campos relacionados à privacidade do usuário final, incluindo: imei, imsi, simSerial, wifiMac, wifiList, bluetoothMac e androidId.

    Nota

    Selecione um modo de coleta alinhado aos requisitos de conformidade e que garanta a integridade dos dados. Dados completos melhoram a identificação de riscos.

    // Initialization callback
    public interface TTInitListener {
        // The code parameter indicates the status code of the interface call.
        void onInitFinish(int code);
    }
    
    /**
     * Initializes the SDK with a callback.
     *
     * @param context The application context.
     * @param appkey The SDK appkey.
     * @param collectType The data collection mode.
     * @param otherOptions Optional parameters.
     * @param listener The initialization callback.
     * @return An error code.
     */
    public static int init(Context context, String appkey, int collectType,
                           Map<String, String> otherOptions, TTInitListener listener);
    • Parâmetros:

      • context: Context. Contexto da aplicação.

      • appkey: String. Appkey do SDK.

      • collectType: int. Modo de coleta. Valores válidos:

        Parâmetro

        Descrição

        Exemplo

        TT_DEFAULT

        Coleta todos os dados.

        TigerTallyAPI.TT_DEFAULT

        TT_NO_BASIC_DATA

        Não coleta dados básicos do dispositivo.

        Inclui: nome do dispositivo (Build.DEVICE), versão do Android (Build.VERSION#RELEASE) e resolução de tela.

        TigerTallyAPI.X | TigerTallyAPI.Y

        (Indica que nem X nem Y são coletados. X e Y representam os nomes dos campos de itens específicos.)

        TT_NO_IDENTIFY_DATA

        Não coleta dados de identificação do dispositivo.

        Inclui: IMEI, IMSI, SimSerial, BuildSerial (SN) e endereço MAC.

        TT_NO_UNIQUE_DATA

        Não coleta dados de identificadores exclusivos.

        Inclui: OAID, Google Advertising ID e Android ID.

        TT_NO_EXTRA_DATA

        Não coleta dados estendidos do dispositivo.

        Inclui: lista de aplicativos maliciosos/área cinzenta, IP LAN, IP DNS, informações de Wi-Fi conectado (SSID, BSSID), lista de Wi-Fi próximo, informações de localização e informações de sensor.

        TT_NOT_GRANTED

        Não coleta nenhum dos dados de privacidade mencionados anteriormente.

        TigerTallyAPI.TT_NOT_GRANTED

      • otherOptions: Map<String, String>. Parâmetros opcionais de coleta de dados. Pode ser nulo. Parâmetros disponíveis:

        Parâmetro

        Descrição

        Exemplo

        IPv6

        Define se deve usar um nome de domínio IPv6 para relatar informações do dispositivo.

        • 0 (padrão): Usa um nome de domínio IPv4.

        • 1: Usa um nome de domínio IPv6.

        1

        Intl

        Define se as informações do dispositivo devem ser relatadas para uma região fora da China continental.

        • 0 (padrão): Relata para a China continental.

        • 1: Relata para uma região fora da China continental.

        1

        CustomUrl

        Nome de domínio do servidor de relatório de dados.

        https://cloudauth-device.us-west-1.aliyuncs.com

        CustomHost

        Host do servidor de relatório de dados.

        cloudauth-device.us-west-1.aliyuncs.com

        Nota

        Para a maioria dos sites internacionais, defina apenas o parâmetro Intl. Para relatar dados a um site específico, configure os parâmetros CustomUrl e CustomHost. Sites disponíveis:

        • Se Intl for 0, os dados serão relatados ao site padrão na China (Xangai): https://cloudauth-device.cn-shanghai.aliyuncs.com

        • Se Intl for 1:

          • Site padrão em Singapura: https://cloudauth-device.ap-southeast-1.aliyuncs.com

          • Indonésia (Jacarta): https://cloudauth-device.ap-southeast-5.aliyuncs.com

          • EUA (Vale do Silício): https://cloudauth-device.us-west-1.aliyuncs.com

          • Alemanha (Frankfurt): https://cloudauth-device.eu-central-1.aliyuncs.com

          • China (Hong Kong): https://cloudauth-device.cn-hongkong.aliyuncs.com

      • listener: TTInitListener. Callback de inicialização do SDK. Use o callback para verificar o status da inicialização. Pode ser nulo.

        TTCode

        Código

        Descrição

        TT_SUCCESS

        0

        SDK inicializado com sucesso.

        TT_NOT_INIT

        -1

        SDK não inicializado.

        TT_NOT_PERMISSION

        -2

        O SDK não recebeu as permissões necessárias do Android.

        TT_UNKNOWN_ERROR

        -3

        Erro desconhecido do sistema.

        TT_NETWORK_ERROR

        -4

        Erro de rede.

        TT_NETWORK_ERROR_EMPTY

        -5

        Erro de rede: resposta vazia.

        TT_NETWORK_ERROR_INVALID

        -6

        Formato de resposta de rede inválido.

        TT_PARSE_SRV_CFG_ERROR

        -7

        Falha ao analisar a configuração do servidor.

        TT_NETWORK_RET_CODE_ERROR

        -8

        O gateway retornou uma resposta de falha.

        TT_APPKEY_EMPTY

        -9

        Appkey vazia.

        TT_PARAMS_ERROR

        -10

        Parâmetro inválido.

        TT_FGKEY_ERROR

        -11

        Erro de cálculo da chave.

        TT_APPKEY_ERROR

        -12

        Incompatibilidade entre a versão do SDK e a appkey.

    • Valor de retorno: int. Retorna 0 em caso de sucesso ou -1 em caso de falha.

    • Código de exemplo:

      // The appkey is the authentication key assigned on the Alibaba Cloud platform.
      final String appkey="******";
      // Optional parameters to configure IPv6 and international reporting.
      Map<String, String> options = new HashMap<>();
      options.put("IPv6", "0");   // Use IPv4.
      options.put("Intl", "0");   // Report to the Chinese mainland.
      //options.put("Intl", "1"); // Report to a region outside the Chinese mainland.
      
      // Report to US (Silicon Valley).
      //options.put("CustomUrl", "https://cloudauth-device.us-west-1.aliyuncs.com"); 
      //options.put("CustomHost", "cloudauth-device.us-west-1.aliyuncs.com"); 
      
      // An initial data collection gathers device information one time. You can call the init function again to perform a new collection based on your business requirements.
      // Full collection.
      int ret = TigerTallyAPI.init(this.getApplicationContext(), appkey, TigerTallyAPI.TT_DEFAULT, options, null);
      
      // Specify privacy data to collect. You can combine different flags by using the "|" operator.
      int privacyFlag = TigerTallyAPI.TT_NO_BASIC_DATA | TigerTallyAPI.TT_NO_UNIQUE_DATA;
      int ret = TigerTallyAPI.init(this.getApplicationContext(), appkey, privacyFlag, options, null);
      
      // Do not collect privacy fields.
      int ret = TigerTallyAPI.init(this.getApplicationContext(), appkey, TigerTallyAPI.TT_NOT_GRANTED, options, null);
      Log.d("AliSDK", "ret:" + ret);
  3. Gere o hash dos dados.

    Este método de assinatura personalizada calcula um hash dos dados de entrada e retorna a string whash gerada como dados de assinatura personalizada. Para requisições POST, PUT e PATCH, passe o corpo da requisição. Para requisições GET e DELETE, passe a URL completa. Adicione a string whash ao campo ali_sign_whash no cabeçalho da requisição HTTP.

    // Request type:
    public enum RequestType { GET, POST, PUT, PATCH, DELETE }
    
    /**
     *  Hashes data for a custom signature.
     *
     * @param type  The request type.
     * @param input The data to hash.
     * @return The whash string.
     */
    public static String vmpHash(RequestType type, byte[] input);
    • Parâmetros:

    • type: RequestType. Tipo de requisição. Valores válidos:

      • GET: requisição GET.

      • POST: requisição POST.

      • PUT: requisição PUT.

      • PATCH: requisição PATCH.

      • DELETE: requisição DELETE.

    • input: byte[]. Dados para gerar o hash.

    • Valor de retorno: String. Retorna a string whash.

    • Código de exemplo:

      // GET request
      String url = "https://tigertally.aliyun.com/apptest";
      String whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.GET, url.getBytes());
      Log.d("AliSDK", "whash:" + whash);
      
      // POST request
      String body = "hello world";
      String whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.POST, body.getBytes());
      Log.d("AliSDK", "whash:" + whash);
      Nota

      Este método não é necessário para a assinatura padrão. Para assinatura personalizada, chame este método para gerar o hash dos dados antes de assiná-los.

  4. Assine os dados.

    Este método utiliza a tecnologia VMP para assinar os dados de entrada e retorna uma string wtoken para autenticação da requisição.

    /**
     * Signs the data.
     *
     * @param type The signature type.
     * @param input The data to sign.
     * @return The wtoken string.
     */
    public static String vmpSign(int type, byte[] input);
    • Parâmetros:

      • type: int. Tipo de assinatura de dados. O valor deve ser 1.

      • input: byte[]. Dados a serem assinados. Geralmente corresponde a todo o corpo da requisição ou à string whash da assinatura personalizada.

    • Valor de retorno: String. Retorna a string wtoken.

    • Código de exemplo:

      // Use this code if default signing is configured in the console (custom signing is not selected).
      String body = "i am the request body, encrypted or not!";
      String wtoken = TigerTallyAPI.vmpSign(1, body.getBytes("UTF-8"));
      Log.d("AliSDK", "wToken:" + wtoken);
      
      // Use this code if custom signing is configured in the console.
      // GET request
      String url = "https://tigertally.aliyun.com/apptest";
      String whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.GET, url.getBytes());
      String wtoken = TigerTallyAPI.vmpSign(1, whash.getBytes());
      Log.d("AliSDK", "whash:" + whash + ", wtoken:" + wtoken);
      
      // POST request
      String body = "hello world";
      String whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.POST, body.getBytes());
      String wtoken = TigerTallyAPI.vmpSign(1, whash.getBytes());
      Log.d("AliSDK", "whash:" + whash + ", wtoken:" + wtoken);
      Nota
      • Ao chamar vmpHash para assinatura personalizada, o parâmetro input do método vmpSign corresponde à string whash gerada. Ao configurar políticas antibot para aplicativos, defina o valor do Campo de Assinatura Personalizada como ali_sign_whash.

      • Ao chamar vmpHash para gerar uma string whash para uma requisição GET, certifique-se de que a URL de entrada seja idêntica à URL final usada na requisição de rede. Preste atenção especial à codificação da URL, pois alguns frameworks codificam automaticamente caracteres chineses ou parâmetros.

      • O parâmetro input do método vmpHash não aceita array de bytes vazio ou string vazia. Se a entrada for uma URL, ela deve conter um caminho ou parâmetros.

      • Ao chamar vmpSign, se o corpo da requisição estiver vazio (por exemplo, em uma requisição GET ou POST com corpo vazio), passe um objeto null ou o valor em bytes de uma string vazia, como "".getBytes("UTF-8").

      • Se o valor de whash ou wtoken for uma das strings abaixo, o SDK retornou um erro:

        • you must call init first: O SDK não foi inicializado. Chame init() primeiro.

        • you must input correct data: Dados de entrada inválidos.

        • you must input correct type: Tipo de entrada inválido.

3. Realizar verificação secundária

  1. Avalie o resultado.

    Verifique os campos cookie e body na resposta para determinar se a verificação secundária é necessária. Antes de chamar este método, mescle várias entradas Set-Cookie em uma única string de cookie.

    /**
     * Determines whether to perform secondary verification.
     *
     * @param cookie The cookie string.
     * @param body The body string.
     * @return 0 for pass, or 1 for secondary verification required.
     */
    public static int cptCheck(String cookie, String body)
    • Parâmetros:

      • cookie: String. Todos os cookies da resposta HTTP.

      • body: String. Todo o corpo da resposta HTTP.

    • Valor de retorno: int. Retorna 0 se a requisição for aprovada ou 1 se a verificação secundária for necessária.

    • Código de exemplo:

      String cookie = "key1=value1;kye2=value2;";
      String body = "....";
      int recheck = TigerTallyAPI.cptCheck(cookie, body);
      Log.d("AliSDK", "recheck:" + recheck);
  2. Crie um slider.

    Se cptCheck retornar 1, crie um objeto slider. O objeto TTCaptcha fornece os métodos show() e dismiss() para exibir e ocultar a janela do slider, respectivamente. TTOption encapsula os parâmetros configuráveis do slider, e TTListener fornece callbacks para estados de sucesso e falha. Para usar uma janela de slider personalizada, passe a URL da página personalizada. Arquivos HTML locais e páginas remotas são suportados.

    /**
     * Creates a slider object.
     *
     * @param activity The activity where the slider is displayed.
     * @param option The slider parameters.
     * @param listener The callback for the slider.
     * @return The slider verification object.
     */
    public static TTCaptcha cptCreate(Activity activity, TTOption option, TTListener listener);
    
    /**
     * The slider object.
     */
    public class TTCaptcha {
        /**
         * Displays the slider.
         */
        public void show();
    
        /**
         * Hides the slider.
         */
        public void dismiss();
    
        /**
         * Gets the slider traceId for data statistics.
         */
        public String getTraceId();
    }
    
    /**
     * The slider parameters.
     */
    public static class TTOption {
        // Specifies whether the slider can be dismissed by clicking a blank area.
        public boolean cancelable;
    
        // The custom page. Local HTML files and remote URLs are supported.
        public String customUri;
    
        // The language.
        public String language;
    }
    
    /**
     * The callback for slider events.
     */
    public interface TTListener {
        /**
         * Called on successful verification.
         *
         * @param captcha The slider object.
         * @param data The token. Defaults to certifyId.
         */
        void success(TTCaptcha captcha, String data);
    
        /**
         * Called on verification failure.
         *
         * @param captcha The slider object.
         * @param code The error code.
         */
        void failed(TTCaptcha captcha, String code);
    }
    • Parâmetros:

      • activity: Activity. Activity da página atual.

      • option: TTOption. Parâmetros de configuração do slider.

      • listener: TTListener. Callback de status do slider.

    • Valor de retorno: TTCaptcha. Objeto slider.

    • Código de exemplo:

    TTCaptcha.TTOption option = new TTCaptcha.TTOption();
    // option.customUri  = "file:///android_asset/ali-tt-captcha-demo.html";
    option.language   = "cn";
    option.cancelable = false;
    
    TTCaptcha captcha = TigerTallyAPI.cptCreate(this, option, new TTCaptcha.TTListener() {
        @Override
        public void success(TTCaptcha captcha, String data) {
            Log.d(TAG, "captcha check success:" + data);
        }
        @Override
        public void failed(TTCaptcha captcha, String code) {
            Log.d(TAG, "captcha check failed:" + code);
        }
    });
    captcha.show();
    Nota

    Uma falha na verificação indica que uma exceção foi detectada após o usuário concluir o desafio do slider.

    Os códigos de erro são descritos abaixo:

    • 1001: Falha na verificação.

    • 1002: Exceção do sistema.

    • 1003: Parâmetro inválido.

    • 1005: Verificação cancelada.

    • 8001: Falha ao exibir o slider.

    • 8002: Dados anormais na verificação do slider.

    • 8003: Exceção interna na verificação do slider.

    • 8004: Erro de rede.

  3. Exemplo de melhor prática

    package com.aliyun.tigertally.apk;
    
    import androidx.appcompat.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.Log;
    import com.aliyun.TigerTally.TigerTallyAPI;
    import com.aliyun.TigerTally.captcha.api.TTCaptcha;
    import okhttp3.MediaType;
    import okhttp3.OkHttpClient;
    import okhttp3.Request;
    import okhttp3.RequestBody;
    import okhttp3.Response;
    
    public class DemoActivity extends AppCompatActivity {
        private final static String TAG = "TigerTally-Demo";
    
        private final static String APP_HOST = "******";
        private final static String APP_URL  = "******";
        private final static String APP_KEY  = "******";
    
        private final static OkHttpClient okHttpClient = new OkHttpClient();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_demo);
    
            doTest();
        }
    
        private void doTest() {
            Log.d(TAG, "captcha flow");
            new Thread(() -> {
                // Initialize the SDK.
                Map<String, String> options = new HashMap<>();
                //options.put("Intl", "1"); // To enable international reporting.
                // Use full data collection mode.
                int ret = TigerTallyAPI.init(this, APP_KEY, TigerTallyAPI.TT_DEFAULT, options, null);
                // Alternatively, do not collect privacy fields.
                // int ret = TigerTallyAPI.init(this, APP_KEY, TigerTallyAPI.TT_NOT_GRANTED, null, null);
                Log.d(TAG, "tiger tally init: " + ret);
    
                // Wait for initialization to complete.
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                // Sign the data.
                String data = "hello world";
                String whash = null, wtoken = null;
                // Use custom signing.
                whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.POST, data.getBytes());
                wtoken = TigerTallyAPI.vmpSign(1, whash.getBytes());
                Log.d(TAG, "tiger tally vmp: " + whash + ", " + wtoken);
    
                // Use standard signing.
                // wtoken = TigerTallyAPI.vmpSign(1, data.getBytes());
                // Log.d(TAG, "tiger tally vmp: " + wtoken);
    
                // Send the request.
                doPost(APP_URL, APP_HOST, whash, wtoken, data, (code, cookie, body) -> {
                    // Check if the slider is required.
                    int recheck = TigerTallyAPI.cptCheck(cookie, body);
                    Log.d(TAG, "captcha check result: " + recheck);
    
                    if (recheck == 0) return;
                    this.runOnUiThread(this::doShow);
                });
            }).start();
        }
    
        // Display the slider.
        public void doShow() {
            Log.d(TAG, "captcha show");
    
            TTCaptcha.TTOption option = new TTCaptcha.TTOption();
            // option.customUri = "file:///android_asset/ali-tt-captcha-demo.html";
            option.language   = "cn";
            option.cancelable = false;
    
            TTCaptcha captcha = TigerTallyAPI.cptCreate(this, option, new TTCaptcha.TTListener() {
                @Override
                public void success(TTCaptcha captcha, String data) {
                    Log.d(TAG, "captcha check success:" + data);
                }
    
                @Override
                public void failed(TTCaptcha captcha, String code) {
                    Log.d(TAG, "captcha check failed:" + code);
                }
            });
    
            captcha.show();
        }
    
        // Send the request.
        public static void doPost(String url, String host, String whash, String wtoken, String body, Callback callback) {
            Log.d(TAG, "start request post");
    
            int responseCode = 0;
            String responseBody = "";
            StringBuilder responseCookie = new StringBuilder();
            try {
                Request.Builder builder = new Request.Builder()
                        .url(url)
                        .addHeader("wToken", wtoken)
                        .addHeader("Host",   host)
                        .post(RequestBody.create(MediaType.parse("text/x-markdown"), body.getBytes()));
    
                if (whash != null) {
                    builder.addHeader("ali_sign_whash", whash);
                }
                Response response = okHttpClient.newCall(builder.build()).execute();
    
                responseCode = response.code();
                responseBody = response.body() == null ? "" : response.body().string();
                for (String item : response.headers("Set-Cookie")) {
                    responseCookie.append(item).append(";");
                }
    
                Log.d(TAG, "response code:" + responseCode);
                Log.d(TAG, "response cookie:" + responseCookie);
                Log.d(TAG, "response body:" + (responseBody.length() > 100 ? responseBody.substring(0, 100) : ""));
    
                if (response.isSuccessful()) {
                    Log.d(TAG, "success: " + response.code() + ", " + response.message());
                } else {
                    Log.e(TAG, "failed: " + response.code() + ", " + response.message());
                }
    
                response.close();
            } catch (Exception e) {
                e.printStackTrace();
                responseCode = -1;
                responseBody = e.toString();
            } finally {
                if (callback != null) {
                    callback.onResponse(responseCode, responseCookie.toString(), responseBody);
                }
            }
        }
    
        public interface Callback {
            void onResponse(int code, String cookie, String body);
        }
    }