返回顶部
首页 > 资讯 > 精选 >【flutter滑动拼图验证码】
  • 249
分享到

【flutter滑动拼图验证码】

flutter 2023-08-19 16:08:52 249人浏览 安东尼
摘要

Java后台使用aj_captcha插件,提供/captcha/get(获取captcha底图和拼块图片)、/captcha/check(验证拼图偏移量)这两个接口。并且这个插件在GitHub上有源码。 1.先准备好aj_captcha的

在这里插入图片描述
Java后台使用aj_captcha插件,提供/captcha/get(获取captcha底图和拼块图片)、/captcha/check(验证拼图偏移量)这两个接口。并且这个插件在GitHub上有源码
1.先准备好aj_captcha的工具类:

import 'dart:convert';import 'package:steel_crypt/steel_crypt.dart';//import 'package:encrypt/encrypt.dart';class EncryptUtil {  ///aes加密  /// [key]AesCrypt加密key  /// [content] 需要加密的内容字符串  static String aesEncode({String key, String content}) {    // var aesEncrypter = AesCrypt(key, 'ecb', 'pkcs7');    var encodeKey = base64UrlEncode(utf8.encode(key));    var aesEncrypter = AesCrypt(padding: PaddingAES.pkcs7, key: encodeKey);    return aesEncrypter.ecb.encrypt(inp: content);  }  ///aes解密  /// [key]aes解密key  /// [content] 需要加密的内容字符串  static String aesDecode({String key, String content}) {    // var aesEncrypter = AesCrypt(key, 'ecb', 'pkcs7');    var encodeKey = base64UrlEncode(utf8.encode(key));    var aesEncrypter = AesCrypt(key: encodeKey, padding: PaddingAES.pkcs7);    // return aesEncrypter.decrypt(content);    return aesEncrypter.ecb.decrypt(enc: content);  }}
import 'dart:convert';class ObjectUtils {  /// isEmpty.  static bool isEmpty(Object value) {    if (value == null) return true;    if (value is String && value.isEmpty) {      return true;    }    return false;  }  //list length == 0  || list == null  static bool isListEmpty(Object value) {    if (value == null) return true;    if (value is List && value.length == 0) {      return true;    }    return false;  }  static String JSONFORMat(Map<dynamic, dynamic> map) {    Map _map = Map<String, Object>.from(map);    jsonEncoder encoder = JsonEncoder.withIndent('  ');    return encoder.convert(_map);  }}
import 'dart:async';import 'package:Flutter/widgets.dart';import 'object_utils.dart';/// Widget Util.class WidgetUtil {  bool _hasMeasured = false;  double _width;  double _height;  /// Widget rendering listener.  /// Widget渲染监听.  /// context: Widget context.  /// isOnce: true,Continuous monitoring  false,Listen only once.  /// onCallBack: Widget Rect CallBack.  void asyncPrepare(      BuildContext context, bool isOnce, ValueChanged<Rect> onCallBack) {    if (_hasMeasured) return;    WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) {      RenderBox box = context.findRenderObject();      if (box != null && box.semanticBounds != null) {        if (isOnce) _hasMeasured = true;        double width = box.semanticBounds.width;        double height = box.semanticBounds.height;        if (_width != width || _height != height) {          _width = width;          _height = height;          if (onCallBack != null) onCallBack(box.semanticBounds);        }      }    });  }  /// Widget渲染监听.  void asyncPrepares(bool isOnce, ValueChanged<Rect> onCallBack) {    if (_hasMeasured) return;    WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) {      if (isOnce) _hasMeasured = true;      if (onCallBack != null) onCallBack(null);    });  }  ///get Widget Bounds (width, height, left, top, right, bottom and so on).Widgets must be rendered completely.  ///获取widget Rect  static Rect getWidgetBounds(BuildContext context) {    RenderBox box = context.findRenderObject();    return (box != null && box.semanticBounds != null)        ? box.semanticBounds        : Rect.zero;  }  ///Get the coordinates of the widget on the screen.Widgets must be rendered completely.  ///获取widget在屏幕上的坐标,widget必须渲染完成  static Offset getWidgetLocalToGlobal(BuildContext context) {    RenderBox box = context.findRenderObject();    return box == null ? Offset.zero : box.localToGlobal(Offset.zero);  }  /// get image width height,load error return Rect.zero.(unit px)  /// 获取图片宽高,加载错误情况返回 Rect.zero.(单位 px)  /// image  /// url network  /// local url , package  static Future<Rect> getImageWH(      {Image image, String url, String localUrl, String package}) {    if (ObjectUtils.isEmpty(image) &&        ObjectUtils.isEmpty(url) &&        ObjectUtils.isEmpty(localUrl)) {      return Future.value(Rect.zero);    }    Completer<Rect> completer = Completer<Rect>();    Image img = image != null        ? image        : ((url != null && url.isNotEmpty)            ? Image.network(url)            : Image.asset(localUrl, package: package));    img.image        .resolve(new ImageConfiguration())        .addListener(new ImageStreamListener(          (ImageInfo info, bool _) {            completer.complete(Rect.fromLTWH(0, 0, info.image.width.toDouble(),                info.image.height.toDouble()));          },          onError: (dynamic exception, StackTrace stackTrace) {            completer.completeError(exception, stackTrace);          },        ));    return completer.future;  }  /// get image width height, load error throw exception.(unit px)  /// 获取图片宽高,加载错误会抛出异常.(单位 px)  /// image  /// url network  /// local url (full path/全路径,example:"assets/images/ali_connors.png",""assets/images/3.0x/ali_connors.png"" );  /// package  static Future<Rect> getImageWHE(      {Image image, String url, String localUrl, String package}) {    if (ObjectUtils.isEmpty(image) &&        ObjectUtils.isEmpty(url) &&        ObjectUtils.isEmpty(localUrl)) {      return Future.error("image is null.");    }    Completer<Rect> completer = Completer<Rect>();    Image img = image != null        ? image        : ((url != null && url.isNotEmpty)            ? Image.network(url)            : Image.asset(localUrl, package: package));    img.image        .resolve(new ImageConfiguration())        .addListener(new ImageStreamListener(          (ImageInfo info, bool _) {            completer.complete(Rect.fromLTWH(0, 0, info.image.width.toDouble(),                info.image.height.toDouble()));          },          onError: (dynamic exception, StackTrace stackTrace) {            completer.completeError(exception, stackTrace);          },        ));    return completer.future;  }}

绘制验证弹窗

import 'dart:convert';import 'package:test/constant.dart';import 'package:test/generated/l10n.dart';import 'package:test/Http/DioManager.dart';import 'package:tset/util/easy_loading_util.dart';import 'package:test/util/encrypt_util.dart';import 'package:test/util/object_utils.dart';import 'package:test/util/widtet_util.dart';import 'package:flutter/material.dart';import 'package:flutter_screenutil/flutter_screenutil.dart';typedef VoidSuccessCallback = dynamic Function(String c);class CaptchaPage extends StatefulWidget {  final VoidSuccessCallback onSuccess; //拖放完成后验证成功回调  final VoidCallback onFail; //拖放完成后验证失败回调  CaptchaPage({this.onSuccess, this.onFail});    _CaptchaPageState createState() => _CaptchaPageState();}class _CaptchaPageState extends State<CaptchaPage>    with TickerProviderStateMixin {  /// 是否启用  bool enable = true;//  String baseImageBase64 =//      "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";  String baseImageBase64 = "";  String slideImageBase64 = "";  String captchaToken = "";  String secreTKEy = "";  Size baseSize = Size.zero; //底部基类图片  Size slideSize = Size.zero; //滑块图片  var sliderColor = Colors.white; //滑块的背景色  var sliderIcon = Icons.arrow_forward; //滑块的图标  var movedXBorderColor = Colors.white; //滑块拖动时,左边已滑的区域边框颜色  double sliderStartX = 0; //滑块未拖前的X坐标  double sliderXMoved = 0;  bool sliderMoveFinish = false; //滑块拖动结束  bool checkResultAfterDrag = false; //拖动后的校验结果  //-------------动画------------  int _checkMilliseconds = 0; //滑动时间  bool _showTimeLine = false; //是否显示动画部件  bool _checkSuccess = false; //校验是否成功  AnimationController controller;  var _ratio = 3.0;  var dialogWidth;  GlobalKey _baseImageKey = new GlobalKey();  //高度动画  Animation<double> offsetAnimation;  //------------动画------------  //校验通过  void checkSuccess(String content) {    setState(() {      checkResultAfterDrag = true;      _checkSuccess = true;      _showTimeLine = true;    });    _forwardAnimation();    updateSliderColorIcon();    //刷新验证码    Future.delayed(Duration(milliseconds: 1000)).then((v) {      _reverseAnimation().then((v) {        setState(() {          _showTimeLine = false;        });        //回调        if (widget.onSuccess != null) {          widget.onSuccess(content);          // NavigatorUtil.pop(value: true);        }        Navigator.pop(context);      });    });  }  //校验失败  void checkFail() {    setState(() {      _showTimeLine = true;      _checkSuccess = false;      checkResultAfterDrag = false;    });    _forwardAnimation();    updateSliderColorIcon();    //刷新验证码    Future.delayed(Duration(milliseconds: 1000)).then((v) {      _reverseAnimation().then((v) {        setState(() {          _showTimeLine = false;        });        loadCaptcha();        //回调        if (widget.onFail != null) {          widget.onFail();        }      });    });  }  //重设滑动颜色与图标  void updateSliderColorIcon() {    var _sliderColor = null; //滑块的背景色    var _sliderIcon = null; //滑块的图标    var _movedXBorderColor = null; //滑块拖动时,左边已滑的区域边框颜色    //滑块的背景色    if (sliderMoveFinish) {      //拖动结束      _sliderColor = checkResultAfterDrag ? Colors.green : Colors.red;      _sliderIcon = checkResultAfterDrag ? Icons.check : Icons.close;      _movedXBorderColor = checkResultAfterDrag ? Colors.green : Colors.red;    } else {      //拖动未开始或正在拖动中      _sliderColor = sliderXMoved > 0 ? Color(0xffe63850) : Colors.white;      _sliderIcon = Icons.arrow_forward;      _movedXBorderColor = Color(0xff89F2D0);    }    sliderColor = _sliderColor;    sliderIcon = _sliderIcon;    movedXBorderColor = _movedXBorderColor;    setState(() {});  }  //加载验证码  void loadCaptcha() {    setState(() {      _showTimeLine = false;      sliderMoveFinish = false;      checkResultAfterDrag = false;      sliderColor = Colors.white; //滑块的背景色      sliderIcon = Icons.arrow_forward; //滑块的图标      movedXBorderColor = Colors.white; //滑块拖动时,左边已滑的区域边框颜色    });    DioManager.getInstance()        .post(Constant.baseUrl + '/captcha/get', {"captchaType": "blockPuzzle"},            (res) async {      if (res['repCode'] != '0000' || res['repData'] == null) {        setState(() {          secretKey = "";        });        if (res['repCode'] == '6202') {          enable = false;          esLoadingError('您失败的次数太多啦,请稍后试试吧!');        }        return;      }      Map<String, dynamic> repData = res['repData'];      print("--------------");      print(repData.keys);      print("${repData["point"]}");      sliderXMoved = 0;      sliderStartX = 0;      captchaToken = '';      checkResultAfterDrag = false;      baseImageBase64 = repData["originalImageBase64"];      baseImageBase64 = baseImageBase64.replaceAll('\n', '');      secretKey = repData['secretKey'] ?? "";      slideImageBase64 = repData["jigsawImageBase64"];      slideImageBase64 = slideImageBase64.replaceAll('\n', '');      captchaToken = repData["token"];      print(baseImageBase64);      var baseR = await WidgetUtil.getImageWH(          image: Image.memory(Base64Decoder().convert(baseImageBase64)));      baseSize = baseR.size;      var silderR = await WidgetUtil.getImageWH(          image: Image.memory(Base64Decoder().convert(slideImageBase64)));      slideSize = silderR.size;      enable = true;      setState(() {});    }, (error) {      print(error);    });  }  //校验验证码  void checkCaptcha(sliderXMoved, captchaToken, {BuildContext myContext}) {    setState(() {      sliderMoveFinish = true;    });    //滑动结束,改变滑块的图标及颜色//    updateSliderColorIcon();    //pointJson参数需要aes加密//    MediaQueryData mediaQuery = MediaQuery.of(myContext);        print('sliderXMoved= $sliderXMoved');    print('_baseImageKeyWidth ${_baseImageKey.currentContext.size.width}');    print('_baseImageKeyWidthRatio= ${_baseImageKey.currentContext.size.width / baseSize.width}');    //由于不同屏幕分辨率或者屏幕设置放大后拖动从最右侧拖动到同一位置的偏移量是不同的(屏幕),根据底图在屏幕上的实际宽度和从接口获取的底图的宽度(baseSize.width)的百分比来计算接口偏移量参数    var pointMap = {"x": sliderXMoved / (_baseImageKey.currentContext.size.width / baseSize.width), "y": 5};//x:拖动的偏移量  y:偏移量误差范围     var pointStr = json.encode(pointMap);    var cryptedStr = pointStr;    /// secretKey 不为空,进行as加密    if (!ObjectUtils.isEmpty(secretKey)) {      // var aesEncrypter = AesCrypt(secretKey, 'ecb', 'pkcs7');      cryptedStr = EncryptUtil.aesEncode(key: secretKey, content: pointStr);      var dcrypt = EncryptUtil.aesDecode(key: secretKey, content: cryptedStr);      // Map _map = json.decode(dcrypt);    }    // print("dcrypt ---- ${_map}");    DioManager.getInstance().post(Constant.baseUrl + '/captcha/check', {      "pointJson": cryptedStr,      "captchaType": "blockPuzzle",      "token": captchaToken    }, (res) {      if (res['repCode'] != '0000' || res['repData'] == null) {        checkFail();        return;      }      Map<String, dynamic> repData = res['repData'];      if (repData["result"] != null && repData["result"] == true) {        // 如果不加密 将 token 和 坐标序列化 通过 --- 链接成字符串        var captchaVerification = '$captchaToken---$pointStr';        if (!ObjectUtils.isEmpty(secretKey)) {          // 如果加密 将 token 和 坐标序列化通过 --- 链接成字符串进行加密 加密秘钥为 _clickWordCaptchaModel.secretKey          captchaVerification = EncryptUtil.aesEncode(              key: secretKey, content: captchaVerification);        }        checkSuccess(captchaVerification);      } else {        checkFail();      }    }, (error) {      loadCaptcha();      print(error);    });  }    void initState() {    super.initState();    initAnimation();    loadCaptcha();  }    void dispose() {    controller.dispose();    super.dispose();  }  // 初始化动画  void initAnimation() {    controller =        AnimationController(duration: Duration(milliseconds: 500), vsync: this);    offsetAnimation = Tween<double>(begin: 0.5, end: 0)        .animate(CurvedAnimation(parent: controller, curve: Curves.ease))          ..addListener(() {            this.setState(() {});          });  }  // 反向执行动画  _reverseAnimation() async {    await controller.reverse();  }  // 正向执行动画  _forwardAnimation() async {    await controller.forward();  }    void didUpdateWidget(CaptchaPage oldWidget) {    // TODO: implement didUpdateWidget    super.didUpdateWidget(oldWidget);  }    Widget build(BuildContext context) {    dialogWidth = 0.9 * MediaQuery.of(context).size.width;   _ratio = MediaQuery.of(context).devicePixelRatio;    return Scaffold(      backgroundColor: Colors.transparent,      body: MediaQuery(        data: MediaQueryData(devicePixelRatio: _ratio),        child: Center(          child: UnconstrainedBox(            child: Container(              width: dialogWidth,              color: Colors.white,              child: Stack(                children: <Widget>[                  Column(                    mainAxisAlignment: MainAxisAlignment.start,                    crossAxisAlignment: CrossAxisAlignment.center,                    children: <Widget>[                      //顶部,提示+关闭                      Container(                        height: 50,                        padding: EdgeInsets.fromLTRB(10, 0, 10, 0),                        decoration: BoxDecoration(                          border: Border(  bottom:  BorderSide(width: 1, color: Color(0xffe5e5e5))),                        ),                        child: Row(                          mainAxisAlignment: MainAxisAlignment.spaceBetween,                          children: <Widget>[Expanded(    child: Text(      S.current.qingwanchenganquanyanzheng,      maxLines: 1,      overflow: TextOverflow.ellipsis,      style: TextStyle(fontSize: 18),      textScaleFactor: 1.0,    )),IconButton(    padding: EdgeInsets.all(3),    icon: Icon(Icons.refresh),    iconSize: 30,    color: Colors.black54,    onPressed: () {      //刷新      loadCaptcha();    }),IconButton(    padding: EdgeInsets.all(3),    icon: Icon(Icons.highlight_off),    iconSize: 30,    color: Colors.black54,    onPressed: () {      //退出      Navigator.pop(context);    }),                          ],                        ),                      ),                      //显示验证码                      Container(                        margin: EdgeInsets.all(10),                        child: Stack(                          children: <Widget>[//底图 310*155baseImageBase64.length > 0    ? Image.memory(Base64Decoder().convert(baseImageBase64),                          fit: BoxFit.fitWidth,                          key: _baseImageKey,                          height: 310.w,                          gaplessPlayback: true,)    : Container(  width: dialogWidth - 20,  height: 310.w,  alignment: Alignment.center,  child: CircularProgressIndicator(),),//滑块图slideImageBase64.length > 0    ? Container(  margin: EdgeInsets.fromLTRB(sliderXMoved, 0, 0, 0),  child: Image.memory(    Base64Decoder().convert(slideImageBase64),   height: 310.w,    fit: BoxFit.fitHeight,    gaplessPlayback: true,  ),)    : Container(),Positioned(    bottom: 0,    left: -10,    right: -10,    child: Offstage(      offstage: !_showTimeLine,      child: FractionalTranslation(        translation: Offset(0, offsetAnimation.value),        child: Container(          margin: EdgeInsets.only(left: 10, right: 10),          padding: EdgeInsets.only(left: 10),          height: 40,          color: _checkSuccess              ? Color(0x7F66BB6A)              : Color.fromRGBO(200, 100, 100, 0.4),          alignment: Alignment.centerLeft,          child: Text(            _checkSuccess                ? "${(_checkMilliseconds / (60.0 * 12)).toStringAsFixed(2)}${S.current.yanzhengchenggong}"                : S.current.yanzhengshibai,            style: TextStyle(color: Colors.white),          ),        ),      ),    )),Positioned(    bottom: -20,    left: 0,    right: 0,    child: Offstage(      offstage: !_showTimeLine,      child: Container(        margin: EdgeInsets.only(left: 10, right: 10),        height: 20,        color: Colors.white,      ),    ))                          ],                        ),                      ),                      //底部,滑动区域                      baseSize.width > 0                          ? Container(                          margin: EdgeInsets.all(10),                          height: slideSize.width * (dialogWidth - 20) / baseSize.width,                          width: baseSize.width * 2.w,                          child: Stack(alignment: AlignmentDirectional.centerStart,children: <Widget>[  Container(    height: slideSize.width * (dialogWidth - 20) / baseSize.width,    decoration: BoxDecoration(      border: Border.all(        width: 1,        color: Color(0xffe5e5e5),      ),      color: Color(0xffe1e1e1),    ),  ),  Container(    alignment: Alignment.center,    child: Text(      S.current.xiangyouhuadong,      style: TextStyle(fontSize: 16),      textScaleFactor: 1.0,    ),  ),  Container(    width: sliderXMoved,    height: 58,    decoration: BoxDecoration(      border: Border.all(        width: sliderXMoved > 0 ? 1 : 0,        color: movedXBorderColor,      ),      color: Color(0xff89F2D0),    ),  ),  GestureDetector(    onPanStart: (startDetails) {      if (!enable) return;      _checkMilliseconds =          new DateTime.now().millisecondsSinceEpoch;      print("startDetails");      print(startDetails.globalPosition);      sliderStartX = startDetails.globalPosition.dx;      print(          "startDetails --- sliderStartX ${sliderStartX} ");    },    onPanUpdate: (updateDetails) {      if (!enable) return;      print("updateDetails");      print(updateDetails.globalPosition);      double offset =          updateDetails.globalPosition.dx  - sliderStartX;      double _w1 = baseSize.width * 2.w - 100.w;      if (offset < 0) {        offset = 0;      } else if ((offset > _w1)) {        offset = _w1;      }      setState(() {        sliderXMoved = offset;      });      //滑动过程,改变滑块左边框颜色      updateSliderColorIcon();    },    onPanEnd: (endDetails) {      if (!enable) return;      checkCaptcha(sliderXMoved, captchaToken);      int _nowTime =          new DateTime.now().millisecondsSinceEpoch;      _checkMilliseconds =          _nowTime - _checkMilliseconds;      //滑动结束    },    child: Container(      width: slideSize.width * (dialogWidth - 20) / baseSize.width,      height: slideSize.width * (dialogWidth - 20) / baseSize.width,      margin: EdgeInsets.fromLTRB(          sliderXMoved > 0 ? sliderXMoved : 1,          0,          0,          0),      decoration: BoxDecoration(        border: Border(          top: BorderSide(            width: 1,            color: Color(0xffe5e5e5),          ),          right: BorderSide(            width: 1,            color: Color(0xffe5e5e5),          ),          bottom: BorderSide(            width: 1,            color: Color(0xffe5e5e5),          ),        ),        color: sliderColor,      ),      child: IconButton(        icon: Icon(sliderIcon),        iconSize: 20,        color: Colors.black54,      ),    ),  )],                          ))                          : Container(),                    ],                  ),                ],              ),            ),          )        ),      )    );  }}

使用:

_sendPhoneCode(setBottomSheetState) {    showDialog<Null>(      context: context,      barrierDismissible: true,      builder: (BuildContext context) {        return CaptchaPage(          onSuccess: (value) async {            Response response = await _dio.post(Loginapi.SEND_MESSAGE_URL,                data: {                  'ic': '+$areaCode',                  'phone': phoneController.text,                  'captchaVerification': value                });            String dataStr = json.encode(response.data);            Map<String, dynamic> dataMap = json.decode(dataStr);            if (dataMap != null && dataMap['code'] == 200) {              if (mounted) {                setBottomSheetState(() {                  isButtonEnable = false; //按钮状态标记                });              }              timer = new Timer.periodic(Duration(seconds: 1), (Timer timer) {                if (mounted) {                  setBottomSheetState(() {                    count--;                    if (count == 0) {                      timer.cancel(); //倒计时结束取消定时器                      isButtonEnable = true; //按钮可点击                      count = 60; //重置时间                      buttonText = S.current.fasongyanzhengma; //重置按钮文本                    } else {                      buttonText = '${count}S'; //更新文本内容                    }                  });                }              });              esLoadingToast(S.current.fasongchenggong);            } else {              esLoadingError(S.current.fasongshibai);            }          },          onFail: () {            // esLoadingError('人机校验失败');          },        );      },    );  }

滑块拼图验证码

来源地址:https://blog.csdn.net/androidhyf/article/details/131534594

--结束END--

本文标题: 【flutter滑动拼图验证码】

本文链接: https://lsjlt.com/news/375673.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 【flutter滑动拼图验证码】
    Java后台使用aj_captcha插件,提供/captcha/get(获取captcha底图和拼块图片)、/captcha/check(验证拼图偏移量)这两个接口。并且这个插件在GitHub上有源码。 1.先准备好aj_captcha的...
    99+
    2023-08-19
    flutter
  • C#滑动验证码拼图验证功能实现(SlideCaptcha)
    目录使用背景:实现分析:后端代码:准备:使用:前端代码:结语:使用背景: 关于滑动验证码的使用场所还是非常多的,如:调取短信接口之前,和 注册请求之前 或者 频繁会调用的接口都需要加...
    99+
    2024-04-02
  • Java实现滑块拼图验证码
    本文实例为大家分享了Java实现滑块拼图验证码的具体代码,供大家参考,具体内容如下 1、后端随机生成抠图和带有抠图阴影的背景图片,后台保存随机抠图位置坐标 2、前端实现滑动交互,将抠...
    99+
    2024-04-02
  • vue实现登录滑动拼图验证
    本文实例为大家分享了vue实现登录滑动拼图验证的具体代码,供大家参考,具体内容如下 一、安装插件 npm install --save vue-monoplasty-slide-ve...
    99+
    2024-04-02
  • Flutter实现滑动块验证码功能
    Flutter实现滑动块验证码功能,供大家参考,具体内容如下 本文实现的是一个用于登录时,向右滑动滑动块到最右边完成验证的一个功能。当滑动未到最右边时,滑动块回弹回左边起始位置。 ...
    99+
    2024-04-02
  • Python实现滑块拼图验证码详解
    目录初级版滑块拼图验证码补充知识点高级版滑动拼图验证码滑动拼图验证码可以算是滑块验证码的进阶版本,其验证机制相对复杂。本节将介绍两种滑动拼图验证码:初级版和高级版本。 初级版滑块拼...
    99+
    2024-04-02
  • Android滑动拼图验证码控件使用方法详解
    简介: 很多软件为了安全防止恶意攻击,会在登录/注册时进行人机验证,常见的人机验证方式有:谷歌点击复选框进行验证,输入验证码验证,短信验证码,语音验证,文字按顺序选择在图片上点击,滑...
    99+
    2024-04-02
  • 微信小程序实现滑动验证拼图
    本文实例为大家分享了微信小程序实现滑动验证拼图的具体代码,供大家参考,具体内容如下 效果图 .wxml <button bindtap="visidlisd">滑动验证...
    99+
    2024-04-02
  • Flutter怎么实现滑动块验证码功能
    这篇文章主要介绍“Flutter怎么实现滑动块验证码功能”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Flutter怎么实现滑动块验证码功能”文章能帮助大家解决问题。本文实现的是一个用于登录时,向右...
    99+
    2023-06-29
  • 使用 Node.js 模拟滑动拼图验证码操作的示例代码
    近几年,网页上各种新型验证码层出不穷,其中一种比较常见的是滑动验证码,比如下图这种。 本文介绍了一种使用纯前端方法寻找滑动终点并模拟滑动的方法。 我们需要三个依赖库: puppeteer 、 Resemb...
    99+
    2022-06-04
    验证码 示例 拼图
  • django滑动验证码
    最近用django写了一个后台系统,使用的是验证码方式。但是开发人员抱怨,输入验证太麻烦,还有可能出错,太影响效率了。是否可以用滑动验证码,一拖动就可以了! 网上大部分文章,用的是极验GeeTest,需要你自己注册账号,才能使用。...
    99+
    2023-01-31
    验证码 django
  • Android 如何实现滑块拼图验证码功能
    本篇内容主要讲解“Android 如何实现滑块拼图验证码功能”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Android 如何实现滑块拼图验证码功能”吧!本篇主要从两方面进行介绍:使用依赖库实现...
    99+
    2023-06-14
  • JavaScript实现拖动滑块拼图验证功能(html5、canvas)
    引言: 滑块拖动验证现在很多地方都用到,周末就琢磨着写了一个,放上来,看看有没有人用得上! 效果: 实现思路:  用一张画布绘制源图,再绘制一个填充的方形,这样就可以达到...
    99+
    2024-04-02
  • Android 简单的实现滑块拼图验证码功能
    目录实现过程:接下来我们对这个库进行介绍:实现滑块拼图验证码功能之前已经写过一篇了,上一篇使用的是自定义控件的方式实现这个功能,主要还是想让童鞋们知其然更知其所以然,还没看的童鞋可以...
    99+
    2024-04-02
  • vue+elementui实现拖住滑块拼图验证
    vue拖住滑块拼图验证,以下是cavas直接写的滑块拼图验证码,直接复制引用即可 ​<template>   <div id="puzzle" ref="puzz...
    99+
    2024-04-02
  • JavaScript实现拼图式滑块验证功能
    目录演示前戏源码介绍主页样式设计滑块验证部分img_ver内部演示 前戏 滑块验证码是在网站、APP等应用中常见的一种验证方式,通过按照一定规则滑动滑块到指定位置完成验证,才可以进...
    99+
    2024-04-02
  • vue实现图片滑动验证
    本文实例为大家分享了vue实现图片滑动验证的具体代码,供大家参考,具体内容如下 效果图: 1、引用自定义组件 import img0 from '../assets/img.jpg...
    99+
    2024-04-02
  • JavaScript如何实现拼图式滑块验证功能
    这篇文章主要介绍“JavaScript如何实现拼图式滑块验证功能”,在日常操作中,相信很多人在JavaScript如何实现拼图式滑块验证功能问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”JavaScript如...
    99+
    2023-07-02
  • 如何在Android中实现一个滑块拼图验证码功能
    本篇文章给大家分享的是有关如何在Android中实现一个滑块拼图验证码功能,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。一、实现步骤:定义自定义属性; 2、确认目标位置,这里使...
    99+
    2023-06-06
  • python 密码验证(滑块验证)
    目录题目描述:解题思路/算法分析/问题及解决实验代码题目描述: (1)模拟登陆界面,判别用户名和密码,给出合适的提示,如果超过三次,锁定输入。用代替密码;或者最新输入显示,前面的变成...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作