当前位置: 代码网 > it编程>编程语言>Java > Java实现简单文字验证码以及人机验证

Java实现简单文字验证码以及人机验证

2025年04月08日 Java 我要评论
一、代码引用首先,如果你想直接用,可以直接用下面这个类。可以调用captchagenerator类中的captchacreateimage方法,其方法参数列表为(intwidth, intheight

一、代码引用

首先,如果你想直接用,可以直接用下面这个类。

可以调用captchagenerator类中的captchacreateimage方法,其方法参数列表为(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; // 导入用于执行几何变换的类
import java.awt.image.bufferedimage; // 导入用于处理图像的类
import java.util.random; // 导入随机数生成器类
import java.util.concurrent.threadlocalrandom;

public class captchagenerator {
    // 创建随机数生成器
    private final random random = threadlocalrandom.current();

    // 创建验证码的方法
    private string createcaptcha(int length) {
        // 定义可选字符池(不包含容易混淆的字符0和o等)
        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(); // 返回生成的验证码字符串
    }

    // 创建颜色的方法,生成指定范围内随机颜色
    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); // 创建并返回颜色对象
    }

    // 添加干扰元素的方法
    private void addinterference(graphics2d g, int degree, int width, int height) {
        // 确保干扰元素的数量在0到5之间
        degree = (degree <= 0 || degree > 5) ? 5 : degree;

        // 根据度数生成干扰元素
        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.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));
        }
    }

    // 创建验证码图像的方法
    private bufferedimage createimage(int width, int height, int captchalength, string[] returncaptcha, int degree) {

        // 创建新图像
        bufferedimage image = new bufferedimage(width, height, bufferedimage.type_int_rgb);
        graphics2d g = image.creategraphics(); // 获取图形上下文
        g.setrenderinghint(renderinghints.key_antialiasing, renderinghints.value_antialias_on); // 启用抗锯齿

        // 创建背景色
        color backgroundcolor = createcolor(0, 255);
        g.setcolor(backgroundcolor);
        g.fillrect(0, 0, width, height); // 填充背景

        // 生成验证码
        string captcha = createcaptcha(captchalength);
        returncaptcha[0] = captcha; // 将生成的验证码存入数组

        // 设置字体大小和干扰详细参数
        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.sans_serif, font.bold, fontsize); // 创建字体对象
        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++) {
            // 根据背景色的亮度生成对比度较强的字体颜色
            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.setfont(font); // 设置当前字体
            g.setcolor(fontcolor); // 设置当前字体颜色

            // 随机旋转角度
            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)); // 更新临时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);
    }
}

二、代码实现

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)创建一个指定宽度、高度和类型的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.验证码内容处理

(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.创建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)为间隔数,(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类提供了关键参数:

  • ascent:基线到字体最高点的距离(如字母"h"的顶部)。

  • descent:基线到字体最低点的距离(如字母"g"的尾部)。

  • leading:行间距(通常较小)。

  • height:总高度(ascent + descent + leading)。

一、将文字想象成一个“盒子”

假设每个字符是一个矩形盒子,其高度由三部分组成:

  • ascent(上升) :基线(baseline)到盒子顶部的距离(如字母"h"的顶部)。

  • descent(下降) :基线到盒子底部的距离(如字母"g"的尾部)。

  • leading(行间距) :盒子下方预留的空白(通常很小,可暂时忽略)。 

二、java绘图的“基线对齐”

当调用 g.drawstring(text, x, y) 时:

  • 参数y是基线的位置,而非文字区域的顶部或中心。

  • 如果直接将画布中点(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.背景对比度优化(yiq模型)

  • yiq亮度模型
    使用公式:

    y = 0.299r + 0.587g + 0.114b
    

    该模型更贴近人眼对亮度的敏感度(绿色权重最高,蓝色最低)。

  • 对比度标准

    • 若背景亮度y > 128(亮色背景),选择深色字体(如黑色、深蓝)

    • y ≤ 128(暗色背景),选择浅色字体(如白色、亮黄)

      综合效果

    • 抗机器识别

      • 颜色差异和色相跳跃破坏ocr工具的颜色聚类算法,使其难以分割相邻字符。

      • 边缘描边干扰轮廓识别算法

    • 人类可读性

      • 高对比度确保文字清晰,符合人眼对亮度、色相的敏感特性。

      • 随机旋转和扭曲保留验证码的防机器特性,但不会影响人类阅读。

(4)字符旋转处理

总体预览

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.旋转字符

1.旋转画布

g.rotate(math.toradians(rotationangle),tempx,fonty);
  • g.rotate(double theta, double x, double y) 方法用于旋转当前的 graphics 上下文,影响之后的绘图操作

    • math.toradians(rotationangle):将角度从度转换为弧度,因为 java 的 rotate 方法需要以弧度为单位的旋转角度。

    • tempx 和 fonty:这里指定了旋转中心的坐标。

      • tempx:是文本的当前 x 坐标,通常根据文本的长度动态计算,使得字符绘制在适当的位置。

      • fonty:是文本的 y 坐标,通常是一个固定值,确保文本在画布的某一高度。

通过这行代码,当前画布将围绕指定的 (tempx, fonty) 点旋转,旋转角度为 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 的字符,并将其转换为字符串(通常可以直接使用 captcha.charat(i))。

  • (int)tempx 和 fonty:这是绘制文本的坐标。

    • tempx:是在之前计算的 x 坐标,代表文本在水平方向上的位置。

    • fonty:是文本在垂直方向上的固定高度。

这行代码则在旋转后的画布上绘制当前字符。

3.恢复画布的旋转状态

g.rotate(-math.toradians(rotationangle),tempx,fonty);
  • 这行代码用于撤销之前的旋转操作。

    • -math.toradians(rotationangle):使用负的角度进行旋转,与之前的旋转相反。

    • tempx 和 fonty:依然是旋转中心的坐标。

这个步骤确保在绘制完当前字符后,画布的状态返回到原始位置,方便后续绘制其他字符时不会受到影响。

c.绘制字符位置处理

changex=fontmetrics.stringwidth(string.valueof(captcha.charat(i)));
tempx+=(changex+(int)(width*0.05));

1. 计算字符宽度

  • captcha.charat(i):从 captcha 字符串中获取索引 i 处的字符。这是当前需要绘制的字符。

  • string.valueof(...):将获取的字符转换为字符串。虽然在 java 中,char 类型可以直接用于字符串操作,但使用 string.valueof 方法能确保我们在需要时转换为字符串类型。

  • fontmetrics.stringwidth(...):这个方法是 fontmetrics 类中的一个方法,它可以返回指定字符串在当前字体下的宽度(以像素为单位)。也就是说,它计算出当前正在使用的字体绘制 string.valueof(captcha.charat(i)) 这一个字符所需要的宽度。

  • changex:这是一个变量,用于存储当前字符的宽度。这将被用来调整下一个字符的绘制位置。

2.更新绘制位置

  • tempx:这是一个变量,表示当前字符在绘图上下文中的 x 坐标。它记录了下一个字符应该绘制的 horizontal (水平方向) 位置。

  • changex:上面计算获得的当前字符宽度。

  • (int)(width * 0.05):这部分代码用于在字符之间添加一定的间距。在这里,width 是整张图片的宽度,而 width * 0.05 表示取宽度的 5%。通过将结果转换为 int,确保我们将这个浮点值作为整数处理,适配绘图 api 对坐标的要求。

  • +=:这个操作符用于将当前的 tempx 值加上当前字符的宽度和额外的间距,然后更新 tempx 的值。这一步确保下一个字符绘制时不会重叠,而是位于上一个字符的右侧,并且留有一定的间隙。

总结 

到此这篇关于java实现简单文字验证码以及人机验证的文章就介绍到这了,更多相关java文字验证码及人机验证内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com