前言:
上一篇分分享了基于阿里云实现的短信验证码文章,考虑到为了防止登录时,非人工操作,频繁获取验证码,趁热打铁,现在添加了图片验证码服务功能。借鉴网上传统的做法,把实现这两个验证的功能做成有个独立的服务,通过Http分别请求获取校验图片验证码和短信验证码。.
一、需求描述:
-
图形验证码为,短信验证码为6位纯数字
-
同一系统图片验证码缓存中只存在一个,没有有效期,每次刷新更新旧图形验证码
-
短信验证码有效期2分钟
-
每个手机号60秒内只能发送一次短信验证码,在服务器端执行校验
-
同一个手机号在同一时间内可以有多个有效的短信验证码,根据不同系统类型区分
-
每个短信验证码至多可被使用3次,无论和请求中的验证码是否匹配,随后立即作废,以防止暴力攻击
-
发送短信验证码之前,先验证图形验证码是否正确
二、图片验证码实现:
生成随机验证码字符串:
/// <summary>/// 获取随机验证码/// </summary>/// <returns></returns>public static string GenerateCaptchaCode(){Random rand = new Random();int maxRand = Letters.Length - 1;StringBuilder sb = new StringBuilder();for (int i = 0; i < 4; i++){int index = rand.Next(maxRand);sb.Append(Letters[index]);}return sb.ToString();}
随机验证码生成验证码图片:
/// <summary>/// 生成随机验证码图片/// </summary>/// <param name="width"></param>/// <param name="height"></param>/// <param name="captchaCode"></param>/// <returns></returns>public static CaptchaResult GenerateCaptcha(int width, int height, string captchaCode){using (Bitmap baseMap = new Bitmap(width, height))using (Graphics graph = Graphics.FromImage(baseMap)){Random rand = new Random();graph.Clear(GetRandomLightColor());DrawCaptchaCode();DrawDisorderLine();AdjustRippleEffect();MemoryStream ms = new MemoryStream();baseMap.Save(ms, ImageFormat.Png);return new CaptchaResult { CaptchaCode = captchaCode, CaptchaByteData = ms.ToArray(), Timestamp = DateTime.Now };int GetFontSize(int imageWidth, int captchCodeCount){var averageSize = imageWidth / captchCodeCount;return Convert.ToInt32(averageSize);}Color GetRandomDeepColor(){int redlow = 160, greenLow = 100, blueLow = 160;return Color.FromArgb(rand.Next(redlow), rand.Next(greenLow), rand.Next(blueLow));}Color GetRandomLightColor(){int low = 180, high = 255;int nRend = rand.Next(high) % (high - low) + low;int nGreen = rand.Next(high) % (high - low) + low;int nBlue = rand.Next(high) % (high - low) + low;return Color.FromArgb(nRend, nGreen, nBlue);}void DrawCaptchaCode(){SolidBrush fontBrush = new SolidBrush(Color.Black);int fontSize = GetFontSize(width, captchaCode.Length);Font font = new Font(FontFamily.GenericSerif, fontSize, FontStyle.Bold, GraphicsUnit.Pixel);for (int i = 0; i < captchaCode.Length; i++){fontBrush.Color = GetRandomDeepColor();int shiftPx = fontSize / 6;//float x = i * fontSize + rand.Next(-shiftPx, shiftPx) + rand.Next(-shiftPx, shiftPx);float x = i * fontSize + rand.Next(-shiftPx, shiftPx) / 2;//int maxY = height - fontSize;int maxY = height - fontSize * 2;if (maxY < 0){maxY = 0;}float y = rand.Next(0, maxY);graph.DrawString(captchaCode[i].ToString(), font, fontBrush, x, y);}}void DrawDisorderLine(){Pen linePen = new Pen(new SolidBrush(Color.Black), 2);//for (int i = 0; i < rand.Next(3, 5); i++)for (int i = 0; i < 2; i++){linePen.Color = GetRandomDeepColor();Point startPoint = new Point(rand.Next(0, width), rand.Next(0, height));Point endPoint = new Point(rand.Next(0, width), rand.Next(0, height));graph.DrawLine(linePen, startPoint, endPoint);//Point bezierPoint1 = new Point(rand.Next(0, width), rand.Next(0, height));//Point bezierPoint2 = new Point(rand.Next(0, width), rand.Next(0, height));//graph.DrawBezier(linePen, startPoint, bezierPoint1, bezierPoint2, endPoint);}}void AdjustRippleEffect(){short nWave = 6;int nWidth = baseMap.Width;int nHeight = baseMap.Height;Point[,] pt = new Point[nWidth, nHeight];for (int x = 0; x < nWidth; ++x){for (int y = 0; y < nHeight; ++y){var xo = nWave * Math.Sin(2.0 * 3.1415 * y / 128.0);var yo = nWave * Math.Cos(2.0 * 3.1415 * x / 128.0);var newX = x + xo;var newY = y + yo;if (newX > 0 && newX < nWidth){pt[x, y].X = (int)newX;}else{pt[x, y].X = 0;}if (newY > 0 && newY < nHeight){pt[x, y].Y = (int)newY;}else{pt[x, y].Y = 0;}}}Bitmap bSrc = (Bitmap)baseMap.Clone();BitmapData bitmapData = baseMap.LockBits(new Rectangle(0, 0, baseMap.Width, baseMap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);BitmapData bmSrc = bSrc.LockBits(new Rectangle(0, 0, bSrc.Width, bSrc.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);int scanline = bitmapData.Stride;IntPtr scan0 = bitmapData.Scan0;IntPtr srcScan0 = bmSrc.Scan0;unsafe{byte* p = (byte*)(void*)scan0;byte* pSrc = (byte*)(void*)srcScan0;int nOffset = bitmapData.Stride - baseMap.Width * 3;for (int y = 0; y < nHeight; ++y){for (int x = 0; x < nWidth; ++x){var xOffset = pt[x, y].X;var yOffset = pt[x, y].Y;if (yOffset >= 0 && yOffset < nHeight && xOffset >= 0 && xOffset < nWidth){if (pSrc != null){p[0] = pSrc[yOffset * scanline + xOffset * 3];p[1] = pSrc[yOffset * scanline + xOffset * 3 + 1];p[2] = pSrc[yOffset * scanline + xOffset * 3 + 2];}}p += 3;}p += nOffset;}}baseMap.UnlockBits(bitmapData);bSrc.UnlockBits(bmSrc);bSrc.Dispose();}}}
三、短信验证码实现:
见上篇:
传送门:基于Aliyun短信验证码实现
四、图片验证码获取与校验:
获取:
代码:
/// <summary>/// 获取图片验证码/// </summary>/// <param name="imgCaptchaDto">图形验证码请求信息</param>[HttpGet("img")]public IActionResult GetImageCaptcha([FromQuery]ImgCaptchaDto imgCaptchaDto){var result = _captchaService.GetImageCaptcha(imgCaptchaDto);var stream = new MemoryStream(result.CaptchaByteData);return new FileStreamResult(stream, "image/png");}
Http调用:详细调用参数和方式见下方接口在线文档。

校验:
代码:
/// <summary>/// 验证图片验证码/// </summary>/// <param name="imgCaptchaDto">图形验证码信息</param>/// <returns></returns>[HttpPost("img")]public IActionResult ValidateImageCaptcha(ImgCaptchaDto imgCaptchaDto){bool isCaptchaValid = _captchaService.ValidateImageCaptcha(imgCaptchaDto);if (isCaptchaValid){HttpResponseDto httpResponseDto = new HttpResponseDto(){IsSuccess = true,Code = StatusCodes.Status200OK,Message = "图形验证码验证成功"};var responJson = JsonConvert.SerializeObject(httpResponseDto);return Ok(responJson);}else{HttpResponseDto httpResponseDto = new HttpResponseDto(){IsSuccess = false,Code = StatusCodes.Status403Forbidden,Message = "验证失败,请输入正确手机号及获取到的验证码"};var responJson = JsonConvert.SerializeObject(httpResponseDto);return StatusCode(StatusCodes.Status403Forbidden, responJson);}}
Http调用:详细调用参数和方式见下方接口在线文档。

五、短信验证码获取与校验:
获取:
代码:
/// <summary>/// 获取短信验证码/// </summary>/// <param name="msgCaptchaDto">短信验证码请求信息</param>/// <returns></returns>[HttpGet("msg")]public IActionResult GetMsgCaptcha([FromQuery]MsgCaptchaDto msgCaptchaDto){var msgSendResult = _captchaService.GetMsgCaptcha(msgCaptchaDto);if (msgSendResult.Item1){return Ok(msgSendResult.Item2);}else{return StatusCode(StatusCodes.Status403Forbidden, msgSendResult.Item2);}}
Http调用:详细调用参数和方式见下方接口在线文档。


校验:
代码:
/// <summary>/// 验证短信验证码/// </summary>/// <param name="msgCaptchaDto">短信验证码信息</param>/// <returns></returns>[HttpPost("msg")]public IActionResult ValidateMsgCaptcha(MsgCaptchaDto msgCaptchaDto){var validateResult = _captchaService.ValidateMsgCaptcha(msgCaptchaDto);if (validateResult.Item1){HttpResponseDto httpResponseDto = new HttpResponseDto(){IsSuccess = true,Code = StatusCodes.Status200OK,Message = validateResult.Item2};var responJson = JsonConvert.SerializeObject(httpResponseDto);return Ok(responJson);}else{HttpResponseDto httpResponseDto = new HttpResponseDto(){IsSuccess = false,Code = StatusCodes.Status403Forbidden,Message = validateResult.Item2};var responJson = JsonConvert.SerializeObject(httpResponseDto);return StatusCode(StatusCodes.Status403Forbidden, responJson);}}
Http调用:详细调用参数和方式见下方接口在线文档。

源码链接地址:
Gitee完整实例地址:
https://github.com/YSGStudyHards/NPOI-ExportWordAndExcel-ImportExcelData
接口在线文档:
链接地址:
https://console-docs.apipost.cn/preview/82c61b0950bae0c8/924487d25ec3df36