using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text;
namespace Controllers.Core.Util
{
public class VerifyCodeHelper
{
// 調色盤:高對比度、色盲友好
private static readonly Color[] Colors = new[]
{
Color.FromArgb(220, 20, 60), // Crimson
Color.FromArgb(128, 0, 128), // Purple
Color.FromArgb(65, 105, 225), // RoyalBlue
Color.FromArgb(70, 130, 180), // SteelBlue
Color.FromArgb(46, 139, 87), // SeaGreen
Color.FromArgb(184, 134, 11), // DarkGoldenrod
Color.FromArgb(255, 140, 0), // DarkOrange
Color.FromArgb(139, 69, 19), // SaddleBrown
Color.FromArgb(0, 191, 255), // DeepSkyBlue
Color.FromArgb(95, 158, 160), // CadetBlue
Color.FromArgb(255, 20, 147), // DeepPink
Color.FromArgb(255, 165, 0) // Orange
};
private static readonly string[] Fonts = { "Arial", "Verdana", "Georgia", "黑體" };
private const int FontSize = 22;
private const int Width = 120;
private const int Height = 40;
private const int CodeLength = 4;
private const int NoiseLineCount = 10;
private const int NoiseDotCount = 100;
///
/// 生成驗證碼圖片,輸出 code 供 Session 儲存
///
public static Bitmap CreateVerifyCodeBmp(out string code)
{
var bmp = new Bitmap(Width, Height);
using (var g = Graphics.FromImage(bmp))
{
var rnd = new Random();
// 1. 白底
g.FillRectangle(Brushes.White, 0, 0, Width, Height);
// 2. 繪製字元
var sb = new StringBuilder();
for (int i = 0; i < CodeLength; i++)
{
string ch = GetRandomChar(rnd);
Font font = new Font(Fonts[rnd.Next(Fonts.Length)], FontSize, FontStyle.Bold);
Color color = Colors[rnd.Next(Colors.Length)];
float x = i * Width / 4f;
g.DrawString(ch, font, new SolidBrush(color), x, 0);
sb.Append(ch);
}
code = sb.ToString();
// 3. 干擾線
for (int i = 0; i < NoiseLineCount; i++)
{
using (var pen = new Pen(Colors[rnd.Next(Colors.Length)], 1))
g.DrawLine(pen,
rnd.Next(Width), rnd.Next(Height),
rnd.Next(Width), rnd.Next(Height));
}
// 4. 正弦波扭曲
bmp = TwistImage(bmp, true, 3, rnd.NextDouble() * Math.PI * 2);
}
// 5. 噪點(扭曲後重新取 Graphics)
using (var g = Graphics.FromImage(bmp))
{
var rnd = new Random();
for (int i = 0; i < NoiseDotCount; i++)
{
using (var pen = new Pen(Colors[rnd.Next(Colors.Length)], 1))
g.DrawRectangle(pen, rnd.Next(Width), rnd.Next(Height), 1, 1);
}
// 6. 邊框
g.DrawRectangle(new Pen(Color.FromArgb(153, 153, 153)), 0, 0, Width - 1, Height - 1);
}
return bmp;
}
private static string GetRandomChar(Random rnd)
{
int n = rnd.Next(62); // 0-61
if (n < 10) return ((char)('0' + n)).ToString();
if (n < 36) return ((char)('A' + n - 10)).ToString();
return ((char)('a' + n - 36)).ToString();
}
///
/// 正弦波扭曲演算法
///
private static Bitmap TwistImage(Bitmap src, bool xDir, double amplitude, double phase)
{
var dest = new Bitmap(src.Width, src.Height);
using (var g = Graphics.FromImage(dest))
g.FillRectangle(Brushes.White, 0, 0, dest.Width, dest.Height);
double baseAxis = xDir ? dest.Height : dest.Width;
for (int x = 0; x < dest.Width; x++)
{
for (int y = 0; y < dest.Height; y++)
{
double dx = xDir ? (Math.PI * 2 * y) / baseAxis : (Math.PI * 2 * x) / baseAxis;
dx += phase;
double dy = Math.Sin(dx) * amplitude;
int srcX = xDir ? x + (int)dy : x;
int srcY = xDir ? y : y + (int)dy;
if (srcX >= 0 && srcX < src.Width && srcY >= 0 && srcY < src.Height)
dest.SetPixel(srcX, srcY, src.GetPixel(x, y));
}
}
return dest;
}
}
}
public ActionResult VerifyCode()
{
string code;
Bitmap bmp = VerifyCodeHelper.CreateVerifyCodeBmp(out code);
// 可選:縮放至 108x36 適應版面
Bitmap output = new Bitmap(bmp, 108, 36);
Session["VerifyCode"] = code; // 不區分大小寫驗證時統一 ToLower
Response.Clear();
Response.ContentType = "image/bmp";
output.Save(Response.OutputStream, ImageFormat.Bmp);
return new EmptyResult();
}
public ActionResult CheckVCode(string vcode)
{
string sessionCode = Session["VerifyCode"]?.ToString() ?? "";
bool ok = sessionCode.Equals(vcode, StringComparison.OrdinalIgnoreCase);
return Json(new { ok }, JsonRequestBehavior.AllowGet);
}
<img id="verifyCode" src="" alt="驗證碼" style="vertical-align:middle; cursor:pointer;" />
<a id="refreshVerifyCode" href="javascript:;">看不清?換一張</a>
<script>
$(function () {
var $img = $('#verifyCode');
function refresh() {
$img.attr('src', '/Home/VerifyCode?t=' + Date.now()); // 時間戳防快取
}
$img.add('#refreshVerifyCode').on('click', refresh);
refresh(); // 頁面載入即顯示
});
// 表單送出前驗證
$('#myForm').on('submit', function(e) {
var vcode = $('#txtVCode').val();
$.get('/Home/CheckVCode', { vcode }, function(res) {
if (!res.ok) { alert('驗證碼錯誤'); return false; }
// 正確則繼續提交
});
});
</script>
| 項目 | 說明 |
|---|---|
| 字元集 | 排除 0/O、1/I、8/B 等易混淆字元可進一步提升辨識率(本範例為簡化未排除)。 |
| 隨機種子 | 每次 new Random() 足夠;高併發下可改用 ThreadLocal<Random> 或 RandomNumberGenerator。 |
| 扭曲參數 | 振幅 3、隨機相位,讓 OCR 分割極其困難,人眼仍可辨識。 |
| 輸出格式 | BMP 無壓縮、生成快;若頻寬敏感可改 ImageFormat.Png。 |
| Session 儲存 | 分散式部署需改用 Redis、資料庫或加密 Cookie,避免 Session 丟失。 |
本文完整呈現了從 GDI+ 繪圖核心演算法、MVC 後端輸出、前端交互到部署注意事項的驗證碼全棧實作。代碼結構清晰、參數可配置,可直接納入專案或作為客製化基礎進一步強化安全性。