208 lines
6.0 KiB
C#
208 lines
6.0 KiB
C#
using Microsoft.Extensions.Caching.Memory;
|
||
using SkiaSharp;
|
||
|
||
namespace MiAssessment.Admin.Services;
|
||
|
||
/// <summary>
|
||
/// 验证码服务实现
|
||
/// </summary>
|
||
public class CaptchaService : ICaptchaService
|
||
{
|
||
private readonly IMemoryCache _cache;
|
||
private readonly Random _random = new();
|
||
|
||
// 验证码字符集(排除容易混淆的字符如0,O,1,I,l)
|
||
private const string CharSet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz";
|
||
|
||
// 验证码配置
|
||
private const int MinLength = 4;
|
||
private const int MaxLength = 6;
|
||
private const int ImageWidth = 120;
|
||
private const int ImageHeight = 40;
|
||
private const int CaptchaExpirationMinutes = 5;
|
||
private const string CacheKeyPrefix = "captcha:";
|
||
|
||
public CaptchaService(IMemoryCache cache)
|
||
{
|
||
_cache = cache;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public CaptchaResult Generate()
|
||
{
|
||
// 生成随机验证码(4-6位)
|
||
var length = _random.Next(MinLength, MaxLength + 1);
|
||
var code = GenerateRandomCode(length);
|
||
|
||
// 生成唯一Key
|
||
var captchaKey = Guid.NewGuid().ToString("N");
|
||
|
||
// 存储到缓存(5分钟过期)
|
||
var cacheKey = CacheKeyPrefix + captchaKey;
|
||
_cache.Set(cacheKey, code, TimeSpan.FromMinutes(CaptchaExpirationMinutes));
|
||
|
||
// 生成图片
|
||
var imageBase64 = GenerateCaptchaImage(code);
|
||
|
||
return new CaptchaResult
|
||
{
|
||
CaptchaKey = captchaKey,
|
||
CaptchaImage = $"data:image/png;base64,{imageBase64}"
|
||
};
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public bool Validate(string captchaKey, string captchaCode)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(captchaKey) || string.IsNullOrWhiteSpace(captchaCode))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
var cacheKey = CacheKeyPrefix + captchaKey;
|
||
|
||
// 尝试获取缓存中的验证码
|
||
if (!_cache.TryGetValue(cacheKey, out string? storedCode))
|
||
{
|
||
return false; // 验证码不存在或已过期
|
||
}
|
||
|
||
// 无论验证成功与否,都删除验证码(单次使用)
|
||
_cache.Remove(cacheKey);
|
||
|
||
// 不区分大小写比较
|
||
return string.Equals(storedCode, captchaCode, StringComparison.OrdinalIgnoreCase);
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 生成随机验证码字符串
|
||
/// </summary>
|
||
private string GenerateRandomCode(int length)
|
||
{
|
||
var chars = new char[length];
|
||
for (int i = 0; i < length; i++)
|
||
{
|
||
chars[i] = CharSet[_random.Next(CharSet.Length)];
|
||
}
|
||
return new string(chars);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成验证码图片
|
||
/// </summary>
|
||
private string GenerateCaptchaImage(string code)
|
||
{
|
||
using var bitmap = new SKBitmap(ImageWidth, ImageHeight);
|
||
using var canvas = new SKCanvas(bitmap);
|
||
|
||
// 填充背景色(浅色随机背景)
|
||
var bgColor = new SKColor(
|
||
(byte)_random.Next(200, 256),
|
||
(byte)_random.Next(200, 256),
|
||
(byte)_random.Next(200, 256)
|
||
);
|
||
canvas.Clear(bgColor);
|
||
|
||
// 绘制干扰线
|
||
DrawNoiseLines(canvas);
|
||
|
||
// 绘制噪点
|
||
DrawNoisePoints(canvas);
|
||
|
||
// 绘制验证码文字
|
||
DrawCaptchaText(canvas, code);
|
||
|
||
// 转换为PNG并编码为Base64
|
||
using var image = SKImage.FromBitmap(bitmap);
|
||
using var data = image.Encode(SKEncodedImageFormat.Png, 100);
|
||
return Convert.ToBase64String(data.ToArray());
|
||
}
|
||
|
||
/// <summary>
|
||
/// 绘制干扰线
|
||
/// </summary>
|
||
private void DrawNoiseLines(SKCanvas canvas)
|
||
{
|
||
using var paint = new SKPaint
|
||
{
|
||
IsAntialias = true,
|
||
StrokeWidth = 1
|
||
};
|
||
|
||
// 绘制5-8条干扰线
|
||
var lineCount = _random.Next(5, 9);
|
||
for (int i = 0; i < lineCount; i++)
|
||
{
|
||
paint.Color = new SKColor(
|
||
(byte)_random.Next(100, 200),
|
||
(byte)_random.Next(100, 200),
|
||
(byte)_random.Next(100, 200)
|
||
);
|
||
|
||
var startX = _random.Next(ImageWidth);
|
||
var startY = _random.Next(ImageHeight);
|
||
var endX = _random.Next(ImageWidth);
|
||
var endY = _random.Next(ImageHeight);
|
||
|
||
canvas.DrawLine(startX, startY, endX, endY, paint);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 绘制噪点
|
||
/// </summary>
|
||
private void DrawNoisePoints(SKCanvas canvas)
|
||
{
|
||
using var paint = new SKPaint();
|
||
|
||
// 绘制50-100个噪点
|
||
var pointCount = _random.Next(50, 101);
|
||
for (int i = 0; i < pointCount; i++)
|
||
{
|
||
paint.Color = new SKColor(
|
||
(byte)_random.Next(0, 256),
|
||
(byte)_random.Next(0, 256),
|
||
(byte)_random.Next(0, 256)
|
||
);
|
||
|
||
var x = _random.Next(ImageWidth);
|
||
var y = _random.Next(ImageHeight);
|
||
|
||
canvas.DrawPoint(x, y, paint);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 绘制验证码文字
|
||
/// </summary>
|
||
private void DrawCaptchaText(SKCanvas canvas, string code)
|
||
{
|
||
using var paint = new SKPaint
|
||
{
|
||
IsAntialias = true,
|
||
TextSize = 24,
|
||
Typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Bold)
|
||
};
|
||
|
||
var charWidth = (ImageWidth - 20) / code.Length;
|
||
var startX = 10;
|
||
|
||
for (int i = 0; i < code.Length; i++)
|
||
{
|
||
// 每个字符使用不同的深色
|
||
paint.Color = new SKColor(
|
||
(byte)_random.Next(0, 100),
|
||
(byte)_random.Next(0, 100),
|
||
(byte)_random.Next(0, 100)
|
||
);
|
||
|
||
// 随机Y位置(垂直偏移)
|
||
var y = _random.Next(25, 35);
|
||
|
||
// 绘制字符
|
||
canvas.DrawText(code[i].ToString(), startX + i * charWidth, y, paint);
|
||
}
|
||
}
|
||
}
|