Java实现拖动滑块图片验证

        在大多数系统中都会有一个防止机器爆破使用某个功能接口,比如有登录、发送短信、下载导出等等,这些功能通常比较敏感的并且发生频率不允许太高,而为了防止机器循环爆破或者用户误触,所以需要在发生功能事件做一个防机器拦截,而如今大多数是使用拖动滑块图片验证来实现其目地的;拖动滑块图片验证中滑块每次所需正确位置完全随机,背景图片也将随机刷新,每次滑错都将重置滑块正确位置和背景图片,使得机器无法自动识别并且暴力破解,至此实现了其功能。        

        本文章将用Java语言实现拖动滑块图片验证

实现样例
实现样例

 1、静态文件

首先准备好静态文件放在项目的resource静态目录,需要的背景图、滑块交互js、样式css、以及ui组件的图片

2、渲染滑块界面接口

该接口需要返回4个参数纵轴坐标Y(本功能只修改横坐标)、背景图imgBg、背景抠图imgBtn、以及标识tokenId;

    /**
     * 验证码生成
     * @return
     * @throws IOException
     */
    Result verifyCodeInit() throws IOException;

业务具体实现:

@Override
public Result verifyCodeInit() throws IOException {
    Map<String,Object> result = new HashMap<>();
    /*本地缓存实现*/
    List<byte[]> imgList = null;
//        VerifyEntity verify = redisTemplate.get( KEY_VALIDATE_IMG,VerifyEntity.class);
//        if (!ObjectUtil.ObjectIsNull(verify)) {
//            imgList = verify.getImgList();
//        }
//
//        if (CollectionUtils.isEmpty(imgList)) {
//            imgList = initFileByte();
//            VerifyEntity verifyEntity = new VerifyEntity();
//            verifyEntity.setImgList(imgList);
//            redisTemplate.set(KEY_VALIDATE_IMG, verifyEntity);
//        }
    //加载内存中的图片
    imgList = DEFAULT_IMG_LIST;

    //随机取出一张验证图
    Random ra = new Random();
    int rd = ra.nextInt(imgList.size() - 1);
    byte[] targetIs = imgList.get(rd);
    //生成验证码
    PuzzleCaptcha puzzleCaptcha = new PuzzleCaptcha(targetIs);
    puzzleCaptcha.setImageQuality(Image.SCALE_AREA_AVERAGING);
    puzzleCaptcha.run();
    //抠块图
    String imgBtn = ImageConvertUtil.toDataUri(puzzleCaptcha.getVacancy(), "png");
    //背景图
    String imgBg = ImageConvertUtil.toDataUri(puzzleCaptcha.getArtwork(), "png");
    String token = UUID.randomUUID().toString().replaceAll("-", "");
    Map<String, Object> cacheObj = new HashMap<>(5);
    cacheObj.put("token", token);
    //偏移量
    cacheObj.put("X", puzzleCaptcha.getX());
    cacheObj.put("Y", puzzleCaptcha.getY());
    //验证起始时间
    cacheObj.put("time", System.currentTimeMillis());
    //保存验证状态
    cacheObj.put("verifyCount", 0);
    //保存2分钟
    redisKit.set(KEY_VALIDATE_TOKEN+":"+token, cacheObj, 120);
    result.put("imgBtn", imgBtn);
    result.put("imgBg", imgBg);
    result.put("tokenId", token);
    result.put("Y", puzzleCaptcha.getY());
    return Result.me().success().setData(result);
}

 初始化背景图片的方法:

    private static List<byte[]> DEFAULT_IMG_LIST;

    static {
        try {
            DEFAULT_IMG_LIST = initFileByte();
            //未加载到图片或缓存被外部清除
            if (DEFAULT_IMG_LIST == null || DEFAULT_IMG_LIST.size() < 1) {
                //重新加载
                DEFAULT_IMG_LIST = initFileByteNew();
            }
        } catch (IOException e) {
            System.out.println("加载背景图失败");
        }
    }

    /**
     * 读取文件字节流
     */
    private static List<byte[]> initFileByte() throws IOException {
        //图片存储路径在resource/static/verify_imgs包下
        String imgPath = "META-INF/static/app/images/verify_imgs";
        URL url = Thread.currentThread().getContextClassLoader().getResource(imgPath);
        if (url != null) {
            String protocol = url.getProtocol();
            System.out.println("protocol ======> " + protocol);
            return "jar".equalsIgnoreCase(protocol) ? initJarImg(url) : initClassImg(url);
        }
        return null;
    }

    /**
     * 加载项目资源图片文件
     *
     * @param url
     * @return
     */
    private static List<byte[]> initClassImg(URL url) throws IOException {
        if (url != null) {
            File fileDir = new File(url.getPath());
            File[] fs = fileDir.listFiles();
            if (fs != null && fs.length > 0) {
                List<byte[]> byteList = new ArrayList<>();
                byte[] bytes;
                for (File f : fs) {
                    bytes = IOUtils.toByteArray(new FileInputStream(f));
                    byteList.add(bytes);
                }
                return byteList;
            }
        }
        return null;
    }

    /**
     * 加载jar包资源图片文件
     *
     * @param url
     * @return
     */
    private static List<byte[]> initJarImg(URL url) throws IOException {
        DSFLog.logDebug(url);
        if (url != null) {
            DSFLog.info("再执行读取jar包文件方法 url="+url);
            String jarPath = url.getPath().substring(0, url.getPath().indexOf("!/") + 2);
            URL jarUrl = new URL("jar:" + jarPath);
            JarURLConnection jarCon = (JarURLConnection) jarUrl.openConnection();
            if (jarCon != null) {
                DSFLog.info("JarURLConnection 成功"+jarCon);
                JarFile jarFile = jarCon.getJarFile();
                Enumeration<JarEntry> jarEntrys = jarFile.entries();
                byte[] bytes;
                List<byte[]> byteList = new ArrayList<>();
                while (jarEntrys.hasMoreElements()) {
                    JarEntry entry = jarEntrys.nextElement();
                    String name = entry.getName();
                    //打包jar后的图片资源地址
                    if (name.startsWith("META-INF/classes/static/verify_imgs") && name.contains(".jpg")) {
                        bytes = IOUtils.toByteArray(ImgValidationController.class.getClassLoader().getResourceAsStream(name));
                        byteList.add(bytes);
                    }
                }
                return byteList;
            }else{
                DSFLog.info("JarURLConnection 失败"+jarCon);
            }
        }
        return null;
    }

    /**
     * 读取文件字节流
     */
    private static List<byte[]> initFileByteNew() throws IOException {
        //图片存储路径在resource/static/verify_imgs包下
        List<byte[]> allImg = new ArrayList<>();;
        for (int i = 1; i <= 10; i++) {
            try {
                String imgUrl = VerifyServiceImpl.class.getResource("/").getPath().concat("templates/verify_imgs/").concat(String.valueOf(i)).concat(".jpg");
                InputStream inputStream = new FileInputStream(imgUrl);
                byte[] imgByte = convert2ByteArray(inputStream) ;
                allImg.add(imgByte);
            }catch (Exception e){
                String imgUrl = "templates/verify_imgs/" + i + ".jpg";
                InputStream inputStream = VerifyServiceImpl.class.getClassLoader().getResourceAsStream(imgUrl);
                byte[] imgByte = convert2ByteArray(inputStream) ;
                allImg.add(imgByte);
            }

        }
        return allImg;
    }

 PuzzleCaptcha实体类

public class PuzzleCaptcha {
    /** 默认宽度,用来计算阴影基本长度 */
    private static final int DEFAULT_WIDTH = 350;
    /** 随机数 */
    private static final Random RANDOM = new Random();
    /** 蒙版 */
    private static Color color = new Color(108, 104, 220, 204);
    /** alpha通道过滤器 */
    private static InvertAlphaFilter alphaFilter = new InvertAlphaFilter();
    /** 边距 */
    private static int margin = 50;

    /** 生成图片的宽度 */
    private int width = DEFAULT_WIDTH;
    /** 生成图片高度 */
    private int height = 160;
    /** x轴的坐标,由算法决定 */
    private int x;
    /** y轴的坐标,由算法决定 */
    private int y;
    /** 拼图长宽 */
    private int vwh = 10 * 3;
    /** 原图 */
    private Image image;
    /** 大图 */
    private Image artwork;
    /** 小图 */
    private Image vacancy;
    /** 是否注重速度 */
    private boolean isFast = true;
    /** 小图描边颜色 */
    private Color vacancyBorderColor = new Color(250, 252, 200, 255);
    /** 小图描边线条的宽度 */
    private float vacancyBorderWidth = 3f;
    /** 主图描边的颜色 */
    private Color artworkBorderColor;
    /** 主图描边线条的宽度 */
    private float artworkBorderWidth = 5f;
    /**
     * 最高放大倍数,合理的放大倍数可以使图像平滑且提高渲染速度
     * 当isFast为false时,此属性生效
     * 放大倍数越高,生成的图像越平滑,受原始图片大小的影响。
     */
    private double maxRatio = 2;
    /**
     * 画质
     *
     * @see Image#SCALE_DEFAULT
     * @see Image#SCALE_FAST
     * @see Image#SCALE_SMOOTH
     * @see Image#SCALE_REPLICATE
     * @see Image#SCALE_AREA_AVERAGING
     */
    private int imageQuality = Image.SCALE_DEFAULT;

    /**
     * 从文件中读取图片
     *
     * @param file
     */
//    public PuzzleCaptcha(File file) {
//        try {
//            image = ImageIO.read(new ByteArrayInputStream(bytes));
//        } catch (IOException var2) {
//            throw new IORuntimeException(var2);
//        }
//    }

    /**
     * 从文件中读取图片,请使用绝对路径,使用相对路径会相对于ClassPath
     *
     * @param imageFilePath
     */
//    public PuzzleCaptcha(String imageFilePath) {
//        image = ImgUtil.read(imageFilePath);
//    }

    /**
     * 从{@link Resource}中读取图片
     *
     * @param resource
     */
//    public PuzzleCaptcha(Resource resource) {
//        image = ImgUtil.read(resource);
//    }

    /**
     * 从流中读取图片
     *
     * @param imageStream
     */
//    public PuzzleCaptcha(InputStream imageStream) {
//        image = ImgUtil.read(imageStream);
//    }

    /**
     * 从图片流中读取图片
     *
     * @param imageStream
     */
//    public PuzzleCaptcha(ImageInputStream imageStream) {
//        image = ImgUtil.read(imageStream);
//    }

    /**
     * 加载图片
     *
     * @param image
     */
//    public PuzzleCaptcha(Image image) {
//        this.image = image;
//    }

    /**
     * 加载图片
     *
     * @param bytes
     */
    public PuzzleCaptcha(byte[] bytes) {
        try {
            this.image = ImageIO.read(new ByteArrayInputStream(bytes));
        } catch (IOException var2) {
            throw new IORuntimeException(var2);
        }
        try {
            this.image = ImageIO.read(new ByteArrayInputStream(bytes));
        } catch (IOException var2) {
            throw new IORuntimeException(var2);
        }
    }

    /**
     * 生成随机x、y坐标
     */
    private void init() {
        if (x == 0 || y == 0) {
            this.x = random(vwh, this.width - vwh - margin);
            this.y = random(margin, this.height - vwh - margin);
        }
    }

    /**
     * 执行
     */
    public void run() {
        init();
        // 缩略图
        Image thumbnail;
        GeneralPath path;
        int realW = image.getWidth(null);
        int realH = image.getHeight(null);
        int w = realW, h = realH;
        double wScale = 1, hScale = 1;
        // 如果原始图片比执行的图片还小,则先拉伸再裁剪
        boolean isFast = this.isFast || w < this.width || h < this.height;
        if (isFast) {
            // 缩放,使用平滑模式
            thumbnail = image.getScaledInstance(width, height, imageQuality);
            path = paintBrick(1, 1);
            w = this.width;
            h = this.height;
        } else {
            // 缩小到一定的宽高,保证裁剪的圆润
            boolean flag = false;
            if (realW > width * maxRatio) {
                // 不超过最大倍数且不超过原始图片的宽
                w = Math.min((int) (width * maxRatio), realW);
                flag = true;
            }
            if (realH > height * maxRatio) {
                h = Math.min((int) (height * maxRatio), realH);
                flag = true;
            }
            if (flag) {
                // 若放大倍数生效,则缩小图片至最高放大倍数,再进行裁剪
                thumbnail = image.getScaledInstance(w, h, imageQuality);
            } else {
                thumbnail = image;
            }
            hScale = NumberUtil.div(h, height);
            wScale = NumberUtil.div(w, width);
            path = paintBrick(wScale, hScale);
        }

        // 创建阴影过滤器
        float radius = 5 * ((float) w / DEFAULT_WIDTH) * (float) wScale;
        int left = 1;
        ShadowFilter shadowFilter = new ShadowFilter(radius, 2 * (float) wScale, -1 * (float) hScale, 0.8f);

        // 创建空白的图片
        BufferedImage artwork = translucent(new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB));
        BufferedImage localVacancy = translucent(new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB));
        // 画小图
        Graphics2D vg = localVacancy.createGraphics();
        // 抗锯齿
        vg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        // 设置画图路径范围
        vg.setClip(path);
        // 将区域中的图像画到小图中
        vg.drawImage(thumbnail, null, null);
        //描边
        if (vacancyBorderColor != null) {
            vg.setColor(vacancyBorderColor);
            vg.setStroke(new BasicStroke(vacancyBorderWidth));
            vg.draw(path);
        }
        // 释放图像
        vg.dispose();

        // 画大图
        // 创建画笔
        Graphics2D g = artwork.createGraphics();
        // 抗锯齿
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        // 画上图片
        g.drawImage(thumbnail, null, null);
        // 设置画图路径范围
        g.setClip(path);
        // 填充缺口透明度 颜色混合,不透明在上
        g.setComposite(AlphaComposite.SrcAtop);
        // 填充一层白色的透明蒙版,透明度越高,白色越深 alpha:0-255
        g.setColor(color);
        g.fill(path);
        //描边
        if (artworkBorderColor != null) {
            g.setColor(artworkBorderColor);
            g.setStroke(new BasicStroke(artworkBorderWidth));
            g.draw(path);
        }
        // 画上基于小图的内阴影,先反转alpha通道,然后创建阴影
        g.drawImage(shadowFilter.filter(alphaFilter.filter(localVacancy, null), null), null, null);
        // 释放图像
        g.dispose();

        // 裁剪掉多余的透明背景
        localVacancy = ImageUtils.getSubimage(localVacancy, (int) (x * wScale - left), 0, (int) Math.ceil(path.getBounds().getWidth() + radius) + left, h);
        if (isFast) {
            // 添加阴影
            this.vacancy = shadowFilter.filter(localVacancy, null);
            this.artwork = artwork;
        } else {
            // 小图添加阴影
            localVacancy = shadowFilter.filter(localVacancy, null);
            // 大图缩放
            this.artwork = artwork.getScaledInstance(width, height, imageQuality);
            // 缩放时,需要加上阴影的宽度,再除以放大比例
            this.vacancy = localVacancy.getScaledInstance((int) ((path.getBounds().getWidth() + radius) / wScale), height, imageQuality);
        }
    }

    /**
     * 绘制拼图块的路径
     *
     * @param xScale x轴放大比例
     * @param yScale y轴放大比例
     * @return
     */
    private GeneralPath paintBrick(double xScale, double yScale) {
        double x = this.x * xScale;
        double y = this.y * yScale;
        // 直线移动的基础距离
        double hMoveL = vwh / 3f * yScale;
        double wMoveL = vwh / 3f * xScale;
        GeneralPath path = new GeneralPath();
        path.moveTo(x, y);
        path.lineTo(x + wMoveL, y);
        // 上面的圆弧正东方向0°,顺时针负数,逆时针正数
        path.append(arc(x + wMoveL, y - hMoveL / 2, wMoveL, hMoveL, 180, -180), true);
        path.lineTo(x + wMoveL * 3, y);
        path.lineTo(x + wMoveL * 3, y + hMoveL);
        // 右边的圆弧
        path.append(arc(x + wMoveL * 2 + wMoveL / 2, y + hMoveL, wMoveL, hMoveL, 90, -180), true);
        path.lineTo(x + wMoveL * 3, y + hMoveL * 3);
        path.lineTo(x, y + hMoveL * 3);
        path.lineTo(x, y + hMoveL * 2);
        // 左边的内圆弧
        path.append(arc(x - wMoveL / 2, y + hMoveL, wMoveL, hMoveL, -90, 180), true);
        path.lineTo(x, y);
        path.closePath();
        return path;
    }

    /**
     * 绘制圆形、圆弧或者是椭圆形
     * 正东方向0°,顺时针负数,逆时针正数
     *
     * @param x      左上角的x坐标
     * @param y      左上角的y坐标
     * @param w      宽
     * @param h      高
     * @param start  开始的角度
     * @param extent 结束的角度
     * @return
     */
    private Arc2D arc(double x, double y, double w, double h, double start, double extent) {
        return new Arc2D.Double(x, y, w, h, start, extent, Arc2D.OPEN);
    }

    /**
     * 透明背景
     *
     * @param bufferedImage
     * @return
     */
    private BufferedImage translucent(BufferedImage bufferedImage) {
        Graphics2D g = bufferedImage.createGraphics();
        bufferedImage = g.getDeviceConfiguration().createCompatibleImage(bufferedImage.getWidth(), bufferedImage.getHeight(), Transparency.TRANSLUCENT);
        g.dispose();
        return bufferedImage;
    }

    /**
     * 随机数
     *
     * @param min
     * @param max
     * @return
     */
    private static int random(int min, int max) {
        return RANDOM.nextInt((max - min) + 1) + min;
    }


    public static Color getColor() {
        return color;
    }

    public static void setColor(Color color) {
        PuzzleCaptcha.color = color;
    }

    public static InvertAlphaFilter getAlphaFilter() {
        return alphaFilter;
    }

    public static void setAlphaFilter(InvertAlphaFilter alphaFilter) {
        PuzzleCaptcha.alphaFilter = alphaFilter;
    }

    public static int getMargin() {
        return margin;
    }

    public static void setMargin(int margin) {
        PuzzleCaptcha.margin = margin;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getVwh() {
        return vwh;
    }

    public void setVwh(int vwh) {
        this.vwh = vwh;
    }

    public Image getImage() {
        return image;
    }

    public void setImage(Image image) {
        this.image = image;
    }

    public Image getArtwork() {
        return artwork;
    }

    public void setArtwork(Image artwork) {
        this.artwork = artwork;
    }

    public Image getVacancy() {
        return vacancy;
    }

    public void setVacancy(Image vacancy) {
        this.vacancy = vacancy;
    }

    public boolean isFast() {
        return isFast;
    }

    public void setFast(boolean fast) {
        isFast = fast;
    }

    public Color getVacancyBorderColor() {
        return vacancyBorderColor;
    }

    public void setVacancyBorderColor(Color vacancyBorderColor) {
        this.vacancyBorderColor = vacancyBorderColor;
    }

    public float getVacancyBorderWidth() {
        return vacancyBorderWidth;
    }

    public void setVacancyBorderWidth(float vacancyBorderWidth) {
        this.vacancyBorderWidth = vacancyBorderWidth;
    }

    public Color getArtworkBorderColor() {
        return artworkBorderColor;
    }

    public void setArtworkBorderColor(Color artworkBorderColor) {
        this.artworkBorderColor = artworkBorderColor;
    }

    public float getArtworkBorderWidth() {
        return artworkBorderWidth;
    }

    public void setArtworkBorderWidth(float artworkBorderWidth) {
        this.artworkBorderWidth = artworkBorderWidth;
    }

    public double getMaxRatio() {
        return maxRatio;
    }

    public void setMaxRatio(double maxRatio) {
        this.maxRatio = maxRatio;
    }

    public int getImageQuality() {
        return imageQuality;
    }

    public void setImageQuality(int imageQuality) {
        this.imageQuality = imageQuality;
    }
}

ImageConvertUtil工具类 

public class ImageConvertUtil {

    /**
     * 将image对象转为base64字符串
     *
     * @param image
     * @return
     */
    public static String toBase64(Image image, String format) {
        BASE64Encoder encoder = new BASE64Encoder();
        return encoder.encode(toBytes(image, format));
    }

    /**
     * 将image对象转为前端img标签识别的base64字符串
     *
     * @param image
     * @param format
     * @return
     */
    public static String toDataUri(Image image, String format) {

        return String.format("data:image/%s;base64,%s", format, toBase64(image, format));
    }

    /**
     * 将image对象转为字节
     *
     * @param image
     * @param format
     * @return
     */
    public static byte[] toBytes(Image image, String format) {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        try {
            ImageIO.write(ImageUtils.convertImageToARGB(image), format, stream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return stream.toByteArray();
    }
}

3、前端页面上加上调用

html文件

<button type="button" class="getYzm" onclick="showPopup()">获取验证码</button>

js文件

//新版获取滑动验证码 start
// 图片验证
var dataList = ["0","1"];
var codeObj={};
var options = {
    dataList: dataList,
    success:function(obj){
        //动态验证码验证通过回调
        hidePopup();
        codeObj = obj;
        sendCode();
    },
    fail: function(){
    }
};
var SliderBarInfo;
// 弹窗
var overlay = document.getElementById("overlay");
function showPopup(){
    if (!$("#loginName").val()) {
        dsf.layer.message("请输入用户名!",false);
        return;
    }
    // if (!$("#password").val()) {
    //     dsf.layer.message("请输入密码!",false);
    //     return;
    // }
    SliderBarInfo = SliderBar("slideBar", options);
    overlay.classList.add('show')
}
function hidePopup(){
    overlay.classList.remove('show')
}
//新版获取滑动验证码 end

 此处用到的SliderBar来自slider.js,已上传至绑定资源

滑块验证通过后 发送登录接口再次验证(可以不再验证)

//获取验证码
function sendCode(piccode){
    // $(".hxphone").hide();
    var phone = $("#loginName").val().trim();
    var flag = '';
    if($(".right .tab li.active").attr("data-id") == 7){
        flag = "stu";
    }else if ($(".right .tab li.active").attr("data-id") == 8){
        flag = "tea";
    }else {
        flag = "dw";
    }
    if(!codeDisabled){
        var params =  {phone:phone,tokenId:codeObj.tokenId,x:codeObj.x,y:codeObj.y,flag:flag};
        codeDisabled = true;
        $(".getYzm").attr("disabled","disabled");
        dsf.http.request(dsf.url.getWebPath("com/teas/sendsms/sendcode"),params, "GET")
            .done(function (res) {
                if (res.success) {
                    top.layer.closeAll();
                    yzmid = res.data;
                    dsf.layer.message("验证码已发送,请注意查收",true);
                    $(".getYzm").text(second+"s后重新获取");
                    getSjhm(params);
                    codetimer=setTimeout(function(){
                        startTime();
                    },1000);
                } else {
                    dsf.layer.message(res.message);
                    codeDisabled = false;
                    $(".getYzm").removeAttr("disabled");
                    loadrcode();
                }
            })
            .error(function (err) {
                dsf.layer.message("发送失败,请联系管理员!");
                codeDisabled = false;
                $(".getYzm").removeAttr("disabled");
            })
            .always(function () {
            })
            .exec();

    }
}

4、检查滑块位置是否正确

    /**
     * 验证码验证
     * @param tokenId 验证码id
     * @param x
     * @param y
     * @return
     */
    Result checkVerifyCode(String tokenId, Integer x, Integer y);
    @Override
    public Result checkVerifyCode(String tokenId, Integer x, Integer y) {
        JSONObject message = new JSONObject();
        int code;
        String resultStr;
        double time = 0.00;
        if (StringUtils.isEmpty(tokenId) || x == null || y == null) {
            message.put("code", 0);
//            message.put("message", "请求参数错误:参数不能为空");
            message.put("message", "验证不通过,请重试!");
            return Result.me().error().setData(message).setMessage("验证不通过,请重试!");
        }
        Map<String, Object> cacheObj = (Map<String, Object>) redisKit.get(KEY_VALIDATE_TOKEN+":"+tokenId);
        if (null == cacheObj) {
            code = -1;
            resultStr = "验证码超期,请重新请求!";
        } else {
            int sX = (Integer) cacheObj.get("X");
            int sY = (Integer) cacheObj.get("Y");
            int sStatus = (Integer) cacheObj.get("verifyCount");
            if (sY != y) {
                code = 0;
//                resultStr = "请求参数错误:位置信息不正确!";
                resultStr = "验证不通过,请重试!";
            } else {
                if (Math.abs(sX - x) <= CHECK_SCOPE) {
                    code = 1;
                    resultStr = "验证通过!";
                    time = System.currentTimeMillis() - (Long) cacheObj.get("time");
                    //更新验证状态,存储60s
                    cacheObj.put("verifyCount", sStatus + 1);
//                    redisTemplate.set(KEY_VALIDATE_TOKEN+":"+tokenId, cacheObj, 60);

                    //存储新token
                    String token = UUID.randomUUID().toString().replaceAll("-", "");
                    cacheObj.put("token",token);
                    redisKit.set(KEY_VALIDATE_TOKEN+":"+token, cacheObj, 120);
                    message.put("tokenId", token);
                } else {
                    code = 2;
                    resultStr = "验证不通过,请重试!";
                }
            }
            //删除旧token
            redisKit.del(KEY_VALIDATE_TOKEN+":"+tokenId);
        }

        message.put("time", NumberUtil.div(time * 1.00, 1000.00, 2));
        message.put("code", code);
        message.put("message", resultStr);
        if (code == 1){
            return Result.me().success().setData(message);
        }else{
            return Result.me().error().setData(message).setMessage(resultStr);
        }
    }

5、业务层全部代码


/**
 * <p>
 * 验证码信息 服务实现类
 * </p>
 */
@Service
public class VerifyServiceImpl implements VerifyService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    RedisKit redisKit;

    /**
     * 验证码缓存KEY
     */
    private static final String KEY_VERIFY_CODE = "KEY_VERIFY_CODE";

    /**
     * Token Cache Key
     */
    private static final String KEY_VALIDATE_TOKEN = "KEY_VALIDATE_TOKEN";

    /**
     * Img Cache Key
     */
    private static final String KEY_VALIDATE_IMG = "KEY_VALIDATE_IMG";

    /**
     * 校验误差范围 px
     */
    private static final Integer CHECK_SCOPE = 10;

    private static List<byte[]> DEFAULT_IMG_LIST;

    static {
        try {
            DEFAULT_IMG_LIST = initFileByte();
            //未加载到图片或缓存被外部清除
            if (DEFAULT_IMG_LIST == null || DEFAULT_IMG_LIST.size() < 1) {
                //重新加载
                DEFAULT_IMG_LIST = initFileByteNew();
            }
        } catch (IOException e) {
            System.out.println("加载背景图失败");
        }
    }

    @Override
    public Result verifyCodeInit() throws IOException {
        Map<String,Object> result = new HashMap<>();
        /*本地缓存实现*/
        List<byte[]> imgList = null;
//        VerifyEntity verify = redisTemplate.get( KEY_VALIDATE_IMG,VerifyEntity.class);
//        if (!ObjectUtil.ObjectIsNull(verify)) {
//            imgList = verify.getImgList();
//        }
//
//        if (CollectionUtils.isEmpty(imgList)) {
//            imgList = initFileByte();
//            VerifyEntity verifyEntity = new VerifyEntity();
//            verifyEntity.setImgList(imgList);
//            redisTemplate.set(KEY_VALIDATE_IMG, verifyEntity);
//        }
        //加载内存中的图片
        imgList = DEFAULT_IMG_LIST;

        //随机取出一张验证图
        Random ra = new Random();
        int rd = ra.nextInt(imgList.size() - 1);
        byte[] targetIs = imgList.get(rd);
        //生成验证码
        PuzzleCaptcha puzzleCaptcha = new PuzzleCaptcha(targetIs);
        puzzleCaptcha.setImageQuality(Image.SCALE_AREA_AVERAGING);
        puzzleCaptcha.run();
        //抠块图
        String imgBtn = ImageConvertUtil.toDataUri(puzzleCaptcha.getVacancy(), "png");
        //背景图
        String imgBg = ImageConvertUtil.toDataUri(puzzleCaptcha.getArtwork(), "png");
        String token = UUID.randomUUID().toString().replaceAll("-", "");
        Map<String, Object> cacheObj = new HashMap<>(5);
        cacheObj.put("token", token);
        //偏移量
        cacheObj.put("X", puzzleCaptcha.getX());
        cacheObj.put("Y", puzzleCaptcha.getY());
        //验证起始时间
        cacheObj.put("time", System.currentTimeMillis());
        //保存验证状态
        cacheObj.put("verifyCount", 0);
        //保存2分钟
        redisKit.set(KEY_VALIDATE_TOKEN+":"+token, cacheObj, 120);
        result.put("imgBtn", imgBtn);
        result.put("imgBg", imgBg);
        result.put("tokenId", token);
        result.put("Y", puzzleCaptcha.getY());
        return Result.me().success().setData(result);
    }

    @Override
    public Result checkVerifyCode(String tokenId, Integer x, Integer y) {
        JSONObject message = new JSONObject();
        int code;
        String resultStr;
        double time = 0.00;
        if (StringUtils.isEmpty(tokenId) || x == null || y == null) {
            message.put("code", 0);
//            message.put("message", "请求参数错误:参数不能为空");
            message.put("message", "验证不通过,请重试!");
            return Result.me().error().setData(message).setMessage("验证不通过,请重试!");
        }
        Map<String, Object> cacheObj = (Map<String, Object>) redisKit.get(KEY_VALIDATE_TOKEN+":"+tokenId);
        if (null == cacheObj) {
            code = -1;
            resultStr = "验证码超期,请重新请求!";
        } else {
            int sX = (Integer) cacheObj.get("X");
            int sY = (Integer) cacheObj.get("Y");
            int sStatus = (Integer) cacheObj.get("verifyCount");
            if (sY != y) {
                code = 0;
//                resultStr = "请求参数错误:位置信息不正确!";
                resultStr = "验证不通过,请重试!";
            } else {
                if (Math.abs(sX - x) <= CHECK_SCOPE) {
                    code = 1;
                    resultStr = "验证通过!";
                    time = System.currentTimeMillis() - (Long) cacheObj.get("time");
                    //更新验证状态,存储60s
                    cacheObj.put("verifyCount", sStatus + 1);
//                    redisTemplate.set(KEY_VALIDATE_TOKEN+":"+tokenId, cacheObj, 60);

                    //存储新token
                    String token = UUID.randomUUID().toString().replaceAll("-", "");
                    cacheObj.put("token",token);
                    redisKit.set(KEY_VALIDATE_TOKEN+":"+token, cacheObj, 120);
                    message.put("tokenId", token);
                } else {
                    code = 2;
                    resultStr = "验证不通过,请重试!";
                }
            }
            //删除旧token
            redisKit.del(KEY_VALIDATE_TOKEN+":"+tokenId);
        }

        message.put("time", NumberUtil.div(time * 1.00, 1000.00, 2));
        message.put("code", code);
        message.put("message", resultStr);
        if (code == 1){
            return Result.me().success().setData(message);
        }else{
            return Result.me().error().setData(message).setMessage(resultStr);
        }
    }




    /**
     * 读取文件字节流
     */
    private static List<byte[]> initFileByte() throws IOException {
        //图片存储路径在resource/static/verify_imgs包下
        String imgPath = "META-INF/static/app/images/verify_imgs";
        URL url = Thread.currentThread().getContextClassLoader().getResource(imgPath);
        if (url != null) {
            String protocol = url.getProtocol();
            System.out.println("protocol ======> " + protocol);
            return "jar".equalsIgnoreCase(protocol) ? initJarImg(url) : initClassImg(url);
        }
        return null;
    }

    /**
     * 读取文件字节流
     */
    private static List<byte[]> initFileByteNew() throws IOException {
        //图片存储路径在resource/static/verify_imgs包下
        DSFLog.info("在执行自己写的获取图片方法");
        List<byte[]> allImg = new ArrayList<>();;
        for (int i = 1; i <= 10; i++) {
            try {
                String imgUrl = VerifyServiceImpl.class.getResource("/").getPath().concat("templates/verify_imgs/").concat(String.valueOf(i)).concat(".jpg");
                InputStream inputStream = new FileInputStream(imgUrl);
                byte[] imgByte = convert2ByteArray(inputStream) ;
                allImg.add(imgByte);
            }catch (Exception e){
                String imgUrl = "templates/verify_imgs/" + i + ".jpg";
                InputStream inputStream = VerifyServiceImpl.class.getClassLoader().getResourceAsStream(imgUrl);
                byte[] imgByte = convert2ByteArray(inputStream) ;
                allImg.add(imgByte);
            }

        }
        DSFLog.info("执行自己写的获取图片方法完成,图片数量:"+allImg.size());
        return allImg;
    }

    /**
     * inputStream转为byte[]
     *
     * @param inStream
     * @return byte[]
     * @throws IOException
     */
    public static byte[] convert2ByteArray(InputStream inStream) throws IOException {
        ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
        byte[] buff = new byte[1024];
        int rc = 0;
        while ((rc = inStream.read(buff, 0, 1024)) > 0) {
            swapStream.write(buff, 0, rc);
        }
        byte[] in_b = swapStream.toByteArray();// in_b为转换之后的结果
        return in_b;
    }


    /**
     * 加载项目资源图片文件
     *
     * @param url
     * @return
     */
    private static List<byte[]> initClassImg(URL url) throws IOException {
        if (url != null) {
            File fileDir = new File(url.getPath());
            File[] fs = fileDir.listFiles();
            if (fs != null && fs.length > 0) {
                List<byte[]> byteList = new ArrayList<>();
                byte[] bytes;
                for (File f : fs) {
                    bytes = IOUtils.toByteArray(new FileInputStream(f));
                    byteList.add(bytes);
                }
                return byteList;
            }
        }
        return null;
    }

    /**
     * 加载jar包资源图片文件
     *
     * @param url
     * @return
     */
    private static List<byte[]> initJarImg(URL url) throws IOException {
        DSFLog.logDebug(url);
        if (url != null) {
            DSFLog.info("再执行读取jar包文件方法 url="+url);
            String jarPath = url.getPath().substring(0, url.getPath().indexOf("!/") + 2);
            URL jarUrl = new URL("jar:" + jarPath);
            JarURLConnection jarCon = (JarURLConnection) jarUrl.openConnection();
            if (jarCon != null) {
                DSFLog.info("JarURLConnection 成功"+jarCon);
                JarFile jarFile = jarCon.getJarFile();
                Enumeration<JarEntry> jarEntrys = jarFile.entries();
                byte[] bytes;
                List<byte[]> byteList = new ArrayList<>();
                while (jarEntrys.hasMoreElements()) {
                    JarEntry entry = jarEntrys.nextElement();
                    String name = entry.getName();
                    //打包jar后的图片资源地址
                    if (name.startsWith("META-INF/classes/static/verify_imgs") && name.contains(".jpg")) {
                        bytes = IOUtils.toByteArray(ImgValidationController.class.getClassLoader().getResourceAsStream(name));
                        byteList.add(bytes);
                    }
                }
                return byteList;
            }else{
                DSFLog.info("JarURLConnection 失败"+jarCon);
            }
        }
        return null;
    }

}

  • 14
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现滑动图片验证的代码可以分为前端和后端两部分。 前端部分: 1. 在页面中添加一个验证的容器和一个滑块,如下所示: ```html <div class="verification-container"> <div class="verification-img"></div> <div class="verification-slider"></div> </div> ``` 2. 在CSS文件中设置验证容器和滑块的样式,如下所示: ```css .verification-container { position: relative; width: 300px; height: 200px; margin: 0 auto; background-color: #f5f5f5; border: 1px solid #ccc; overflow: hidden; } .verification-img { position: absolute; left: 0; top: 0; width: 300px; height: 200px; background-image: url(verification-image.jpg); background-size: cover; } .verification-slider { position: absolute; left: 0; top: 80px; width: 60px; height: 40px; background-color: #fff; border: 1px solid #ccc; cursor: pointer; } ``` 3. 编写JavaScript代码,实现滑块拖动验证的功能,如下所示: ```javascript // 获取验证容器和滑块元素 var container = document.querySelector('.verification-container'); var slider = document.querySelector('.verification-slider'); // 设置滑块拖动事件 slider.addEventListener('mousedown', function (event) { // 记录滑块的起始位置 var startX = event.clientX - slider.offsetLeft; // 设置鼠标移动事件 document.addEventListener('mousemove', moveHandler); // 设置鼠标松开事件 document.addEventListener('mouseup', upHandler); function moveHandler(event) { // 计算滑块的位置 var sliderX = event.clientX - startX; // 限制滑块的位置在验证容器内 if (sliderX < 0) { sliderX = 0; } if (sliderX > container.clientWidth - slider.offsetWidth) { sliderX = container.clientWidth - slider.offsetWidth; } // 设置滑块的位置 slider.style.left = sliderX + 'px'; } function upHandler(event) { // 移除鼠标事件 document.removeEventListener('mousemove', moveHandler); document.removeEventListener('mouseup', upHandler); // 验证滑块的位置是否正确 if (slider.offsetLeft > 240) { alert('验证通过!'); } else { alert('验证失败!'); } // 重置滑块的位置 slider.style.left = 0; } }); ``` 后端部分: 1. 在Java实现一个Servlet,用于接收前端传递的验证结果,如下所示: ```java @WebServlet("/verification") public class VerificationServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取验证结果 String result = request.getParameter("result"); // 验证结果是否正确 if ("success".equals(result)) { response.getWriter().write("验证通过!"); } else { response.getWriter().write("验证失败!"); } } } ``` 2. 在JSP页面中添加一个隐藏的表单域和一个提交按钮,用于将验证结果传递给Servlet,如下所示: ```html <form id="verification-form" method="post" action="verification"> <input type="hidden" name="result" id="verification-result"> <button type="submit" id="verification-submit">提交</button> </form> ``` 3. 在JavaScript代码中,将验证结果设置到隐藏的表单域中,并提交表单,如下所示: ```javascript // 获取表单元素 var form = document.querySelector('#verification-form'); var result = document.querySelector('#verification-result'); var submit = document.querySelector('#verification-submit'); // 设置提交事件 submit.addEventListener('click', function (event) { // 获取验证结果 var verificationResult = slider.offsetLeft > 240 ? 'success' : 'fail'; // 设置验证结果到表单域中 result.value = verificationResult; // 提交表单 form.submit(); }); ``` 这样,前端和后端的代码就都实现了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值