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); } } }