摘要

分析验证码素材图片混淆原理,并采用selenium模拟人拖动滑块过程,进而破解验证码。

人工验证的过程

  1. 打开威锋网注册页面(https://passport.feng.com/?r=user/register
  2. 移动鼠标至小滑块,一张完整的图片会出现(如下图1)

  3. 点击鼠标左键,图片中间会出现一个缺块(如下图2)

  4. 移动小滑块正上方图案至缺块处
  5. 验证通过

selenium模拟验证的过程

  1. 加载威锋网注册页面(https://passport.feng.com/?r=user/register
  2. 下载图片1和缺块图片2
  3. 根据两张图片的差异计算平移的距离x
  4. 模拟鼠标点击事件,点击小滑块向右移动x
  5. 验证通过

详细分析

  1. 打开chrome浏览器控制台,会发现图1所示的验证码图片并不是极验后台返回的原图。而是由多个div拼接而成(如下图3)



    通过图片显示div的style属性可知,极验后台把图片进行切割加错位处理。把素材图片切割成10 * 58大小的52张小图,再进行错位处理。在网页上显示的时候,再通过css的background-position属性对图片进行还原。以上的图1和图2都是经过了这种处理。在这种情况下,使用selenium模拟验证是需要对下载的验证码图片进行还原。如上图3的第一个div.gt_cut_fullbg_slice标签,它的大小为10px * 58px,其中style属性为background-image: url("http://static.geetest.com/pictures/gt/969ffa43c/969ffa43c.webp"); background-position: -157px -58px;会把该属性对应url的图片进行一个平移操作,以左上角为参考,向左平移157px,向上平移58px,图片超出部分不会显示。所以上图1所示图片是由26 * 2个10px * 58px大小的div组成(如下图4)。每一个小方块的大小58 * 10

  2. 下载图片并还原,上一步骤分析了图片具体的混淆逻辑,具体还原图片的代码实现如下,主要逻辑是把原图裁剪为52张小图,然后拼接成一张完整的图。

    /**
     *还原图片
     * @param type
     */
    private static void restoreImage(String type) throws IOException {
        //把图片裁剪为2 * 26份
        for(int i = 0; i < 52; i++){
            cutPic(basePath + type +".jpg"
                    ,basePath + "result/" + type + i + ".jpg", -moveArray[i][0], -moveArray[i][1], 10, 58);
        }
        //拼接图片
        String[] b = new String[26];
        for(int i = 0; i < 26; i++){
            b[i] = String.format(basePath + "result/" + type + "%d.jpg", i);
        }
        mergeImage(b, 1, basePath + "result/" + type + "result1.jpg");
        //拼接图片
        String[] c = new String[26];
        for(int i = 0; i < 26; i++){
            c[i] = String.format(basePath + "result/" + type + "%d.jpg", i + 26);
        }
        mergeImage(c, 1, basePath + "result/" + type + "result2.jpg");
        mergeImage(new String[]{basePath + "result/" + type + "result1.jpg",
                basePath + "result/" + type + "result2.jpg"}, 2, basePath + "result/" + type + "result3.jpg");
        //删除产生的中间图片
        for(int i = 0; i < 52; i++){
            new File(basePath + "result/" + type + i + ".jpg").deleteOnExit();
        }
        new File(basePath + "result/" + type + "result1.jpg").deleteOnExit();
        new File(basePath + "result/" + type + "result2.jpg").deleteOnExit();
    }

    还原过程需要注意的是,后台返回错位的图片是312 * 116大小的。而网页上图片div的大小是260 * 116。

  3. 计算平移距离,遍历图片的每一个像素点,当两张图的R、G、B之差的和大于255,说明该点的差异过大,很有可能就是需要平移到该位置的那个点,代码如下。

    BufferedImage fullBI = ImageIO.read(new File(basePath + "result/" + FULL_IMAGE_NAME + "result3.jpg"));
        BufferedImage bgBI = ImageIO.read(new File(basePath + "result/" + BG_IMAGE_NAME + "result3.jpg"));
        for (int i = 0; i < bgBI.getWidth(); i++){
            for (int j = 0; j < bgBI.getHeight(); j++) {
                int[] fullRgb = new int[3];
                fullRgb[0] = (fullBI.getRGB(i, j)  & 0xff0000) >> 16;
                fullRgb[1] = (fullBI.getRGB(i, j)  & 0xff00) >> 8;
                fullRgb[2] = (fullBI.getRGB(i, j)  & 0xff);
    
                int[] bgRgb = new int[3];
                bgRgb[0] = (bgBI.getRGB(i, j)  & 0xff0000) >> 16;
                bgRgb[1] = (bgBI.getRGB(i, j)  & 0xff00) >> 8;
                bgRgb[2] = (bgBI.getRGB(i, j)  & 0xff);
                if(difference(fullRgb, bgRgb) > 255){
                    return i;
                }
            }
        }
  4. 模拟鼠标移动事件,这一步骤是最关键的步骤,极验验证码后台正是通过移动滑块的轨迹来判断是否为机器所为。整个移动轨迹的过程越随机越好,我这里提供一种成功率较高的移动算法,代码如下。

        public static void move(WebDriver driver, WebElement element, int distance) throws InterruptedException {
            int xDis = distance + 11;
            System.out.println("应平移距离:" + xDis);
            int moveX = new Random().nextInt(8) - 5;
            int moveY = 1;
            Actions actions = new Actions(driver);
            new Actions(driver).clickAndHold(element).perform();
            Thread.sleep(200);
            printLocation(element);
            actions.moveToElement(element, moveX, moveY).perform();
            System.out.println(moveX + "--" + moveY);
            printLocation(element);
            for (int i = 0; i < 22; i++){
                int s = 10;
                if (i % 2 == 0){
                    s = -10;
                }
                actions.moveToElement(element, s, 1).perform();
                printLocation(element);
                Thread.sleep(new Random().nextInt(100) + 150);
            }
    
            System.out.println(xDis + "--" + 1);
            actions.moveByOffset(xDis, 1).perform();
            printLocation(element);
            Thread.sleep(200);
            actions.release(element).perform();
        }
  5. 完整代码如下

    package com.github.wycm;
    
    import org.apache.commons.io.FileUtils;
    import org.jsoup.Jsoup;
    import org.jsoup.nodes.Document;
    import org.jsoup.nodes.Element;
    import org.jsoup.select.Elements;
    import org.openqa.selenium.By;
    import org.openqa.selenium.Point;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.chrome.ChromeDriver;
    import org.openqa.selenium.interactions.Actions;
    import org.openqa.selenium.support.ui.ExpectedCondition;
    import org.openqa.selenium.support.ui.WebDriverWait;
    
    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.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.net.URL;
    import java.util.Iterator;
    import java.util.Random;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class GeettestCrawler {
        private static String basePath = "src/main/resources/";
        private static String FULL_IMAGE_NAME = "full-image";
        private static String BG_IMAGE_NAME = "bg-image";
        private static int[][] moveArray = new int[52][2];
        private static boolean moveArrayInit = false;
        private static String INDEX_URL = "https://passport.feng.com/?r=user/register";
        private static WebDriver driver;
    
        static {
            System.setProperty("webdriver.chrome.driver", "D:/dev/selenium/chromedriver_V2.30/chromedriver_win32/chromedriver.exe");
            if (!System.getProperty("os.name").toLowerCase().contains("windows")){
                System.setProperty("webdriver.chrome.driver", "/Users/wangyang/workspace/selenium/chromedriver_V2.30/chromedriver");
            }
            driver = new ChromeDriver();
        }
    
        public static void main(String[] args) throws InterruptedException {
            for (int i = 0; i < 10; i++){
                try {
                    invoke();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            driver.quit();
        }
        private static void invoke() throws IOException, InterruptedException {
            //设置input参数
            driver.get(INDEX_URL);
    
            //通过[class=gt_slider_knob gt_show]
            By moveBtn = By.cssSelector(".gt_slider_knob.gt_show");
            waitForLoad(driver, moveBtn);
            WebElement moveElemet = driver.findElement(moveBtn);
            int i = 0;
            while (i++ < 15){
                int distance = getMoveDistance(driver);
                move(driver, moveElemet, distance - 6);
                By gtTypeBy = By.cssSelector(".gt_info_type");
                By gtInfoBy = By.cssSelector(".gt_info_content");
                waitForLoad(driver, gtTypeBy);
                waitForLoad(driver, gtInfoBy);
                String gtType = driver.findElement(gtTypeBy).getText();
                String gtInfo = driver.findElement(gtInfoBy).getText();
                System.out.println(gtType + "---" + gtInfo);
                /**
                 * 再来一次:
                 * 验证失败:
                 */
                if(!gtType.equals("再来一次:") && !gtType.equals("验证失败:")){
                    Thread.sleep(4000);
                    System.out.println(driver);
                    break;
                }
                Thread.sleep(4000);
            }
        }
    
        /**
         * 移动
         * @param driver
         * @param element
         * @param distance
         * @throws InterruptedException
         */
        public static void move(WebDriver driver, WebElement element, int distance) throws InterruptedException {
            int xDis = distance + 11;
            System.out.println("应平移距离:" + xDis);
            int moveX = new Random().nextInt(8) - 5;
            int moveY = 1;
            Actions actions = new Actions(driver);
            new Actions(driver).clickAndHold(element).perform();
            Thread.sleep(200);
            printLocation(element);
            actions.moveToElement(element, moveX, moveY).perform();
            System.out.println(moveX + "--" + moveY);
            printLocation(element);
            for (int i = 0; i < 22; i++){
                int s = 10;
                if (i % 2 == 0){
                    s = -10;
                }
                actions.moveToElement(element, s, 1).perform();
    //            printLocation(element);
                Thread.sleep(new Random().nextInt(100) + 150);
            }
    
            System.out.println(xDis + "--" + 1);
            actions.moveByOffset(xDis, 1).perform();
            printLocation(element);
            Thread.sleep(200);
            actions.release(element).perform();
        }
        private static void printLocation(WebElement element){
            Point point  = element.getLocation();
            System.out.println(point.toString());
        }
        /**
         * 等待元素加载,10s超时
         * @param driver
         * @param by
         */
        public static void waitForLoad(final WebDriver driver, final By by){
            new WebDriverWait(driver, 10).until(new ExpectedCondition<Boolean>() {
                public Boolean apply(WebDriver d) {
                    WebElement element = driver.findElement(by);
                    if (element != null){
                        return true;
                    }
                    return false;
                }
            });
        }
    
        /**
         * 计算需要平移的距离
         * @param driver
         * @return
         * @throws IOException
         */
        public static int getMoveDistance(WebDriver driver) throws IOException {
            String pageSource = driver.getPageSource();
            String fullImageUrl = getFullImageUrl(pageSource);
            FileUtils.copyURLToFile(new URL(fullImageUrl), new File(basePath + FULL_IMAGE_NAME + ".jpg"));
            String getBgImageUrl = getBgImageUrl(pageSource);
            FileUtils.copyURLToFile(new URL(getBgImageUrl), new File(basePath + BG_IMAGE_NAME + ".jpg"));
            initMoveArray(driver);
            restoreImage(FULL_IMAGE_NAME);
            restoreImage(BG_IMAGE_NAME);
            BufferedImage fullBI = ImageIO.read(new File(basePath + "result/" + FULL_IMAGE_NAME + "result3.jpg"));
            BufferedImage bgBI = ImageIO.read(new File(basePath + "result/" + BG_IMAGE_NAME + "result3.jpg"));
            for (int i = 0; i < bgBI.getWidth(); i++){
                for (int j = 0; j < bgBI.getHeight(); j++) {
                    int[] fullRgb = new int[3];
                    fullRgb[0] = (fullBI.getRGB(i, j)  & 0xff0000) >> 16;
                    fullRgb[1] = (fullBI.getRGB(i, j)  & 0xff00) >> 8;
                    fullRgb[2] = (fullBI.getRGB(i, j)  & 0xff);
    
                    int[] bgRgb = new int[3];
                    bgRgb[0] = (bgBI.getRGB(i, j)  & 0xff0000) >> 16;
                    bgRgb[1] = (bgBI.getRGB(i, j)  & 0xff00) >> 8;
                    bgRgb[2] = (bgBI.getRGB(i, j)  & 0xff);
                    if(difference(fullRgb, bgRgb) > 255){
                        return i;
                    }
                }
            }
            throw new RuntimeException("未找到需要平移的位置");
        }
        private static int difference(int[] a, int[] b){
            return Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]) + Math.abs(a[2] - b[2]);
        }
        /**
         * 获取move数组
         * @param driver
         */
        private static void initMoveArray(WebDriver driver){
            if (moveArrayInit){
                return;
            }
            Document document = Jsoup.parse(driver.getPageSource());
            Elements elements = document.select("[class=gt_cut_bg gt_show]").first().children();
            int i = 0;
            for(Element element : elements){
                Pattern pattern = Pattern.compile(".*background-position: (.*?)px (.*?)px.*");
                Matcher matcher = pattern.matcher(element.toString());
                if (matcher.find()){
                    String width = matcher.group(1);
                    String height = matcher.group(2);
                    moveArray[i][0] = Integer.parseInt(width);
                    moveArray[i++][1] = Integer.parseInt(height);
                } else {
                    throw new RuntimeException("解析异常");
                }
            }
            moveArrayInit = true;
        }
        /**
         *还原图片
         * @param type
         */
        private static void restoreImage(String type) throws IOException {
            //把图片裁剪为2 * 26份
            for(int i = 0; i < 52; i++){
                cutPic(basePath + type +".jpg"
                        ,basePath + "result/" + type + i + ".jpg", -moveArray[i][0], -moveArray[i][1], 10, 58);
            }
            //拼接图片
            String[] b = new String[26];
            for(int i = 0; i < 26; i++){
                b[i] = String.format(basePath + "result/" + type + "%d.jpg", i);
            }
            mergeImage(b, 1, basePath + "result/" + type + "result1.jpg");
            //拼接图片
            String[] c = new String[26];
            for(int i = 0; i < 26; i++){
                c[i] = String.format(basePath + "result/" + type + "%d.jpg", i + 26);
            }
            mergeImage(c, 1, basePath + "result/" + type + "result2.jpg");
            mergeImage(new String[]{basePath + "result/" + type + "result1.jpg",
                    basePath + "result/" + type + "result2.jpg"}, 2, basePath + "result/" + type + "result3.jpg");
            //删除产生的中间图片
            for(int i = 0; i < 52; i++){
                new File(basePath + "result/" + type + i + ".jpg").deleteOnExit();
            }
            new File(basePath + "result/" + type + "result1.jpg").deleteOnExit();
            new File(basePath + "result/" + type + "result2.jpg").deleteOnExit();
        }
        /**
         * 获取原始图url
         * @param pageSource
         * @return
         */
        private static String getFullImageUrl(String pageSource){
            String url = null;
            Document document = Jsoup.parse(pageSource);
            String style = document.select("[class=gt_cut_fullbg_slice]").first().attr("style");
            Pattern pattern = Pattern.compile("url\\(\"(.*)\"\\)");
            Matcher matcher = pattern.matcher(style);
            if (matcher.find()){
                url = matcher.group(1);
            }
            url = url.replace(".webp", ".jpg");
            System.out.println(url);
            return url;
        }
        /**
         * 获取带背景的url
         * @param pageSource
         * @return
         */
        private static String getBgImageUrl(String pageSource){
            String url = null;
            Document document = Jsoup.parse(pageSource);
            String style = document.select(".gt_cut_bg_slice").first().attr("style");
            Pattern pattern = Pattern.compile("url\\(\"(.*)\"\\)");
            Matcher matcher = pattern.matcher(style);
            if (matcher.find()){
                url = matcher.group(1);
            }
            url = url.replace(".webp", ".jpg");
            System.out.println(url);
            return url;
        }
        public static boolean cutPic(String srcFile, String outFile, int x, int y,
                                     int width, int height) {
            FileInputStream is = null;
            ImageInputStream iis = null;
            try {
                if (!new File(srcFile).exists()) {
                    return false;
                }
                is = new FileInputStream(srcFile);
                String ext = srcFile.substring(srcFile.lastIndexOf(".") + 1);
                Iterator<ImageReader> it = ImageIO.getImageReadersByFormatName(ext);
                ImageReader reader = it.next();
                iis = ImageIO.createImageInputStream(is);
                reader.setInput(iis, true);
                ImageReadParam param = reader.getDefaultReadParam();
                Rectangle rect = new Rectangle(x, y, width, height);
                param.setSourceRegion(rect);
                BufferedImage bi = reader.read(0, param);
                File tempOutFile = new File(outFile);
                if (!tempOutFile.exists()) {
                    tempOutFile.mkdirs();
                }
                ImageIO.write(bi, ext, new File(outFile));
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            } finally {
                try {
                    if (is != null) {
                        is.close();
                    }
                    if (iis != null) {
                        iis.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    return false;
                }
            }
        }
        /**
         * 图片拼接 (注意:必须两张图片长宽一致哦)
         * @param files 要拼接的文件列表
         * @param type  1横向拼接,2 纵向拼接
         * @param targetFile 输出文件
         */
        private static void mergeImage(String[] files, int type, String targetFile) {
            int length = files.length;
            File[] src = new File[length];
            BufferedImage[] images = new BufferedImage[length];
            int[][] ImageArrays = new int[length][];
            for (int i = 0; i < length; i++) {
                try {
                    src[i] = new File(files[i]);
                    images[i] = ImageIO.read(src[i]);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                int width = images[i].getWidth();
                int height = images[i].getHeight();
                ImageArrays[i] = new int[width * height];
                ImageArrays[i] = images[i].getRGB(0, 0, width, height, ImageArrays[i], 0, width);
            }
            int newHeight = 0;
            int newWidth = 0;
            for (int i = 0; i < images.length; i++) {
                // 横向
                if (type == 1) {
                    newHeight = newHeight > images[i].getHeight() ? newHeight : images[i].getHeight();
                    newWidth += images[i].getWidth();
                } else if (type == 2) {// 纵向
                    newWidth = newWidth > images[i].getWidth() ? newWidth : images[i].getWidth();
                    newHeight += images[i].getHeight();
                }
            }
            if (type == 1 && newWidth < 1) {
                return;
            }
            if (type == 2 && newHeight < 1) {
                return;
            }
            // 生成新图片
            try {
                BufferedImage ImageNew = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
                int height_i = 0;
                int width_i = 0;
                for (int i = 0; i < images.length; i++) {
                    if (type == 1) {
                        ImageNew.setRGB(width_i, 0, images[i].getWidth(), newHeight, ImageArrays[i], 0,
                                images[i].getWidth());
                        width_i += images[i].getWidth();
                    } else if (type == 2) {
                        ImageNew.setRGB(0, height_i, newWidth, images[i].getHeight(), ImageArrays[i], 0, newWidth);
                        height_i += images[i].getHeight();
                    }
                }
                //输出想要的图片
                ImageIO.write(ImageNew, targetFile.split("\\.")[1], new File(targetFile));
    
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    
  6. pom文件依赖如下

    <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-server</artifactId> <version>3.0.1</version> </dependency> <!-- https://mvnrepository.com/artifact/org.jsoup/jsoup --> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.7.2</version> </dependency>

最后

  1. 完整代码已上传至github,地址:https://github.com/wycm/selenium-geetest-crack
  2. 附上一张滑动效果图

selenium+java破解极验滑动验证码的更多相关文章

  1. selenium+java破解极验滑动验证码的示例代码

    转自: https://www.jianshu.com/p/1466f1ba3275 selenium+java破解极验滑动验证码 卧颜沉默 关注 2017.08.15 20:07* 字数 3085  ...

  2. Python 破解极验滑动验证码

    Python 破解极验滑动验证码 测试开发社区  1周前 阅读目录 极验滑动验证码 实现 位移移动需要的基础知识 对比两张图片,找出缺口 获得图片 按照位移移动 详细代码 回到顶部 极验滑动验证码 以 ...

  3. Python——破解极验滑动验证码

    极验滑动验证码 以上图片是最典型的要属于极验滑动认证了,极验官网:http://www.geetest.com/. 现在极验验证码已经更新到了 3.0 版本,截至 2017 年 7 月全球已有十六万家 ...

  4. 破解极验(geetest)验证码

      破解极验(geetest)验证码 这是两年前的帖子: http://www.v2ex.com/t/138479 一个月前的破解程序,我没用过 asp.net ,不知道是不是真的破解了, demo ...

  5. python验证码识别(2)极验滑动验证码识别

    目录 一:极验滑动验证码简介 二:极验滑动验证码识别思路 三:极验验证码识别 一:极验滑动验证码简介   近些年来出现了一些新型验证码,不想旧的验证码对人类不友好,但是这种验证码对于代码来说识别难度上 ...

  6. thinkphp整合系列之极验滑动验证码

    对于建站的筒子们来说:垃圾广告真是让人深恶痛绝:为了清净:搞个难以识别的验证码吧:又被用户各种吐槽:直到后来出现了极验这个滑动的验证码:这真是一个体验好安全高的方案:官网:http://www.gee ...

  7. vue_drf之实现极验滑动验证码

    一.需求 1,场景 我们在很多登录和注册场景里,为了避免某些恶意攻击程序,我们会添加一些验证码,也就是行为验证,让我们相信现在是一个人在交互,而不是一段爬虫程序.现在市面上用的比较多的,比较流行的是极 ...

  8. selenium处理极验滑动验证码

    要爬取一个网站遇到了极验的验证码,这周都在想着怎么破解这个,网上搜了好多知乎上看到有人问了这问题https://www.zhihu.com/question/28833985,我按照这思路去大概实现了 ...

  9. luffy之多条件登录与极验滑动验证码

    多条件登录 JWT扩展的登录视图,在收到用户名与密码时,也是调用Django的认证系统中提供的authenticate()来检查用户名与密码是否正确. 我们可以通过修改Django认证系统的认证后端( ...

随机推荐

  1. Redux-Saga学习心得

    # Redux Saga ## 简述- Reducers负责处理action的state更新:- Sagas负责协调那些复杂或异步的操作. ## 安装 npm install --save redux ...

  2. 关于XML(可扩展标记语言)的基础知识与写法

    XML(Extensible Markup Language) HTML:超文本标记语言,主要用来展示   XML:可扩展标记语言,用来做数据传输XML特点:1.树状结构,有且只有一个根2.标签名自定 ...

  3. maven(一) maven到底是个啥玩意~

    我记得在搞懂maven之前看了几次重复的maven的教学视频.不知道是自己悟性太低还是怎么滴,就是搞不清楚,现在弄清楚了,基本上入门了.写该篇博文,就是为了帮助那些和我一样对于maven迷迷糊糊的人. ...

  4. Linux 安装依赖库

    ###安装依赖库###yum -y install rsync net-snmp syslog net-snmp-devel wget patch screen gcc gcc-c++ autocon ...

  5. 通用JSONHelp 的通用的封装

    1. 最近项目已经上线了 ,闲暇了几天 想将JSON  的序列化 以及反序列化进行重新的封装一下本人定义为JSONHelp,虽然Microsoft 已经做的很好了.但是我想封装一套为自己开发的项目使用 ...

  6. js实现图片旋转、模板文件查看图片大图之记录篇[二]

    一个小小的前端需求送给大家,使用js实现图片旋转,并且点击图片能够实现规定格式的大图. 主要使用的是jQuery的delegate()方法实现图片旋转,该方法主要的功能就是给某个组件绑定一个或一组事件 ...

  7. 如何通过css设置表格居中

    CSS控制整个表格居中,不只是让表格里的文字居中,是整个表格居中1. 不用table的Align="center",要用CSS实现2. 不加<center></c ...

  8. mybatis代理类Demo

    前言 简单实现通过代理接口来实现对数据的查询demo,也是对mybatis的一个熟练.首先是编写接口代理. public interface IBookMapper { List<BookMod ...

  9. 数控G代码编程详解大全

    一.G代码功能简述 G00------快速定位 G01------直线插补 G02------顺时针方向圆弧插补 G03------逆时针方向圆弧插补 G04------定时暂停 G05------通 ...

  10. MVC MVC+EF快速搭建

    MVC+EF快速搭建 一.准备: vs2017(个人用的) 二.开始MVC+EF之旅吧: 1.创建mvc项目: Web-ASP.NET Web Application(.NET Framework)  ...