using Microsoft.Extensions.Caching.Memory;
using SkiaSharp;
namespace MiAssessment.Admin.Services;
///
/// 验证码服务实现
///
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;
}
///
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}"
};
}
///
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);
}
///
/// 生成随机验证码字符串
///
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);
}
///
/// 生成验证码图片
///
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());
}
///
/// 绘制干扰线
///
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);
}
}
///
/// 绘制噪点
///
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);
}
}
///
/// 绘制验证码文字
///
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);
}
}
}