mi-assessment/server/MiAssessment/src/MiAssessment.Admin/Services/CaptchaService.cs
2026-02-03 14:25:01 +08:00

208 lines
6.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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