0
0

SlideJigsawCaptchaTool.cs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. using Newtonsoft.Json;
  2. using System;
  3. using System.IO;
  4. using System.Threading.Tasks;
  5. using System.Collections.Generic;
  6. using SixLabors.ImageSharp;
  7. using SixLabors.ImageSharp.Drawing.Processing;
  8. using SixLabors.ImageSharp.Processing;
  9. using SixLabors.ImageSharp.Formats.Png;
  10. using SixLabors.ImageSharp.Drawing;
  11. using SixLabors.ImageSharp.PixelFormats;
  12. using ZhonTai.Admin.Core.Attributes;
  13. using ZhonTai.Admin.Tools.Cache;
  14. namespace ZhonTai.Admin.Tools.Captcha;
  15. /// <summary>
  16. /// 滑块拼图验证
  17. /// </summary>
  18. [SingleInstance]
  19. public class SlideJigsawCaptchaTool : ICaptchaTool
  20. {
  21. private readonly ICacheTool _cache;
  22. private readonly Random _random = new();
  23. public SlideJigsawCaptchaTool(ICacheTool cache)
  24. {
  25. _cache = cache;
  26. }
  27. /// <summary>
  28. /// 随机范围内数字
  29. /// </summary>
  30. /// <param name="startNum"></param>
  31. /// <param name="endNum"></param>
  32. /// <returns></returns>
  33. private int GetRandomInt(int startNum, int endNum)
  34. {
  35. return (endNum > startNum ? _random.Next(endNum - startNum) : 0) + startNum;
  36. }
  37. /// <summary>
  38. /// 随机生成拼图坐标
  39. /// </summary>
  40. /// <param name="originalWidth"></param>
  41. /// <param name="originalHeight"></param>
  42. /// <param name="templateWidth"></param>
  43. /// <param name="templateHeight"></param>
  44. /// <returns></returns>
  45. private PointModel GeneratePoint(int originalWidth, int originalHeight, int templateWidth, int templateHeight)
  46. {
  47. int widthDifference = originalWidth - templateWidth;
  48. int heightDifference = originalHeight - templateHeight;
  49. int x;
  50. if (widthDifference <= 0)
  51. {
  52. x = 5;
  53. }
  54. else
  55. {
  56. x = _random.Next(originalWidth - templateWidth - 100) + 100;
  57. }
  58. int y;
  59. if (heightDifference <= 0)
  60. {
  61. y = 5;
  62. }
  63. else
  64. {
  65. y = _random.Next(originalHeight - templateHeight - 5) + 5;
  66. }
  67. return new PointModel(x, y);
  68. }
  69. /// <summary>
  70. /// 随机生成干扰图坐标
  71. /// </summary>
  72. /// <param name="originalWidth"></param>
  73. /// <param name="originalHeight"></param>
  74. /// <param name="templateWidth"></param>
  75. /// <param name="templateHeight"></param>
  76. /// <param name="blockX"></param>
  77. /// <param name="blockY"></param>
  78. /// <returns></returns>
  79. private PointModel GenerateInterferencePoint(int originalWidth, int originalHeight, int templateWidth, int templateHeight, int blockX, int blockY)
  80. {
  81. int x;
  82. if (originalWidth - blockX - 5 > templateWidth * 2)
  83. {
  84. //在原扣图右边插入干扰图
  85. x = GetRandomInt(blockX + templateWidth + 5, originalWidth - templateWidth);
  86. }
  87. else
  88. {
  89. //在原扣图左边插入干扰图
  90. x = GetRandomInt(100, blockX - templateWidth - 5);
  91. }
  92. int y;
  93. if (originalHeight - blockY - 5 > templateHeight * 2)
  94. {
  95. //在原扣图下边插入干扰图
  96. y = GetRandomInt(blockY + templateHeight + 5, originalHeight - templateHeight);
  97. }
  98. else
  99. {
  100. //在原扣图上边插入干扰图
  101. y = GetRandomInt(5, blockY - templateHeight - 5);
  102. }
  103. return new PointModel(x, y);
  104. }
  105. private static ComplexPolygon CalcBlockShape(Image<Rgba32> templateDarkImage)
  106. {
  107. int temp = 0;
  108. var pathList = new List<IPath>();
  109. templateDarkImage.ProcessPixelRows(accessor =>
  110. {
  111. for (int y = 0; y < templateDarkImage.Height; y++)
  112. {
  113. var rowSpan = accessor.GetRowSpan(y);
  114. for (int x = 0; x < rowSpan.Length; x++)
  115. {
  116. ref Rgba32 pixel = ref rowSpan[x];
  117. if (pixel.A != 0)
  118. {
  119. if (temp == 0)
  120. {
  121. temp = x;
  122. }
  123. }
  124. else
  125. {
  126. if (temp != 0)
  127. {
  128. pathList.Add(new RectangularPolygon(temp, y, x - temp, 1));
  129. temp = 0;
  130. }
  131. }
  132. }
  133. }
  134. });
  135. return new ComplexPolygon(new PathCollection(pathList));
  136. }
  137. /// <summary>
  138. /// 获得验证数据
  139. /// </summary>
  140. /// <param name="captchaKey"></param>
  141. /// <returns></returns>
  142. public async Task<CaptchaOutput> GetAsync(string captchaKey)
  143. {
  144. //获取网络图片
  145. //var client = new HttpClient();
  146. //var stream = await client.GetStreamAsync("https://picsum.photos/310/155");
  147. //client.Dispose();
  148. //底图
  149. using var baseImage = await Image.LoadAsync<Rgba32>($@"{Directory.GetCurrentDirectory()}\wwwroot\captcha\jigsaw\backgrounds\{_random.Next(1, 6)}.jpg".ToPath());
  150. var randomTemplate = _random.Next(1, 7);
  151. //深色模板图
  152. using var darkTemplateImage = await Image.LoadAsync<Rgba32>($@"{Directory.GetCurrentDirectory()}\wwwroot\captcha\jigsaw\templates\{randomTemplate}\dark.png".ToPath());
  153. //透明模板图
  154. using var transparentTemplateImage = await Image.LoadAsync<Rgba32>($@"{Directory.GetCurrentDirectory()}\wwwroot\captcha\jigsaw\templates\{randomTemplate}\transparent.png".ToPath());
  155. int baseWidth = baseImage.Width;
  156. int baseHeight = baseImage.Height;
  157. int blockWidth = 50;
  158. int blockHeight = 50;
  159. //调整模板图大小
  160. darkTemplateImage.Mutate(x =>
  161. {
  162. x.Resize(blockWidth, blockHeight);
  163. });
  164. transparentTemplateImage.Mutate(x =>
  165. {
  166. x.Resize(blockWidth, blockHeight);
  167. });
  168. //新建拼图
  169. using var blockImage = new Image<Rgba32>(blockWidth, blockHeight);
  170. //新建滑块拼图
  171. using var sliderBlockImage = new Image<Rgba32>(blockWidth, baseHeight);
  172. //随机生成拼图坐标
  173. PointModel blockPoint = GeneratePoint(baseWidth, baseHeight, blockWidth, blockHeight);
  174. //根据深色模板图计算轮廓形状
  175. var blockShape = CalcBlockShape(darkTemplateImage);
  176. //生成拼图
  177. blockImage.Mutate(x =>
  178. {
  179. x.Clip(blockShape, p => p.DrawImage(baseImage, new Point(-blockPoint.X, -blockPoint.Y), 1));
  180. });
  181. //拼图叠加透明模板图层
  182. blockImage.Mutate(x => x.DrawImage(transparentTemplateImage, new Point(0, 0), 1));
  183. //生成滑块拼图
  184. sliderBlockImage.Mutate(x => x.DrawImage(blockImage, new Point(0, blockPoint.Y), 1));
  185. var opacity = (float)(_random.Next(70, 100) * 0.01);
  186. //底图叠加深色模板图
  187. baseImage.Mutate(x => x.DrawImage(darkTemplateImage, new Point(blockPoint.X, blockPoint.Y), opacity));
  188. //生成干扰图坐标
  189. PointModel interferencePoint = GenerateInterferencePoint(baseWidth, baseHeight, blockWidth, blockHeight, blockPoint.X, blockPoint.Y);
  190. //底图叠加深色干扰模板图
  191. baseImage.Mutate(x => x.DrawImage(darkTemplateImage, new Point(interferencePoint.X, interferencePoint.Y), opacity));
  192. var token = Guid.NewGuid().ToString();
  193. var captchaData = new CaptchaOutput
  194. {
  195. Token = token,
  196. Data = new SlideJigsawCaptchaDto()
  197. {
  198. BaseImage = baseImage.ToBase64String(PngFormat.Instance),
  199. BlockImage = sliderBlockImage.ToBase64String(PngFormat.Instance)
  200. }
  201. };
  202. var key = string.Format(captchaKey, token);
  203. await _cache.SetAsync(key, blockPoint.X);
  204. return captchaData;
  205. }
  206. /// <summary>
  207. /// 检查验证数据
  208. /// </summary>
  209. /// <param name="input"></param>
  210. /// <returns></returns>
  211. public async Task<bool> CheckAsync(CaptchaInput input)
  212. {
  213. if (input == null || input.Data.IsNull())
  214. {
  215. return false;
  216. }
  217. var key = string.Format(input.CaptchaKey, input.Token);
  218. if (await _cache.ExistsAsync(key))
  219. {
  220. try
  221. {
  222. var point = JsonConvert.DeserializeObject<PointModel>(input.Data);
  223. var x = await _cache.GetAsync<int>(key);
  224. if (Math.Abs(x - point.X) < 5)
  225. {
  226. if (input.DeleteCache)
  227. {
  228. await _cache.DelAsync(key);
  229. }
  230. return true;
  231. }
  232. else
  233. {
  234. await _cache.DelAsync(key);
  235. return false;
  236. }
  237. }
  238. catch
  239. {
  240. await _cache.DelAsync(key);
  241. return false;
  242. }
  243. }
  244. else
  245. {
  246. return false;
  247. }
  248. }
  249. }