移动端APP阿里云验证码2.0接入实录

移动端APP阿里云验证码2.0接入实录

背景

由于我司WEB端已经全部将验证码升级到2.0,因此移动端APP也需要同步升级到2.0。在此之前虽然也在其他APP上使用了2.0验证码,但是在显示效果上,总是感觉差强人意。因此此次着重讲解如何优化阿里云验证码2.0的显示样式。

准备过程

首先按照官方文档流程进行开发设计

help.aliyun.com/zh/captcha/...zh-V_1: https://help.aliyun.com/zh/captcha/captcha2-0/user-guide/use-webview-to-access-aliyun-verification-code-2-0-in-android?spm=a2c4g.11186623.help-menu-28308.d_1_2_1_0.63485fb6CgArF7&scm=20140722.H_2508951._.OR_help-T_cnzh-V_1

接入后的优化调整

  1. 禁用iOS上下滑动时显示滚动条
css 复制代码
 body {
        overflow: hidden !important;
        height: 100% !important;
        margin: 0 !important;
        padding: 0 !important;
        -webkit-overflow-scrolling: auto !important; /* 禁用 iOS 弹性滚动 */
 }
  1. 设置滑动验证码的宽度
javascript 复制代码
window.addEventListener('load', function() {
        // 使用 viewport 宽度,这个值更接近实际显示宽度
        const width = Math.min(
            window.innerWidth,
            document.documentElement.clientWidth,
            screen.width
        );
        console.log("viewport width = "+ width);
        initConfig(width);
});

function initConfig(width){
  ...,
  slideStyle: {
                width: captchaWidth,
                height: 50,
            },
   ...
}
  1. 增加加载动画
css 复制代码
<div class="loading-container">
      <div class="loading-dots">
      <div class="dot"></div>
      <div class="dot"></div>
      <div class="dot"></div>
</div>

<style>
     ...,
     /* Loading 动画样式 */
     .loading-container {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        height: 50px;
        margin: 0 auto;
        width: 320px;
    }

    .loading-dots {
        display: flex;
        gap: 6px;
    }

    .dot {
        width: 8px;
        height: 8px;
        background-color: #dc3d08;
        border-radius: 50%;
        animation: wave 1.5s infinite ease-in-out;
    }

    .dot:nth-child(1) { animation-delay: -0.32s; }
    .dot:nth-child(2) { animation-delay: -0.16s; }
    .dot:nth-child(3) { animation-delay: 0s; }

    @keyframes wave {
        0%, 100% { 
            transform: translateY(0);
        }
        40% { 
            transform: translateY(-10px);
        }
        50% {
            transform: translateY(-10px);
        }
    }

</style>
  1. 滑块背景调整
css 复制代码
#aliyunCaptcha-sliding-wrapper #aliyunCaptcha-sliding-body #aliyunCaptcha-sliding-left {
      position: absolute;
      height: 100%;
      border-radius: 2px;
      background-color: #dec9c93b!important;  // 调整滑动后左边滑块区域的颜色
      
}

#aliyunCaptcha-sliding-wrapper #aliyunCaptcha-sliding-body #aliyunCaptcha-sliding-slider,body #aliyunCaptcha-sliding-wrapper #aliyunCaptcha-sliding-body #aliyunCaptcha-sliding-slider.ok {
       background: #0F8EE9!important; // 调整滑块的颜色
}

#aliyunCaptcha-sliding-wrapper #aliyunCaptcha-sliding-body #aliyunCaptcha-sliding-text-box {
        box-sizing: border-box;
        border: 1px solid #D4D9E2; // 验证码的边框和边框颜色
        color: #666;
        border-radius: 4px;
        overflow: hidden;
}

#aliyunCaptcha-sliding-wrapper #aliyunCaptcha-sliding-body {
       background: #ea6d0e70!important; // 验证码填充颜色
}
  1. 完整验证码示例源码:
html 复制代码
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">

</head>
<style>

    body {
        overflow: hidden !important;
        height: 100% !important;
        margin: 0 !important;
        padding: 0 !important;
        -webkit-overflow-scrolling: auto !important; /* 禁用 iOS 弹性滚动 */
   }
    #aliyunCaptcha-sliding-wrapper #aliyunCaptcha-sliding-body #aliyunCaptcha-sliding-slider,body #aliyunCaptcha-sliding-wrapper #aliyunCaptcha-sliding-body #aliyunCaptcha-sliding-slider.ok {
        background: #dc3d08!important;
    }

   #aliyunCaptcha-sliding-wrapper span {
        background-color: transparent!important;
    }
    #aliyunCaptcha-sliding-wrapper #aliyunCaptcha-sliding-body #aliyunCaptcha-sliding-text-box {
        box-sizing: border-box;
        border: 1px solid #D4D9E2;
        color: #666;
        border-radius: 4px;
        overflow: hidden;
    }
    #aliyunCaptcha-sliding-wrapper #aliyunCaptcha-sliding-body {
        background: #00000000;
    }

    #aliyunCaptcha-sliding-wrapper #aliyunCaptcha-sliding-body #aliyunCaptcha-sliding-left {
      position: absolute;
      height: 100%;
      border-radius: 2px;
      background-color: #dec9c93b!important;
    }


     /* Loading 动画样式 */
     .loading-container {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        height: 50px;
        margin: 0 auto;
        width: 320px;
    }

    .loading-dots {
        display: flex;
        gap: 6px;
    }

    .dot {
        width: 8px;
        height: 8px;
        background-color: #dc3d08;
        border-radius: 50%;
        animation: wave 1.5s infinite ease-in-out;
    }

    .dot:nth-child(1) { animation-delay: -0.32s; }
    .dot:nth-child(2) { animation-delay: -0.16s; }
    .dot:nth-child(3) { animation-delay: 0s; }

    @keyframes wave {
        0%, 100% { 
            transform: translateY(0);
        }
        40% { 
            transform: translateY(-10px);
        }
        50% {
            transform: translateY(-10px);
        }
    }

</style>
<script type="text/javascript"
        src="https://m.juming.com/wap/js/yzm.js"></script>
<script>
    
    let captchaWidth = 320;
    let captcha;

    function initConfig(width) {
        captchaWidth = Math.max(320, width); // 确保不小于最小宽度
        console.log("initConfig width = "+ captchaWidth);
        // 初始化验证码
        initAliyunCaptcha({
            SceneId: 'xxxxx', //  场景ID。通过步骤一添加验证场景后,您可以在验证码场景列表,获取该场景的场景ID
            prefix: 'xxxx', //  身份标。开通阿里云验证码2.0后,您可以在控制台概览页面的实例基本信息卡片区域,获取身份标
            mode: 'embed', // 验证码模式。embed表示要集成的验证码模式为嵌入式。无需修改
            element: '#captcha-element', // 页面上预留的渲染验证码的元素,与原代码中预留的页面元素保持一致。
            button: '#captcha-button', // 触发业务请求的元素。button表示单击登录按钮后,触发captchaVerifyCallback函数。您可以根据实际使用的元素修改element的值
            captchaVerifyCallback: captchaVerifyCallback, // 业务请求(带验证码校验)回调函数,无需修改
            onBizResultCallback: onBizResultCallback, // 业务请求结果回调函数,无需修改
            getInstance: getInstance, // 绑定验证码实例函数,无需修改
            slideStyle: {
                width: captchaWidth,
                height: 50,
            }, // 滑块验证码样式,支持自定义宽度和高度,单位为px。其中,width最小值为320 px
            language: 'cn', // 验证码语言类型,支持简体中文(cn)、繁体中文(tw)、英文(en)
            immediate: true, // 完成验证后,是否立即发送验证请求(调用captchaVerifyCallback函数)
            autoRefresh: false, //设置验证码验证通过后是否自动刷新验证码
            rem: 1.0,
        });
    }

     window.addEventListener('load', function() {
        // 使用 viewport 宽度,这个值更接近实际显示宽度
        const width = Math.min(
            window.innerWidth,
            document.documentElement.clientWidth,
            screen.width
        );
        console.log("viewport width = "+ width);
        initConfig(width);
    });


    // 绑定验证码实例函数。该函数为固定写法,无需修改
    function getInstance(instance) {
        captcha = instance;
        const loadingContainer = document.querySelector('.loading-container');
        if (loadingContainer) {
            loadingContainer.style.display = 'none';
        }
    }


    // 业务请求(带验证码校验)回调函数
    /**
     * @name verifyCaptchaCallback
     * @function
     * 请求参数:由验证码脚本回调的验证参数,不需要做任何处理,直接传给服务端即可
     * @params {string} captchaVerifyParam
     * 返回参数:字段名固定,captchaResult为必选;如无业务验证场景时,bizResult为可选
     * @returns {{captchaResult: boolean, bizResult?: boolean|undefined}}
     */
    async function captchaVerifyCallback(captchaVerifyParam) {
      const verifyResult = { captchaResult: true, bizResult: true, msg: "success"};
      console.log("result = "+ captchaVerifyParam);
      window.flutter_inappwebview.callHandler("getSlideData", captchaVerifyParam);
      return verifyResult;
    }

    // 业务请求结果回调函数
    function onBizResultCallback(bizResult) {
      console.log("bizResult = "+ bizResult);
      if (bizResult != true) {
        // 如果业务验证不通过,给出不通过提示。此处不通过提示为业务验证不通过!
        window.location.reload();
      }

    }

    function reset(){
        console.log("reset");
        if(captcha != undefined){
          captcha.refresh()
       }
    }



</script>

<body style="padding: 0px; margin:0px; background-color: #00000000;width:100%;">
  <div id="captcha-element">
    <div class="loading-container">
      <div class="loading-dots">
          <div class="dot"></div>
          <div class="dot"></div>
          <div class="dot"></div>
      </div>
  </div>
  </div>
</body>
</html>
  1. 调用方代码示例源码 dart:
dart 复制代码
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../common_module.dart';

/// 滑动验证码控制器
class SlideCaptcha2Controller {
  final InAppWebViewController controller;

  SlideCaptcha2Controller(this.controller);

  /// 重置滑动验证码状态
  void reset() {
    LOG("reset");
    controller.evaluateJavascript(source: "javascript:reset()");
  }
}

/// 滑动验证码
class SlideCaptchaV2 extends StatefulWidget {
  final Function(bool isSlideSuccess)? close;
  final Future<void> Function(AliCaptchaV2 captchaParams) onSlideDone;
  final Function(SlideCaptcha2Controller controller)? onController;
  @Deprecated("页面传入宽度,暂时不使用该参数")
  final double width;

  const SlideCaptchaV2(
      {super.key, this.close, required this.onSlideDone, this.onController, this.width = 340});

  @override
  createState() => _SlideCaptchaState();
}

class _SlideCaptchaState extends State<SlideCaptchaV2> {
  bool isSlideSuccess = false;
  InAppWebViewSettings settings = InAppWebViewSettings(
    isInspectable: true,
    mediaPlaybackRequiresUserGesture: false,
    allowsInlineMediaPlayback: false,
    iframeAllow: 'camera; microphone',
    iframeAllowFullscreen: true,
    allowFileAccess: false,
    allowFileAccessFromFileURLs: false,
    allowUniversalAccessFromFileURLs: false,
    allowContentAccess: false,
    useWideViewPort: true,
    horizontalScrollBarEnabled: false,
    cacheMode: CacheMode.LOAD_NO_CACHE,
    underPageBackgroundColor: Colors.transparent,
    // NOTE 暗黑模式会导致第一次加载webview黑屏 关闭 设置 transparentBackground true
    // forceDark: ForceDark.OFF,
    transparentBackground: true,

    /// 增加useHybridComposition false 能与背景同步消失 但是在部分机型上会导致异常崩溃,
    /// java.lang.NullPointerException: Attempt to read from field 'android.view.WindowManager$LayoutParams
    /// android.view.ViewRootImpl.mWindowAttributes' on a null object reference
    useHybridComposition: true,
    displayZoomControls: false,
    supportZoom: false,
    loadWithOverviewMode: true,
    // disableHorizontalScroll: true,
    disableVerticalScroll: true,
    disallowOverScroll: true,
    scrollbarFadingEnabled: false,
  );

  InAppWebViewController? webViewController;

  @override
  void initState() {
    super.initState();

    if (Platform.isAndroid) {
      InAppWebViewController.setWebContentsDebuggingEnabled(kDebugMode);
    }
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: 360,
      height: 70,
      child: Stack(
        children: [
          InAppWebView(
            initialSettings: settings,
            onWebViewCreated: (controller) async {
              webViewController = controller;
              final slideController = SlideCaptcha2Controller(controller);
              widget.onController?.call(slideController);
              LOG('onWebViewCreated');
              webViewController?.loadFile(assetFilePath: "assets/html/slide_captcha_v2.html");
              // webViewController?.loadUrl(urlRequest: URLRequest(url: WebUri("https://www.baidu.com")));
            },
            onLoadStart: (controller, url) async {
              LOG('onLoadStart url $url');
            },
            onLoadStop: (controller, url) async {
              LOG('onLoadStop url:$url');
              // webViewController?.evaluateJavascript(source: """
              // javascript:initConfig(${widget.width});
              // """);
              controller.addJavaScriptHandler(
                  handlerName: 'getSlideData',
                  callback: (args) async {
                    String result = args[0];
                    Map<String, dynamic> map = jsonDecode(result);
                    LOG('getSlideData args:$map');
                    final captchaParams = AliCaptchaV2.fromJson(map);
                    isSlideSuccess = true;
                    await widget.onSlideDone.call(captchaParams);
                  });
            },
            onProgressChanged: (controller, progress) async {
              LOG('onProgressChanged progress:$progress');
            },
            onConsoleMessage: (controller, consoleMessage) {
              LOG('consoleMessage:${consoleMessage.message}');
            },
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    widget.close?.call(isSlideSuccess);
    super.dispose();
  }
}
相关推荐
jiet_h13 分钟前
深入解析Kapt —— Kotlin Annotation Processing Tool 技术博客
android·开发语言·kotlin
alexhilton41 分钟前
实战:探索Jetpack Compose中的SearchBar
android·kotlin·android jetpack
uhakadotcom1 小时前
EventBus:简化组件间通信的利器
android·java·github
笑鸿的学习笔记2 小时前
ROS2笔记之服务通信和基于参数的服务通信区别
android·笔记·microsoft
8931519603 小时前
Android开发融云获取多个会话的总未读数
android·android开发·android教程·融云获取多个会话的总未读数·融云未读数
zjw_swun4 小时前
实现了一个uiautomator玩玩
android
pengyu4 小时前
系统化掌握Dart网络编程之Dio(二):责任链模式篇
android·flutter·dart
水w4 小时前
【Android Studio】如何卸载干净(详细步骤)
android·开发语言·android studio·activity
亦是远方4 小时前
2025华为软件精英挑战赛2600w思路分享
android·java·华为
jiet_h4 小时前
深入解析KSP(Kotlin Symbol Processing):现代Android开发的新利器
android·开发语言·kotlin