.Net 6实现旋转验证码

news2025/1/16 2:34:32

前几篇文章,介绍了.Net 6实现的滑动验证码功能,最近把滑动验证码的ImageSharp替换成了SkiaSharp,其中抠图部分参考了pojianbing大神的代码。滑动验证码完成之后,心想着。做一个旋转验证码。其实旋转验证码跟滑动验证码及其类似。

先看下效果图:
在这里插入图片描述
旋转的实现原理与滑动验证码一样:

  • 1、获取背景图
  • 2、获取模板的凹槽图与滑块图
  • 3、进行凹槽抠图
  • 4、旋转画布
  • 5、贴图到滑块图上
  • 6、背景图上叠加凹槽图模板

目录

  • 验证码实体类ImageCaptchaInfo
  • 定义旋转验证码实体RotateImageCaptchaInfo
  • 定义接口IRotateCaptchaImageGenerator
  • 生成验证码
  • 获取凹槽抠图
  • 前端代码
  • 总结

验证码实体类ImageCaptchaInfo

public class ImageCaptchaInfo
{
    /// <summary>
    /// 背景图宽
    /// </summary>
    public int BackgroundImageWidth { get; set; }
    /// <summary>
    /// 背景图高
    /// </summary>
    public int BackgroundImageHeight { get; set; }
    /// <summary>
    /// 背景图
    /// </summary>
    public string BackgroundImageBase64 { get; set; }
    /// <summary>
    /// 滑动块图宽
    /// </summary>
    public int SliderImageWidth { get; set; }
    /// <summary>
    /// 滑动块图高
    /// </summary>
    public int SliderImageHeight { get; set; }
    /// <summary>
    /// 滑动块图
    /// </summary>
    public string SliderImageBase64 { get; set; }

    /// <summary>
    /// 随机值
    /// </summary>
    public int RandomX { get; set; }

    /// <summary>
    /// 容错值,可以为空!
    /// </summary>
    public float Tolerant { get; set; }
    /// <summary>
    /// 验证码类型
    /// </summary>
    public string CaptchaType { get; set; }

    public float Percentage
    {
        get
        {
            if (BackgroundImageWidth <= 0) return 0;
            return 1.0F * RandomX / BackgroundImageWidth;
        }
    }
}

其中随机值RandomX 是要参与到滑动比例Percentage的运算中的。Percentage的作用后面再说。

定义旋转验证码实体RotateImageCaptchaInfo

public class RotateImageCaptchaInfo : ImageCaptchaInfo
{
    /// <summary>
    /// 旋转多少度
    /// </summary>
    public float Degree { get; set; }


    public void Check()
    {
        //校验
        if (this.Degree <= 0) throw new TaCaptchaException($"RotateImageCaptchaInfo数据异常: {nameof(Degree)}小于等于0");
        if (this.RandomX <= 0) throw new TaCaptchaException($"RotateImageCaptchaInfo数据异常: {nameof(RandomX)}小于等于0");
        if (this.BackgroundImageWidth <= 0) throw new TaCaptchaException($"RotateImageCaptchaInfo数据异常: {nameof(BackgroundImageWidth)}小于等于0");
        if (this.BackgroundImageHeight <= 0) throw new TaCaptchaException($"RotateImageCaptchaInfo数据异常: {nameof(BackgroundImageHeight)}小于等于0");
        if (this.SliderImageWidth <= 0) throw new TaCaptchaException($"RotateImageCaptchaInfo数据异常: {nameof(SliderImageWidth)}小于等于0");
        if (this.SliderImageHeight <= 0) throw new TaCaptchaException($"RotateImageCaptchaInfo数据异常: {nameof(SliderImageHeight)}小于等于0");
        if (string.IsNullOrWhiteSpace(this.BackgroundImageBase64)) throw new TaCaptchaException($"RotateImageCaptchaInfo数据异常: {nameof(BackgroundImageBase64)}为空");
        if (string.IsNullOrWhiteSpace(this.SliderImageBase64)) throw new TaCaptchaException($"RotateImageCaptchaInfo数据异常: {nameof(SliderImageBase64)}为空");
    }
}

Check校验方法中,如果旋转的度数Degree、随机值RandomX等不满足条件,返回自动以错误。

定义接口IRotateCaptchaImageGenerator

定义旋转验证码接口,这个比较简单,就一个生成验证码的方法。

public interface IRotateCaptchaImageGenerator
{
	Task<RotateImageCaptchaInfo> Generate(string captchaId);
}

生成验证码

public class RotateCaptchaImageGenerator : IRotateCaptchaImageGenerator
{
    private readonly IResourceManager _resourceManager;
    private readonly Random _random = new Random();
      
    public RotateCaptchaImageGenerator(IResourceManager resourceManager)
    {
        _resourceManager = resourceManager;
            
    }

    public async Task<RotateImageCaptchaInfo> Generate(string captchaId)
    {

        //获取背景图
        var background = await _resourceManager.RandomBackground();
        //获取滑块图、凹槽图
        (var slider, var notch) = await _resourceManager.RandomTemplate(CaptchaTypeConstant.ROTATE);

        using var backgroundImage = SKBitmap.Decode(background);
        //滑块
        using var sliderTemplateImage = SKBitmap.Decode(slider);
        //凹槽
        using var notchTemplateImage = SKBitmap.Decode(notch);

        //定义空的凹槽图与滑块图
        using var notchMattingImage = new SKBitmap(notchTemplateImage.Width, notchTemplateImage.Height);
        using var sliderBarImage = new SKBitmap(360,360);


        //根据透明度计算凹槽图轮廓形状(形状由不透明区域形成)
        var notchShape = CaptchaImageUtils.GetImageShape(notchTemplateImage);

        //滑块图画布
        using var sliderCanvas = new SKCanvas(notchMattingImage);
        //叠加轮廓图
        sliderCanvas.ClipPath(notchShape, SKClipOperation.Intersect, true);
        //计算居中的位置
        int x = backgroundImage.Width / 2 - notchTemplateImage.Width / 2;
        int y = backgroundImage.Height / 2 - notchTemplateImage.Height / 2;
        //随机旋转抠图部分

        int randomX = _random.Next(notchTemplateImage.Width + 10, backgroundImage.Width - 10);
        float degree = 360f - randomX / (backgroundImage.Width / 360f);
        sliderCanvas.RotateDegrees(degree, notchMattingImage.Width/2, notchMattingImage.Height/2);

        //绘制抠图
        sliderCanvas.DrawBitmap(backgroundImage,-x,-y);
        //叠加滑块模板
        sliderCanvas.DrawBitmap(sliderTemplateImage, 0, 0);
            

        int bw = 360;
        int bh = 360;
        int cw = notchMattingImage.Width;
        int ch = notchMattingImage.Height;
        int iw = (int)(bw / 2 - cw / 2);
        int ih = (int)(bh / 2 - ch / 2);

        using var sliderBarCanvas = new SKCanvas(sliderBarImage);
        sliderBarCanvas.DrawBitmap(notchMattingImage, iw, ih);
           


           

        using var backgroundCanvas = new SKCanvas(backgroundImage);
        //叠加凹槽
        backgroundCanvas.DrawBitmap(notchTemplateImage, x, y);

        return new RotateImageCaptchaInfo
        {
            BackgroundImageWidth = backgroundImage.Width,
            BackgroundImageHeight = backgroundImage.Height,
            BackgroundImageBase64 = backgroundImage.ToBase64String(SKEncodedImageFormat.Png),
            RandomX = randomX,
            Degree = degree,
            Tolerant = 0.03f,
            SliderImageWidth = notchMattingImage.Width,
            SliderImageHeight = notchMattingImage.Height,
            SliderImageBase64 = sliderBarImage.ToBase64String(SKEncodedImageFormat.Png),
            CaptchaType = CaptchaTypeConstant.ROTATE
        };

    }
}

代码中,首先获取背景图、凹槽图与滑块图,并将他们转为SKBitmap

定义一个空的凹槽图与滑块图。根据凹槽图,获取凹槽图的路径。

获取凹槽抠图

/// <summary>
/// 获取图形轮廓,形状由不透明的区域形成
/// </summary>
/// <param name="image">要获取轮廓的图片</param>
/// <returns></returns>
public static SKPath GetImageShape(SKBitmap image)
{
    int temp = 0;
    var path = new SKPath();
    for(int y=0;y< image.Height;y++)
    {
        for(int x=0;x< image.Width;x++)
        {
            var pixel = image.GetPixel(x, y);
            if(pixel.Alpha!=0)
            {
                if(temp==0)
                {
                    temp = x;
                }
            }
            else
            {
                if(temp!=0)
                {
                    path.AddRect(new SKRect(temp, y, x, y + 1));
                    temp= 0;
                }
            }
        }
    }
    return path;
}

方法中,定义一个路径path,接着逐列获取每一块的像素信息,使用到了SKBitmap GetPixel方法,得到一个SKColorSKColor有5个属性

  • Alpha
  • Blue
  • Green
  • Hue
  • Red
    其中,我们只需要判断判断Alpha不为0就可以了,也就是他不是透明的。

获得凹槽抠图后,需要定义一个SKCanvas,我叫他为画布。名字是sliderCanvas,需要在sliderCanvas上绘制背景抠图。
使用ClipPath方法先在sliderCanvas上叠加轮廓图notchShape,也就是我们刚刚生成的凹槽抠图。ClipPath方法有多个重载,这里用到的参数分别是:SKPathSKClipOperationantialias
第一个参数是刚刚获取到的轮廓图notchShape,第二个参数选择SKClipOperation.Intersect,取两个区域交集,第三个参数设置为true,表示抗锯齿。
叠加好轮廓图之后,先要计算下要在背景图上扣取的位置。首先获取背景图的中心位置,减去凹槽图的中心位置。得到的x,y值就是要绘制的起点坐标。
接着根据随机值,随机旋转画布,使用RotateDegrees方法。RotateDegrees方法第一个参数是旋转的度数,第二个和第三个参数确定旋转的中心点。
写来就是把背景抠图部分绘制到画布上,字把滑块图模板绘制画布上。
再定义一个滑块的画布sliderBarCanvas。把制作好的抠图绘制到滑块上。
最后,把凹槽图绘制到背景图上。
都绘制好了之后,把信息返回给前端,就可以了。代码中返回base64字符串是我自己写了一个SKBitmap的扩展,非常简单

public static string ToBase64String(this SKBitmap source, SKEncodedImageFormat format)
{
    using var img = SKImage.FromBitmap(source);
    using SKData data = img.Encode(format, 100);
    var array = data.ToArray();
    return "data:" + format.ToString().ToLower() + ";base64," + Convert.ToBase64String(array, 0, array.Length);
}

至此。旋转验证码的代码就全部完成了。

前端代码

与之前的滑动验证码一样,需要修改的地方非常少,reset方法中,设置滑块位置为中心点位置,设置旋转为0

$(".captcha_slider").css('left', (captchaMainWidth - captchaMainHeight)/2);
$(".captcha_slider").css("transform", "rotate(0deg)")

handleDragMoving方法中,设置滑块为选中,而不是从左向右移动

$('#sliderSrc').css('transform',"rotate("+(_x/(206/360))+"deg)");

其他的没有变化。前端代码在这篇文章中有详细介绍
.NET 6 实现滑动验证码(十)、大结局:前端代码实战,vue3与HTML+JQuery

总结

还有其他种类的验证码,比如点选验证码、图文验证码等。会慢慢完善、实现。

点击下方公众号卡片,关注我!一起学习,一起进步!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/133566.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

跨系统实时同步数据解决方案

数据量太大&#xff0c;单存储节点存不下&#xff0c;就只能把数据分片存储。 数据分片后&#xff0c;对数据的查询就没那么自由。如订单表按用户ID作为Sharding Key&#xff0c;就只能按用户维度查询。我是商家&#xff0c;我想查我店铺的订单&#xff0c;做不到。&#xff0…

ubuntu18.04下mysql数据库C语言API操作总结

通过C/C去操作数据库需要调用mysql客户端api&#xff0c;常用api和调用举例见后面。 目录 一.常用api 1.环境初始化 2.连接 mysql 的服务器 3.增删改查操作 4.事务处理 5.处理查询数据集合 6.释放资源&#xff0c;关闭连接 7.字符集相关 8.获取错误信息 二.api调用举…

Linux杂谈之sudo

一 sudo配置文件/etc/sudoers介绍 ① 什么是sudo 1) sudo 的英文全称是 super user do&#xff0c;即以超级用户root 用户的方式执行命令2) /etc/sudoers 是一个文本文件,只有root用户有该命令的执行权限 1) 允许普通用户以特权用户的权限去执行某些特权命令,访问和使用本…

优秀国土空间规划设计网络评选投票投票怎么进行小程序免费使用

如果通过一个小程序免费制作一个微信投票活动呢&#xff1f;文章详细讲解如何利用一款免费好用的微信小程序“活动星投票”小程序来制作投票活动&#xff0c;无需注册即可免费制作&#xff0c;非常的方便快捷&#xff0c;可以实现视频投票、分组投票、隐藏选手票数、导出投票数…

【Linux】进程概念(下)

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《学会Linux》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;进程状态&…

手把手教你用Python和OpenCV搭建一个半自动标注工具(详细步骤 + 源码)

导 读 本文将手把手教你用Python和OpenCV搭建一个半自动标注工具&#xff08;包含详细步骤 源码&#xff09;。 背景介绍 样本标注是深度学习项目中最关键的部分&#xff0c;甚至在模型学习效果上起决定性作用。但是&#xff0c;标注工作往往非常繁琐且耗时。一种解决方案是…

【苹果家庭群发推】创作AppleScript脚本来控制MacOS附有的iMessage客户端停止考据,近似于组iMessage

推荐内容IMESSGAE相关 作者推荐内容iMessage苹果推软件 *** 点击即可查看作者要求内容信息作者推荐内容1.家庭推内容 *** 点击即可查看作者要求内容信息作者推荐内容2.相册推 *** 点击即可查看作者要求内容信息作者推荐内容3.日历推 *** 点击即可查看作者要求内容信息作者推荐…

ENVI_IDL:如何对文件名的日期进行格式化输出?

目录 00 前言 01 第一步&#xff0c;使用file_basename()函数路径中的获取文件名 02 第二步&#xff0c;使用strmid()函数获取文件名的日期 03 第三步&#xff0c;将获取的日期进行类型转换 04 第四步&#xff0c;将日期进行整理 05 第五步&#xff0c;进行格式化输出 …

BiSeNetv2:语义分割经典方法BiSeNet的升级版本

分享IJCV2021上发表的一篇文章BiSeNetv2&#xff0c;这是BiSeNet的升级版本。开源代码地址&#xff1a;https://github.com/open-mmlab/mmsegmentation/tree/master/configs/bisenetv2 1.动机 语义分割是指为每个像素分配一个标签&#xff0c;它广泛用于场景理解、自动驾驶、人…

uniapp中app真机模拟以及小程序编译后css样式异常失效问题原因及解决方案

前言 最近写使用uniapp写app&#xff0c;开发的时候写样式都是使用浏览器h5进行调试一切正常&#xff0c;但是最后进行手机真机调试的时候css样式出现了异常&#xff0c;本文归纳常见的问题 比如在h5页面显示正常&#xff1a; 但是在真机调试app的实现则显示&#xff1a; H5正…

【PyTorch深度学习实践】02_梯度下降

文章目录梯度下降1.梯度下降算法实现代码2.随机梯度下降实现代码3.小批量随机梯度下降梯度下降 1.梯度下降算法 之前可以使用穷举的方法逐个测试找使损失函数最小的点&#xff0c;但当数据过多时&#xff0c;维度过高&#xff0c;会使穷举变得非常困难&#xff0c;因此需要优…

K8s 数据管理

目录前言一、Volume1.1 emptyDir1.1.1 基本概念1.1.2 应用案例1.2 hostPath1.2.1 基本概念1.2.2 应用案例1.3 外部 Storage Provider二、Persistent Volume2.1 基本概念2.1.1 PersistentVolume2.1.2 PersistentVolumeClaim2.2 NFS PersistentVolume前言 与 Docker 类似&#x…

QML教程(一)基础语法

目录 一、导入 二、对象声明 三、对象属性 1.声明对象属性 2.信号属性 3.方法属性 4.附加属性略 5.枚举属性 6.对象属性赋值 四、自定义对象 一、导入 模块导入 语法&#xff1a; import <ModuleIdentifier> [<Version.Number>] [as <Qualifier>…

面向对象设计原则概述

面向对象设计原则概述 软件的可维护性和可复用性 软件工程和建模大师Peter coad认为&#xff0c;一个好的系统设计与应该具备如下三个性质 可扩展性 灵活性 可插入性 软件的可维护性和可复用性 软件的复用和重用拥有众多优点&#xff0c;如可以提高软件的开发效率&#xf…

Educational Codeforces Round 92 (Rated for Div. 2) B. Array Walk

翻译&#xff1a; 给定一个数组&#x1d44e;1&#xff0c;&#x1d44e;2&#xff0c;…&#xff0c;&#x1d44e;&#x1d45b;&#xff0c;由&#x1d45b;个正整数组成。 最初&#xff0c;您位于索引1&#xff0c;分数等于&#x1d44e;1。你可以执行两种动作: 向右移动…

CDN

CDN——Content Delivery Network&#xff0c;内容分发网络。 具体来说&#xff0c;CDN就是采用更多的缓存服务器&#xff08;CDN边缘节点&#xff09;&#xff0c;布放在用户访问相对集中的地区或网络中。当用户访问网站时&#xff0c;利用全局负载技术&#xff0c;将用户的访…

【CSP】邻域均值

邻域均值 邻域均值 题意比较好理解&#xff0c;就是算一些数字。如果采用暴力方法的话&#xff0c;就是用一个边长为 2∗r12*r12∗r1 的正方形框框住大矩阵&#xff0c;然后遍历这个框&#xff0c;求出其平均值&#xff0c;然后移动正方形框&#xff0c;直到大矩阵内所有像…

【免费开放源码】审批类小程序项目实战(预约审批端)

第一节&#xff1a;什么构成了微信小程序、创建一个自己的小程序 第二节&#xff1a;微信开发者工具使用教程 第三节&#xff1a;深入了解并掌握小程序核心组件 第四节&#xff1a;初始化云函数和数据库 第五节&#xff1a;云数据库的增删改查 第六节&#xff1a;项目大纲以及制…

6.5 特殊用途语言特性

文章目录默认实参使用默认实参调用函数默认实参声明默认实参初始值内联函数和constexpr函数内联函数constexpr 函数把内联函数和constexpr函数声明在头文件内调试帮助assert预处理宏NDEBUG预处理变量默认实参 某些函数有这样一种形参,在函数的很多次调用中它们都被赋予一个相同…

电子游戏销售之缺失值检测与处理

电子游戏销售之缺失值检测与处理 文章目录电子游戏销售之缺失值检测与处理0、写在前面1、数据缺失值预处理1.1 表的形状1.2 原始数据每个特征缺失和非缺失的数目1.3 每个特征缺失的率1.4 处理后各特征缺失值的数目1.5 删除缺失值后的数据展示2、替换法处理缺失值2.1 替换法2.2 …