移动端APP阿里云验证码2.0接入实录
背景
由于我司WEB端已经全部将验证码升级到2.0,因此移动端APP也需要同步升级到2.0。在此之前虽然也在其他APP上使用了2.0验证码,但是在显示效果上,总是感觉差强人意。因此此次着重讲解如何优化阿里云验证码2.0的显示样式。
准备过程
首先按照官方文档流程进行开发设计
接入后的优化调整
- 禁用iOS上下滑动时显示滚动条
css
body {
overflow: hidden !important;
height: 100% !important;
margin: 0 !important;
padding: 0 !important;
-webkit-overflow-scrolling: auto !important; /* 禁用 iOS 弹性滚动 */
}
- 设置滑动验证码的宽度
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,
},
...
}
- 增加加载动画
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>
- 滑块背景调整
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; // 验证码填充颜色
}
- 完整验证码示例源码:
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>
- 调用方代码示例源码 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();
}
}