天天看點

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;
    }

}
           

打賞

如果覺得我的文章對你有幫助,有錢就捧個錢場,沒錢就捧個人場,歡迎點贊或轉發 ,并請注明原出處,謝謝....
JAVA 圖檔點選驗證碼打賞
JAVA 圖檔點選驗證碼打賞