```go package main
import (
"fmt"
"net/http"
)
//创建处理器函数 func handler(w http.ResponseWriter,r*http.Request) { //这里面的参数是不能变的
fmt.Fprintln(w,"Hello world","abc",r.URL.Path,"def") //fprintln函数可以随意拼接自己想要的字符
}
func main() {
http.HandleFunc("/abc",handler) //定义一个函数类型,就可以把函数作为参数传入,handlerfunc函数当访问根目录时就会自动执行handler函数
//handlerfunc函数会将指定的url拼接到后面,当拼接了指定的url时,会自动执行handler函数
//创建路由
http.ListenAndServe(":8088",nil) //ListenAndServer函数会映射指定的端口,第一个参数就是映射到哪个端口,第二个参数是
//ListenAndServer函数需要传入两个参数都需要监听的端口和handler,第一个是监听的端口,第二个是处理请求的接口,
}
2. handler函数:是一个接口,接口名随便起,参数是固定的,必须是w http.ResponseWriter 和 r*http.Request,自定义的,所以不需要写http.包的名字
3. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20221014212121.png)
4. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20221014212749.png)
5. HandlerFunc,是调用http包的函数,所以必须调用http.来说明是包内的函数,第一个参数是后面拼接的后缀(映射的地址)(url为string类型),第二个参数是一个处理器,说明映射的地址交给哪个处理器去完成
6. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20221014212520.png)
7. ListenAndServe函数第一个参数是监听TCP地址addr(端口),并且会使用handler参数调用Serve函数处理接收到的链接,handler参数一般设置为nil,此时会使用DefaultServeMux
8. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20221014213351.png)
1. ```go
package main
import (
"fmt"
"net/http"
)
type MyHandler struct {}
func (m *MyHandler) ServeHTTP(w http.ResponseWriter , r *http.Request) {
fmt.Fprintln(w,"通过自己创建的处理器处理请求!")
}
func main() {
myHandler := MyHandler{}
http.Handle("/myHandler",&myHandler) //Handle函数的第二个参数是myHandler的地址,实例对象的地址
http.ListenAndServe(":8088",nil)
}
//这个是比较麻烦的自己创建处理器的方法,不推荐使用
如果调用的是HandleFunc函数会自动转换成处理器,不需要一个struct来实现接口
如果调用的是Handle函数,则必须要实现ServeHTTP方法(一个接口)
请求报文
``` 第一行是请求首行,包含请求方式,请求地址 和 请求协议 第二行开始是请求头信息,就是请求的属性信息 后面紧跟一个空行 空行后面是请求体
5. get请求没有请求体,post请求才有请求体
6. 可以通过浏览器的network来查看报文信息,其中view-source选项可以查看具有报文结构的报文信息
7. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20221015113803.png)
8. ```go
package main
import (
"fmt"
"net/http"
)
//创建处理器函数
func handler(w http.ResponseWriter , r *http.Request){
fmt.Fprintln(w , "测试http协议")
}
func main() {
//调用处理器处理请求
http.HandleFunc("/http",handler)
//路由
http.ListenAndServe(":8080",nil)
}
```html
用户名: 密码:
10. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20221102110231.png)
# 通过go语言链接数据库
1. 查看如何拼接sql语句官方文档看database/sql库中的
2. [Go语言标准库文档中文版 | Go语言中文网 | Golang中文社区 | Golang中国 (studygolang.com)](https://studygolang.com/pkgdoc)
3. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20221015164640.png)
4. 使用database/sql包来操作数据库
5. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20221015164950.png)
6. 因为go语言没有提供任何官方的数据库驱动,所以我们需要导入第三方的数据库驱动,“github.com/go-sql-driver/mysql”
7. 放在 gomodcache 指向的目录下,有一个github.com目录
8. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20221015165822.png)
9. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20221015165843.png)
10. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20221015170131.png)
11. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20221015170715.png)
12. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20221015170727.png)
## 链接数据库
1. ```go
package utils
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
var (
Db *sql.DB
err error
)
func init() {
Db,err := sql.Open("mysql","root:20030729a@tcp(localhost:3306)/ctfshow")
if err != nil {
panic(err.Error())
}
DB.SetConnMaxLifetime(10)
DB.SetMaxIdleConns(5)
if err := DB.Ping() ; err != nil {
fmt.Println("open database fail")
return
}
}
func (user *User) AddUser() error {
//写sql语句
sqlStr := "insert into users(username,password,email) values(?,?,?)"
//预编译
inStmt , err := utils.Db.Prepare()
}
func (db *DB) Exec(query string,args ...interface()) {Result,error}
func (db *DB) Query(query string,args ...interface()) {*Rows,error}
func (db *DB) QueryRow(query string,args ...interface()) *Row
// AddUser 添加用户的方法一
func (user *User) AddUser() error {
//写sql语句
sqlStr := "insert into users(username,password,email) values(?,?,?)"
//预编译
inStmt , err := utils.Db.Prepare(sqlStr) //预编译得到的是inStmt,通过操作inStmt得到不同的结果
if err != nil {
fmt.Println("预编译出现异常",err)
return err
}
//3.执行
_,err2 := inStmt.Exec("admin","123456","admin@atguigu.com")
if err2 != nil {
fmt.Println("执行出现异常",err2)
return err2
}
return nil
}
单元测试就是为了验证单元的正确性而设置的自动化测试,一个单元就是程序中的一个模块化部分
一般来说,一个单元通常会和程序中的一个函数或者一个方法对应
go的单元测试需要用到testing包以及go test命令,而且对测试文件也有以下要求
```
被测试的源文件和测试文件必须位于同一个包下
测试文件必须以 _test.go结尾
虽然go对测试文件_test.go的前缀没有强制要求,不过一般都设置魏被测试文件的文件名,对user.go测试,名字一般设置为user_test.go
测试文件中的测试函数为 TestXXX(*test.T) 其中,XXX的首字母必须是大写的英文字母 函数参数必须是test.T的指针类型
Test测试函数的参数必须是 t *test.T
5. ```go
package model
import (
"fmt"
"testing"
)
func TestAddUser(t *testing.T) {
fmt.Println("测试添加用户:")
user := &User{}
//调用添加用户的方法
user.AddUser()
user.AddUser2()
}
如果函数名不是以Test开头,那么函数默认不执行,我们可以将它设置成为一个子测试程序
在主Test函数中调用子测试程序,可以将声明的test.T指针对象指向子测试函数
```go t.Run(“测试添加用户”,testAddUser) //第一个参数是自己写的string类型,在调用时自动输出,第二个参数是要调用哪个子测试程序
9. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20221102193939.png)
## 预处理SQL
1. 预编译语句是将需要反复调用的某一条sql语句的值用占位符代替,可以视为将sql语句模板化或者参数化,这类语句即为prepared statements,预编译语句
2. 优势在于:一次编译,多次运行,省去了解析优化的过程,此外预编译语句能够防止sql注入
3. ```sql
# 定义预处理语句
prepare state_string from prepareable_state_ment;
# 执行预处理语句
execute state_string [using @var_name , @var_name]
# 删除(释放)定义
{deallocate|drop} prepare state_ment;
QueryRow函数返回的是一行结果的指针,row的内容隐藏或非导出字段,代表单行查询结果
实际查询代码
```go func (user *User) GetUserById() (*User,error) {
//写sql语句
sqlStr := "select id,username,password,email from users where id = ?"
//执行
row := utils.Db.QueryRow(sqlStr,user.ID)
//声明
var id int
var username string
var password string
var email string
err := row.Scan(&id,&username,&password,&email)
if err != nil {
return nil,err
}
u := &User {
ID : id,
Username : username,
Password: password,
Email: email,
}
return u , nil
}
6. 单元测试代码
7. ```go
//测试获取一个User
func testGetUserById(t *testing.T){
fmt.Println("测试一条查询数据")
user := User {
ID : 3,
}
//调用获取User的方法
u,err2 := user.GetUserById()
if err2 != nil{
fmt.Println(err2)
} else {
fmt.Println("得到的User信息是:",u)
}
}
实际查询代码
```go //GetUsers 获取数据库中的所有记录 func (user *User) GetUsers() ([]*User,error) { //在一个切片上存储指针或带指针的值,典型的例子是[]*string
//写sql语句
sqlStr := "select id,username,password,email from users"
//执行
rows ,_ := utils.Db.Query(sqlStr)
// if err3 != nil {
// return err3,nil
// }
//创建User切片
var users []*User
for rows.Next(){
var id int
var username string
var password string
var email string
err := rows.Scan(&id,&username,&password,&email)
if err != nil {
return nil,err
}
u := &User { //要被添加的值是地址
ID : id,
Username : username,
Password: password,
Email: email,
}
users = append(users,u) //append函数的第一个值是被添加到的slice的地址,第二个值是添加的值,因为这个地方是一个存储地址的数组,所以添加的值也是地址
}
return users,nil
}
4. 单元测试代码
5. ```go
//测试获取所有的User
func testGetUsers(t *testing.T){
fmt.Println("测试查询所有记录:")
user := &User{}
//调用获取所有User的方法
rows,_ := user.GetUsers()
//遍历输出切片中的内容
for k , v := range rows{
fmt.Printf("第%v个用户是%v:",k+1,v)
fmt.Println()
}
}
这个地方的Request类型和handler处理器中的参数是一个类型
其中Request数据类型中的URL属性,也是一个结构体type
```go package main
import (
"fmt"
"net/http"
)
//创建处理器函数 func handler(w http.ResponseWriter,r *http.Request){
fmt.Fprintln(w,"你发送的请求地址是:",r.URL.Path)
fmt.Fprintln(w,"你发送的请求地址后的查询字符串是:",r.URL.RawQuery)
}
func main(){
http.HandleFunc("/hello",handler)
http.ListenAndServe(":8080",nil)
}
7. Request类型的变量r,r.URL.Path获得到的是请求地址,r.URL.RawQuery获得到的是传递的参数
## 获取请求行和请求体
1. Request类型中的Header字段即为请求头
2. Request变量r中的Header字段,代表了请求头中的所有信息
3. 如果想要获取Header字段中的某个信息,可以通过中括号取下标的方式来获取
4. ```go
r.Header["Accept-Encodeing"] //这样可以获取请求头中的报文编码格式
获取Header字段中某个信息的属性值用Get方法
```go r.Header.Get(“Accept-Encoding”)
7. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20221103114058.png)
8. Get返回键对应的第一个值,如果键不存在会返回"",如果获取该键对应的值切片,请直接用规范格式的键访问map
9. ```go
package main
import (
"fmt"
"net/http"
)
//创建处理器函数
func handler(w http.ResponseWriter,r *http.Request){
fmt.Fprintln(w,"你发送的请求地址是:",r.URL.Path)
fmt.Fprintln(w,"你发送的请求地址后的查询字符串是:",r.URL.RawQuery)
fmt.Fprintln(w,"请求头中所有的信息有:",r.Header)
fmt.Fprintln(w,"请求头中Accept-Encoding的信息是:",r.Header["Accept-Encoding"])
}
func main(){
http.HandleFunc("/hello",handler)
http.ListenAndServe(":8080",nil)
}
其中不同的是:r.Header[属性]获取到的是map
r.Header.Get()获取到的是值,没有大括号,Get函数中的参数是string类型的属性,和大括号取值中大括号中的内容相同
通过net/http库中的Request结构的字段以及方法获取请求URL后面的query参数和POST或PUT的表单数据
如果想要获取postform字段中的数据,需要特定enctype的属性值为application/x-www-form-urlencoded,指定编码方式,如果编码方式为multipart/form-data的属性值,则使用postform字段无法获取表单中的数据
form表单的enctype属性的默认值时application/x-www-form-urlencode编码,实现文件上传时需要将该属性的值设置为multipart/form-data的编码格式
Request的type中,ContentLength属性记录相关内容的长度,在客户端,如果Body非nil而该字段为0,则表示不知道Body的长度
```go //获取请求体中内容的长度
len := r.ContentLength //ContentLength属性在Request对象中
11. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20221103171021.png)
12. 将Body中的内容读到body中
13. ```go
//将Body中的内容读到body中
r.Body.Read(body)
handler处理器的第一个参数,w http.ResponseWriter类型的对象w
例子
```go func handler(w http.ResponseWriter , r *http.Request){
w.write([]byte("你的请求我已经收到"))
}
6. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20221103174031.png)
7. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20221103174806.png)
### 给客户端响应改变为json格式
1. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20221103184358.png)
2. 记住,一定要**导入encoding/json的包**
3. ```go
func testJsonRes(w http.ResponseWriter,r *http.Request){
//设置响应内容的类型
w.Header().Set("Content-Type","application/json")
//创建User
user := model.User{
ID:1,
Username:"admin",
Password:"123456",
Email:"admin@atguigu.com",
}
//将User转换为Json格式
json,_ := json.Marshal(user)
//将json格式的数据相应给客户端
w.Write(json)
}
func main(){
http.HandleFunc("/hello",handler)
http.HandleFunc("/testJson",testJsonRes)
http.ListenAndServe(":8080",nil)
}
处理器端代码
```go func handler(w http.ResponseWriter,r *http.Request){
//以下操作必须得在WriteHeader之前运行
w.Header().Set("Location","https://www.baidu.com") //第一个参数是表明Location地址,第二个参数指定重定向位置
w.WriteHeader(302) //设置响应的状态码
}
3. 可以看出,*http.Request 参数是用来处理用户的请求
4. http.ReponseWriter用来给用户响应
## 模板引擎,处理响应数据
1. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20221104090340.png)
2. ![](https://strongwillpro.oss-cn-beijing.aliyuncs.com/img/20221104090544.png)
3. ```go
func testTemplate(w http.ResponseWriter,r *http.Request) {
//解析模板
t,_ := template.ParseFiles("index.html")
t.Execute(w,"")
}