java生成图形验证码2.0详细教程 带注释 拷贝即用 兼容linux系统及jdk8后续版本

先看效果

在这里插入图片描述
在这里插入图片描述

这是正常状态下的验证码大小

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最近在用jdk8之后的版本做项目 发现之前写的验证码生成器不好使了
发现jdk8之后没有sun.font.FontDesignMetrics这个类了
于是开发了这个2.0版本同时也在1.0基础上进行了优化

这里是1.0版本:
https://blog.csdn.net/u010756046/article/details/103535179

1.准备字体

在这里插入图片描述

把字体丢到项目目录下的fonts文件夹里 这里不提供字体下载了 网上找找就有了
没有字体怎么办?

打开控制面板
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里的字体随便拷就行了 注意有些字体是不支持中文的 当使用了那些不支持中文的字体生成含有中文的验证码时会出现乱码

2.创建好一个普通的java项目之后就可以开始敲了

package cn.liziguo.utils;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;

/**
 * @author Liziguo
 * @date 2021/12/19 23:32
 */
public final class Captcha {

    public final String id;

    public final String code;

    public final BufferedImage image;

    public Captcha(String id, String code, BufferedImage image) {
        this.id = id;
        this.code = code;
        this.image = image;
    }

    /**
     * @return 获取小写验证码
     */
    public String getLowerCode() {
        return code.toLowerCase();
    }

    /**
     * @return 大写验证码
     */
    public String getUpperCode() {
        return code.toUpperCase();
    }

    /**
     * 把图片转换成字节数组
     *
     * @return
     */
    public byte[] toBytes() {
        try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            ImageIO.write(image, "png", out);
            return out.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 生成图片的base64编码
     *
     * @return
     */
    public String toBase64Code() {
        return Base64.getEncoder().encodeToString(toBytes());
    }

}


package cn.liziguo.utils;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;

/**
 * @author Liziguo
 * @date 2021/12/19 23:32
 */
public final class CaptchaUtils {

    /**
     * 所有字母和数字
     */
    private static final String ALL = "0123456789qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM";

    /**
     * 去除容易混淆的字母
     */
    private static final String UNAMBIGUOUS = "234678wertyuipadfghjkzxcvbnmQWERTYUIPADFGHJKLZXCVBNM";

    /**
     * 字体颜色和背景颜色的最小色差
     */
    private static final int MIN_TOLERANCE = 100;

    /**
     * 噪声率
     */
    private static final float RATIO = 0.05f;

    /**
     * 本地字体库
     */
    private static final Font[] FONTS = initFonts();

    /**
     * 加载fonts文件夹下的所有字体
     *
     * @return 字体数组
     */
    private static Font[] initFonts() {
        final File file = new File("fonts");
        final File[] files = file.listFiles();
        if (files == null) {
            throw new RuntimeException("未找到字体目录");
        }
        if (files.length == 0) {
            throw new RuntimeException("fonts目录下至少放置一种字体");
        }
        final Font[] fonts = new Font[files.length];
        for (int i = 0, len = files.length; i < len; i++) {
            try {
                fonts[i] = Font.createFont(Font.TRUETYPE_FONT, files[i]);
            } catch (FontFormatException | IOException e) {
                e.printStackTrace();
                throw new RuntimeException("字体[" + files[i].getName() + "]初始化异常");
            }
        }
        return fonts;
    }

    /**
     * 获取随机颜色
     *
     * @param random random
     * @return 随机颜色
     */
    public static Color randomColor(final Random random) {
        return new Color(random.nextInt(0x1000000));
    }

    /**
     * 计算c1 c2的色差
     *
     * @param c1 颜色1
     * @param c2 颜色2
     * @return 色差
     */
    public static int tolerance(final Color c1, final Color c2) {
        final int r = c1.getRed() - c2.getRed();
        final int g = c1.getGreen() - c2.getGreen();
        final int b = c1.getBlue() - c2.getBlue();
        int max = 0;
        if (r > max) max = r;
        if (g > max) max = g;
        if (b > max) max = b;
        int min = 0;
        if (r < min) min = r;
        if (g < min) min = g;
        if (b < min) min = b;
        return max - min;
    }

    /**
     * 生成长度为len的随机字符串
     *
     * @param len 长度
     * @return 随机字符串
     */
    public static String randomStr(final int len) {
        return randomStr(ThreadLocalRandom.current(), len);
    }

    /**
     * 生成长度为len的随机字符串
     *
     * @param random random
     * @param len    长度
     * @return 随机字符串
     */
    public static String randomStr(final Random random, final int len) {
        final String c = ALL;
        final StringBuilder b = new StringBuilder();
        for (int i = 0; i < len; i++) {
            b.append(c.charAt(random.nextInt(c.length())));
        }
        return b.toString();
    }

    /**
     * 生成长度为len不易混淆的随机字符串
     *
     * @param len 长度
     * @return 随机字符串
     */
    public static String randomCode(final int len) {
        return randomCode(ThreadLocalRandom.current(), len);
    }

    /**
     * 生成长度为len不易混淆的随机字符串
     *
     * @param len 长度
     * @return 随机字符串
     */
    public static String randomCode(final Random random, final int len) {
        final String c = UNAMBIGUOUS;
        final StringBuilder b = new StringBuilder();
        for (int i = 0; i < len; i++) {
            b.append(c.charAt(random.nextInt(c.length())));
        }
        return b.toString();
    }

    /**
     * 生成180 * 60 字体大小为60 长度为4 的随机验证码
     * 最佳宽高计算公式 height = fontSize  width = height * len * 0.75
     *
     * @return 验证码
     */
    public static Captcha create() {
        return create(randomCode(4), 180, 60, 60);
    }

    /**
     * 生成指定字符串验证码 最佳宽高计算公式 height = fontSize  width = height * len * 0.75
     *
     * @param code     验证码
     * @param width    图片宽度
     * @param height   图片高度
     * @param fontSize 字体大小
     * @return 验证码
     */
    public static Captcha create(final String code, final int width, final int height, final int fontSize) {
        final ThreadLocalRandom random = ThreadLocalRandom.current();
        final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        final Graphics2D g = image.createGraphics();
        //抗锯齿
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        //干扰线 噪点粗细
        final float stroke = fontSize / 40f;

        //背景颜色
        final Color backColor = randomColor(random);

        //先给背景上色
        g.setColor(backColor);
        g.fillRect(0, 0, width, height);

        //绘制干扰线
        g.setStroke(new BasicStroke(stroke));//干扰线粗细
        for (int i = 0; i < 20; i++) {
            //设置线条的颜色
            g.setColor(randomColor(random));
            g.drawLine(random.nextInt(width), random.nextInt(height), random.nextInt(width * 2) - width / 2, random.nextInt(height * 2) - height / 2);
        }

        //绘制验证码
        //Font.deriveFont(int style, float size)通过复制此 Font 对象并应用新样式和大小,创建一个新 Font 对象
        //从我们的字体库里随机取一种字体并重新设置样式大小
        final Font font = FONTS[random.nextInt(FONTS.length)].deriveFont(Font.BOLD, fontSize);
        g.setFont(font);
        //通过这个类获取字体的宽高 以保证验证码画在图片合适的位置
        final FontMetrics fontMetrics = g.getFontMetrics();
        //确定每个字符的位置
        final int bodyX = width / code.length();
        for (int i = 0; i < code.length(); i++) {
            //取第i个字符
            final String str = code.substring(i, i + 1);
            //字体宽度
            final int strWidth = fontMetrics.stringWidth(str);
            //字体高度
            final int strHeight = fontMetrics.getHeight();
            //基线baseline之上的长度
            final int ascent = fontMetrics.getAscent();
            //xy为每个字符的中心坐标
            final float x = i * bodyX + bodyX / 2f;
            final float y = height / 2f;
            Color fontColor = randomColor(random);
            //获取一个和背景色相差比较大的颜色 取值0到510 越大与背景色越好区分 这里取100表示色差小于100的话就创新取色
            while (tolerance(backColor, fontColor) < MIN_TOLERANCE) {
                fontColor = randomColor(random);
            }
            g.setColor(fontColor);
            //角度在-45°到45°之间
            final double angle = random.nextDouble() * Math.PI / 2 - Math.PI / 4;
            //旋转画笔
            g.rotate(angle, x, y);
            //把验证码的第i个字符画到图片上
            g.drawString(str, x - strWidth / 2F, y + ascent - strHeight / 2F);
            //再转回来
            g.rotate(-angle, x, y);
        }

        //绘制干扰线
        for (int i = 0; i < 5; i++) {
            //设置线条的颜色
            g.setColor(randomColor(random));
            g.drawLine(random.nextInt(width), random.nextInt(height), random.nextInt(width * 2) - width, random.nextInt(height * 2) - height);
        }

        //绘制噪点
        //噪声率 × 总面积 ÷ 点面积 保证图片越大噪点不会越多
        final int area = (int) (RATIO * (width * height) / (Math.PI * stroke / 2 * stroke / 2));
        final int strokeInt = stroke > 1 ? (int) stroke : 1;
        final int strokeDiv2 = (int) (stroke / 2);
        for (int i = 0; i < area; i++) {
            final int x = random.nextInt(width) - strokeDiv2;
            final int y = random.nextInt(height) - strokeDiv2;
            g.setColor(randomColor(random));
            g.fillOval(x, y, strokeInt, strokeInt);
        }

        //<img src="data:image/png;base64,(你的base64编码)"/>
        //给验证码随机生成一个id
        final String id = randomStr(32);
        return new Captcha(id, code, image);
    }

}


3.创建测试类

package cn.liziguo;

import cn.liziguo.utils.Captcha;
import cn.liziguo.utils.CaptchaUtils;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.filechooser.FileSystemView;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

/**
 * @author Liziguo
 * @date 2021/12/19 23:32
 */
public class Test extends JFrame {

    private BufferedImage image;

    public Test() {
        setResizable(false);//用户不能随意调节窗口大小
        reImage();
        setSize(image.getWidth(), image.getHeight());//设置大小
        setLocationRelativeTo(null);//窗口居中
        setDefaultCloseOperation(3);//x掉后彻底退出程序
        setAlwaysOnTop(true);//窗口始终在最上方
        setUndecorated(true);//去除修饰(去除窗口边框、最大化、最小化、关闭按钮等)
        //添加一个键盘事件 按Esc关闭窗口 F5重新生成验证码
        addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == 27) {
                    //27是键盘Esc的Ascii码
                    System.exit(0);
                } else if (e.getKeyCode() == 116) {
                    //116是键盘F5的Ascii码
                    reImage();
                }
            }
        });
        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                reImage();
            }
        });
        setVisible(true);//显示
    }

    private void reImage() {
        final String code = CaptchaUtils.randomCode(4);//随机生成4位数字符串
        //最佳宽高计算公式 height = fontSize  width = height * len * 0.75
//        final Captcha v = CaptchaUtils.create(code, 160, 60, 60);//生成图形验证码
        final Captcha v = CaptchaUtils.create(code, 1200, 400, 400);//生成图形验证码
//        final Captcha v = CaptchaUtils.create("我好想你", 1920, 1080, 400);//生成图形验证码
        image = v.image;
        setSize(image.getWidth(), image.getHeight());//设置大小
        setLocationRelativeTo(null);//窗口居中
        repaint();//重绘
        System.out.println("验证码信息:");
        System.out.println("id:" + v.id);
        System.out.println("code:" + v.code);
        System.out.println("lowerCode:" + v.getLowerCode());
        System.out.println("upperCode:" + v.getUpperCode());
        System.out.println("imageBase64Code:" + v.toBase64Code());
        //获取桌面路径
        File file = new File(FileSystemView.getFileSystemView().getHomeDirectory().getAbsolutePath() + "/captcha.png");
        //把这张验证码保存到桌面
        try {
            ImageIO.write(v.image, "png", file);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        //这里我们重写一下paint方法 把图片画出来
        g.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null);
    }

    public static void main(String[] args) throws IOException {
        new Test();
    }
}


4.运行

点击窗口 或 按F5 重新生成验证码
按Esc关闭
在这里插入图片描述

5.补充

如果报"sun.awt.FontConiguration.head" is null的话执行下面命令即可

yum install fontconfig
fc-cache --force

调用captcha.toBase64Code()可以生成一串很长的base64编码
把这一串编码放到img标签上 并在前面加上"data:image/png;base64,"是能直接使用的

<img src="data:image/png;base64,[你的base64编码]"/>
<img src="data:image/gif;base64,[你的base64编码]"/>

验证码最佳宽高计算公式:字体大小和图片高度相等,图片宽度=图片高度×验证码长度×0.75
由于这里使用的不是本地字体 所以直接丢到linux系统也能正常运行
验证码可以输入中文 前提是你的字体支持中文

最后送大家一张1920×1080 字体大小400的验证码壁纸 哪去用吧

在这里插入图片描述
走之前留个赞呗

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值