将图片放入项目resources/original
抠图放入 resources/templates
Controller 代码
@GetMapping("/verification")
@ApiOperation("获取滑动验证码")
public CommonResult<?> getVerification() {
return CommonResult.success(imageSlideVerification.getSlideCode());
}
@GetMapping("/check")
@ApiOperation("校验验证码")
public CommonResult<?> check(@RequestParam String imageToken, @RequestParam String x, @RequestParam String y) {
if (imageSlideVerification.checkSlideCode(imageToken, x, y)) {
return CommonResult.success("验证成功!");
}
return CommonResult.failed("验证失败!");
}
import com.alibaba.fastjson.JSONObject;
import com.dpxdata.backend.admin.vo.SlideVerificationVO;
import com.dpxdata.backend.db.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
/**
*
* add by Lish 2022/04/29 滑动验证码
*/
@Slf4j
@Component
public class ImageSlideVerification {
/**
* 源图目录路径
*/
private static String ORIGIN_CATALOG = "original";
/**
* 模板图目录路径
*/
private static String TEMPLATE_CATALOG = "templates";
/**
* 描边图目录路径
*/
private static String BORDER_CATALOG = "templates";
/**
* Image-key
*/
private static String IMAGE_KEY = "IMAGE:KEY:";
@Autowired
private RedisService redisService;
/**
* 获取滑动验证码
*
* @return
*/
public SlideVerificationVO getSlideCode() {
InputStream oIn = null;
InputStream tIn = null;
InputStream bIn = null;
SlideVerificationVO vo = null;
try {
// 原图
final int index = new Random().nextInt(6);
log.info(index + "");
File originalFile = new File(index + ".png");
oIn = ImageSlideVerification.class.getClassLoader().getResourceAsStream("original/" + index + ".png");
FileUtils.copyInputStreamToFile(oIn, originalFile);
// 滑块模板图
File templateFile = new File("template.png");
tIn = ImageSlideVerification.class.getClassLoader().getResourceAsStream("templates/template.png");
FileUtils.copyInputStreamToFile(tIn, templateFile);
// 滑块描边图片
File borderFile = new File("border.png");
bIn = ImageSlideVerification.class.getClassLoader().getResourceAsStream("templates/border.png");
FileUtils.copyInputStreamToFile(bIn, borderFile);
// 生成随机x、y
int x = generateX(originalFile, templateFile);
int y = generateY(originalFile, templateFile);
// 生成滑块
String slide = cutSlideImage(originalFile, templateFile, borderFile, x, y);
// 生成缺块图
String back = cutBackImage(originalFile, templateFile, borderFile, x, y);
// 生成token
String imageToken = UUID.randomUUID().toString().replace("-", "");
// 放入redis
Map<String, Integer> cacheMap = new HashMap<>();
cacheMap.put("x", x);
cacheMap.put("y", y);
log.info("验证码Key => " + imageToken);
redisService.set(imageToken, cacheMap, 2);
vo = new SlideVerificationVO();
vo.setImageToken(imageToken);
vo.setBackImage(back);
vo.setSlideImage(slide);
vo.setY(y + "");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (oIn != null) {
oIn.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (tIn != null) {
tIn.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (bIn != null) {
bIn.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return vo;
}
/**
* 验证滑动验证码
*
* @param imageToken
* @return
*/
public boolean checkSlideCode(String imageToken, String codeX, String codeY) {
if (!redisService.hasKey(imageToken)) {
return false;
}
final Map<String, Integer> cacheMap = (Map<String, Integer>) redisService.get(imageToken);
// 获取缓存中的x、y
int x = cacheMap.get("x");
int y = cacheMap.get("y");
// 验证 x
int errorX = Integer.parseInt(codeX) - x;
// update by Lish 2022/05/09 滑动验证码,校验范围修改
if (errorX > 5 || errorX < -5) {
return false;
}
// 验证 y
int errorY = Integer.parseInt(codeY) - y;
if (errorY > 1 || errorY < -1) {
return false;
}
// *****验证路径*****
// *****验证路径*****
// 验证成功,修改key的值
redisService.set(imageToken, "T", 2);
return true;
}
/**
* 获取跟目录
*
* @return
*/
private static String getClassPath() {
return ImageSlideVerification.class.getClass().getResource("/").getPath();
}
/**
* 获取随机原图图片
*/
private static File getOriginalFile() {
StringBuilder builder = new StringBuilder(getClassPath());
builder.append(ORIGIN_CATALOG);
builder.append("/");
builder.append("1");
builder.append(".png");
return new File(builder.toString());
}
/**
* 滑块模板图片
*/
private static File getTemplateFile() {
StringBuilder builder = new StringBuilder(getClassPath());
builder.append(TEMPLATE_CATALOG);
builder.append("/");
builder.append("template.png");
return new File(builder.toString());
}
/**
* 获取描边原图图片
*/
private static File getBorderFile() {
StringBuilder builder = new StringBuilder(getClassPath());
builder.append(BORDER_CATALOG);
builder.append("/");
builder.append("border.png");
return new File(builder.toString());
}
/**
* 随机X坐标
*
* @param originalFile
* @param templateFile
* @return
*/
private static int generateX(File originalFile, File templateFile) {
try {
BufferedImage originalImage = ImageIO.read(originalFile);
BufferedImage templateImage = ImageIO.read(templateFile);
// 原图宽度
int width = originalImage.getWidth();
// 模板宽度
int templateImageWidth = templateImage.getWidth();
Random random = new Random(System.currentTimeMillis());
return random.nextInt(width - templateImageWidth) % (width - templateImageWidth - templateImageWidth + 1) + templateImageWidth;
} catch (IOException e) {
e.printStackTrace();
}
return 0;
}
/**
* 随机Y坐标
*
* @param originalFile
* @param templateFile
* @return
*/
private static int generateY(File originalFile, File templateFile) {
try {
BufferedImage originalImage = ImageIO.read(originalFile);
BufferedImage templateImage = ImageIO.read(templateFile);
// 原图高度
int height = originalImage.getHeight();
// 抠图模板高度
int templateImageHeight = templateImage.getHeight();
Random random = new Random(System.currentTimeMillis());
if (templateImageHeight - height >= 0) {
return random.nextInt(10);
}
return random.nextInt(height - templateImageHeight) % (height - templateImageHeight - templateImageHeight + 1) + templateImageHeight;
} catch (IOException e) {
e.printStackTrace();
}
return 0;
}
/**
* 裁剪滑块
*
* @param originalFile
* @param templateFile
* @param borderFile
* @param x
* @param y
* @return
*/
private static String cutSlideImage(File originalFile, File templateFile, File borderFile, int x, int y) {
ImageInputStream originImageInputStream = null;
ByteArrayOutputStream byteArrayOutputStream = null;
try {
BufferedImage templateImage = ImageIO.read(templateFile);
BufferedImage borderImage = ImageIO.read(borderFile);
int with = templateImage.getWidth();
int height = templateImage.getHeight();
// 创建滑块
BufferedImage cutoutImage = new BufferedImage(with, height, templateImage.getType());
// 从原图中获取与滑块一样大小的4边形图片
ImageReader originalReader = ImageIO.getImageReadersByFormatName("png").next();
originImageInputStream = ImageIO.createImageInputStream(originalFile);
// 图片输入流顺序读写
originalReader.setInput(originImageInputStream, true);
// 根据坐标生成矩形
ImageReadParam imageReadParam = originalReader.getDefaultReadParam();
Rectangle rectangle = new Rectangle(x, y, with, height);
imageReadParam.setSourceRegion(rectangle);
// 从原图中裁剪下来的图片
BufferedImage cutImage = originalReader.read(0, imageReadParam);
// 获取剪切图片矩阵颜色
int[][] cutColorArray = getColorArray(cutImage);
// 获取模板图片矩阵颜色
int[][] templateColorArray = getColorArray(templateImage);
// 获取边框图片矩阵颜色
int[][] borderColorArray = getColorArray(borderImage);
// 将模板图非透明像素设置到剪切图中
for (int i = 0; i < templateColorArray.length; i++) {
for (int j = 0; j < templateColorArray[0].length; j++) {
// 滑块模板颜色不为透明时设置剪切图颜色
int templateRGB = templateColorArray[i][j];
if (templateRGB != 16777215 && templateRGB < 0) {
cutoutImage.setRGB(i, j, cutColorArray[i][j]);
}
// 设置边框颜色
int borderRGB = borderColorArray[i][j];
if (borderRGB != 16777215 && borderRGB < 0) {
int[] colorArray = {(borderColorArray[i][j] & 0xff0000) >> 16,
(borderColorArray[i][j] & 0xff00) >> 8,
(borderColorArray[i][j] & 0xff)};
// 我的边框是黑色所以我这是加
colorArray[0] += (cutColorArray[i][j] & 0xff0000) >> 16;
colorArray[1] += (cutColorArray[i][j] & 0xff00) >> 8;
colorArray[2] += (cutColorArray[i][j] & 0xff);
cutoutImage.setRGB(i, j, new Color(colorArray[0] > 255 ? 255 : colorArray[0],
colorArray[1] > 255 ? 255 : colorArray[1],
colorArray[2] > 255 ? 255 : colorArray[2]).getRGB());
}
}
}
// 图片绘图
int bold = 5;
Graphics2D graphics = cutoutImage.createGraphics();
graphics.setBackground(Color.white);
// 设置抗锯齿属性
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setStroke(new BasicStroke(bold, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
graphics.drawImage(cutoutImage, 0, 0, null);
graphics.dispose();
// 输出图片流
byteArrayOutputStream = new ByteArrayOutputStream();
ImageIO.write(cutoutImage, "png", byteArrayOutputStream);
// 图片转为二进制字符串
byte[] cutoutImageBytes = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.reset();
// 图片加密成base64字符串
return String.format("data:image/png;base64,%s", cn.hutool.core.codec.Base64.encode(cutoutImageBytes));
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (originImageInputStream != null) {
originImageInputStream.close();
}
if (byteArrayOutputStream != null) {
byteArrayOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
* 生成背景图
*
* @param originalFile
* @param templateFile
* @param borderFile
* @param x
* @param y
* @return
*/
private static String cutBackImage(File originalFile, File templateFile, File borderFile, int x, int y) {
ByteArrayOutputStream byteArrayOutputStream = null;
try {
BufferedImage originalImage = ImageIO.read(originalFile);
BufferedImage templateImage = ImageIO.read(templateFile);
int with = templateImage.getWidth();
int height = templateImage.getHeight();
// 根据原图,创建支持alpha通道的rgb图片
BufferedImage shadeImage = new BufferedImage(originalImage.getWidth(), originalImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
// 获取原图片矩阵
int[][] originColorArray = getColorArray(originalImage);
// 将原图的像素拷贝到遮罩图
for (int i = 0; i < originColorArray.length; i++) {
for (int j = 0; j < originColorArray[0].length; j++) {
int originalRGB = originalImage.getRGB(i, j);
// 在模板区域内
if (i > x && i < (x + with) && j > y && j < (y + height)) {
int templateRGB = templateImage.getRGB(i - x, j - y);
//对源文件备份图像(x+i,y+j)坐标点进行透明处理
if (templateRGB != 16777215 && templateRGB < 0) {
int[] rgb = new int[3];
rgb[0] = (originalRGB & 0xff0000) >> 16;
rgb[1] = (originalRGB & 0xff00) >> 8;
rgb[2] = (originalRGB & 0xff);
// 对遮罩透明处理
shadeImage.setRGB(i, j, new Color(rgb[0] / 3, rgb[1] / 3, rgb[2] / 3).getRGB());
continue;
}
}
// 无透明处理
shadeImage.setRGB(i, j, originalRGB);
}
}
// 输出图片流
byteArrayOutputStream = new ByteArrayOutputStream();
ImageIO.write(shadeImage, "png", byteArrayOutputStream);
// 图片转为二进制字符串
byte[] cutoutImageBytes = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.reset();
// 图片加密成base64字符串
return String.format("data:image/png;base64,%s", cn.hutool.core.codec.Base64.encode(cutoutImageBytes));
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (byteArrayOutputStream != null) {
byteArrayOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
* 图片生成图像矩阵
*
* @param bufferedImage 图片源
* @return 图片矩阵
*/
private static int[][] getColorArray(BufferedImage bufferedImage) {
int[][] matrix = new int[bufferedImage.getWidth()][bufferedImage.getHeight()];
for (int i = 0; i < bufferedImage.getWidth(); i++) {
for (int j = 0; j < bufferedImage.getHeight(); j++) {
matrix[i][j] = bufferedImage.getRGB(i, j);
}
}
return matrix;
}
public static void main(String[] args) {
for (int i = 0; i < 50; i++) {
new Thread(new Runnable() {
@Override
public void run() {
// 原图
File originalFile = getOriginalFile();
// 模板图
File templateFile = getTemplateFile();
// 描边图片
File borderFile = getBorderFile();
int x = generateX(originalFile, templateFile);
int y = generateY(originalFile, templateFile);
String slide = cutSlideImage(originalFile, templateFile, borderFile, x, y);
String back = cutBackImage(originalFile, templateFile, borderFile, x, y);
String imageToken = UUID.randomUUID().toString().replace("-", "");
SlideVerificationVO vo = new SlideVerificationVO();
vo.setImageToken(imageToken);
vo.setBackImage(back);
vo.setSlideImage(slide);
vo.setY(y + "");
System.out.println(JSONObject.toJSONString(vo));
}
}).start();
}
}
}
vo对象
import lombok.Data;
@Data
public class SlideVerificationVO {
private String imageToken;
private String backImage;
private String slideImage;
private String x;
private String y;
}