Java 图形验证码

Like this:

package com.seliote.mlb.biz.service.impl;

import com.seliote.mlb.biz.domain.so.country.CaptchaSo;
import com.seliote.mlb.biz.service.CommonService;
import com.seliote.mlb.common.exception.UtilException;
import com.seliote.mlb.common.service.RedisService;
import com.seliote.mlb.common.util.CommonUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.QuadCurve2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Duration;
import java.util.UUID;

/**
 * 通用功能 Service 实现
 *
 * @author seliote
 * @version 2021-08-29
 */
@Slf4j
@Service
public class CommonServiceImpl implements CommonService {

    // 图形验证码 Redis 缓存 key
    private final String CAPTCHA_REDIS_KEY = "captcha";
    // 图形验证码过期时间
    private final Duration CAPTCHA_TIMEOUT = Duration.ofMinutes(3);
    // 图形验证码文本长度
    private final Integer CAPTCHA_TEXT_LEN = 4;
    // 图形验证码允许的字符范围
    private final String CAPTCHA_TEXT_RANGE = "qazwsxedcrfvtgbyhnujmkpQAZWSXEDCRFVTGBYHNUJMKP23456789";
    // 图形验证码背景色
    private final Color CAPTCHA_BG_COLOR = new Color(0, 0, 0, 0);
    // 图形验证码前景色范围
    private final Color[] CAPTCHA_FG_COLOR = new Color[]{Color.GRAY, Color.BLACK, Color.GREEN, Color.YELLOW,
            Color.ORANGE, Color.RED, Color.PINK, Color.BLUE};
    // 图形验证码基大小
    int CAPTCHA_BASE_SIZE = 200;

    private final RedisService redisService;

    @Autowired
    public CommonServiceImpl(RedisService redisService) {
        this.redisService = redisService;
    }

    @Override
    public CaptchaSo createCaptcha() {
        var uuid = UUID.randomUUID().toString();
        var captchaText = createCaptchaText();
        var bytes = createCaptchaImg(captchaText);
        redisService.set(CAPTCHA_TIMEOUT, captchaText, CAPTCHA_REDIS_KEY, uuid);
        return CaptchaSo.builder().uuid(uuid).captchaImg(CommonUtils.base64Encode(bytes)).build();
    }

    /**
     * 创建图形验证码文本
     *
     * @return 图形验证码文本
     */
    private String createCaptchaText() {
        var sb = new StringBuilder();
        for (int i = 0; i < CAPTCHA_TEXT_LEN; i++) {
            var index = CommonUtils.getRandom().nextInt(CAPTCHA_TEXT_RANGE.length());
            var c = CAPTCHA_TEXT_RANGE.charAt(index);
            sb.append(c);
        }
        return sb.toString();
    }

    /**
     * 创建验证码图片
     *
     * @param captchaText 验证码文本
     * @return 验证码图片
     */
    private byte[] createCaptchaImg(String captchaText) {
        var img = new BufferedImage((captchaText.length() + 1) * CAPTCHA_BASE_SIZE,
                CAPTCHA_BASE_SIZE * 2, BufferedImage.TYPE_INT_RGB);
        var g2d = img.createGraphics();
        g2d.setColor(CAPTCHA_BG_COLOR);
        g2d.fillRect(0, 0, img.getWidth(), img.getHeight());
        drawCaptchaText(g2d, captchaText);
        drawCaptchaInterference(img, g2d);
        g2d.dispose();
        var outputStream = new ByteArrayOutputStream();
        try {
            ImageIO.write(img, "PNG", outputStream);
        } catch (IOException e) {
            log.error("Failed write captcha image, IO error, {}", e.getMessage());
            throw new UtilException(e);
        }
        return outputStream.toByteArray();
    }

    /**
     * 绘制验证码文本
     *
     * @param g2d         Graphics2D 对象
     * @param captchaText 验证码文本
     */
    private void drawCaptchaText(Graphics2D g2d, String captchaText) {
        // 左侧留白
        final var leftSpace = CAPTCHA_BASE_SIZE / 2;
        // 角度转弧度
        final var angle2Radian = Math.PI / 180;
        // 画字
        for (int i = 0; i < captchaText.length(); i++) {
            // 随机设置字体样式
            g2d.setColor(CAPTCHA_FG_COLOR[CommonUtils.getRandom().nextInt(CAPTCHA_FG_COLOR.length)]);
            g2d.setFont(new Font(null,
                    CommonUtils.getRandom().nextInt(2) == 0 ? Font.ITALIC : Font.BOLD,
                    CAPTCHA_BASE_SIZE));
            // 计算当前字体的起点位置,左上角
            var x = leftSpace + i * CAPTCHA_BASE_SIZE + CommonUtils.getRandom().nextInt(CAPTCHA_BASE_SIZE / 2);
            var y = CAPTCHA_BASE_SIZE + CommonUtils.getRandom().nextInt(CAPTCHA_BASE_SIZE);
            var radian = (CommonUtils.getRandom().nextInt(90) - 45) * angle2Radian;
            // 转换并绘制
            g2d.translate(x, y);
            g2d.rotate(radian);
            g2d.drawString(captchaText.substring(i, i + 1), 0, 0);
            // 转回去
            g2d.rotate(-radian);
            g2d.translate(-x, -y);
        }
    }

    /**
     * 绘制干扰线
     *
     * @param img BufferedImage 对象
     * @param g2d Graphics2D 对象
     */
    private void drawCaptchaInterference(BufferedImage img, Graphics2D g2d) {
        final var imageWidth = img.getWidth();
        final var imageHeight = img.getHeight();
        // 干扰线数量
        final var interferenceLineCount = (imageWidth / imageHeight) * 20;
        // 干扰线最大宽度
        final var interferenceLineMaxWidth = Math.round(imageHeight * 0.03F);
        for (int i = 0; i < interferenceLineCount; i++) {
            g2d.setColor(CAPTCHA_FG_COLOR[CommonUtils.getRandom().nextInt(CAPTCHA_FG_COLOR.length)]);
            g2d.setStroke(new BasicStroke(CommonUtils.getRandom().nextInt(interferenceLineMaxWidth)));
            if (CommonUtils.getRandom().nextBoolean()) {
                // 画直线
                g2d.drawLine(CommonUtils.getRandom().nextInt(imageWidth), CommonUtils.getRandom().nextInt(imageHeight),
                        CommonUtils.getRandom().nextInt(imageWidth), CommonUtils.getRandom().nextInt(imageHeight));
            } else {
                // 画曲线
                g2d.draw(new QuadCurve2D.Double(
                        CommonUtils.getRandom().nextInt(imageWidth), CommonUtils.getRandom().nextInt(imageHeight),
                        CommonUtils.getRandom().nextInt(imageWidth), CommonUtils.getRandom().nextInt(imageHeight),
                        CommonUtils.getRandom().nextInt(imageWidth), CommonUtils.getRandom().nextInt(imageHeight)));
            }
        }
    }
}
posted @ 2020-07-09 21:47  seliote  阅读(259)  评论(0)    收藏  举报