111 lines
3.7 KiB
C#

using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Security.Cryptography;
using System.Text;
namespace MilliSim.Services.Infrastructure;
public interface ICaptchaService
{
(string CaptchaId, string Svg) GenerateCaptcha();
bool ValidateCaptcha(string captchaId, string code);
}
public class CaptchaService : ICaptchaService
{
private static readonly Dictionary<string, (string Code, DateTime Expire)> _store = new();
private static readonly object _lock = new();
private static readonly char[] _chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789".ToCharArray();
public (string CaptchaId, string Svg) GenerateCaptcha()
{
var code = new string(Enumerable.Range(0, 4).Select(_ => _chars[RandomNumberGenerator.GetInt32(_chars.Length)]).ToArray());
var captchaId = Guid.NewGuid().ToString("N");
lock (_lock)
{
_store[captchaId] = (code, DateTime.Now.AddMinutes(5));
// 清理过期
var expired = _store.Where(x => x.Value.Expire < DateTime.Now).Select(x => x.Key).ToList();
foreach (var key in expired) _store.Remove(key);
}
var svg = DrawSvg(code);
return (captchaId, svg);
}
public bool ValidateCaptcha(string captchaId, string code)
{
if (string.IsNullOrEmpty(captchaId) || string.IsNullOrEmpty(code))
return false;
lock (_lock)
{
if (!_store.TryGetValue(captchaId, out var item))
return false;
_store.Remove(captchaId);
if (item.Expire < DateTime.Now)
return false;
return string.Equals(item.Code, code, StringComparison.OrdinalIgnoreCase);
}
}
private static string DrawSvg(string code)
{
var random = new Random();
var width = 120;
var height = 40;
var sb = new StringBuilder();
sb.Append($"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"{width}\" height=\"{height}\">");
// 背景
sb.Append($"<rect width=\"{width}\" height=\"{height}\" fill=\"#E6F4FF\"/>");
// 干扰线
for (var i = 0; i < 4; i++)
{
var x1 = random.Next(width);
var y1 = random.Next(height);
var x2 = random.Next(width);
var y2 = random.Next(height);
var color = GetRandomColor(random);
sb.Append($"<line x1=\"{x1}\" y1=\"{y1}\" x2=\"{x2}\" y2=\"{y2}\" stroke=\"{color}\" stroke-width=\"1\" opacity=\"0.5\"/>");
}
// 干扰点
for (var i = 0; i < 20; i++)
{
var x = random.Next(width);
var y = random.Next(height);
var color = GetRandomColor(random);
sb.Append($"<circle cx=\"{x}\" cy=\"{y}\" r=\"1\" fill=\"{color}\" opacity=\"0.5\"/>");
}
// 文字
var colors = new[] { "#0958D9", "#1677FF", "#52C41A", "#722ED1", "#FA8C16" };
for (var i = 0; i < code.Length; i++)
{
var x = 15 + i * 25;
var y = 28 + random.Next(-3, 4);
var angle = random.Next(-15, 16);
var color = colors[random.Next(colors.Length)];
var fontSize = random.Next(22, 28);
sb.Append($"<text x=\"{x}\" y=\"{y}\" font-size=\"{fontSize}\" font-family=\"Arial\" font-weight=\"bold\" fill=\"{color}\" transform=\"rotate({angle} {x} {y})\">{code[i]}</text>");
}
sb.Append("</svg>");
return sb.ToString();
}
private static string GetRandomColor(Random random)
{
var colors = new[] { "#0958D9", "#1677FF", "#52C41A", "#722ED1", "#FA8C16", "#13C2C2" };
return colors[random.Next(colors.Length)];
}
}