总体思路,
1.从一个静态资源文件夹下获取一张图片,然后根据图片模板,随机取一个碎片,
2.把背景图和图片模板碎片,还有当前图片模板碎片的纵坐标,请求响应时间(用于判断接口超时)传给前端
3.把接口响应时间,滑块拖动的横坐标给后端,后端判断一下横坐标是否在范围内,如果在就验证通过。
下面是后端代码:
package com.jkys.crm.controller.sys;import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.jkys.crm.utils.VerifyImageUtil;
import com.jkys.crm.view.ViewUtil;
import com.jkys.crm.view.body.SimpleBpmBody;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.web.bind.annotation.*;import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;/*** * @模块名:javaslidingverification* @包名:com.liangyt.javaslidingverification.controller* @类名称: SliderIMageController* @类描述:【类描述】滑块验证码控制层* @版本:1.0* @创建人:cc* @创建时间:2019年10月24日上午10:44:30*/
@Slf4j
@Api(tags = "验证码相关")
@RequestMapping("/sys/slider")
@RestController
public class SliderIMageController {// 保存横轴位置用于对比,并设置最大数量为10000,多了就先进先出,并设置超时时间为70秒public static Cache< String, Integer > cacheg = CacheBuilder.newBuilder().expireAfterWrite(70, TimeUnit.SECONDS).maximumSize(10000).build();@ResponseBody@GetMapping("/getPic")public Object getPic(HttpServletRequest request) throws IOException {// 随机选择需要切的图int randNum = new Random().nextInt(23)+1;Resource resource = new ClassPathResource("/static/images/"+ randNum + ".jpg");InputStream inputStream = resource.getInputStream();BufferedImage targetImg = ImageIO.read(inputStream);// 随机选择剪切模版int num = new Random().nextInt(6) + 1;Resource templatesUrl = new ClassPathResource("/static/templates/"+ num + "-w.png");InputStream inputStream2 = templatesUrl.getInputStream();BufferedImage tempImg = ImageIO.read(inputStream2);// 根据模板裁剪图片try {Map < String, Object > resultMap = VerifyImageUtil.pictureTemplatesCut(tempImg, targetImg);// 生成流水号,这里就使用时间戳代替String lno = Calendar.getInstance().getTimeInMillis() + "";cacheg.put(lno, Integer.valueOf(resultMap.get("xWidth") + ""));resultMap.put("capcode", lno);HttpSession session = request.getSession();session.setAttribute("verifycode", lno);// 移除横坐标送前端resultMap.remove("xWidth");return ViewUtil.defaultView(new SimpleBpmBody(resultMap));}catch (Exception e) {e.printStackTrace();return null;}}@ResponseBody@GetMapping("/checkCapCode")public Object checkcapcode(@RequestParam("xpos") int xpos,@RequestParam("capcode") String capcode, HttpServletRequest request) throws IOException {Map < String, Object > result = new HashMap < String, Object >();Integer x = cacheg.getIfPresent(capcode);if (x == null) {// 超期result.put("code", 3);}else if (xpos - x > 5 || xpos - x < -5) {// 验证失败result.put("code", 2);}else {// 验证成功result.put("code", 1);// .....做自己的操作,发送验证码}return ViewUtil.defaultView(new SimpleBpmBody(result));}}
package com.jkys.crm.utils;import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Base64Utils;import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;/*** @desc【类描述】滑块验证码生成工具类.* @版本:1.0.* @创建人:longcheng.* @创建时间:2020年11月19日上午10:11:22.*/
@Slf4j
public class VerifyImageUtil {private static int BOLD = 5;private static final String IMG_FILE_TYPE = "jpg";private static final String TEMP_IMG_FILE_TYPE = "png";/*** 根据模板切图* * @param imageTemplate* @param targetFile* @return* @throws Exception*/public static Map < String, Object > pictureTemplatesCut(BufferedImage imageTemplate, BufferedImage targetFile) throws Exception {Map < String, Object > pictureMap = new HashMap <>();// 模板图int templateWidth = imageTemplate.getWidth();int templateHeight = imageTemplate.getHeight();// 原图int oriImageWidth = targetFile.getWidth();int oriImageHeight = targetFile.getHeight();// 随机生成抠图坐标X,Y// X轴距离右端targetWidth Y轴距离底部targetHeight以上Random random = new Random();int widthRandom = random.nextInt(oriImageWidth - 2 * templateWidth) + templateWidth;// int heightRandom = 1;int heightRandom = random.nextInt(oriImageHeight - templateHeight);log.info("原图大小{} x {},随机生成的坐标 X,Y 为({},{})", oriImageWidth, oriImageHeight, widthRandom, heightRandom);// 新建一个和模板一样大小的图像,TYPE_4BYTE_ABGR表示具有8位RGBA颜色分量的图像,正常取imageTemplate.getType()BufferedImage newImage = new BufferedImage(templateWidth, templateHeight, imageTemplate.getType());// 得到画笔对象Graphics2D graphics = newImage.createGraphics();// 如果需要生成RGB格式,需要做如下配置,Transparency 设置透明newImage = graphics.getDeviceConfiguration().createCompatibleImage(templateWidth, templateHeight,Transparency.TRANSLUCENT);// 新建的图像根据模板颜色赋值,源图生成遮罩cutByTemplate(targetFile, imageTemplate, newImage, widthRandom, heightRandom);// 设置“抗锯齿”的属性graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);graphics.setStroke(new BasicStroke(BOLD, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));graphics.drawImage(newImage, 0, 0, null);graphics.dispose();ByteArrayOutputStream newImageOs = new ByteArrayOutputStream();// 新建流。ImageIO.write(newImage, TEMP_IMG_FILE_TYPE, newImageOs);// 利用ImageIO类提供的write方法,将bi以png图片的数据模式写入流。byte[] newImagebyte = newImageOs.toByteArray();ByteArrayOutputStream oriImagesOs = new ByteArrayOutputStream();// 新建流。ImageIO.write(targetFile, IMG_FILE_TYPE, oriImagesOs);// 利用ImageIO类提供的write方法,将bi以jpg图片的数据模式写入流。byte[] oriImageByte = oriImagesOs.toByteArray();pictureMap.put("slidingImage", Base64Utils.encodeToString(newImagebyte));pictureMap.put("backImage", Base64Utils.encodeToString(oriImageByte));pictureMap.put("xWidth", widthRandom);pictureMap.put("yHeight", heightRandom);return pictureMap;}/*** 添加水印* * @param oriImage*//** private static BufferedImage addWatermark(BufferedImage oriImage) throws IOException { Graphics2D graphics2D =* oriImage.createGraphics(); graphics2D .setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints* .VALUE_INTERPOLATION_BILINEAR); // 设置水印文字颜色 graphics2D.setColor(Color.BLUE); // 设置水印文字Font graphics2D.setFont(new* java.awt.Font("宋体", java.awt.Font.BOLD, 50)); // 设置水印文字透明度 graphics2D.setComposite* (AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.5f)); // 第一参数->设置的内容,后面两个参数->文字在图片上的坐标位置(x,y)* graphics2D.drawString("zhoujin@qq.com", 400,300); graphics2D.dispose(); //释放 return oriImage; }*//*** @param oriImage 原图* @param templateImage 模板图* @param newImage 新抠出的小图* @param x 随机扣取坐标X* @param y 随机扣取坐标y* @throws Exception*/private static void cutByTemplate(BufferedImage oriImage, BufferedImage templateImage, BufferedImage newImage,int x, int y) {// 临时数组遍历用于高斯模糊存周边像素值int[][] martrix = new int[3][3];int[] values = new int[9];int xLength = templateImage.getWidth();int yLength = templateImage.getHeight();// 模板图像宽度for (int i = 0; i < xLength; i++) {// 模板图片高度for (int j = 0; j < yLength; j++) {// 如果模板图像当前像素点不是透明色 copy源文件信息到目标图片中int rgb = templateImage.getRGB(i, j);if (rgb < 0) {newImage.setRGB(i, j, oriImage.getRGB(x + i, y + j));// 抠图区域高斯模糊readPixel(oriImage, x + i, y + j, values);fillMatrix(martrix, values);oriImage.setRGB(x + i, y + j, avgMatrix(martrix));}// 防止数组越界判断if (i == (xLength - 1) || j == (yLength - 1)) {continue;}int rightRgb = templateImage.getRGB(i + 1, j);int downRgb = templateImage.getRGB(i, j + 1);// 描边处理,,取带像素和无像素的界点,判断该点是不是临界轮廓点,如果是设置该坐标像素是白色if ((rgb >= 0 && rightRgb < 0) || (rgb < 0 && rightRgb >= 0) || (rgb >= 0 && downRgb < 0)|| (rgb < 0 && downRgb >= 0)) {newImage.setRGB(i, j, Color.white.getRGB());oriImage.setRGB(x + i, y + j, Color.white.getRGB());}}}}private static void readPixel(BufferedImage img, int x, int y, int[] pixels) {int xStart = x - 1;int yStart = y - 1;int current = 0;for (int i = xStart; i < 3 + xStart; i++)for (int j = yStart; j < 3 + yStart; j++) {int tx = i;if (tx < 0) {tx = -tx;}else if (tx >= img.getWidth()) {tx = x;}int ty = j;if (ty < 0) {ty = -ty;}else if (ty >= img.getHeight()) {ty = y;}pixels[current++] = img.getRGB(tx, ty);}}private static void fillMatrix(int[][] matrix, int[] values) {int filled = 0;for (int i = 0; i < matrix.length; i++) {int[] x = matrix[i];for (int j = 0; j < x.length; j++) {x[j] = values[filled++];}}}private static int avgMatrix(int[][] matrix) {int r = 0;int g = 0;int b = 0;for (int i = 0; i < matrix.length; i++) {int[] x = matrix[i];for (int j = 0; j < x.length; j++) {if (j == 1) {continue;}Color c = new Color(x[j]);r += c.getRed();g += c.getGreen();b += c.getBlue();}}return new Color(r / 8, g / 8, b / 8).getRGB();}public static void main(String[] args) {}
}
完成!