开发环境:C#,VS2019,.NET Core 3.1,ASP.NET Core
1、建立一个验证码控制器
新建两个方法Create和Check,Create用于创建验证码(返回1张图片和令牌),Check用于验证(验证图片旋转角度)它是否有效。
声明一个静态类变量存放列表,列表中存放包含令牌和验证码的对象。
/// <summary>
/// 返回一张图片和令牌.
/// </summary>
/// <returns></returns>
public string Create()
{
try
{
// 记录验证码到缓存中
VCodeCircleModel model = new VCodeCircleModel();
model.id = Guid.NewGuid().ToString(); // 生成令牌
var vcode = VCodeCircleModel.GetVCode(); // 生成验证码
model.code = vcode;
_list.Add(model);
// 返回图片
var images = VCodeCircleModel.GetImage(Convert.ToInt32(vcode));
VCodeCircleController_Create_Receive result = new VCodeCircleController_Create_Receive();
result.code = "0";
result.data.id = model.id;
result.data.img = VCodeCircleModel.BitmapToBase64Str(images);
var json = JsonConvert.SerializeObject(result);
return json;
}
catch (Exception ex)
{
_logger.LogWarning(exception: ex, message: ex.Message);
VCodeCircleController_Create_Receive result = new VCodeCircleController_Create_Receive();
result.code = "999999";
result.msg = "系统异常";
var json = JsonConvert.SerializeObject(result);
return json;
}
}
public string Check(string id, string code)
{
try
{
// 旋转图片的误差在±5
var temp = Convert.ToInt32(code) - 5;
var index = _list.FindIndex(m =>
{
if (m.id.Equals(id))
{
for (int i = 0; i < 10; i++)
{
if (m.code.Equals(temp.ToString()))
{
return true;
}
temp++;
}
}
return false;
});
ReceiveObject result = new ReceiveObject();
if (index >= 0)
{
_list.RemoveAt(index);
result.code = "0";
result.msg = "验证成功";
var json = JsonConvert.SerializeObject(result);
return json;
}
else
{
result.code = "1";
result.msg = "验证失败";
var json = JsonConvert.SerializeObject(result);
return json;
}
}
catch (Exception ex)
{
_logger.LogError(exception: ex, message: ex.Message);
ReceiveObject result = new ReceiveObject();
result.code = "999999";
result.msg = "系统异常";
var json = JsonConvert.SerializeObject(result);
return json;
}
}
public class VCodeCircleModel
{
/// <summary>
/// 令牌.
/// </summary>
public string id { get; set; }
/// <summary>
///验证码.
/// </summary>
public string code { get; set; }
/// <summary>
/// 获取随机验证码.
/// </summary>
/// <returns></returns>
public static string GetVCode()
{
// 这里的随机码是旋转图片的角度,至少旋转60度,最多旋转300度
Random random = new Random();
return random.Next(60, 300).ToString();
}
/// <summary>
/// 随机获取一张图片.
/// </summary>
/// <returns></returns>
public static Bitmap GetImage(int angle)
{
// 从文件加载原图
Random random = new Random();
var image_index = random.Next(0, 2);
Image originImage;
switch (image_index)
{
case 0:
originImage = Image.FromFile(string.Format(@"{0}\Images\{1}", PathHelper.Path, "circleImg1.png"));
break;
case 1:
default:
originImage = Image.FromFile(string.Format(@"{0}\Images\{1}", PathHelper.Path, "circleImg2.png"));
break;
}
originImage = Rotate(originImage as Bitmap, angle);
return (Bitmap)originImage;
}
/// <summary>
/// 图片旋转
/// </summary>
/// <param name="ImageOriginal">原图.</param>
/// <param name="AngleValue">旋转角度.</param>
/// <returns></returns>
public static Bitmap Rotate(Bitmap ImageOriginal, float AngleValue)
{
AngleValue = AngleValue % 360;
double radian = AngleValue * Math.PI / 180.0;
double cos = Math.Cos(radian);
double sin = Math.Sin(radian);
int w = ImageOriginal.Width;
int h = ImageOriginal.Height;
int W = (int)(Math.Max(Math.Abs(w * cos - h * sin), Math.Abs(w * cos + h * sin)));
int H = (int)(Math.Max(Math.Abs(w * sin - h * cos), Math.Abs(w * sin + h * cos)));
Bitmap ImageBaseOriginal = new Bitmap(W, H, PixelFormat.Format32bppArgb);
System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(ImageBaseOriginal);
g.InterpolationMode = InterpolationMode.NearestNeighbor;
g.SmoothingMode = SmoothingMode.HighQuality;
Point Offset = new Point((W - w) / 2, (H - h) / 2);
Rectangle rect = new Rectangle(Offset.X, Offset.Y, w, h);
Point center = new Point(rect.X + rect.Width / 2, rect.Y + rect.Height / 2);
g.Clear(Color.White);
g.TranslateTransform(center.X, center.Y);
g.RotateTransform(360 - AngleValue);
g.TranslateTransform(-center.X, -center.Y);
g.DrawImage(ImageOriginal, rect);
g.ResetTransform();
g.Save();
g.Dispose();
return ImageBaseOriginal;
}
/// <summary>
/// 将图片对象转成Base64的字符串.
/// </summary>
/// <param name="bitmap"></param>
/// <returns></returns>
public static string BitmapToBase64Str(Bitmap bitmap)
{
using (MemoryStream memoryStream = new MemoryStream())
{
bitmap.Save(memoryStream, ImageFormat.Jpeg);
byte[] bytes = memoryStream.ToArray();
return Convert.ToBase64String(memoryStream.ToArray());
}
}
}
2、新建一个视图文件,引入jquery,css文件,js方法中添加三个鼠标事件 - 鼠标按下,鼠标移动和鼠标松开。页面首次加载时调用控制器的Create方法获取图片和令牌,在鼠标松开时调用Check方法验证旋转的角度是否有效。
本来想实现鼠标在图片上拖动后让图片的效果,但发现难度有点大,就换成了拖动滑块后让图片旋转的形式。
<link href="~/css/circle_slide.css" rel="stylesheet" type="text/css" />
<!-- 展示验证码 -->
<div class="container">
<div class="main">
<div id="content" class="content">
<img id="backImage" src="" alt="">
</div>
</div>
<div id="slider">
<div id="sliderBlock"></div>
</div>
</div>
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/js/MyCircleSlide.js"></script>
* {
margin: 0;
padding: 0;
}
.main {
position: relative;
margin-left: 20px;
margin-top: 20px;
width: 200px;
background-color: white;
}
.content {
width: 100%;
}
.content img {
width: 100%;
height: auto;
}
#slider {
width: 85%;
height: 40px;
background-color: aliceblue;
position: relative;
}
#sliderBlock {
position: absolute;
left: 5px;
height: 30px;
width: 45px;
top: 5px;
background-color: white;
border-radius: 5px;
box-shadow: 0 0 10px 2px lightgray;
}
$(function () {
var _imageBase64; // 大图
var _id;
// 鼠标左键是否按下
var isMouseDown;
// 鼠标按下x值
var mouseDownStartX;
// 鼠标移动距离
var mouseMoveLength;
init();
document.onmousedown = function (event) {
var obj = getElementPosition(document.getElementById('sliderBlock'))
if (event.clientX > obj.left &&
event.clientX < (obj.left + obj.width) &&
event.clientY > obj.top &&
event.clientY < (obj.top + obj.height)) {
// 鼠标点击事件发生在滑动条的范围内
this.isMouseDown = true
this.mouseDownStartX = event.clientX
console.log("鼠标点击事件发生在滑动条的范围内");
}
}
document.onmousemove = function (event) {
if (this.isMouseDown) {
this.mouseMoveLength = event.clientX - this.mouseDownStartX; // 计算滑块拖动的距离
if (this.mouseMoveLength > 0 &&
this.mouseMoveLength < 360) {
// 滑块拖动的最小距离是5px,最大距离是大图的宽度 - 小图的宽度
document.getElementById('sliderBlock').style.left = 5 + this.mouseMoveLength + 'px'
$("#backImage").css("transform", "rotate(" + (this.mouseMoveLength) + "deg)");
}
}
}
document.onmouseup = function (event) {
// 鼠标松开后停止拖动
if (this.isMouseDown) {
this.isMouseDown = false
// console.log("小图移动了:" + this.mouseMoveLength);
// check(x.replace("px", ""));
check(this.mouseMoveLength);
}
}
// dom在浏览器的位置
function getElementPosition(element) {
let top = element.offsetTop
let left = element.offsetLeft
let width = element.offsetWidth
let height = element.offsetHeight
var currentParent = element.offsetParent;
while (currentParent !== null) {
top += currentParent.offsetTop
left += currentParent.offsetLeft
currentParent = currentParent.offsetParent
}
return { top, left, width, height }
}
function check(x) {
$.get("Check?code=" + x + "&id=" + _id, function (data) {
var obj = JSON.parse(data);
if (obj.code == "0") {
alert("验证成功");
}
else {
alert("验证失败");
}
});
}
// 设置当前图片
function setCurrentImageBase64(imageBase64) {
_imageBase64 = imageBase64
document.getElementById('backImage').src = _imageBase64
}
function init() {
$.get("Create", function (data) {
// 获取两张图片和令牌
var obj = JSON.parse(data);
_id = obj.data.id;
setCurrentImageBase64('data:image/webp;base64,' + obj.data.img);
});
}
});
效果图: