开发环境:C#,VS2019,.NET Core 3.1,ASP.NET Core
前几年使用12306购买火车票时使用过这种验证码,根据文字描述选择对应的图片,文字是随机的,图片也是随机的。
1、建立一个验证码控制器
新建两个方法Create和Check,Create用于创建验证码,Check用于验证它是否有效。
声明一个静态类变量存放列表,列表中存放包含令牌和验证码的对象。
/// <summary>
/// 返回多张图片,一个文字和令牌.
/// </summary>
/// <returns></returns>
public string Create()
{
try
{
VCodeImageSelectModel model = new VCodeImageSelectModel();
model.id = Guid.NewGuid().ToString(); // 生成令牌
var vcode = VCodeImageSelectModel.GetVCode(); // 生成验证码
model.code.text = vcode.Item1;
model.code.image = vcode.Item4;
_list.Add(model);
// 返回结果
var image = VCodeImageSelectModel.DrawImage(model.code.text);
var base64 = VCodeImageSelectModel.BitmapToBase64Str(image);
var images = vcode.Item2;
VCodeImageSelectController_Create_Receive result = new VCodeImageSelectController_Create_Receive();
result.code = "0";
result.data.id = model.id;
result.data.img = VCodeImageSelectModel.BitmapToBase64Str(images);
result.data.img_index = vcode.Item3;
result.data.img_text = base64;
var json = JsonConvert.SerializeObject(result);
return json;
}
catch (Exception ex)
{
_logger.LogWarning(exception: ex, message: ex.Message);
VCodeImageSelectController_Create_Receive result = new VCodeImageSelectController_Create_Receive();
result.code = "999999";
result.msg = "系统异常";
var json = JsonConvert.SerializeObject(result);
return json;
}
}
/// <summary>
/// 检查验证码是否有效
/// </summary>
/// <param name="id">令牌.</param>
/// <param name="code">验证码.</param>
/// <returns></returns>
[HttpGet]
public string Check(string id, string code)
{
try
{
ReceiveObject result = new ReceiveObject();
var list_result = code.Split(',').ToList(); // 拆分验证码为数组
var list_temp = new List<string>();
if(_list.Find(m => m.id == id) == null)
{
result.code = "999999";
result.msg = "系统异常";
var json = JsonConvert.SerializeObject(result);
return json;
}
_list.Find(m => m.id == id).code.image.ForEach(m =>
{
// 复制数组,避免重复提交后仍然能获取到完整的数据
list_temp.Add(m);
});
var index = _list.FindIndex(m =>
{
var flag = false;
for (int i = 0; i < list_result.Count; i++)
{
var item = list_result[i];
flag = m.code.image.Exists(m => m.Equals(item));
if(flag == false)
{
// 选中了错误的
return false;
}
else
{
// 每次将选中的项删除,这样最终得到的数组应该是空的
list_temp.Remove(item);
}
}
if(list_temp.Count > 0)
{
// 未全部选中
return false;
}
if (m.id.Equals(id) && flag)
{
// 通过
return true;
}
return false;
});
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;
}
}
2、建立一个验证码模型
验证码模型类包括:令牌和验证码属性。
再创建一个类存放GetVCode方法返回的对象包括:图片的类型(它又叫文字描述)的图片,图片数组,图片对应的序号,该类型的图片对应的序号。
DrawImage方法生成图片的类型(它又叫文字描述)的图片
GetVCodeList方法生成图片数组,图片对应的序号,该类型的图片对应的序号。
BitmapToBase64Str方法用来将图片对象转成Base64字符串
/// <summary>
/// 获取随机的文字和验证码.
/// </summary>
/// <returns>
/// 第一个参数 - 图片的类型(文字描述)
/// 第二个参数 - 图片数组,用来显示图片
/// 第三个参数 - 图片对应的序号
/// 第四个参数 - 该类型的图片对应的序号
/// </returns>
public static Tuple<string, List<Bitmap>, List<string>, List<string>> GetVCode()
{
Random random = new Random();
var type = random.Next(1, List_Text.Count + 1).ToString();
var typeName = List_Text.ElementAt(Convert.ToInt32(type) - 1);
var result = GetVCodeList(type);
return new Tuple<string, List<Bitmap>, List<string>, List<string>>(typeName, result.Item1, result.Item2, result.Item3);
}
/// <summary>
/// 获取随机的验证码.
/// </summary>
/// <param name="type">图片的类型.</param>
/// <returns>
/// 第一个参数 - 图片数组,用来显示图片
/// 第二个参数 - 图片对应的序号
/// 第三个参数 - 该类型的图片对应的序号
/// </returns>
private static Tuple<List<Bitmap>, List<string>, List<string>> GetVCodeList(string type)
{
// 这里的随机码是一个有四个元素的数组,如果发现没有生成指定类型的,就重新生成.
var list_files = Directory.GetFiles(PathHelper.Path + @"Images\imageSelect");
var count = list_files.Count();
List<string> list_index = new List<string>();
var list_fileName = new List<string>();
List<string> list_selectedIndex = new List<string>();
Random random = new Random();
while (true)
{
while (true)
{
var index = random.Next(0, count).ToString();
if (list_index.Exists(m => m.Equals(index)) == false)
{
list_index.Add(index);
var temp = list_files.ElementAt(Convert.ToInt32(index)).Replace(PathHelper.Path + @"Images\imageSelect", ""); // 只保留文件名,去掉路径
list_fileName.Add(temp);
if(temp.Replace("\\img", "").Substring(0, 1) == type)
{
list_selectedIndex.Add(index);
}
}
if (list_index.Count >= 4)
{
break;
}
}
// 判断是否至少生成了一个指定类型的图片
var flag = false;
flag = list_fileName.Exists(m =>
{
if (m.Contains("img" + type))
{
return true;
}
return false;
});
if (flag == false)
{
list_index.Clear();
list_fileName.Clear();
list_selectedIndex.Clear();
continue;
}
else
{
// 至少生成了一个指定类型的图片
break;
}
}
// 加载图片
List<Bitmap> list_image = new List<Bitmap>();
for (int i = 0; i < list_fileName.Count; i++)
{
var image = Image.FromFile(string.Format(@"{0}Images\imageSelect\{1}", PathHelper.Path, list_fileName.ElementAt(i)));
list_image.Add((Bitmap)image);
}
return new Tuple<List<Bitmap>, List<string>, List<string>>(list_image, list_index, list_selectedIndex);
}
/// <summary>
/// 将图片对象转成Base64的字符串.
/// </summary>
/// <param name="bitmap"></param>
/// <returns></returns>
public static List<string> BitmapToBase64Str(List<Bitmap> bitmap)
{
List<string> list = new List<string>();
for (int i = 0; i < bitmap.Count; i++)
{
using (MemoryStream memoryStream = new MemoryStream())
{
bitmap.ElementAt(i).Save(memoryStream, ImageFormat.Jpeg);
byte[] bytes = memoryStream.ToArray();
list.Add(Convert.ToBase64String(memoryStream.ToArray()));
}
}
return list;
}
/// <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());
}
}
/// <summary>
/// 绘制验证码的图片.
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
public static Bitmap DrawImage(string code)
{
Color[] list_color =
{
Color.FromArgb(240, 230, 140), // 黄褐色(亮)
Color.FromArgb(138, 54, 15), // 黄褐色(暗)
Color.FromArgb(51, 161, 201), // 蓝色(亮)
Color.FromArgb(25, 25, 112), // 蓝色(暗)
Color.FromArgb(192, 192, 192), // 灰白(亮)
Color.FromArgb(128, 128, 105), // 灰白(暗)
};
Random random = new Random();
// 创建画板
Bitmap bitmap = new Bitmap(85, 50);
// 创建画笔
Graphics grp = Graphics.FromImage(bitmap);
grp.Clear(Color.White); // 设置背景色为白色
// 绘制噪点
for (int i = 0; i < random.Next(30, 40); i++)
{
int x = random.Next(bitmap.Width);
int y = random.Next(bitmap.Height);
grp.DrawLine(new Pen(Color.LightGray, 1), x, y, x + 1, y);
}
// 绘制随机的线条
grp.DrawLine(
new Pen(list_color[random.Next(list_color.Length)], random.Next(3)),
new Point(random.Next(bitmap.Width / 2), random.Next(bitmap.Height / 2)),
new Point(bitmap.Width / 2 + random.Next(bitmap.Width / 2), bitmap.Height / 2 + random.Next(bitmap.Height / 2))
);
// 绘制验证码
for (int i = 0; i < code.Length; i++)
{
var item = code[i];
grp.DrawString(item.ToString(),
new Font(FontFamily.GenericSansSerif, 25, FontStyle.Bold),
new SolidBrush(list_color[random.Next(list_color.Length)]),
x: (75 / 2) * i,
y: random.Next(5));
}
// 在验证码上绘制噪点
for (int i = 0; i < random.Next(15, 25); i++)
{
int x = random.Next(bitmap.Width);
int y = random.Next(bitmap.Height);
grp.DrawLine(new Pen(list_color[random.Next(list_color.Length)], 1), x, y, x + 1, y);
}
// 绘制随机的线条
grp.DrawLine(
new Pen(list_color[random.Next(list_color.Length)], random.Next(3)),
new Point(random.Next(bitmap.Width / 2), random.Next(bitmap.Height / 2)),
new Point(bitmap.Width / 2 + random.Next(bitmap.Width / 2), bitmap.Height / 2 + random.Next(bitmap.Height / 2))
);
return bitmap;
}
3、新建一个视图文件,引入jquery,css文件,js方法中添加几个事件 - 点击图片,提交按钮。页面首次加载时调用控制器的Create方法获取图片和令牌。
<link href="~/css/image_select.css" rel="stylesheet" />
<!-- 展示验证码 -->
<div class="container">
<div class="main">
<div>
请选择所有的<img id="backTextImage" src="" alt="">
</div>
@for (int i = 1; i < 5; i++)
{
@if (i % 2 == 1)
{
<div style="float:left;">
<div>
<img id="backImage@(i)" + src="" class="img_check" checked="0">
</div>
<div id="div_icon@(i)" class="divIconClass">
<img id="imgIcon@(i)" src="~/img/Select.png" class="imgIconClass"/>
</div>
</div>
}
else
{
<div>
<div>
<img id="backImage@(i)" + src="" class="img_check" checked="0">
</div>
<div id="div_icon@(i)" class="divIconClass">
<img id="imgIcon@(i)" src="~/img/Select.png" class="imgIconClass"/>
</div>
</div>
}
}
</div>
<div style="position:relative;left:20px;">
<input type="button" value="提交" id="button_submit" />
</div>
</div>
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/js/image_select.js"></script>
* {
margin: 0;
padding: 0;
}
.main {
position: relative;
margin-left: 20px;
margin-top: 20px;
width: 300px;
background-color: white;
}
.img_check {
width: 100px;
height: 100px;
}
#div_icon1 {
bottom: 30px;
left: 70px;
}
#div_icon2 {
bottom: 56px;
left: 168px;
}
#div_icon3 {
bottom: 30px;
left: 70px;
}
#div_icon4 {
bottom: 56px;
left: 168px;
}
.divIconClass {
position: relative;
width: 25px;
height: 25px;
}
.imgIconClass {
width: 1px;
height: 1px;
left: 70px;
}
// 图片列表
var _imageBase64 = new Array();
// 文字描述的图片
var _imageText = new String();
// 令牌
var _id;
/**
* 设置当前图片
* @param {any} imageText 描述文字的图片
* @param {any} imageBase64 图片列表
* @param {any} img_index 图片的序号
*/
function setCurrentImageBase64(imageText, imageBase64, img_index)
{
// 显示描述文字
_imageText = 'data:image/webp;base64,' + imageText;
document.getElementById('backTextImage').src = _imageText;
// 显示图片
for (var i = 0; i < imageBase64.length; i++)
{
_imageBase64[i] = 'data:image/webp;base64,' + imageBase64[i];
document.getElementById('backImage' + (i + 1)).src = _imageBase64[i];
document.getElementById('backImage' + (i + 1)).attributes["val1"] = img_index[i];
}
}
/**
* 验证方法
* @param {any} code 验证码
*/
function check(code)
{
$.get("Check?code=" + code + "&id=" + _id, function (data) {
var obj = JSON.parse(data);
if (obj.code == "0") {
alert("验证成功");
}
else {
alert("验证失败");
}
location.reload();
});
}
window.onload = function () {
$.get("Create", function (data) {
// 获取图片和令牌
var obj = JSON.parse(data);
_id = obj.data.id;
// console.log(obj.data.img_index.length);
setCurrentImageBase64(obj.data.img_text, obj.data.img, obj.data.img_index);
});
// 点击图片
$(".img_check").click(function (event) {
var id = event.currentTarget.id;
var index = id.replace("backImage", "");
// console.log(event.currentTarget.attributes["val1"]);
if (event.currentTarget.attributes["checked"].value == "0")
{
// 选中状态
event.currentTarget.attributes["checked"].value = "1";
$("#imgIcon" + index).css("height", "25");
$("#imgIcon" + index).css("width", "25");
}
else
{
// 取消选中状态
event.currentTarget.attributes["checked"].value = "0";
$("#imgIcon" + index).css("height", "1");
$("#imgIcon" + index).css("width", "1");
}
});
// 提交按钮
$("#button_submit").click(function () {
var result = "";
var v1 = $("#backImage1");
var v2 = $("#backImage2");
var v3 = $("#backImage3");
var v4 = $("#backImage4");
if (v1[0].attributes["checked"].value == "1") {
var code = v1[0].attributes["val1"];
result = result + code + ","
}
if (v2[0].attributes["checked"].value == "1") {
var code = v2[0].attributes["val1"];
result = result + code + ","
}
if (v3[0].attributes["checked"].value == "1") {
var code = v3[0].attributes["val1"];
result = result + code + ","
}
if (v4[0].attributes["checked"].value == "1") {
var code = v4[0].attributes["val1"];
result = result + code + ","
}
if (result.length <= 0) {
alert("请选择图片");
return;
}
else {
result = result.substring(0, result.length - 1);
}
check(result);
})
}
效果图: