开始使用模糊测试

文章目录

  • 开始使用模糊测试
    • 一、介绍
    • 二、准备
    • 三、实践
      • 3.1 为你的代码创建一个目录
      • 3.2 添加代码用于测试
        • 写代码
        • 运行代码
      • 3.3 添加单元测试
        • 写代码
        • 运行代码
      • 3.4 添加模糊测试
        • 写代码
        • 运行代码
      • 3.5 修复无效的字符串错误
        • 诊断错误
        • 写代码
        • 运行代码
        • 修复错误
          • 写代码
          • 运行代码
      • 3.6 修复两次反转的错误
        • 诊断错误
        • 写代码
        • 运行代码
        • 修复错误
        • 写代码
        • 运行代码

一、介绍

这篇指导介绍Go中基础的模糊测试。通过模糊测试,随机数据会针对您的测试运行,以尝试找出漏洞或导致崩溃的输入。例如,通过模糊测试能够发现SQL注入的漏洞,缓冲区溢出,拒绝服务或者跨域攻击。

在这篇指导中,你将为一个简单的函数写一个模糊测试,运行Go命令行,调试并修复代码中的问题。

你将通过下面的部分:

  1. 为你的代码创建一个目录;
  2. 添加代码用于测试;
  3. 添加单元测试;
  4. 添加模糊测试;
  5. 修复两个bug;
  6. 探索更多的资源;

二、准备

  • 安装Go1.18 或以上版本;
  • 一个用于编写代码的工具;
  • 命令行工具;
  • 一个支持模糊测试的环境。目前仅在 AMD64 和 ARM64 架构上使用覆盖检测进行模糊测试。

三、实践

3.1 为你的代码创建一个目录

  1. 打开命令行,进入到工作目录

    从为你将要写的代码创建目录开始。

    $ cd
    $
    
  2. 通过命令行,创建一个名称为fuzz的目录

    $ mkdir fuzz
    $ cd fuzz/
    $
    
  3. 为你的代码创建一个模块

    运行go mod init命令,并给它一个新的代码模块路径

    $ go mod init example/fuzz
    go: creating new go.mod: module example/fuzz
    $
    

接下来,你将添加一些简单代码去反转字符串,它将用于模糊。

3.2 添加代码用于测试

在这一步,将添加函数用于反转字符串。

写代码

  1. 使用文本编辑器,在fuzz目录中,创建一个叫main.go的文件。

  2. 进入main.go文件,在文件的顶部,粘贴下面的代码声明。

    package main
    

    一个独立的程序(和标准库相反),总是在main包中。

  3. 在包声明的下面,粘贴下面的函数声明

    func Reverse(s string) string {b := []byte(s)for i,j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1{b[i], b[j] = b[j], b[i]}return string(b)
    }
    

    函数接受一个字符串,同时循环byte,最后返回反转的字符串。

    注意:这个代码基于golang.org/x/examplestringUtils.Reverse函数。

  4. main.go文件的顶部,在包声明的下面,粘贴下面的main函数去初始化一个字符串,反转它,打印输出,并重复这样做

func main() {input := "The quick brown fox jumped over the lazy dog"rev := Reverse(input)doubleRev := Reverse(rev)fmt.Printf("original: %q\n", input)fmt.Printf("reversed: %q\n", rev)fmt.Printf("reversed agained: %q\n", doubleRev)
}

这个函数将运行Reverse 操作,然后打印命令行的输出。这个是有用的,在实践中查看代码,也是有用的对于发现潜在的debug。

  1. main 函数使用了fmt包,因此,你将需要导入它。

    第一行代码看起来像这样:

    package main
    import "fmt"
    

运行代码

在包含main.go文件目录的命令行下,运行代码:

$ go run .
original: "The quick brown fox jumped over the lazy dog"
reversed: "god yzal eht revo depmuj xof nworb kciuq ehT"
reversed agained: "The quick brown fox jumped over the lazy dog"
$

你能看到原始数据,反转的结果数据,然后对结果再次旋转。它和原始数据相等。

现在代码正在运行,是时间去测试它了。

3.3 添加单元测试

在这一步,你将为Reverse函数写基础的单元测试。

写代码

  1. 使用你的文本编辑器,在fuzz目录中,创建一个文件叫做reverse_test.go

  2. 粘贴下面的代码到reverse_test.go

    package mainimport ("testing"
    )func TestReverse(t *testing.T) {testcases := []struct {in, want string}{{"Hello, world", "dlrow ,olleH"},{" ", " "},{"!12345", "54321!"},}for _, tc := range testcases {rev := Reverse(tc.in)if rev != tc.want {t.Errorf("Reverse: %q, want %q", rev, tc.want)}}
    }
    

    这个简单的函数将断言,那一列输入的字符串将被正确的反转。

运行代码

使用go test 运行单元测试。

$ go test
PASS
ok      example/fuzz    0.325s
$ go test -v
=== RUN   TestReverse
--- PASS: TestReverse (0.00s)
PASS
ok      example/fuzz    0.253s
$

接下来,你将改变单元测试变成模糊测试。

3.4 添加模糊测试

单元测试拥有局限性,即每次输入必须被开发者添加到测试。一个模糊测试的好处是它能为你的代码创建输入,并且能确认边缘化的用例,那些你没有想象出来的用例。

在这一部分你将转换单元测试变成模糊测试,以便于你能通过少量的工作产生更多输入。

注意,你将保留单元测试、基准测试和模糊测试在相同的*_test.go文件下面。但是对于这个案例,你将转换单元测试成为模糊测试。

写代码

在你的文本编译器中,使用下面的模糊测试替换单元测试。

func FuzzReverse(f *testing.F) {testcase := []string{"Hello World", " ", "!12345"}for _, tc := range testcase {f.Add(tc)}f.Fuzz(func(t *testing.T, orig string) {rev := Reverse(orig)doubleRev := Reverse(rev)if orig != doubleRev {t.Errorf("Before: %q, after: %q", orig, doubleRev)}if utf8.ValidString(orig) && !utf8.ValidString(rev) {t.Errorf("Reverse produced invalid UTF-8 string %q", rev)}})
}

Fuzzing也有一些局限性。在你的单元测试中,你能预测Reverse函数期待的输出,并且验证实际的输出满足预期。

例如,在测试用例Reverse("Hello, world")单元测试明确返回dlrow ,olleH

使用模糊查询,你不能预测期待的输出,因为你不能控制输入。

可是,这里有一些Reverse函数的属性,你能在fuzz测试中进行验证。其中两个能在模糊测试中验证的是:

  1. 反转字符串两次,保留原始值;
  2. 反转的字符串将其状态保留在UTF-8;

注意在单元测试和模糊测试中的语法不同。

  • 函数以FuzzXxx开头代替TestXxx,并且获取*testing.F代替*testing.T
  • 在你期待看到t.Run执行的地方,你替换成f.Fuzz,在那儿你获取一个模糊测试的函数,它的参数是*testing.T和一个用于模糊执行的类型。单元测试的输入使用 f.Add 作为种子语料库输入提供。

确保新的包unicode/utf8是被导入。

package mainimport ("testing""unicode/utf8"
)

使用模糊测试覆盖单元测试,同时,再次运行测试。

运行代码

  1. 运行模糊测试,不带模糊属性,确保种子输入能够通过

    $ go test
    PASS
    ok      example/fuzz    1.089s
    $
    
  2. 带上模糊,运行FuzzReverse,去看是否任意的随机产生的字符串输入将产生错误。这次执行使用go test带上一个新的标识,-fuzz

    $ go test -fuzz=Fuzz
    fuzz: elapsed: 0s, gathering baseline coverage: 0/3 completed
    fuzz: elapsed: 0s, gathering baseline coverage: 3/3 completed, now fuzzing with 8 workers
    fuzz: elapsed: 0s, execs: 780 (19533/sec), new interesting: 5 (total: 8)
    --- FAIL: FuzzReverse (0.04s)--- FAIL: FuzzReverse (0.00s)reverse_test.go:20: Reverse produced invalid UTF-8 string "0\x86\xcb"Failing input written to testdata/fuzz/FuzzReverse/51fd8412ffb50aa7caa0c705c8d9b9f44bd34af7973d0f5940d60a7b24e6ff34To re-run:go test -run=FuzzReverse/51fd8412ffb50aa7caa0c705c8d9b9f44bd34af7973d0f5940d60a7b24e6ff34
    FAIL
    exit status 1
    FAIL    example/fuzz    1.106s
    $
    

    使用模糊测试的时候一个错误产生了,并且引起问题的输入是被写到了一个种子语料库文件。它将在下次go test被调用的时候运行,即便不带-fuzz标识。为了展示引起错误的输入,在编译器中打开被写到testdata/fuzz/FuzzReverse 目录下的语料库文件。你的语料库文件可能包含不同的字符串,但是格式是相同的。

    $ cat testdata/fuzz/FuzzReverse/51fd8412ffb50aa7caa0c705c8d9b9f44bd34af7973d0f5940d60a7b24e6ff34
    go test fuzz v1
    string("ˆ0")
    $
    

    语料库文件第一行包含了编码版本。以下每一行代表构成语料库条目的每种类型的值。因为模糊测试的目标仅获取一个值,在版本后面仅仅有一个值。

  3. 再次不带-fuzz运行go test,将使用新的失败种子语料库条目。

    $ go test
    --- FAIL: FuzzReverse (0.00s)--- FAIL: FuzzReverse/51fd8412ffb50aa7caa0c705c8d9b9f44bd34af7973d0f5940d60a7b24e6ff34 (0.00s)reverse_test.go:20: Reverse produced invalid UTF-8 string "0\x86\xcb"
    FAIL
    exit status 1
    FAIL    example/fuzz    1.076s
    $
    

因为我们的测试失败了,是时候进行调试了。

3.5 修复无效的字符串错误

在这一部分,你将调试错误,并修复错误。

在继续之前,请随意花一些时间思考这个问题并尝试自己解决问题。

诊断错误

这儿有一些不同的方法用来调试错误。如果你使用VS Code作为你的编译器,你能设置断点进行调试。

在这个教程中,你将打印有用的调试信息到你的控制台。

首先,思考对于utf8.ValidString的文档。

ValidString 报告 s 是否完全由有效的 UTF-8 编码符文组成。

当前的Reverse函数,一个字节一个字节的反转字符串,这就是我们的问题。为了保留原始字符串的 UTF-8 编码符文,我们必须逐个符文反转字符串。

为了检查当反转的时候为什么那个输入(在这种情况下,字符串ˆ0)造成了Reverse 产生了一个无效的字符串,你能检查反转字符串符文的数量。

写代码

在文本编译器中,使用下面的代码替换FuzzReverse下的模糊目标。

f.Fuzz(func(t *testing.T, orig string){rev := Reverse(orig)doubleRev := Reverse(rev)t.Logf("Number of runes: orig=%d, rev=%d, doubleRev=%d", utf8.RuneCountInString(orig), utf8.RuneCountInString(rev), utf8.RuneCountInString(doubleRev))if orig != doubleRev {t.Errorf("Before: %q, after: %q", orig, doubleRev)}if utf8.ValidString(orig) && !utf8.ValidString(rev) {t.Errorf("Reverse produced invalid UTF-8 string %q", rev)}
})

这个t.Logf行将打印到命令行,如果一个错误发生。获取如果执行测试带上-v,它能帮助你调试特定的议题。

运行代码

使用go test运行测试

$ go test
--- FAIL: FuzzReverse (0.00s)--- FAIL: FuzzReverse/51fd8412ffb50aa7caa0c705c8d9b9f44bd34af7973d0f5940d60a7b24e6ff34 (0.00s)reverse_test.go:16: Number of runes: orig=2, rev=3, doubleRev=2reverse_test.go:21: Reverse produced invalid UTF-8 string "0\x86\xcb"
FAIL
exit status 1
FAIL    example/fuzz    1.098s
$

整个种子语料库使用字符串,其中每个字符都是一个字节。可是,字符像^需要若干个字节。因此,一个字节一个字节的反转字符串是无效的对于多字节字符。

注意,如果你好奇关于Go处理字符串,阅读博客Strings, bytes, runes and characters in Go进行更深度的理解。

为了更好的理解错误,更正反向功能的错误。

修复错误

为了修复Reverse函数,让我们按照符文遍历字符,代替通过字节。

写代码

在文本编译器中,使用下面的代码替换存在的Reverse()函数。

func Reverse(s string) string {r := []rune(s)for i, j := 0, len(r)-1; i<len(r)/2; i, j = i+1, j-1 {r[i], r[j] = r[j], r[i]}return string(r)
}

关键的不同是,Reverse现在是遍历字符串中的每个符文,而不是每一个字符。

运行代码
  1. 使用go test运行测试

    $ go test
    PASS
    ok      example/fuzz    1.207s
    $
    
  2. 再次使用go test -fuzz=Fuzz进行模糊测试,去看现在是否有新的问题。

    $ go test -fuzz=Fuzz
    fuzz: elapsed: 0s, gathering baseline coverage: 0/9 completed
    fuzz: minimizing 31-byte failing input file
    fuzz: elapsed: 0s, gathering baseline coverage: 4/9 completed
    --- FAIL: FuzzReverse (0.03s)--- FAIL: FuzzReverse (0.00s)reverse_test.go:16: Number of runes: orig=1, rev=1, doubleRev=1reverse_test.go:18: Before: "\xa0", after: "�"Failing input written to testdata/fuzz/FuzzReverse/1d81da97512e2e81bf08cdb8b161334d7d9639f5c5b18255c920ad25370ff2aaTo re-run:go test -run=FuzzReverse/1d81da97512e2e81bf08cdb8b161334d7d9639f5c5b18255c920ad25370ff2aa
    FAIL
    exit status 1
    FAIL    example/fuzz    1.081s
    $
    

    我们能够看到,经过两次反转的结果是不同于原始结果。这次输入是一个无效的字符串。如果我们使用字符串进行模糊测试,这怎么可能?

    让我们再次调试。

3.6 修复两次反转的错误

在这一部分,我们将调试两次反转的错误,并修复这个错误。

在继续之前,自由的好一些时间去思考这个问题,并修复这个问题。

诊断错误

就像之前,有若干个方法调试错误,在这种场景下,使用断点是一个好的方法。

在这个教程中,我们将在Reverse函数中打印有用的调试信息。

仔细查看反转的字符串以发现错误。在Go中,一个字符串实际上是一个字符切片。并且能够包含非有效的UTF-8字节。原始字符串是一个字节切片,有一个字节,‘\x91’。 当输入字符串设置为 []rune 时,Go 将字节切片编码为 UTF-8,并将字节替换为 UTF-8 字符 �。 当我们将替换的 UTF-8 字符与输入字节切片进行比较时,它们显然不相等。

写代码

  1. 在文本编辑器中,使用下面的代码替换Reverse函数

    func Reverse(s string) string {fmt.Printf("input: %q\n", s)r := []rune(s)fmt.Printf("runes: %q\n", r)for i, j := 0, len(r)-1; i<len(r)-1; i, j = i+1, j-1 {r[i], r[j] = r[j], r[i]}return string(r)
    }
    

    这将帮助我们理解发生了什么错误,当字符串转化成符文切片。

运行代码

这个时候,我们仅仅想运行失败的测试为了检查日志。为了做这个,我们将使用go test -run

$ go test -run FuzzReverse/1d81da97512e2e81bf08cdb8b161334d7d9639f5c5b18255c920ad25370ff2aa
input: "\xa0"
runes: ['�']
input: "�"
runes: ['�']
--- FAIL: FuzzReverse (0.00s)--- FAIL: FuzzReverse/1d81da97512e2e81bf08cdb8b161334d7d9639f5c5b18255c920ad25370ff2aa (0.00s)reverse_test.go:16: Number of runes: orig=1, rev=1, doubleRev=1reverse_test.go:18: Before: "\xa0", after: "�"
FAIL
exit status 1
FAIL    example/fuzz    0.435s
$

为了运行包含在FuzzXxx/testdata中特定语料库,你能提供{FuzzTestName}/filename-run。这是有用的在调试的时候。

知道了输入是无效的unicode,让我们修复在Reverse函数的错误。

修复错误

为了修复这个问题,如果Reverse输入不是一个有效的的UTF-8,让我们返回一个错误。

写代码

  1. 在你的编译器中,使用下面的函数替换存在的Reverse函数

    func Reverse(s string) (string, error) {if !utf8.ValidString(s) {return s, errors.New("输入是一个无效的UTF-8")}fmt.Printf("input: %q\n", s)r := []rune(s)fmt.Printf("runes: %q\n", r)for i, j := 0, len(r)-1; i < len(r)-1; i, j = i+1, j-1 {r[i], r[j] = r[j], r[i]}return string(r), nil
    }
    

    这次改变,当输入的字符串是一个无效的UTF-8时,将返回一个错误。

  2. 因为Reverse 函数现在返回了一个错误,修复main函数去丢弃额外的错误值。使用下面的代码替换已经存在的main函数。

    func main() {input := "The quick brown fox jumped over the lazy dog"rev, revErr := Reverse(input)doubleRev, doubleErr := Reverse(rev)fmt.Printf("original: %q\n", input)fmt.Printf("reversed: %q, err: %v\n", rev, revErr)fmt.Printf("reversed agained: %q, err: %v\n", doubleRev, doubleErr)
    }
    

    这些调用将会返回一个nil 错误,因为输入是有效的UTF-8。

  3. 你需要导入errorsunicode/utf8包。在main.go 中的导入语句看起来像这样:

    import ("errors""fmt""unicode/utf8"
    )
    
  4. 修改reverse_test.go 文件,检查错误并跳过测试,如果错误是被返回值产生。

    func FuzzReverse(f *testing.F) {testcase := []string{"Hello World", " ", "!12345"}for _, tc := range testcase {f.Add(tc) // 使用 f.Add 提供种子语料库}f.Fuzz(func(t *testing.T, orig string) {rev, err1 := Reverse(orig)if err1 != nil {return}doubleRev, err2 := Reverse(rev)if err2 != nil {return}if orig != doubleRev {t.Errorf("Before: %q, after: %q", orig, doubleRev)}if utf8.ValidString(orig) && !utf8.ValidString(rev) {t.Errorf("Reverse produced invalid UTF-8 string %q", rev)}})
    }
    

    除了返回,您还可以调用 t.Skip() 来停止执行该模糊输入。

运行代码

  1. 使用 go test 运行测试

    $ go test
    PASS
    ok      example/fuzz    0.491s
    $
    
  2. 使用带有go test -fuzz=Fuzz进行模糊化,然后若干秒后通过,使用ctrl-C停止模糊测试

    $ go test -fuzz=Fuzz
    fuzz: elapsed: 0s, gathering baseline coverage: 0/9 completed
    fuzz: elapsed: 0s, gathering baseline coverage: 9/9 completed, now fuzzing with 8 workers
    fuzz: elapsed: 3s, execs: 306930 (102280/sec), new interesting: 32 (total: 41)
    fuzz: elapsed: 6s, execs: 870767 (187915/sec), new interesting: 33 (total: 42)
    fuzz: elapsed: 9s, execs: 1293839 (141029/sec), new interesting: 34 (total: 43)
    ......
    fuzz: elapsed: 1m18s, execs: 10520829 (142569/sec), new interesting: 36 (total: 45)
    fuzz: elapsed: 1m21s, execs: 10909615 (129590/sec), new interesting: 37 (total: 46)
    fuzz: elapsed: 1m24s, execs: 11289776 (126692/sec), new interesting: 37 (total: 46)
    fuzz: elapsed: 1m27s, execs: 11689172 (133198/sec), new interesting: 37 (total: 46)
    fuzz: elapsed: 1m30s, execs: 12068776 (126454/sec), new interesting: 37 (total: 46)
    fuzz: elapsed: 1m33s, execs: 12461830 (131088/sec), new interesting: 37 (total: 46)
    fuzz: elapsed: 1m36s, execs: 12843006 (127040/sec), new interesting: 37 (total: 46)
    ^Cfuzz: elapsed: 1m38s, execs: 13067321 (134596/sec), new interesting: 37 (total: 46)
    PASS
    ok      example/fuzz    98.729s
    lifeideMacBook-Pro:fuzz lifei$
    

    除非您通过 -fuzztime 标志,否则模糊测试将一直运行,直到遇到失败的输入。如果没有发生故障,默认是永远运行,并且可以使用 ctrl-C 中断该过程。

  3. 使用go test -fuzz=Fuzz -fuzztime 30s进行模糊化,如果没有发现失败,它将在退出之前模糊 30 秒。

    $ go test -fuzz=Fuzz -fuzztime 30s
    fuzz: elapsed: 0s, gathering baseline coverage: 0/46 completed
    fuzz: elapsed: 0s, gathering baseline coverage: 46/46 completed, now fuzzing with 8 workers
    fuzz: elapsed: 3s, execs: 432881 (144195/sec), new interesting: 3 (total: 49)
    fuzz: elapsed: 6s, execs: 881372 (149498/sec), new interesting: 3 (total: 49)
    fuzz: elapsed: 9s, execs: 1320812 (146554/sec), new interesting: 3 (total: 49)
    fuzz: elapsed: 12s, execs: 1735749 (138323/sec), new interesting: 3 (total: 49)
    fuzz: elapsed: 15s, execs: 2140404 (134860/sec), new interesting: 3 (total: 49)
    fuzz: elapsed: 18s, execs: 2553033 (137558/sec), new interesting: 3 (total: 49)
    fuzz: elapsed: 21s, execs: 2934209 (127072/sec), new interesting: 3 (total: 49)
    fuzz: elapsed: 24s, execs: 3289376 (118391/sec), new interesting: 3 (total: 49)
    fuzz: elapsed: 27s, execs: 3670350 (126988/sec), new interesting: 4 (total: 50)
    fuzz: elapsed: 30s, execs: 4071006 (133510/sec), new interesting: 4 (total: 50)
    fuzz: elapsed: 30s, execs: 4071006 (0/sec), new interesting: 4 (total: 50)
    PASS
    ok      example/fuzz    30.994s
    $
    

    模糊化测试通过。

除了添加-fuzz标识,若干个新的标识能够添加到go test上,在文档中能够看到:custom-settings 。

点击文档,go.dev/doc/fuzz 进一步阅读。

Go的简单入门:开始使用模糊测试相关推荐

  1. 模糊测试(fuzz testing)介绍(一)

    模糊测试(fuzz testing)是一类安全性测试的方法.说起安全性测试,大部分人头脑中浮现出的可能是一个标准的"黑客"场景:某个不修边幅.脸色苍白的年轻人,坐在黑暗的房间中,正 ...

  2. 模糊测试Fuzzing详细总结

    目录 1.简介 2.测试步骤 (1)确定输入向量 (2)生成模糊测试数据 (3)执行模糊测试 (4)监视异常 (5)根据被测系统的状态判断是否存在潜在的安全漏洞 3.举例和方法 (1)方法1 (2)方 ...

  3. 模糊测试技术简单整理(一)

    模糊测试技术 2022-01-09- 文章目录 模糊测试技术 预处理 1. 插桩: 2. 符号执行 3. 污点分析 基于AFL的模糊测试方法 输入构造 评估 结果分析 具体应用场景下的模糊测试 物联网 ...

  4. 模糊测试入门案例,利用AFL和Honggfuzz模糊测试Tiff

    1.AFL模糊测试tiff AFL的安装已经在前文记录过American Fuzzy Lop(AFL)的安装与简单使用,不再赘述.这里主要记录一下在使用AFL时的可以注意的点. 最好选择由C或者C++ ...

  5. 漏洞挖掘 | 简单高效的模糊测试Fuzzing

    文章目录 定义 用途 技术 详细说明 流程图 1.大量的测试用例 2.对测试用例做过滤 3.要用正确的方法 4.花90%时间阅读文档 5.Fuzzing工具 摘要:Fuzzing是一种通过构造输入来发 ...

  6. 知识普及:关于Fuzzing模糊测试入门原理及实践的讨论

    狩猎者网络安全旗下--知柯信息安全团队(知柯信安) 漏洞挖掘是否是真正的安全呢? "The best alternative to defense mechanisms is to find ...

  7. 渗透测试入门24之渗透测试参考书、课程、工具、认证

    白帽子渗透测试入门资源:参考书.课程.工具.认证文章目录 前言 名词解析 Pwk课程与OSCP证书 CTF 工具 参考书 相关文献推荐 资源打包前言 初入渗透测试领域,过程中遇到不少错综复杂的知识,也 ...

  8. linux 符号执行,[原创]符号执行Symcc与模糊测试AFL结合实践

    上个月末无聊的划水时间段内,在推上看到有人发了一篇关于如何结合去年新发布的符号执行Symcc与模糊测试引擎AFL,以提升Fuzz效率的视频贴.打开这个链接后才发现是个卖课的,emmm.... , 看价 ...

  9. Mybatis简单入门

    1 mybatis简单介绍 MyBatis是一个ORM的数据库持久化框架. Mybatis是一个支撑框架,它以映射sql语句orm方式来数据库持久化操作. ORM:对象关系映射(Object Rela ...

最新文章

  1. jQuery Mobile基础
  2. 软件作坊模式工件应用论
  3. 测试的艺术:测试用例的设计
  4. GraphQL入门2
  5. Tomcat启动项目没问题,网页一片空白
  6. 【今日CV 计算机视觉论文速览 第136期】Wed, 26 Jun 2019
  7. Flutter底部导航栏BottomNavigationBar页面状态保持解决方案
  8. mysql的order by,group by和distinct优化
  9. 《中学生可以这样学Python》84节配套微课免费观看地址
  10. KVM的安装和配置命令详解
  11. system verilog中的参数传递——ref,input,output
  12. java 换行分割_java – 如何通过换行分割字符串?
  13. 用golang生成6位数的唯一id
  14. 《测绘管理与法律法规》真题易错本
  15. C语言深度剖析笔记2
  16. 计算三维空间中直线和三角形的交点
  17. http网页返回状态码含义
  18. Linux中DNS服务器地址查询命令nslookup使用教程
  19. 实现海康监控视频播放(录像回放)(抓拍,录像等功能)
  20. real——表单样式

热门文章

  1. DNS 缓存授权服务器攻击简介
  2. 大数据实训第八天总结
  3. Android中EditText输入字数统计和限制
  4. android电池波形检测图表,智能手机平台心电图波形实时准确绘制方法
  5. 视频监控边缘分析盒 yolov5
  6. DDR的rank,bank的含义和介绍
  7. 展讯平台-sensor驱动
  8. 常见的数组越界问题的一些解决办法
  9. 初步了解MATLAB金融工具箱
  10. echarts字符云相关配置说明