滑动验证码
滑动验证码是一个比较流行的验证码手段,比字符图形验证码更具备随机性,其基本原理就是利用一张图片,在此图片上随机的x,y处抠出一个方框(当然可以在此方框上再凸出凹进形成七巧板的形式),然后将这两张图和抠图的y轴坐标给前端,并生成流水号,记录流水号与x,y的关系。前端绘制原图,在y的地方的最左边绘制抠图,拖动此抠图获取的x和流水号传递给后端进行验证。可以将图片base64后通过一个接口给前端,减少磁盘使用。
生成抠图和抠图后的原图的工具类
import sun.misc.BASE64Decoder;
import top.jfunc.common.utils.ArrayUtil;
import top.jfunc.common.utils.RandomUtil;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Random;
/**
* 滑动验证码相关工具方法
*/
public class VerifyImageUtil {
/**
* 模板图宽度
*/
private static int CUT_WIDTH = 50;
/**
* 模板图高度
*/
private static int CUT_HEIGHT = 50;
/**
* 抠图凸起圆心
*/
private static int CIRCLE_R = 5;
/**
* 抠图内部矩形填充大小
*/
private static int RECTANGLE_PADDING = 8;
/**
* 抠图的边框宽度
*/
private static int SLIDER_IMG_OUT_PADDING = 1;
private static int CENTER_INDICATOR = 1;
private static int BORDER_INDICATOR = 2;
private static int OTHER_INDICATOR = 0;
/**
* 根据传入的file生成验证码图片
*/
public static VerifyImage getVerifyImage(File file) throws IOException {
BufferedImage srcImage = ImageIO.read(file);
return getVerifyImage(srcImage);
}
/**
* 根据转入的流生成验证码图片
*/
public static VerifyImage getVerifyImage(InputStream inputStream) throws IOException {
BufferedImage srcImage = ImageIO.read(inputStream);
return getVerifyImage(srcImage);
}
/**
* 以左上角为计数起点
*/
public static VerifyImage getVerifyImage(BufferedImage srcImage) throws IOException {
//中线往右,最右减两个方块儿
int locationX = RandomUtil.randomInt(srcImage.getWidth()/2, srcImage.getWidth() - CUT_WIDTH * 2);
//固定中线
int locationY = (srcImage.getHeight() - CUT_HEIGHT)/2;
BufferedImage markImage = new BufferedImage(CUT_WIDTH,CUT_HEIGHT,BufferedImage.TYPE_4BYTE_ABGR);
cutImgByTemplate(srcImage, markImage, getBlockData(), locationX, locationY);
return new VerifyImage(getImageBASE64(srcImage),
getImageBASE64(markImage),
locationX,
locationY,
srcImage.getWidth(),
srcImage.getHeight());
}
/**
* 生成随机滑块形状
* <p>
* 0 透明像素
* 1 滑块像素
* 2 阴影像素
* @return int[][]
*/
private static int[][] getBlockData() {
int[][] data = new int[CUT_WIDTH][CUT_HEIGHT];
Random random = new Random();
//(x-a)²+(y-b)²=r²
//x中心位置左右5像素随机
double x1 = RECTANGLE_PADDING + (CUT_WIDTH - 2 * RECTANGLE_PADDING) / 2.0/* - 5 + random.nextInt(10)*/;
//y 矩形上边界半径-1像素移动
double y1_top = RECTANGLE_PADDING - random.nextInt(3);
double y1_bottom = CUT_HEIGHT - RECTANGLE_PADDING + random.nextInt(3);
double y1 = random.nextInt(2) == 1 ? y1_top : y1_bottom;
double x2_right = CUT_WIDTH - RECTANGLE_PADDING - CIRCLE_R + random.nextInt(2 * CIRCLE_R - 4);
double x2_left = RECTANGLE_PADDING + CIRCLE_R - 2 - random.nextInt(2 * CIRCLE_R - 4);
double x2 = random.nextInt(2) == 1 ? x2_right : x2_left;
double y2 = RECTANGLE_PADDING + (CUT_HEIGHT - 2 * RECTANGLE_PADDING) / 2.0 - 4 + random.nextInt(10);
double po = Math.pow(CIRCLE_R, 2);
for (int i = 0; i < CUT_WIDTH; i++) {
for (int j = 0; j < CUT_HEIGHT; j++) {
//矩形区域
boolean fill;
if ((i >= RECTANGLE_PADDING && i < CUT_WIDTH - RECTANGLE_PADDING)
&& (j >= RECTANGLE_PADDING && j < CUT_HEIGHT - RECTANGLE_PADDING)) {
data[i][j] = CENTER_INDICATOR;
fill = true;
} else {
data[i][j] = OTHER_INDICATOR;
fill = false;
}
//凸出区域
double d3 = Math.pow(i - x1, 2) + Math.pow(j - y1, 2);
if (d3 < po) {
data[i][j] = CENTER_INDICATOR;
} else {
if (!fill) {
data[i][j] = OTHER_INDICATOR;
}
}
//凹进区域
double d4 = Math.pow(i - x2, 2) + Math.pow(j - y2, 2);
if (d4 < po) {
data[i][j] = OTHER_INDICATOR;
}
}
}
//边界阴影
for (int i = 0; i < CUT_WIDTH; i++) {
for (int j = 0; j < CUT_HEIGHT; j++) {
//四个正方形边角处理
for (int k = 1; k <= SLIDER_IMG_OUT_PADDING; k++) {
//左上、右上
if (i >= RECTANGLE_PADDING - k && i < RECTANGLE_PADDING
&& ((j >= RECTANGLE_PADDING - k && j < RECTANGLE_PADDING)
|| (j >= CUT_HEIGHT - RECTANGLE_PADDING - k && j < CUT_HEIGHT - RECTANGLE_PADDING +1))) {
data[i][j] = BORDER_INDICATOR;
}
//左下、右下
if (i >= CUT_WIDTH - RECTANGLE_PADDING + k - 1 && i < CUT_WIDTH - RECTANGLE_PADDING + 1) {
for (int n = 1; n <= SLIDER_IMG_OUT_PADDING; n++) {
if (((j >= RECTANGLE_PADDING - n && j < RECTANGLE_PADDING)
|| (j >= CUT_HEIGHT - RECTANGLE_PADDING - n && j <= CUT_HEIGHT - RECTANGLE_PADDING ))) {
data[i][j] = BORDER_INDICATOR;
}
}
}
}
if (data[i][j] == 1 && j - SLIDER_IMG_OUT_PADDING > 0 && data[i][j - SLIDER_IMG_OUT_PADDING] == 0) {
data[i][j - SLIDER_IMG_OUT_PADDING] = BORDER_INDICATOR;
}
if (data[i][j] == 1 && j + SLIDER_IMG_OUT_PADDING > 0 && j + SLIDER_IMG_OUT_PADDING < CUT_HEIGHT && data[i][j + SLIDER_IMG_OUT_PADDING] == 0) {
data[i][j + SLIDER_IMG_OUT_PADDING] = BORDER_INDICATOR;
}
if (data[i][j] == 1 && i - SLIDER_IMG_OUT_PADDING > 0 && data[i - SLIDER_IMG_OUT_PADDING][j] == 0) {
data[i - SLIDER_IMG_OUT_PADDING][j] = BORDER_INDICATOR;
}
if (data[i][j] == 1 && i + SLIDER_IMG_OUT_PADDING > 0 && i + SLIDER_IMG_OUT_PADDING < CUT_WIDTH && data[i + SLIDER_IMG_OUT_PADDING][j] == 0) {
data[i + SLIDER_IMG_OUT_PADDING][j] = BORDER_INDICATOR;
}
}
}
return data;
}
/**
* 裁剪区块
* 根据生成的滑块形状,对原图和裁剪块进行变色处理
* @param oriImage 原图
* @param targetImage 裁剪图
* @param blockImage 滑块
* @param x 裁剪点x
* @param y 裁剪点y
*/
private static void cutImgByTemplate(BufferedImage oriImage, BufferedImage targetImage, int[][] blockImage, int x, int y) {
for (int i = 0; i < CUT_WIDTH; i++) {
for (int j = 0; j < CUT_HEIGHT; j++) {
int _x = x + i;
int _y = y + j;
int rgbFlg = blockImage[i][j];
int rgb_ori = oriImage.getRGB(_x, _y);
// 原图中对应位置变色处理
if (rgbFlg == CENTER_INDICATOR) {
//中心
//抠图上复制对应颜色值
targetImage.setRGB(i,j, rgb_ori);
//原图对应位置颜色变化[透明50%]
oriImage.setRGB(_x, _y, rgb_ori & 0x80ffffff);
} else if (rgbFlg == BORDER_INDICATOR) {
//边框
targetImage.setRGB(i, j, Color.WHITE.getRGB());
oriImage.setRGB(_x, _y, Color.GRAY.getRGB());
}else if(rgbFlg == OTHER_INDICATOR){
//int alpha = 0;
targetImage.setRGB(i, j, rgb_ori & 0x00ffffff);
}
}
}
}
/**
* 随机获取一张图片文件
*/
public static File getRandomFile(File dir) throws IOException {
File[] fileList = dir.listFiles();
if(ArrayUtil.isEmpty(fileList)){
return null;
}
List<String> fileNameList = new ArrayList<>(fileList.length);
for (File tempFile: fileList){
if (tempFile.isDirectory()) {
continue;
}
if (tempFile.getName().endsWith(".png") || tempFile.getName().endsWith(".jpg")){
fileNameList.add(tempFile.getAbsolutePath().trim());
}
}
int randomIndex = new Random().nextInt(fileNameList.size());
return new File(fileNameList.get(randomIndex));
}
/**
* 将IMG输出为文件
*/
public static void writeImg(BufferedImage image, File file) throws IOException {
ByteArrayOutputStream bao=new ByteArrayOutputStream();
ImageIO.write(image,"png",bao);
try (FileOutputStream out = new FileOutputStream(file)){
out.write(bao.toByteArray());
}
}
/**
* 将图片转换为BASE64
*/
public static String getImageBASE64(BufferedImage image) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageIO.write(image,"png",out);
//转成byte数组
byte[] bytes = out.toByteArray();
return Base64.getEncoder().encodeToString(bytes);
}
/**
* 将BASE64字符串转换为图片
*/
public static BufferedImage base64String2Image(String base64String) throws IOException {
BASE64Decoder decoder=new BASE64Decoder();
byte[] bytes1 = decoder.decodeBuffer(base64String);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes1);
return ImageIO.read(byteArrayInputStream);
}
public static void main(String[] args) throws IOException {
VerifyImage verifyImage = VerifyImageUtil.getVerifyImage(new File("C:\\Users\\xiongshiyan\\Desktop\\ssss.png"));
System.out.println(verifyImage);
VerifyImageUtil.writeImg(VerifyImageUtil.base64String2Image(verifyImage.getSrcImage()), new File("C:\\Users\\xiongshiyan\\Desktop\\src.png"));
VerifyImageUtil.writeImg(VerifyImageUtil.base64String2Image(verifyImage.getCutImage()), new File("C:\\Users\\xiongshiyan\\Desktop\\cut.png"));
}
}
其中getData获取的数据形如
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 0 0 0 0 0 0 0 2 2 2 2 2 2 2 2 2 2 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
生成的图片bean,srcImage表示原图,在此基础上抠出了方块的,cutImage表示抠图,xPosition、yPosition表示横纵坐标
public class VerifyImage {
private String srcImage;
private String cutImage;
private Integer xPosition;
private Integer yPosition;
private Integer srcImageWidth;
private Integer srcImageHeight;
public VerifyImage(String srcImage, String cutImage, Integer xPosition, Integer yPosition, Integer srcImageWidth, Integer srcImageHeight) {
this.srcImage = srcImage;
this.cutImage = cutImage;
this.xPosition = xPosition;
this.yPosition = yPosition;
this.srcImageWidth = srcImageWidth;
this.srcImageHeight = srcImageHeight;
}
}
给前端的bean,需要把y轴位置给前端
public class SlideImageBean {
private String serialNumber;
private String srcImage;
private String cutImage;
//把y轴给前端,只校验x
private Integer yPosition;
public SlideImageBean(String serialNumber, String srcImage, String cutImage, Integer yPosition) {
this.serialNumber = serialNumber;
this.srcImage = srcImage;
this.cutImage = cutImage;
this.yPosition = yPosition;
}
}
参考:图形滑动验证码JAVA实现【前后端结合】_J.R.Leonardo的博客-CSDN博客
vue 滑动拼图验证 + 后端验证完整流程_jomexiaotao的博客-CSDN博客
图形滑动验证码JAVA实现【前后端结合】_J.R.Leonardo的博客-CSDN博客
上一篇: 关于滑动验证码
下一篇: Mysql大法--增删改查