一、go语言简介
- 天然支持高并发
- 内存自动回收,不需要开发人员管理内存
- 支持管道,从而支持多个协程之间通信
- 多返回值,一个函数可以允许多个返回值
- 异常和错误的区分,异常是不能预料到的事情发生了,错误是预料到的事情发生了。
二、理解gopath
- gopath go项目的工作目录,目录下面包含三个目录,src,pkg,bin
- goroot go源码的安装路径
- gobin 存放go编译后生成的可执行文件
- go get 执行go get会把源码放在第一个gopath的src目录下面
三、go基础知识
3.1 基本数据结构和操作符
-
- 文件名&关键字&标识符(略)
-
- _是特殊标识符,用来忽略结果
-
- Go程序基本结构
-
- 任何一个代码文件隶属于一个包
-
- import 关键字,引用其他包:
-
- golang可执行程序,package main,并且有且只有一个main入口函数
-
- 包中函数调用:a.同一个包中直接实用名称调用;b。不同包中的函数通过包名+点+函数名称进行调用
-
- 包访问控制规则:大写意味函数公有;小写意味函数私有;
-
- 常量和变量
- 常量使用const 修饰,代表永远是只读的,不能修改。
-
- const 只能修饰boolean,number(int相关类型、浮点类型、complex)和string。
-
- 数据类型和操作符
- 值类型:变量直接存储值,内存通常在栈中分配。
- 值类型:基本数据类型int、float、bool、string以及数组和struct。
- 引用类型:变量存储的是一个地址,这个地址存储最终的值。内存通常在堆上分配。通过GC回收。
- 引用类型:指针、slice、map、chan等都是引用类型
- 在函数内部声明的变量叫做局部变量,生命周期仅限于函数内部。
- 在函数外部声明的变量叫做全局变量,生命周期作用于整个包,如果是大写的,则作用于整个程序。
- bool类型,只能存true和false
- 相关操作符, !、&&、||
- 数字类型,主要有int、int8、int16、int32、int64、uint8、uint16、uint32、uint64、float32、float64
- 类型转换,type(variable),比如:var a int=8; var b int32=int32(a)
- 逻辑操作符:== 、!=、<、<=、>和 >=
- 数学操作符:+、-、*、/等等
-
- 字符串类型
- 字符类型:var a byte
- 字符串类型: var str string
- 字符串表示两种方式: 1)双引号2)``(不需要转义)
3.2 字符串处理&时间和日期类型&指针类型&流程控制&函数详解
- strings和strconv使用
- strings.HasPrefix(s string, prefix string) bool:判断字符串s是否以prefix开头。
-
- strings.HasSuffix(s string, suffix string) bool:判断字符串s是否以suffix结尾。
-
- strings.Index(s string, str string) int:判断str在s中首次出现的位置,如果没有出现,则返回-1
-
- strings.LastIndex(s string, str string) int:判断str在s中最后出现的位置,如果没有出现,则返回-1
-
- strings.Replace(str string, old string, new string, n int):字符串替换
-
- strings.Count(str string, substr string)int:字符串计数
-
- strings.Repeat(str string, count int)string:重复count次str
-
- strings.ToLower(str string)string:转为小写
-
- strings.ToUpper(str string)string:转为大写
- strings.TrimSpace(str string):去掉字符串首尾空白字符
- strings.Trim(str string, cut string):去掉字符串首尾cut字符
- strings.TrimLeft(str string, cut string):去掉字符串首cut字符
- strings.TrimRight(str string, cut string):去掉字符串首cut字符
- strings.Field(str string):返回str空格分隔的所有子串的slice
- strings.Split(str string, split string):返回str split分隔的所有子串的slice
- strings.Join(s1 []string, sep string):用sep把s1中的所有元素链接起来
- strings.Itoa(i int):把一个整数i转成字符串
- strings.Atoi(str string)(int, error):把一个字符串转成整数
- Go中的时间和日期类型
-
- time包
-
- time.Time类型,用来表示时间
-
- 获取当前时间, now := time.Now()
-
- time.Now().Day(),time.Now().Minute(),time.Now().Month(),time.Now().Year()
-
- 格式化,fmt.Printf("%02d/%02d%02d %02d:%02d:%02d", now.Year()…)
-
- time.Duration用来表示纳秒
-
- 指针类型
-
- 普通类型,变量存的就是值,也叫值类型
-
- 获取变量的地址,用&,比如: var a int, 获取a的地址:&a
-
- 指针类型,变量存的是一个地址,这个地址存的才是值
-
- 获取指针类型所指向的值,使用:*,比如:var p int, 使用p获取p指向的值
-
- 流程控制
-
- If / else分支判断
-
- switch case语句,可以使用fallthrough强制执行后面的case代码
-
- for 语句
-
- for range 语句
-
- break continue语句
-
- goto 和 label 语句 (明天补充)
-
- 函数详解
-
- 声明语法:func 函数名 (参数列表) [(返回值列表)] {}
-
- golang函数特点:
- a. 不支持重载,一个包不能有两个名字一样的函数
- b. 函数是一等公民,函数也是一种类型,一个函数可以赋值给变量
- c. 匿名函数
- d. 多返回值
-
- 函数参数传递方式:
- 1). 值传递
- 2). 引用传递 注意1:无论是值传递,还是引用传递,传递给函数的都是变量的副本,不过,值传递是值的拷贝。引用传递是地址的拷贝,一般来说,地址拷贝更为高效。而值拷贝取决于拷贝的对象大小,对象越大,则性能越低。 注意2:map、slice、chan、指针、interface默认以引用的方式传递
-
- _标识符,用来忽略返回值:
-
- 可变参数:func add(a int, b int, arg…int) int {} //两个或多个参数
- 注意:其中arg是一个slice,我们可以通过arg[index]依次访问所有参数,通过len(arg)来判断传递参数的个数
-
- defer用途:
-
- 当函数返回时,执行defer语句。因此,可以用来做资源清理
-
- 多个defer语句,按先进后出的方式执行
-
- defer语句中的变量,在defer声明时就决定了。
-
- defer的用途:关闭文件句柄;释放资源;关闭数据库连接
-
3.3 内置函数、递归函数、闭包
内置函数
-
- close:主要用来关闭channel
-
- len:用来求长度,比如string、array、slice、map、channel
-
- new:用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针
-
- make:用来分配内存,主要用来分配引用类型,比如chan、map、slice
-
- append:用来追加元素到数组、slice中
-
- panic和recover:用来做不可预料的异常捕获
-
- new和make的区别
- New与make都是用来分配内存。不同点:new用来分配值类型,make用来分配引用类型;new返回的是一个指针,make返回的是值类型。
递归函数
-
- 一个函数调用自己,就叫做递归。
-
- 斐波那契数
-
- 递归的设计原则
- 1)一个大的问题能够分解成相似的小问题
- 2)定义好出口条件
闭包
- 一个函数和与其相关的引用环境而组合成的实体。
3.4 数组和切片、map数据结构
数组
- 数组:是同一种数据类型的固定长度的序列。
- 数组定义:var a [len]int,比如:var a[5]int
- 长度是数组类型的一部分,因此,var a[5] int和var a[10]int是不同的类型
- 数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1
- 访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic
- 数组是值类型,因此改变副本的值,不会改变本身的值
- 数组初始化
切片
- 切片:切片是数组的一个引用,因此切片是引用类型
- 切片的长度可以改变,因此,切片是一个可变的数组
- 切片遍历方式和数组一样,可以用len()求长度
- cap可以求出slice最大的容量,0 <= len(slice) <= (array),其中array是slice引用的数组
- 切片的定义:var 变量名 []类型,比如 var str []string var arr []int
Slice的数据结构
Type slice struct {
ptr *[5]type
len int
cap int
}
Cap:不指定的话,默认为2^0 2^1 2^2 2^n n为元素的个数
- 通过make来创建切片(另一种通过数组创建切片)
- var slice []type = make([]type, len)
- slice := make([]type, len)
- slice := make([]type, len, cap)
- 用append内置函数操作切片,当切片容量不够,会自动扩容,创建一个新的地址。
- For range 遍历切片
- 切片拷贝
- string与slice
- string底层就是一个byte的数组,因此,也可以进行切片操作
Type string struct {
ptr *[5]byte
len int
}
- 想要改变string中的字符值,首先需要把字符串转换成byte切片
- 排序和查找操作
- sort.Ints对整数进行排序, sort.Strings对字符串进行排序, sort.Float64s对浮点数排序
- sort.SearchInts(a []int, b int) 从数组a中查找b,前提是a必须有序
- sort.SearchFloats(a []float64, b float64) 从数组a中查找b,前提是a必须有序
- sort.SearchStrings(a []string, b string) 从数组a中查找b,前提是a必须有序
map数据结构
- key-value的数据结构,又叫字典或关联数组
- 声明 var a map[string]string
- 声明是不会分配内存的,初始化需要make
- map相关操作
- var a = map[string]string{"key":"value"}
- a = make(map[string]string, 10)
- a[“hello”] = “world” //插入或者更新
- Val, ok := a[“hello”] // 查找
- for k, v := range a //遍历
- delete(a, “hello”) //删除
- map是引用类型
- slice of map
- 字典类型的数组
- Map类型的slice初始化
Items := make([]map[int][int], 5)
For I := 0; I < 5; i++ {
items[i] = make(map[int][int])
}
- map排序
- a. 先获取所有key,把key进行排序
- b. 按照排序好的key,进行遍历
- Map反转
- a. 初始化另外一个map,把key、value互换即可
3.5 package介绍
- golang中的包
- a. golang目前有150个标准的包,覆盖了几乎所有的基础库
- b. golang.org有所有包的文档,没事都翻翻
- 线程同步
- a. import(“sync”)
- b. 互斥锁, var mu sync.Mutex
- c. 读写锁, var mu sync.RWMutex
- d. 为什么要有锁:十字路口是公有的资源,红绿灯就是锁。为了避免小车争抢资源就要上锁。
- 课后作业 实现一个冒泡排序、选择排序、插入排序、快速排序(参考书籍及搜索引擎)
3.6 结构体&方法&接口
结构体的特点
- 用来自定义复杂数据结构
- struct里面可以包含多个字段(属性)
- struct类型可以定义方法,注意和函数的区分
- struct类型是值类型
- struct类型可以嵌套
- Go语言没有class类型,只有struct类型
结构体的定义以及初始化
- struct 声明:
- struct 中字段访问:和其他语言一样,使用点
- struct定义的三种形式:
- var stu Student
- var stu *Student = new (Student)
- var stu *Student = &Student{}
- 其中2和3返回的都是指向结构体的指针,访问形式:a. stu.Name、stu.Age和stu.Score或者 (*stu).Name、(*stu).Age等
- struct的内存布局:struct中的所有字段在内存是连续的,布局如下:
- 链表定义
- 每个节点包含下一个节点的地址,这样把所有的节点串起来了,通常把 链表中的第一个节点叫做链表头
type Student struct {
Name string
Next* Student
}
- 双链表定义
- 如果有两个指针分别指向前一个节点和后一个节点,我们叫做双链表
type Student struct {
Name string
Next* Student
Prev* Student
}
- 二叉树定义
- 如果每个节点有两个指针分别用来指向左子树和右子树,我们把这样的结构叫做二叉树
type Student struct {
Name string
left* Student
right* Student
}
- 结构体是用户单独定义的类型,不能和其他类型进行强制转换
- golang中的struct没有构造函数,一般可以使用工厂模式来解决这个问题
type student struct {
Name string
Age int
}
func NewStudent(name string, age int) *student {
return &student{
Name:name,
Age:age,
}
}
func main() {
s := new(student)
a := NewStudent("tony", 23)
s = a
fmt.Println(*s)
fmt.Println(*a)
}
- 再次强调:
- make 用来创建map、slice、channel、interface,new用来创建值类型
- 我们可以为struct中的每个字段,写上一个tag。这个tag可以通过反射的机制获取到,最常用的场景就是json序列化和反序列化
type student struct {
Name stirng "this is name field"
Age int "this is age field"
}
- 结构体中字段可以没有名字,即匿名字段
type Train struct {
Car
Start time.Time
int
}
3.7 方法
- Golang中的方法是作用在特定类型的变量上,因此自定义类型,都可以有方法,而不仅仅是struct
- 定义:func (recevier type) methodName(参数列表)(返回值列表){}
- 方法的调用
type A struct {
a int
}
func (this A) test() {
fmt.Println(this.a)
}
var t A
t.test()
- 方法和函数的区别
- 函数调用: function(variable, 参数列表)
- 方法调用:variable.function(参数列表)
- 指针receiver vs 值receiver
- 本质上和函数的值传递和地址传递是一样的
- 方法的访问控制,通过大小写控制
- 继承
- 如果一个struct嵌套了另一个匿名结构体,那么这个结构可以直接访问匿名结构体的方法,从而实现了继承。
- 组合和匿名字段
- 如果一个struct嵌套了另一个有名结构体,那么这个模式就叫组合。
- 多重继承
- 如果一个struct嵌套了多个匿名结构体,那么这个结构可以直接访问多个匿名结构体的方法,从而实现了多重继承。
- 实现String()
- 如果一个变量实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出。
3.8 接口
接口的定义
- interface类型可以定义一组方法,但是这些方法不需要实现。并且interface不能包含任何变量。
- 定义接口
type example interface {
Method1(parse1) return1
method2(parse1) return1
}
- interface类型默认是一个指针
- 接口实现
- a. Golang中的接口,不需要显示的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,golang中没有implement类似的关键字
- b. 如果一个变量含有了多个interface类型的方法,那么这个变量就实现了多个接口。
- c. 如果一个变量只含有了1个interface的方部分方法,那么这个变量没有实现这个接口。
- 多态 一种事物的多种形态,都可以按照统一的接口进行操作
- 接口嵌套 一个接口可以嵌套在另外的接口
package main
type ReadWrite interface {
Read()
Write()
}
type Lock interface {
Lock()
Unlock()
}
type File interface {
ReadWrite
Lock
Close()
}
8.类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,可以采用以下方法进行转换:
var t int
var x interface{}
x = t
y, ok := x.(int) //转成int
if ok {...}
switch b := x.(type) {
case int:
fmt.Println("int:", b)
case string:
fmt.Println("string", b)
default:
fmt.Println("i don't know")
}
- 空接口 interface{},空接口没有任何方案,所有类型都实现了空接口,可以使用空接口
var a int
var b interface{}
b = a
-
判断一个变量是否实现了指定接口
- if sv, ok := v.(Stringer); ok {}
-
问题
-
- 指针类型和值类型的区别
-
- 实现一个通用的链表类
-
- 实现一个负载均衡调度算法,支持随机、轮训等算法
-
- 变量slice和接口slice之间赋值操作,for range???
-
3.9 反射
- 反射:可以在运行时动态获取变量的相关信息Import (“reflect”)
- a. reflect.TypeOf,获取变量的类型,返回reflect.Type类型
- b. reflect.ValueOf,获取变量的值,返回reflect.Value类型
- c. reflect.Value.Kind,获取变量的类别,返回一个常量
- d. reflect.Value.Interface(),转换成interface{}类型
- 变量 <--> interface <--> Reflect.Value
- 获取变量的值
- reflect.ValueOf(x).Float()
- reflect.ValueOf(x).Int()
- reflect.ValueOf(x).String()
- reflect.ValueOf(x).Bool()
- 通过反射的来改变变量的值
- reflect.Value.SetXX相关方法,比如:
- reflect.Value.SetFloat(),设置浮点数
- reflect.Value.SetInt(),设置整数
- reflect.Value.SetString(),设置字符串
package main
import (
"fmt"
"reflect"
)
func main() {
var a float64
fv := reflect.ValueOf(&a)
fv.Elem().SetFloat(3.3)
fmt.Printf("%v\n", a)
}
fv.Elem()用来获取指针指向的变量
- 用反射操作结构体 ???
- a. reflect.Value.NumField()获取结构体中字段的个数
- b. reflect.Value.Method(n).Call来调用结构体中的方法
3.9 终端以及文件操作
- 终端读写
- os.Stdin:标准输入
- os.Stdout:标准输出
- os.Stderr:标准错误输出
- 带缓冲区的读写:
- var inputReader *bufio.Reader //声明一个结构体类型的变量
- inputReader = bufio.NewReader(os.Stdin) //读取标准输入
- input, err = inputReader.ReadString('\n') // 读取换行
- os.File封装所有文件相关操作,之前讲的 os.Stdin,os.Stdout, os.Stderr都是*os.File
- 打开一个文件进行读操作: os.Open(name string) (*File, error)
- 关闭一个文件:File.Close()
- bufio.NewReader //构造一个reader结构体
- inputReader.ReadString //逐行读取
- ioutil.ReadFile(inputFile) //读取整个文件
- ioutil.WriteFile(outputFile,buf,0x644) //写入整个文件
- gzip.NewReader(fi) //构造一个压缩文件的reader结构体
- 文件写入
- os.OpenFile(“output.dat”, os.O_WRONLY|os.O_CREATE, 0666)
- 第二个参数:文件打开模式:
- 第三个参数:权限控制:
- bufio.NewWriter(outputFile) //构建一个写入文件的writer
- outputWriter.WriteString(outputString) //写入文件
- outputWriter.Flush() 提交
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func main() {
inputFile, err := os.Open("file")
if err != nil {
fmt.Printf("open file err:%v\n", err)
return
}
defer inputFile.Close()
inputReader := bufio.NewReader(inputFile)
for {
inputString, readerError := inputReader.ReadString('\n')
if readerError == io.EOF {
return
}
fmt.Printf("the input was %s", inputString)
}
}
//读取以及写入整个文件
package main
import (
"fmt"
"io/ioutil"
"os"
)
func main() {
inputFile := "file"
outputFile := "output_file"
buf, err := ioutil.ReadFile(inputFile)
if err != nil {
fmt.Fprintf(os.Stderr, "File Error:%s\n", err)
return
}
fmt.Printf("%s\n",string(buf))
err = ioutil.WriteFile(outputFile,buf,0x644)
if err != nil {
panic(err.Error())
}
}
//读取压缩文件
gzip.NewReader(fi)
//写入文件
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
outputFile, outputErr := os.OpenFile("new_file", os.O_WRONLY|os.O_CREATE,0666)
if outputErr != nil {
fmt.Printf("An error occurred with file creation\n")
return
}
defer outputFile.Close()
outputWriter := bufio.NewWriter(outputFile)
outputString := "hello world!\n"
for i:= 0;i < 10; i++ {
outputWriter.WriteString(outputString)
}
outputWriter.Flush()
}
- 拷贝文件 io.Copy(dst, src)
- os.Args是一个string的切片,用来存储所有的命令行参数
- flag包的使用,用来解析命令行参数:
flag.BoolVar(&test, "b", false, "print on newline")
flag.StringVar(&str, "s", "", "print on newline")
flag.IntVar(&count, "c", 1001, "print on newline")
package main
import (
"flag"
"fmt"
)
func main() {
var test bool
var str string
var count int
flag.BoolVar(&test, "b", false, "print on newline")
flag.StringVar(&str, "s", "hello", "print on newline")
flag.IntVar(&count, "c", 1001, "print on newline")
flag.Parse()
fmt.Println(test,str,count)
}
package main
import (
"bufio"
"fmt"
"os"
)
//带缓冲的区的终端读写
func main() {
fmt.Fprintf(os.Stdout,"%s\n","hello world!-unbuffered")
buf := bufio.NewWriter(os.Stdout)
fmt.Fprintf(buf, "%s\n", "hello world!-buffered" )
buf.Flush()
}
四、中阶知识
4.1 json数据协议
- golang --> json字符串 --> 网络传输 --> 程序 --> 其他语言
- 导入包:Import “encoding/json”
- 序列化: json.Marshal(data interface{}) //json化
- 反序列化: json.UnMarshal(data []byte, v interface{}) //解析json
- json序列化结构体
- json序列化map
4.2 错误处理
- 定义错误 var errNotFound error = errors.New("Not found error")
- 自定义错误
- 如何判断自定义错误
- pannic 这一部分参考俯瞰四维
五、并发编程
六、高阶编程
6.1 tcp编程
6.2 redis
redis是个开源的高性能的key-value的内存数据库,可以把它当成远程的数据结构。支持的value类型非常多,比如string、list(链表)、set(集合)、hash表等等。redis性能非常高,单机能够达到15w qps,通常适合做缓存。使用第三方开源的redis库: github.com/garyburd/redigo/redis
- 链接redis c, err := redis.Dial("tcp", "localhost:6379")
- Set 接口 _, err = c.Do("Set", "abc", 100) r, err := redis.Int(c.Do("Get", "abc"))
- Hash表 _, err = c.Do("HSet", "books", "abc", 100) r, err := redis.Int(c.Do("HGet", "books", "abc"))
- . 批量Set _, err = c.Do("MSet", "abc", 100, "efg", 300) r, err := redis.Ints(c.Do("MGet", "abc", "efg"))
- 过期时间 _, err = c.Do("expire", "abc", 10)
- 队列操作 _, err = c.Do("lpush", "book_list", "abc", "ceg", 300) r, err := redis.String(c.Do("lpop", "book_list"))
6.3 http编程
特点
- a. Go原生支持http,import(“net/http”)
- b. Go的http服务性能和nginx比较接近
- c. 几行代码就可以实现一个web服务
- 处理http请求 使用 net/http 包提供的 http.ListenAndServe() 方法,可以在指定的地址进行监听, 开启一个HTTP,服务端该方法的原型:func ListenAndServe(addr string, handler Handler) error <br> 第二个参数表示服务端处理程序, 通常为空,这意味着服务端调用 http.DefaultServeMux 进行处理,而服务端编写的业务逻 辑处理程序 http.Handle() 或 http.HandleFunc() 默认注入 http.DefaultServeMux 中
- 处理https请求 func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) error
- 路由 http.HandleFunc()方法接受两个参数
- 第一个参数是HTTP请求的 目标路径"/hello",该参数值可以是字符串,也可以是字符串形式的正则表达式
- 第二个参数指定具体的回调方法,比如helloHandler
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(“hello beifeng!”))
})
- post访问
resp, err:=http.Get(“.....”)
defer resp.Body.Close()
body,err:=ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
- post访问
resp, err:=http.Post(“.....”, ”application/x-www-form-urlencoded”, strings.NewReader(“..=...”))
defer resp.Body.Close()
body,err:=ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
6.4 正则表达式
Go语言标准库内建提供了regexp包
. 匹配除换行符以外的任意字符
\w 匹配字母或数字或下划线或汉字
\s 匹配任意的空白符
\d 匹配数字
\b 匹配单词的开始或结束
^ 匹配字符串的开始
$ 匹配字符串的结束
* 重复零次或更多次
+ 重复一次或更多次
? 重复零次或一次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次
捕获 (exp) 匹配exp,并捕获文本到自动命名的组里
(?<name>exp) 匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp)
(?:exp) 匹配exp,不捕获匹配的文本,也不给此分组分配组号
func Match(pattern string, b []byte) (matched bool, err error)
func MatchString(pattern string, s string) (matched bool, err error)
func MustCompile(str string) *Regexp
func (re *Regexp) FindAllString(s string, n int) []string
https://my.oschina.net/kuerant/blog/199146
6.5 mysql编程
-
mysql驱动 https://github.com/Go-SQL-Driver/MySQL
-
sqlx
- database, err := sqlx.Open("mysql", "root:@tcp(127.0.0.1:3306)/test")
- r, err := Db.Exec("insert into person(username, sex, email)values(?, ?, ?)", "stu001", "man", "stu01@qq.com")
- err := Db.Select(&person, "select user_id, username, sex, email from person where user_id=?", 1)
- _, err := Db.Exec("update person set username=? where user_id=?", "stu0001", 1)
- _, err := Db.Exec("delete from person where user_id=?", 1)
6.6 beego框架
beego.me
beego框架之请求数据处理
- ``符号,表示里面的不用转义
- 直接解析到struct
- c.ParseForm(&u)
- 获取request body里的内容
-
- 在配置文件里设置 copyrequestbody = true
-
- this.Ctx.Input.RequestBody
-