JAVA 图片点选验证码

之前做的一个点选验证码,把代码贴出来,如果有需要可以根据实际情况修改

普通验证码:https://blog.csdn.net/weisong530624687/article/details/78861721

JAVA Graphics实现变色、渐变、阴影、倾斜、立体

https://blog.csdn.net/weisong530624687/article/details/80048478

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.util.Random;

public abstract class AbstractRandCode {
    
    /**
     * 验证码缓存
     */
    public static final String LOGIN_CAPTCHA = "_LOGIN_CAPTCHA";
    public static final String LOGIN_CAPTCHA_NAMESPACE = "RandCode";
    
    /**
     * 随机码Img
     */
    private BufferedImage bufferedImage;
    /**
     * 国际化语言
     */
    private String language;

    public BufferedImage getBufferedImage() {
        return bufferedImage;
    }

    public void setBufferedImage(BufferedImage bufferedImage) {
        this.bufferedImage = bufferedImage;
    }
    
    public String getLanguage() {
        return language;
    }

    public void setLanguage(String language) {
        this.language = language;
    }

    /**
     * 获取给定范围随机色
     * @param basicValue
     * @param extentValue
     * @return
     */
    protected Color getRandColor(int basicValue, int extentValue) {
        Random random = new Random();
        int basic = basicValue > 255 ? 255 : basicValue;
        int extent = extentValue > 255 ? 255 : extentValue;
        int r = basic + random.nextInt(extent - basic);
        int g = basic + random.nextInt(extent - basic);
        int b = basic + random.nextInt(extent - basic);
        return new Color(r, g, b);
    }
    
}
 
 
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.imageio.ImageIO;

import com.alibaba.fastjson.JSONArray;

/**
 * 点选验证码
 */
public class PointSelectRandCode extends AbstractRandCode implements RandCode {

    Log log = LogFactory.getLog(PointSelectRandCode.class);
    
    /**
     * 生成验证码的宽度
     */
    private static final int WIDTH = 230;
    /**
     * 生成验证码的高度
     */
    private static final int HEIGHT = 100;
    /**
     * 底部trip信息高度
     */
    private static final int BOTTOM_HEIGHT = 35;
    /**
     * 字符大小
     */
    private static final int FONT_SIZE = 24;
    /**
     * 定义点选文字图片验证码允许的误差值,单位是px
     */
    private static final double ERROR_AMOUNT = FONT_SIZE / 3 * 2.0;
    /**
     * 干扰线个数
     */
    private static final int LINE_COUNT = 120;
    /**
     * 设置字符位置数
     */
    private static final Integer[] PLACE_ARRAY = new Integer[] {1, 2, 3, 4, 5};
    /**
     * 需要校验的字符个数
     */
    private static final int VALIDATE_CHAR_NUM = 3;
    /**
     * 字符的颜色随机范围
     */
    @SuppressWarnings("unused")
    private static final Color[] CHAR_COLORS =
            {Color.BLACK, Color.CYAN, Color.DARK_GRAY, Color.GRAY, Color.GREEN, Color.LIGHT_GRAY, Color.PINK, Color.ORANGE};
    /**
     * 字符的颜色随机范围
     */
    //private static final Color[] LIGHT_CHAR_COLORS = {Color.CYAN, Color.GREEN, Color.MAGENTA, Color.ORANGE, Color.WHITE, Color.YELLOW};
    private static final Color[] LIGHT_CHAR_COLORS = {new Color(72, 76, 119)};
    private static String separator = File.separator;
    /**
     * 图片资源路径
     */
    private static final String IMG_PATH = separator + "static-res" + separator + "img" + separator + "login" + separator;
    /**
     * 背景图片资源路径
     */
    private static final String BACK_GROUD_IMG_PATH = IMG_PATH + "backgroundimg" + separator;
    /**
     * 是否设置随机线
     */
    private boolean isSetRandLine = false;
    /**
     * 校验队列
     */
    List<Point> validatedList;
    /**
     * 页面展示用户操作提示
     */
    String showOperatMessage;
    /**
     * 资源路径地址
     */
    String realPath;
    /**
     * 随机码队列
     */
    private List<String> randCodeList = new ArrayList<>();


    @Override
    public boolean checkRandCode(String checkCode, String checkCodeKey) {

        CacheServiceByRedis redisCache = new CacheServiceByRedis();
        String redisValidatedJson = redisCache.getVal(LOGIN_CAPTCHA_NAMESPACE, checkCodeKey);
        List<Point> redisValidatedList = JSONArray.parseArray(redisValidatedJson, Point.class);
        redisCache.remove(LOGIN_CAPTCHA_NAMESPACE, checkCodeKey);

        if (!checkRedisValidatedList(redisValidatedList)) {
            return false;
        }

        //校验checkCode
        String[] checkCodeArray = checkCode.split(",");
        if (!checkCheckCode(checkCodeArray)) {
            return false;
        }

        //比对checkCode和redisValidate 判断坐标参数是否正确
        for (int i = 0; i < checkCodeArray.length; i++) {
            String[] checkCodePoint = checkCodeArray[i].split("_");
            Point centerPoint = redisValidatedList.get(i);
            Point randPoint = new Point();
            randPoint.setLocation(new Double(checkCodePoint[0]), new Double(checkCodePoint[1]));
            if (!isInnerCircle(randPoint, centerPoint, ERROR_AMOUNT)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 获取圆心
     * @param point
     * @return
     */
    @SuppressWarnings("unused")
    private Point getCenterPoint(Point point) {
        Point centerPoint = new Point();
        centerPoint.setLocation(point.getX() + FONT_SIZE / 2.0, point.getY() + FONT_SIZE / 2.0);
        return centerPoint;
    }

    /** 
     * 判断是否在圆形内 
     * 判断点与圆心之间的距离和圆半径的关系  
     * @param randPoint 
     * @param centerPoint 
     * @return 
     */
    private boolean isInnerCircle(Point randPoint, Point centerPoint, double radius) {
        double distance = Math.hypot(randPoint.getX() - centerPoint.getX(), randPoint.getY() - centerPoint.getY());
        return distance > radius ? false : true;
    }

    private boolean checkCheckCode(String[] checkCodeArray) {
        if (checkCodeArray.length != VALIDATE_CHAR_NUM) {
            return false;
        }
        for (int i = 0; i < checkCodeArray.length; i++) {
            if (EmptyUtils.isEmpty(checkCodeArray[i])) {
                return false;
            }
        }
        return true;
    }

    private boolean checkRedisValidatedList(List<Point> redisValidatedList) {
        return EmptyUtils.isNotEmpty(redisValidatedList) && redisValidatedList.size() == VALIDATE_CHAR_NUM;
    }

    @Override
    public AbstractRandCode getRandCode() {
        randCodeList = new ArrayList<>();
        return getPointSelectRandCode(isEnLanguage(getLanguage()));
    }

    private AbstractRandCode getPointSelectRandCode(boolean isEnLanguage) {
        //生成背景图片
        BufferedImage pointSelectImg = getBackGround();
        Graphics graphics = pointSelectImg.getGraphics();
        setGraphicsFont(isEnLanguage, graphics);

        //随机排序位置集合
        @SuppressWarnings({"unchecked", "rawtypes"})
        CopyOnWriteArrayList<Integer> randPlaceList = new CopyOnWriteArrayList(Arrays.asList(PLACE_ARRAY));
        Collections.shuffle(randPlaceList);

        //list参数坐标参数 用于存储校验原点坐标
        List<Point> validatedCodeList = new ArrayList<>();
        //记录要校验的字符
        StringBuilder readyClickCharBuilder = new StringBuilder();

        //随机N个place不需要校验
        List<Integer> notValidatedPlaces = getNotValidatePlaces();
        Point pointLast = null;
        for (int i = 0; i < PLACE_ARRAY.length; i++) { // 5个汉字,只点4个
            String randChar = getRandChar(true);
            randCodeList.add(randChar);
            int place = randPlaceList.get(i);
            Point point = getPoint(place);
            while (isCloser(point, pointLast)) {
                point = getPoint(place);
            }
            pointLast = point;
            Point pointNew = setRotateAndGradient(randChar, point.x, point.y, graphics);
            if (!notValidatedPlaces.contains(place)) {
                readyClickCharBuilder.append(randChar);
                validatedCodeList.add(pointNew);
            }
        }

        String operateMessage = getOperateMessage(isEnLanguage, readyClickCharBuilder);
        BufferedImage bufferBottomImg = getBottomImg(operateMessage, isEnLanguage);
        BufferedImage customImg = getCustomImg();
        BufferedImage combinedImg = getCombinedImg(pointSelectImg, bufferBottomImg, customImg);

        PointSelectRandCode pointSelectRandCode = new PointSelectRandCode();
        pointSelectRandCode.setBufferedImage(combinedImg);
        pointSelectRandCode.setShowOperatMessage(operateMessage);
        pointSelectRandCode.setValidatedList(validatedCodeList);
        return pointSelectRandCode;
    }

    private List<Integer> getNotValidatePlaces() {
        List<Integer> notValidatedPlaces = new ArrayList<>();
        Random random = new Random();
        while (true) {
            Integer notValidatedPlace = random.nextInt(PLACE_ARRAY.length) + 1;
            if (!notValidatedPlaces.contains(notValidatedPlace)) {
                notValidatedPlaces.add(notValidatedPlace);
                if (notValidatedPlaces.size() == (PLACE_ARRAY.length - VALIDATE_CHAR_NUM)) {
                    break;
                }
            }
        }
        return notValidatedPlaces;
    }

    private void setGraphicsFont(boolean isEnLanguage, Graphics graphics) {
        if (isEnLanguage) {
            graphics.setFont(new Font("Times New Roman", Font.BOLD, FONT_SIZE));
        } else {
            graphics.setFont(new Font("宋体", Font.BOLD, FONT_SIZE));
        }
    }

    private boolean isCloser(Point point, Point pointLast) {
        if (EmptyUtils.isAnyoneEmpty(pointLast)) {
            return false;
        }
        if (Math.abs(point.getX() - pointLast.getX()) > FONT_SIZE || Math.abs(point.getY() - pointLast.getY()) > FONT_SIZE) {
            return false;
        }
        return true;
    }

    /**
     * 获取合并后的最终Img
     * @param pointSelectImg
     * @param bufferBottomImg
     * @param customImg
     * @return
     */
    private BufferedImage getCombinedImg(BufferedImage pointSelectImg, BufferedImage bufferBottomImg, BufferedImage customImg) {
        BufferedImage combinedImg =
                new BufferedImage(WIDTH, HEIGHT + bufferBottomImg.getHeight() + customImg.getHeight(), BufferedImage.TYPE_INT_RGB);
        Graphics combinedGrap = combinedImg.getGraphics(); //合并
        combinedGrap.drawImage(pointSelectImg, 0, 0, null);
        combinedGrap.drawImage(customImg, 0, pointSelectImg.getHeight(), null);
        combinedGrap.drawImage(bufferBottomImg, 0, pointSelectImg.getHeight() + customImg.getHeight(), null);
        return combinedImg;
    }

    /**
     * 获取自定义img
     * @return
     */
    private BufferedImage getCustomImg() {
        BufferedImage customImg = new BufferedImage(WIDTH, 15, BufferedImage.TYPE_INT_RGB);
        Graphics gra = customImg.getGraphics();
        gra.setColor(new Color(60, 59, 65));
        gra.fillRect(0, 0, customImg.getWidth(), customImg.getHeight());
        return customImg;
    }

    /** 
     * 旋转并且画出指定字符串 
     * @param drawString 需要旋转的字符串 
     * @param x 字符串的x坐标 
     * @param y 字符串的Y坐标 
     * @param graphics 画笔g 
     * @return 
     */
    private Point setRotateAndGradient(String drawString, int x, int y, Graphics graphics) {
        Point point = new Point();
        //如果在画字之前旋转图片   degree 旋转的角度 
        Random random = new Random();
        int maxDegree = random.nextInt(2) % 2 == 0 ? 0 : 305;
        int degree = random.nextInt(45) + maxDegree;
        Graphics2D graphics2d = (Graphics2D) graphics.create();
        //平移原点到图形环境的中心  ,这个方法的作用实际上就是将字符串移动到某一个位置
        graphics2d.translate(x, y);
        //旋转文本
        double degreeValue = degree * Math.PI / 180;
        graphics2d.rotate(degreeValue);
        double newPointX = Math.cos(degreeValue) * (FONT_SIZE / 2) + x;
        double newPointY = Math.sin(degreeValue) * (FONT_SIZE / 2) + y;
        point.setLocation(newPointX, newPointY);
        //创建循环渐变的GraphientPaint对象
        GradientPaint paint =
                new GradientPaint(x, y, LIGHT_CHAR_COLORS[random.nextInt(LIGHT_CHAR_COLORS.length)], x + 24, y + 24, LIGHT_CHAR_COLORS[random.nextInt(LIGHT_CHAR_COLORS.length)], true);
        graphics2d.setPaint(paint);

        //特别需要注意的是,这里的画笔已经具有了上次指定的一个位置,所以这里指定的其实是一个相对位置  
        graphics2d.drawString(drawString, 0, 0);
        return point;
    }

    private String getOperateMessage(boolean isEnLanguage, StringBuilder readyClickCharBuilder) {
        StringBuilder operatMessage = new StringBuilder();
        if (isEnLanguage) {
            operatMessage.append(" Click In Order");
        } else {
            operatMessage.append(" 请依次点击");
        }
        for (int i = 0; i < readyClickCharBuilder.length(); i++) {
            char charAt = readyClickCharBuilder.charAt(i);
            operatMessage.append(" \"" + charAt + "\"");
        }
        return operatMessage.toString();
    }

    /**
     * 自定义每个位置的随机坐标
     * @param place
     * @return
     */
    private Point getPoint(int place) {
        int x = 0;
        int maxDynamicRange = WIDTH / 5 / 3;
        int minDynamicRange = WIDTH / 5 / 3;
        switch (place) {
            case 1:
                x = new Random().nextInt(maxDynamicRange) + minDynamicRange;
                break;
            case 2:
                x = new Random().nextInt(maxDynamicRange) + minDynamicRange + WIDTH / 5;
                break;
            case 3:
                x = new Random().nextInt(maxDynamicRange) + minDynamicRange + WIDTH / 5 * 2;
                break;
            case 4:
                x = new Random().nextInt(maxDynamicRange) + minDynamicRange + WIDTH / 5 * 3;
                break;
            case 5:
                x = new Random().nextInt(maxDynamicRange) + WIDTH / 5 * 4;
                break;
            default:
                break;
        }
        int y = new Random().nextInt(HEIGHT / 3) + HEIGHT / 3;
        if (place == 5) {
            y = new Random().nextInt(HEIGHT / 6 * 4) + HEIGHT / 6 * 2;
        }
        return new Point(x, y);
    }

    /**
     * 获取顶部要展示的Image
     * @param operateMessage
     * @return
     */
    private BufferedImage getBottomImg(String operateMessage, boolean isEnLanguage) {
        // 创建底部图片
        BufferedImage bufferImgTop = new BufferedImage(WIDTH, BOTTOM_HEIGHT, BufferedImage.TYPE_INT_RGB);
        Graphics gra = bufferImgTop.getGraphics();

        gra.fillArc(0, 0, WIDTH, BOTTOM_HEIGHT, 20, 50);
        String filePath = getRealPath() + IMG_PATH + "white.png";
        File file = new File(filePath);
        BufferedImage sourceImg = null;
        try {
            sourceImg = ImageIO.read(new FileInputStream(file));
        } catch (Exception e) {
            log.error(e);
            throw new BusinessException("获取验证码失败");
        }
        //2.内部圆角处理
        //填充指定的矩形
        gra.setColor(new Color(60, 59, 65));
        gra.fillRect(0, 0, WIDTH, BOTTOM_HEIGHT);
        gra.drawImage(sourceImg, 0, 0, WIDTH, BOTTOM_HEIGHT, null);

        //1.内部不做圆角处理
        //gra.setColor(new Color(247, 249, 250));
        //gra.fillRect(0, 0, bufferImgTop.getWidth(), bufferImgTop.getHeight());

        // 设置文字背景颜色
        Font font = new Font("宋体", 0, 14);
        gra.setFont(font);
        gra.setColor(Color.DARK_GRAY);
        if (isEnLanguage) {
            gra.drawString(operateMessage, 20, bufferImgTop.getHeight() / 2 + font.getSize() / 2);
        } else {
            gra.drawString(operateMessage, 32, bufferImgTop.getHeight() / 2 + font.getSize() / 2);
        }
        return bufferImgTop;
    }

    /**
     * 生成背景图片
     * @return
     */
    private BufferedImage getBackGround() {
        BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        //创建Graphics2D对象
        Graphics2D graphics = (Graphics2D) image.getGraphics();
        graphics.fillArc(0, 0, WIDTH, HEIGHT, 20, 50);
        Random random = new Random();
        String filePath = getRealPath() + BACK_GROUD_IMG_PATH;
        File file = new File(filePath);
        File[] picFiles = file.listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                if (file.isDirectory()) {
                    return false;
                }
                BufferedImage bi;
                try {
                    bi = ImageIO.read(file);
                } catch (IOException e) {
                    log.info(e);
                    return false;
                }
                if (bi == null) {
                    return false;
                }
                return true;
            }
        });
        File picture = picFiles[random.nextInt(picFiles.length)];
        BufferedImage sourceImg = null;
        try {
            sourceImg = ImageIO.read(new FileInputStream(picture));
        } catch (Exception e) {
            log.error(e);
            throw new BusinessException("获取验证码失败");
        }
        //2.内部圆角处理
        //填充指定的矩形
        graphics.setColor(new Color(60, 59, 65));
        graphics.fillRect(0, 0, WIDTH, HEIGHT);
        
        graphics.drawImage(sourceImg, 0, 0, WIDTH, HEIGHT, null);
        
        BasicStroke basicStroke = new BasicStroke(2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL);
        graphics.setStroke(basicStroke);
        setRandLine(graphics);
        //输出生成的验证码图片  
        graphics.dispose();
        return image;
    }

    /**
     * 设置随机线
     * @param graphics
     */
    private void setRandLine(Graphics2D graphics) {
        if (!isSetRandLine) {
            return;
        }
        //绘制随机线数量/粗细/颜色/位置 
        for (int i = 0; i < LINE_COUNT; i++) {
            Line2D line = getRandLine();
            graphics.draw(line);
            graphics.setColor(getRandColor(30, 150));
        }
    }

    /**
     * 获取随机线
     * @return
     */
    private Line2D getRandLine() {
        Random random = new Random();
        int x = random.nextInt(WIDTH - 1);
        int y = random.nextInt(HEIGHT - 1);
        int x1 = random.nextInt(100) + 1;
        int y1 = random.nextInt(120) + 1;
        return new Line2D.Double(x, y, x + x1, y + y1);
    }

    private String getRandChar(boolean isEnLanguage) {
        if (isEnLanguage) {
            return getRandomEnChar();
        }
        return getRandomChineseChar();
    }

    private String getRandomEnChar() {
        //混合字符串
        String mixString = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        Random random = new Random();
        String randomEnChar = String.valueOf(mixString.charAt(random.nextInt(mixString.length())));
        while (randCodeList.contains(randomEnChar)) {
            randomEnChar = getRandomEnChar();
        }
        return randomEnChar;
    }

    /**
     * 获取随机中文字符
     * @return
     */
    private String getRandomChineseChar() {
        Random random = new Random();
        Integer highPosition = 176 + Math.abs(random.nextInt(39));
        Integer lowPosition = 161 + Math.abs(random.nextInt(93));
        byte[] b = new byte[2];
        b[0] = highPosition.byteValue();
        b[1] = lowPosition.byteValue();
        try {
            return new String(b, "GBk");
        } catch (UnsupportedEncodingException e) {
            log.info(e);
            return getRandomChineseChar();
        }
    }

    private boolean isEnLanguage(String language) {
        return !(EmptyUtils.isEmpty(language) || "zh_CN".equals(language));
    }

    public List<Point> getValidatedList() {
        return validatedList;
    }

    public void setValidatedList(List<Point> validatedList) {
        this.validatedList = validatedList;
    }

    public String getShowOperatMessage() {
        return showOperatMessage;
    }

    public void setShowOperatMessage(String showOperatMessage) {
        this.showOperatMessage = showOperatMessage;
    }

    public String getRealPath() {
        return realPath;
    }

    public void setRealPath(String realPath) {
        this.realPath = realPath;
    }

}


打赏

如果觉得我的文章对你有帮助,有钱就捧个钱场,没钱就捧个人场,欢迎点赞或转发 ,并请注明原出处,谢谢....








猜你喜欢

转载自blog.csdn.net/weisong530624687/article/details/80049250