111 lines
3.7 KiB
C#
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)];
|
|
}
|
|
}
|