在Java中实现图片验证码的功能,通常包括生成验证码图片和验证用户输入的验证码是否正确两个主要步骤。以下是如何实现这两个步骤的详细说明:
1.生成验证码的工具
准备Random随机数工具类,避免每次生成随机数都需要创建对象
import java.util.Random;/*** @author wujiada* 确保整个应用程序中只使用一个 Random 实例*/
public class RandomUtils {// 单例Random对象private static final Random RANDOM = new Random();// 私有构造函数,防止实例化private RandomUtils() {}public static Random getRandomObject() {return RANDOM;}// 提供一个公共静态方法来获取随机数public static int generateRandomInt(int bound) {return RANDOM.nextInt(bound);}// 如果需要其他类型的随机数,可以添加更多的方法public static double generateRandomDouble() {return RANDOM.nextDouble();}public static float generateRandomFloat() {return RANDOM.nextFloat();}public static boolean generateRandomBoolean() {return RANDOM.nextBoolean();}}
生成验证码图片的工具类
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.OutputStream;
import java.util.Random;public class GraphicHelper {// 验证码范围,去掉0(数字)和O(拼音)容易混淆的(小写的1和L也可以去掉,大写不用了)public static final String VERIFY_CODES = "ABCDEFGHIJKLMNPQRSTUVWXYZ23456789";/*** 以字符串形式返回生成的验证码,同时输出一个图片** @param width 图片的宽度* @param height 图片的高度* @param imgType 图片的类型* @param output 图片的输出流(图片将输出到这个流中)* @return 返回所生成的验证码(字符串)*/public static String createImage(final int width, final int height, final String imgType, OutputStream output) {// 填充的字符串// 缓存生成的验证码StringBuilder codeStringBuilder = new StringBuilder();// 新建RGB图片BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);// 创建载体 Graphics2D 映射集Graphics2D g2 = image.createGraphics();// 开启抗锯齿微调g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);// 设置边框色g2.setColor(Color.GRAY);g2.fillRect(0, 0, width, height);Color c = getRandColor(200, 250);// 设置背景色g2.setColor(c);g2.fillRect(0, 2, width, height - 4);// 绘制干扰线// 设置线条的颜色g2.setColor(getRandColor(160, 200));for (int i = 0; i < 20; i++) {int x = RandomUtils.generateRandomInt(width - 1);int y = RandomUtils.generateRandomInt(height - 1);int xl = RandomUtils.generateRandomInt(6) + 1;int yl = RandomUtils.generateRandomInt(12) + 1;g2.drawLine(x, y, x + xl + 40, y + yl + 20);}// 添加噪点float yawRate = 0.05f;int area = (int) (yawRate * width * height);for (int i = 0; i < area; i++) {int x = RandomUtils.generateRandomInt(width);int y = RandomUtils.generateRandomInt(height);int rgb = getRandomIntColor();image.setRGB(x, y, rgb);}// 使图片扭曲shear(g2, width, height, c);g2.setColor(getRandColor(100, 160));// 设置字体大小int fontSize = height - 4;Font font = new Font("Algerian", Font.ITALIC, fontSize);g2.setFont(font);// 生成4位验证码String code = generateVerifyCode(4);codeStringBuilder.append(code);char[] chars = code.toCharArray();for (int i = 0; i < chars.length; i++) {AffineTransform affine = new AffineTransform();affine.setToRotation(Math.PI / 4 * RandomUtils.generateRandomDouble() * (RandomUtils.generateRandomBoolean() ? 1 : -1),((double) width / 4) * i + (double) fontSize / 2, (double) height / 2);g2.setTransform(affine);// 规定数字位置g2.drawChars(chars, i, 1, ((width - 10) / 4) * i + 5, height / 2 + fontSize / 2 - 10);}// 关闭g2.dispose();// 将图片发送给浏览器try (output) {ImageIO.write(image, imgType, output);} catch (Exception e) {throw new RuntimeException("生成验证码失败");}return codeStringBuilder.toString();}/*** 生成随机颜色*/private static Color getRandColor(int fc, int bc) {if (fc > 255) {fc = 255;}if (bc > 255) {bc = 255;}int r = fc + RandomUtils.generateRandomInt(bc - fc);int g = fc + RandomUtils.generateRandomInt(bc - fc);int b = fc + RandomUtils.generateRandomInt(bc - fc);return new Color(r, g, b);}/*** 扭曲*/private static void shear(Graphics g, int w1, int h1, Color color) {shearX(g, w1, h1, color);shearY(g, w1, h1, color);}/*** 剪切X轴*/private static void shearX(Graphics g, int w1, int h1, Color color) {int period = RandomUtils.generateRandomInt(2);int frames = 1;int phase = RandomUtils.generateRandomInt(2);for (int i = 0; i < h1; i++) {double d = (double) (period >> 1)* Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);g.copyArea(0, i, w1, 1, (int) d, 0);g.setColor(color);g.drawLine((int) d, i, 0, i);g.drawLine((int) d + w1, i, w1, i);}}/*** 剪切Y轴*/private static void shearY(Graphics g, int w1, int h1, Color color) {// 50;int period = RandomUtils.generateRandomInt(40) + 10;int frames = 20;int phase = 7;for (int i = 0; i < w1; i++) {double d = (double) (period >> 1)* Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);g.copyArea(i, 0, 1, h1, 0, (int) d);g.setColor(color);g.drawLine(i, (int) d, i, 0);g.drawLine(i, (int) d + h1, i, h1);}}/*** 生成随机颜色*/private static int getRandomIntColor() {int[] rgb = new int[3];int color = 0;for (int i = 0; i < 3; i++) {rgb[i] = RandomUtils.generateRandomInt(255);}for (int c : rgb) {color = color << 8;color = color | c;}return color;}/*** 生成验证码** @param verifySize 验证码长度*/public static String generateVerifyCode(int verifySize) {return generateVerifyCode(verifySize, VERIFY_CODES);}/*** 使用指定源生成验证码** @param verifySize 验证码长度* @param sources 验证码字符源*/public static String generateVerifyCode(int verifySize, String sources) {if (sources == null || sources.isEmpty()) {sources = VERIFY_CODES;}int codesLen = sources.length();Random random = RandomUtils.getRandomObject();random.setSeed(System.currentTimeMillis());StringBuilder verifyCode = new StringBuilder(verifySize);for (int i = 0; i < verifySize; i++) {verifyCode.append(sources.charAt(random.nextInt(codesLen - 1)));}return verifyCode.toString();}
}
2.登录验证码校验逻辑
编写一个接口生成UUID返回给前端
UUID.randomUUID().toString()
生成验证码接口,前端携带UUID
调用生成图片验证码的工具类,生成验证码图片之后,输出到响应流中返回,同时将验证码缓存到Redis中,将UUID+业务标识作为key,验证码作为value ,并过期时间60s
try {response.setHeader("content-type", "image/jpeg");String verifyCode = GraphicHelper.createImge(130, 36, "png", response.getOutputStream());// 图片验证码存放到缓存60秒redisClientTemplate.del(vtoken + SysBaseConstant.IMAGE_CODE_SUFFIX);redisClientTemplate.setex(vtoken +"_IMAGE_CODE", 60, verifyCode);log.info("img_code"+verifyCode);} catch (Exception e) {log.error("获取图片验证码异常", e);}
请求登录接口,携带UUID和验证码
// 验证图片验证码String code = redisClientTemplate.get(vtoken + "_IMAGE_CODE");if (StringUtils.isEmpty(code)) {return Response.error(ResponseCode.EXCEPTION, "验证码失效,请刷新页面");}if (!code.equalsIgnoreCase(verifyCode)) {return Response.error(ResponseCode.EXCEPTION, "验证码错误");}
总结:
关于 java.util.Random
对象的使用。Random
类在创建时会生成一个随机种子,如果频繁地创建和丢弃 Random
对象,可能会导致生成的随机数质量下降,因为种子可能不够随机。此外,频繁创建对象也是一种资源浪费。