Java實現(xiàn)簡單文字驗證碼以及人機驗證
一、代碼引用
首先,如果你想直接用,可以直接用下面這個類。
可以調(diào)用CaptchaGenerator類中的captchaCreateImage方法,其方法參數(shù)列表為(int width, int height, int captchaLength, String[] returnCaptcha, int degree),方法返回驗證碼圖像。
width -----------------------文字驗證碼圖片的寬度
height-----------------------文字驗證碼圖片的高度
captchaLength-----------文字驗證碼的長度
returnCaptcha-----------返回的文字驗證碼
degree---------------------干擾的程度(1-5,不在范圍默認為5)
程度展示
| 一 | ![]() |
| 二 | ![]() |
| 三 | ![]() |
| 四 | ![]() |
| 五 | ![]() |
代碼引用處
import java.awt.*; // 導入 AWT 圖形庫
import java.awt.geom.AffineTransform; // 導入用于執(zhí)行幾何變換的類
import java.awt.image.BufferedImage; // 導入用于處理圖像的類
import java.util.Random; // 導入隨機數(shù)生成器類
import java.util.concurrent.ThreadLocalRandom;
public class CaptchaGenerator {
// 創(chuàng)建隨機數(shù)生成器
private final Random random = ThreadLocalRandom.current();
// 創(chuàng)建驗證碼的方法
private String createCaptcha(int length) {
// 定義可選字符池(不包含容易混淆的字符0和O等)
String charPool = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
StringBuilder result = new StringBuilder(); // 用于構(gòu)建驗證碼字符串
// 隨機選擇字符生成驗證碼
for (int i = 0; i < length; i++) {
result.append(charPool.charAt(random.nextInt(charPool.length()))); // 從字符池中隨機選擇字符
}
return result.toString(); // 返回生成的驗證碼字符串
}
// 創(chuàng)建顏色的方法,生成指定范圍內(nèi)隨機顏色
private Color createColor(int min, int max) {
int r = min + random.nextInt(max - min+1); // 隨機生成紅色分量
int g = min + random.nextInt(max - min+1); // 隨機生成綠色分量
int b = min + random.nextInt(max - min+1); // 隨機生成藍色分量
return new Color(r, g, b); // 創(chuàng)建并返回顏色對象
}
// 添加干擾元素的方法
private void addInterference(Graphics2D g, int degree, int width, int height) {
// 確保干擾元素的數(shù)量在0到5之間
degree = (degree <= 0 || degree > 5) ? 5 : degree;
// 根據(jù)度數(shù)生成干擾元素
for (int i = 0; i < degree * 20; i++) {
int x = random.nextInt(width); // 隨機生成x坐標
int y = random.nextInt(height); // 隨機生成y坐標
// 隨機選擇干擾元素的顏色
Color color = (random.nextBoolean()) ? createColor(0, 255) : ((random.nextBoolean()) ? Color.WHITE : Color.BLACK);
g.setColor(color); // 設置畫筆顏色
// 隨機選擇干擾元素的類型并畫出
switch (random.nextInt(3)) {
case 0 -> g.fillOval(x, y, random.nextInt(3) + 1, random.nextInt(3) + 1); // 畫圓點
case 1 -> {
int change = random.nextInt(3); // 隨機變化值
// 畫出線條構(gòu)成的隨機圖形
g.drawLine(x, y, x + change, y + change);
g.drawLine(x + change, y + change, x + 2 * change, y);
g.drawLine(x, y, x + change, y - change);
g.drawLine(x + change, y - change, x + 2 * change, y);
}
case 2 -> g.drawLine(x, y, x + random.nextInt(5) + 1, y + random.nextInt(5) + 1); // 畫線
}
}
// 生成更多隨機干擾線
for (int i = 0; i < 5 * degree; i++) {
Color color = (random.nextBoolean()) ? createColor(0, 255) : ((random.nextBoolean()) ? Color.WHITE : Color.BLACK);
g.setColor(color);
// 隨機生成干擾線的起止點
g.drawLine(random.nextInt(width), random.nextInt(height), random.nextInt(width), random.nextInt(height));
}
}
// 創(chuàng)建驗證碼圖像的方法
private BufferedImage createImage(int width, int height, int captchaLength, String[] returnCaptcha, int degree) {
// 創(chuàng)建新圖像
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics(); // 獲取圖形上下文
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // 啟用抗鋸齒
// 創(chuàng)建背景色
Color backgroundColor = createColor(0, 255);
g.setColor(backgroundColor);
g.fillRect(0, 0, width, height); // 填充背景
// 生成驗證碼
String captcha = createCaptcha(captchaLength);
returnCaptcha[0] = captcha; // 將生成的驗證碼存入數(shù)組
// 設置字體大小和干擾詳細參數(shù)
int fontSize = (int) (height * 0.5);
AffineTransform at = new AffineTransform(); // 創(chuàng)建變換對象
at.shear(random.nextDouble() * 0.4 - 0.2, random.nextDouble() * 0.4 - 0.2); // 隨機傾斜變換
Font font = new Font(Font.SANS_SERIF, Font.BOLD, fontSize); // 創(chuàng)建字體對象
font = font.deriveFont(at); // 生成傾斜字體
addInterference(g, degree, width, height); // 添加干擾元素
Color fontColor, prevFontColor = null; // 字體顏色和前一個字體顏色
int fontX, fontY, fontWidth, changeX; // 不同的坐標和寬度
FontMetrics fontMetrics = g.getFontMetrics(); // 字體度量
fontWidth = fontMetrics.stringWidth(captcha) + (captchaLength - 1) * (int) (width * 0.05); // 計算驗證碼的寬度
fontX = (width - fontWidth) / 2; // 計算x坐標以居中對齊
fontY = (height - (fontMetrics.getAscent() + fontMetrics.getDescent())) / 2 + fontMetrics.getAscent(); // 計算y坐標
double tempX = fontX; // 保存當前x坐標
// 逐個繪制驗證碼字符
for (int i = 0; i < captchaLength; i++) {
// 根據(jù)背景色的亮度生成對比度較強的字體顏色
fontColor = (backgroundColor.getRed() > 180) ? createColor(0, 160) : createColor(200, 255);
int maxAttempts = 10,count=0;//設置最大循環(huán)數(shù),避免死循環(huán)
// 確保字體顏色與前一個字體顏色不相近
while (prevFontColor != null&&count++<maxAttempts) {
double brightness = (backgroundColor.getRed() * 299 + backgroundColor.getBlue() * 114 + backgroundColor.getGreen() * 587) / 1000.0;
fontColor = (brightness > 128) ? createColor(0, 128) : createColor(128, 255); // 確定字體顏色
int dr = fontColor.getBlue() - prevFontColor.getBlue();
int dg = fontColor.getGreen() - prevFontColor.getGreen();
int db = fontColor.getRed() - prevFontColor.getRed();
prevFontColor = fontColor; // 更新前一個顏色
// 如果顏色差異大于亮度則退出循環(huán)
if (Math.sqrt(dr * dr + dg * dg + db * db) > brightness) break;
}
prevFontColor = fontColor; // 更新前一個顏色
g.setFont(font); // 設置當前字體
g.setColor(fontColor); // 設置當前字體顏色
// 隨機旋轉(zhuǎn)角度
int RotationAngle = random.nextInt(60) - 30;
g.rotate(Math.toRadians(RotationAngle), tempX, fontY); // 繞中心點旋轉(zhuǎn)
// 繪制字符
g.drawString(String.valueOf(captcha.charAt(i)), (int) tempX, fontY);
g.rotate(-Math.toRadians(RotationAngle), tempX, fontY); // 逆旋轉(zhuǎn)恢復狀態(tài)
changeX = fontMetrics.stringWidth(String.valueOf(captcha.charAt(i))); // 獲取當前字符寬度
tempX += (changeX + (int) (width * 0.05)); // 更新臨時x坐標,為下一個字符準備空間
}
g.dispose(); // 釋放圖形上下文資源
if(captchaLength <= 0|| returnCaptcha[0].isEmpty()) {
throw new IllegalArgumentException("returnCaptcha array must be non-null and have at least one element");
}
return image; // 返回生成的圖像
}
public BufferedImage captchaCreateImage(int width, int height, int captchaLength, String[] returnCaptcha, int degree){
return createImage(width, height, captchaLength, returnCaptcha, degree);
}
}二、代碼實現(xiàn)
1.生成驗證碼
由于0和O等字符容易混淆,因此需要去除這些字符。
private String createCaptcha(int length){
String charPool = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
StringBuilder result = new StringBuilder();
for (int i = 0; i < length; i++) {
result.append(charPool.charAt(random.nextInt(charPool.length())));
}
return result.toString();
}2.生成圖片前的準備
(1)創(chuàng)建一個指定寬度、高度和類型的BufferedImage對象
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
(2)獲取Graphics2D對象,用于繪制圖像,并啟用抗鋸齒
Graphics2D g = image.createGraphics(); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
(3)填充背景顏色
Color backgroundColor = createColor(0, 255); // 隨機生成背景顏色 g.setColor(backgroundColor); // 設置背景顏色 g.fillRect(0, 0, width, height); // 填充背景色
(4)獲取驗證碼
String captcha = createCaptcha(captchaLength); returnCaptcha[0]=captcha;
3.圖片添加干擾
總體預覽
private void addInterference(Graphics2D g,int degree,int width,int height){
degree=(degree <= 0||degree > 5)?5:degree;
for (int i = 0; i < degree*20; i++) {
int x=random.nextInt(width);
int y=random.nextInt(height);
Color color=(random.nextBoolean())?createColor(0,255):((random.nextBoolean())?Color.WHITE:Color.BLACK);
g.setColor(color);
switch (random.nextInt(3)){
case 0-> g.fillOval(x,y, random.nextInt(3)+1,random.nextInt(3)+1);
case 1-> {
int change=random.nextInt(3);
g.drawLine(x,y,x+change,y+change);
g.drawLine(x+change,y+change,x+2*change,y);
g.drawLine(x,y,x+change,y-change);
g.drawLine(x+change,y-change,x+2*change,y);
}
case 2->g.drawLine(x,y,x+random.nextInt(5)+1,y+random.nextInt(5)+1);
}
}
for(int i=0;i<5*degree;i++){
Color color=(random.nextBoolean())?createColor(0,255):((random.nextBoolean())?Color.WHITE:Color.BLACK);
g.setColor(color);
g.drawLine(random.nextInt(width),random.nextInt(height),random.nextInt(width),random.nextInt(height));
}
}(1)確保程度合法
degree=(degree <= 0||degree > 5)?5:degree;
(2)生成噪點
a.隨機坐標
int x=random.nextInt(width); int y=random.nextInt(height);
b.顏色設置
要么彩色,要么黑白。
Color color=(random.nextBoolean())?createColor(0,255):((random.nextBoolean())?Color.WHITE:Color.BLACK); g.setColor(color);
c.噪點樣式選擇
總體預覽
switch (random.nextInt(3)){
case 0-> g.fillOval(x,y, random.nextInt(3)+1,random.nextInt(3)+1);
case 1-> {
int change=random.nextInt(3);
g.drawLine(x,y,x+change,y+change);
g.drawLine(x+change,y+change,x+2*change,y);
g.drawLine(x,y,x+change,y-change);
g.drawLine(x+change,y-change,x+2*change,y);
}
case 2->g.drawLine(x,y,x+random.nextInt(5)+1,y+random.nextInt(5)+1);
}1.橢圓形
case 0-> g.fillOval(x,y, random.nextInt(3)+1,random.nextInt(3)+1);
2.菱形
case 1-> {
int change=random.nextInt(3);
g.drawLine(x,y,x+change,y+change);
g.drawLine(x+change,y+change,x+2*change,y);
g.drawLine(x,y,x+change,y-change);
g.drawLine(x+change,y-change,x+2*change,y);
}3.隨機短線段
case 2->g.drawLine(x,y,x+random.nextInt(5)+1,y+random.nextInt(5)+1);
4.驗證碼內(nèi)容處理
(1)處理字體
總體預覽
int fontSize=(int)(height*0.5);
AffineTransform at = new AffineTransform();
at.shear(random.nextDouble()*0.4-0.2,random.nextDouble()*0.4-0.2);
Font font=new Font("微軟雅黑", Font.BOLD,fontSize);
font=font.deriveFont(at);
g.setFont(font);a.設置字體大小
int fontSize=(int)(height*0.5);
b.創(chuàng)建AffineTransform對象,用于幾何變換,將字體扭曲,程度為[-0.2,0.2]。
AffineTransform at = new AffineTransform(); at.shear(random.nextDouble()*0.4-0.2,random.nextDouble()*0.4-0.2);
c.設置字體樣式,并應用扭曲
Font font=new Font("微軟雅黑", Font.BOLD,fontSize);
font=font.deriveFont(at);(2)字符居中處理
總體預覽
int fontX,fontY,fontWidth,changeX; FontMetrics fontMetrics = g.getFontMetrics(); fontWidth=fontMetrics.stringWidth(captcha)+(captchaLength-1)*(int)(width*0.05); fontX=(width-fontWidth)/2; fontY=(height - (fontMetrics.getAscent() + fontMetrics.getDescent())) / 2 + fontMetrics.getAscent();
a.獲取橫坐標
fontMetrics.stringWidth(captcha)是所有的字符的總寬度。
(captchaLength-1)*(int)(width*0.05)是字符之間的總間隔大小,其中(captchaLength-1)為間隔數(shù),(width*0.05)為間隔大小。
fontWidth=fontMetrics.stringWidth(captcha)+(captchaLength-1)*(int)(width*0.05); fontX=(width-fontWidth)/2;
b.獲取縱坐標
FontMetrics fontMetrics = g.getFontMetrics(); fontY=(height - (fontMetrics.getAscent() + fontMetrics.getDescent())) / 2 + fontMetrics.getAscent();
fontMetrics.getAscent():基線到字體最高點的距離
fontMetrics.getDescent():基線到字體最低點的距離
怎么理解?
FontMetrics類提供了關鍵參數(shù):
Ascent:基線到字體最高點的距離(如字母"h"的頂部)。
Descent:基線到字體最低點的距離(如字母"g"的尾部)。
Leading:行間距(通常較?。?。
Height:總高度(
Ascent + Descent + Leading)。
一、將文字想象成一個“盒子”
假設每個字符是一個矩形盒子,其高度由三部分組成:
Ascent(上升) :基線(baseline)到盒子頂部的距離(如字母"h"的頂部)。
Descent(下降) :基線到盒子底部的距離(如字母"g"的尾部)。
Leading(行間距) :盒子下方預留的空白(通常很小,可暫時忽略)。
二、Java繪圖的“基線對齊”
當調(diào)用 g.drawString(text, x, y) 時:
參數(shù)
y是基線的位置,而非文字區(qū)域的頂部或中心。如果直接將畫布中點(
height/2)作為基線位置,文字會整體偏下,因為Ascent部分會向上延伸,而Descent部分會向下延伸。
畫布頂部(y=50) | | | | | —————————— ← Ascent(y=35) | | —————————— ← 基線(y=30) | | —————————— ← 畫布中心(y=25) | ▲ | | | ▼ | —————————— ← Descent(y=15) | | | | 畫布底部(y=0)
(3)字符顏色處理
總體預覽
fontColor=(backgroundColor.getRed()>180)?createColor(0,160):createColor(200,255);
int maxAttempts = 10,count=0;
while (prevFontColor!=null&&count++<maxAttempts) {
double brightness=(backgroundColor.getRed()*299+backgroundColor.getBlue()*114+backgroundColor.getGreen()*587)/1000.0;
fontColor=(brightness>128)?createColor(0,128):createColor(128,255);
int dr=fontColor.getBlue()-prevFontColor.getBlue();
int dg=fontColor.getGreen()-prevFontColor.getGreen();
int db=fontColor.getRed()-prevFontColor.getRed();
prevFontColor=fontColor;
if(Math.sqrt(dr*dr+dg*dg+db*db)>brightness)break;
}
prevFontColor=fontColor;
g.setColor(fontColor);a.隨機顏色
避免與背景顏色相近。
fontColor=(backgroundColor.getRed()>180)?createColor(0,160):createColor(200,255);
b.處理相鄰顏色相近與對比度不明顯問題
總體預覽
while (prevFontColor!=null&&count++<maxAttempts) {
double brightness=(backgroundColor.getRed()*299+backgroundColor.getBlue()*114+backgroundColor.getGreen()*587)/1000.0;
fontColor=(brightness>128)?createColor(0,128):createColor(128,255);
int dr=fontColor.getBlue()-prevFontColor.getBlue();
int dg=fontColor.getGreen()-prevFontColor.getGreen();
int db=fontColor.getRed()-prevFontColor.getRed();
prevFontColor=fontColor;
if(Math.sqrt(dr*dr+dg*dg+db*db)>brightness)break;
}
prevFontColor=fontColor;1.顏色差異檢測機制(歐氏距離)
顏色距離公式:
使用歐氏距離公式計算顏色差異:
\Delta = \sqrt{(R_1-R_2)^2 + (G_1-G_2)^2 + (B_1-B_2)^2}該公式能綜合衡量RGB三個通道的差異,更符合人眼對顏色差異的感知
2.背景對比度優(yōu)化(YIQ模型)
YIQ亮度模型:
使用公式:Y = 0.299R + 0.587G + 0.114B
該模型更貼近人眼對亮度的敏感度(綠色權(quán)重最高,藍色最低)。
對比度標準:
若背景亮度
Y > 128(亮色背景),選擇深色字體(如黑色、深藍)若
Y ≤ 128(暗色背景),選擇淺色字體(如白色、亮黃)綜合效果
抗機器識別:
顏色差異和色相跳躍破壞OCR工具的顏色聚類算法,使其難以分割相鄰字符。
邊緣描邊干擾輪廓識別算法。
人類可讀性:
高對比度確保文字清晰,符合人眼對亮度、色相的敏感特性。
隨機旋轉(zhuǎn)和扭曲保留驗證碼的防機器特性,但不會影響人類閱讀。
(4)字符旋轉(zhuǎn)處理
總體預覽
int RotationAngle=random.nextInt(60)-30; g.rotate(Math.toRadians(RotationAngle),tempX,fontY); g.drawString(String.valueOf(captcha.charAt(i)),(int)tempX,fontY); g.rotate(-Math.toRadians(RotationAngle),tempX,fontY); changeX=fontMetrics.stringWidth(String.valueOf(captcha.charAt(i))); tempX+=(changeX+(int)(width*0.05));
a.隨機角度[-30°,30°]
int RotationAngle=random.nextInt(60)-30;
b.旋轉(zhuǎn)字符
1.旋轉(zhuǎn)畫布
g.rotate(Math.toRadians(RotationAngle),tempX,fontY);
g.rotate(double theta, double x, double y)方法用于旋轉(zhuǎn)當前的Graphics上下文,影響之后的繪圖操作。Math.toRadians(RotationAngle):將角度從度轉(zhuǎn)換為弧度,因為 Java 的rotate方法需要以弧度為單位的旋轉(zhuǎn)角度。tempX和fontY:這里指定了旋轉(zhuǎn)中心的坐標。tempX:是文本的當前 x 坐標,通常根據(jù)文本的長度動態(tài)計算,使得字符繪制在適當?shù)奈恢谩?/p>fontY:是文本的 y 坐標,通常是一個固定值,確保文本在畫布的某一高度。
通過這行代碼,當前畫布將圍繞指定的 (tempX, fontY) 點旋轉(zhuǎn),旋轉(zhuǎn)角度為 RotationAngle。
2.繪制字符
g.drawString(String.valueOf(captcha.charAt(i)),(int)tempX,fontY);
g.drawString(String str, int x, int y)方法用于在畫布上繪制字符串。String.valueOf(captcha.charAt(i)):從名為captcha的字符串中獲取索引為i的字符,并將其轉(zhuǎn)換為字符串(通常可以直接使用captcha.charAt(i))。(int)tempX和fontY:這是繪制文本的坐標。tempX:是在之前計算的 x 坐標,代表文本在水平方向上的位置。fontY:是文本在垂直方向上的固定高度。
這行代碼則在旋轉(zhuǎn)后的畫布上繪制當前字符。
3.恢復畫布的旋轉(zhuǎn)狀態(tài)
g.rotate(-Math.toRadians(RotationAngle),tempX,fontY);
這行代碼用于撤銷之前的旋轉(zhuǎn)操作。
-Math.toRadians(RotationAngle):使用負的角度進行旋轉(zhuǎn),與之前的旋轉(zhuǎn)相反。tempX和fontY:依然是旋轉(zhuǎn)中心的坐標。
這個步驟確保在繪制完當前字符后,畫布的狀態(tài)返回到原始位置,方便后續(xù)繪制其他字符時不會受到影響。
c.繪制字符位置處理
changeX=fontMetrics.stringWidth(String.valueOf(captcha.charAt(i))); tempX+=(changeX+(int)(width*0.05));
1. 計算字符寬度
captcha.charAt(i):從captcha字符串中獲取索引i處的字符。這是當前需要繪制的字符。String.valueOf(...):將獲取的字符轉(zhuǎn)換為字符串。雖然在 Java 中,char類型可以直接用于字符串操作,但使用String.valueOf方法能確保我們在需要時轉(zhuǎn)換為字符串類型。fontMetrics.stringWidth(...):這個方法是FontMetrics類中的一個方法,它可以返回指定字符串在當前字體下的寬度(以像素為單位)。也就是說,它計算出當前正在使用的字體繪制String.valueOf(captcha.charAt(i))這一個字符所需要的寬度。changeX:這是一個變量,用于存儲當前字符的寬度。這將被用來調(diào)整下一個字符的繪制位置。
2.更新繪制位置
tempX:這是一個變量,表示當前字符在繪圖上下文中的 x 坐標。它記錄了下一個字符應該繪制的 horizontal (水平方向) 位置。changeX:上面計算獲得的當前字符寬度。(int)(width * 0.05):這部分代碼用于在字符之間添加一定的間距。在這里,width是整張圖片的寬度,而width * 0.05表示取寬度的 5%。通過將結(jié)果轉(zhuǎn)換為int,確保我們將這個浮點值作為整數(shù)處理,適配繪圖 API 對坐標的要求。+=:這個操作符用于將當前的tempX值加上當前字符的寬度和額外的間距,然后更新tempX的值。這一步確保下一個字符繪制時不會重疊,而是位于上一個字符的右側(cè),并且留有一定的間隙。
總結(jié)
到此這篇關于Java實現(xiàn)簡單文字驗證碼以及人機驗證的文章就介紹到這了,更多相關Java文字驗證碼及人機驗證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Spring Boot高級教程之使用Redis實現(xiàn)session共享
這篇文章主要為大家詳細介紹了Spring Boot高級教程之使用Redis實現(xiàn)session共享,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-10-10
Java toString方法重寫工具之ToStringBuilder案例詳解
這篇文章主要介紹了Java toString方法重寫工具之ToStringBuilder案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-08-08
SpringCloud客戶端的負載均衡Ribbon的實現(xiàn)
微服務架構(gòu),不可避免的存在單個微服務有多個實例,這篇文章主要介紹了SpringCloud客戶端的負載均衡Ribbon的實現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-06-06
被kafka-client和springkafka版本坑到自閉及解決
這篇文章主要介紹了被kafka-client和springkafka版本坑到自閉及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
Spring Boot實戰(zhàn)之發(fā)送郵件示例代碼
本篇文章主要介紹了Spring Boot實戰(zhàn)之發(fā)送郵件示例代碼,具有一定的參考價值,有興趣的可以了解一下。2017-03-03






