为了账号安全,请及时绑定邮箱和手机立即绑定

Go语言10-http和mysql

标签:
Go


http 编程

Go 原生支持http:

import "net/http"

Go 的http服务性能和nginx比较接近:

就是说用Go写的Web程序上线,程序前面不需要再部署nginx的Web服务器,这里省掉的是Web服务器。如果服务器上部署了多个Web应用,还是需要反向代理的,一般这也是nginx或apache。

几行代码就可以实现一个web服务:

package main

import (

    "fmt"

    "net/http"

)

func Hello(w http.ResponseWriter, r *http.Request) {

    fmt.Println(*r)

    fmt.Fprintf(w, "Hello World")

}

func main() {

    http.HandleFunc("/", Hello)

    err := http.ListenAndServe("0.0.0.0:8000", nil)

    if err != nil {

        fmt.Println("http Listen failed")

    }

}

http client

http 常见的请求方法:

Get请求

Post请求

Put请求

Delete请求

Head请求

Get 请求

使用Get请求网站的示例:

package main

import (

    "fmt"

    "io/ioutil"

    "net/http"

)

func main() {

    res, err := http.Get("http://edu.51cto.com")

    if err != nil {

        fmt.Println("http get ERRPR:", err)

        return

    }

    data, err := ioutil.ReadAll(res.Body)

    if err != nil {

        fmt.Println("get data ERROR:", err)

        return

    }

    fmt.Println(string(data))

}

Head请求

Head请求只返回响应头。如果只想要获取一些状态信息的话,可以用Head请求。这样避免返回响应体,响应体的数据是比较多的,适合做监控。Head请求的示例:

package main

import (

    "fmt"

    "net/http"

)

var urls = []string{

    "http://×××w.baidu.com",

    "http://×××w.google.com",

    "http://×××w.sina.com.cn",

    "http://×××w.163.com",

}

func main() {

    for _, v := range urls {

        resp, err := http.Head(v)

        if err != nil {

            fmt.Println("Head request ERROR:", err)

            continue

        }

        fmt.Println(resp.Status)

    }

}

http 常见状态码

http.StatusContinue = 100

http.StatusOK = 200

http.StatusFound = 302 跳转

http.StatusBadRequest = 400 非法请求

http.StatusUnanthorized = 401 没有权限

http.StatusForbidden = 403 禁止访问

http.Status.NotFound = 404 页面不存在

http.StatusInternalServerError = 500 内部错误

处理form表单

package main

import (

    "fmt"

    "io"

    "net/http"

)

const form = `

<html>

<body>

<form action="#" method="post" name="bar">

    <input type="text" name="in" />

    <input type="text" name="in" />

    <input type="submit" value="Submit" />

</form>

</body>

</html>`

func FormServer(w http.ResponseWriter, request *http.Request) {

    w.Header().Set("content-Type", "text/html")

    switch request.Method {

    case "GET":

        io.WriteString(w, form)

    case "POST":

        request.ParseForm()

        io.WriteString(w, request.Form["in"][0])  // 注意上面的2个input的name是一样的

        io.WriteString(w, request.Form["in"][1])  // 所以这是一个数组

        io.WriteString(w, "</br>")

        io.WriteString(w, request.FormValue("in"))  // 一般去一个值,就用这个方法

    }

}

func main() {

    http.HandleFunc("/form", FormServer)

    if err := http.ListenAndServe(":8000", nil); err != nil {

        fmt.Println("监听端口ERROR:", err)

    }

}

panic 处理

如果处理函数里有panic,会导致整个程序崩溃,所以要 defer revoer() 来处理 panic。在处理函数开头defer一个匿名函数:

func FormServer(w http.ResponseWriter, request *http.Request) {

    // 增加一个defer来处理panic

    defer func() {

        if x := recover(); x != nil {

            log.Println(request.RemoteAddr, "捕获到异常:", x)

        }

    }()

    // 原本的处理函数的内容

    w.Header().Set("content-Type", "text/html")

    switch request.Method {

    case "GET":

        io.WriteString(w, form)

    case "POST":

        request.ParseForm()

        io.WriteString(w, request.FormValue("in"))  // 一般去一个值,就用这个方法

    }

    // 搞个panic出来

    zero := 0

    tmp := 1 / zero

    io.WriteString(w, string(tmp))

}

优化统一处理

按照上面的做法,要在每个处理函数的开头都加上panic的处理。由于每个处理函数的panic处理方法都一样,所以可以写一个自定义的处理函数:

// 自定义的panic处理的函数

func logPanics(handle http.HandlerFunc) http.HandlerFunc {

    return func(writer http.ResponseWriter, request *http.Request) {

        defer func() {

            if x := recover(); x != nil {

                log.Println(request.RemoteAddr, "捕获到异常:", x)

            }

        }()

        // 上面先处理panic,再接着下面调用业务逻辑

        handle(writer, request)

    }

}

func main() {

    // http.HandleFunc("/form", FormServer)  // 修改调用处理函数的方法

    http.HandleFunc("/form", logPanics(FormServer))  // 把处理函数传给自己写的封装了panic处理的函数里

    if err := http.ListenAndServe(":8000", nil); err != nil {

        fmt.Println("监听端口ERROR:", err)

    }

}

原本直接调用处理函数。现在调用自定义的函数,把处理函数传进去。在自定义的函数里先加载defer,然后再调用执行原本的处理函数。逻辑很简单,就是把处理函数作为参数传给自定义的函数,在自定义的函数里再调用处理函数。在自定义的函数里写上defer,这样就相当于所有的处理函数都有defer了。

模板

使用模板需要用到 "text/template" 包。然后调用模板的t.Execute()方法输出。

替换

先准备一个简单的模板:

<p>Hello {{.Name}}</p>

<p>Age: {{.Age}}</p>

然后在Go里使用模板:

package main

import (

    "fmt"

    "os"

    "text/template"

)

type Person struct {

    Name string

    Age int

}

func main() {

    t, err := template.ParseFiles("index.html")

    if err != nil {

        fmt.Println("模板解析异常:", err)

        return

    }

    p := Person{"Bob", 32}

    if err := t.Execute(os.Stdout, p); err != nil {

        fmt.Println("模板加载数据异常:", err)

    }

}

/* 执行结果

PS H:\Go\src\go_dev\day10\http\use_template> go run main.go

<p>Hello Bob</p>

<p>Age: 32</p>

PS H:\Go\src\go_dev\day10\http\use_template>

*/

如果直接用 {{.}} 不加字段名的话,就是输出结构体打印的效果。

输出到浏览器里

要输出到浏览器里,只需要在 t.Execute(os.Stdout, p) 里,把原本输出到终端换成输出到处理函数的 w http.ResponseWriter 类型,就好了。

html模板的内容不变,下面是go的代码:

package main

import (

    "fmt"

    "net/http"

    "text/template"

)

func Hello(w http.ResponseWriter, r *http.Request) {

    fmt.Fprintf(w, "Hello World")

}

type Person struct {

    Name string

    Age int

}

func Index(w http.ResponseWriter, r *http.Request) {

    p := Person{"Cara", 18}

    t, err := template.ParseFiles("index.html")

    if err != nil {

        fmt.Println("加载模板ERROR:", err)

        return

    }

    t.Execute(w, p)

}

func main() {

    http.HandleFunc("/", Hello)

    http.HandleFunc("/index", Index)

    err := http.ListenAndServe("0.0.0.0:8000", nil)

    if err != nil {

        fmt.Println("http Listen failed")

    }

}

判断

用法示例:

<body>

{{if gt .Age 18}}

<p>已成年</p>

{{else}}

<p>未成年</p>

{{end}}

</body>

更多判断逻辑:

not 非

{{if not .condition}} 

{{end}}

and 与

{{if and .condition1 .condition2}} 

{{end}}

or 或

{{if or .condition1 .condition2}} 

{{end}}

eq 等于

{{if eq .var1 .var2}} 

{{end}}

ne 不等于

{{if ne .var1 .var2}} 

{{end}}

lt 小于

{{if lt .var1 .var2}} 

{{end}}

le 小于等于

{{if le .var1 .var2}} 

{{end}}

gt 大于

{{if gt .var1 .var2}} 

{{end}}

ge 大于等于

{{if ge .var1 .var2}} 

{{end}}

with 封装

with语句就是创建一个封闭的作用域,在其范围内,{{.}}代表with的变量,而与外面的{{.}}无关,只与with的参数有关:

<body>

{{with .Name}}

<p>{{.}}</p>

{{end}}

</body>

上面这样包在 {{with .Var}} 里,with 里的 {{.}} 代表的就是 Var 这个变量。

with 可以封装常数:

{{ with "world"}}

    Now the dot is set to {{ . }}

{{ end }}

循环(遍历)

golang的template支持range循环来遍历map、slice内的内容,在range循环内,还可以使用$设置循环变量,我们可以通过 $i $v 来访问遍历的值。语法为:

{{range $i, $v := .slice}}

    <li>key: {{ $key }}, value: {{ $value }}</li>

{{end}}

这是另外一种遍历方式,这种方式无法访问到index或者key的值,需要通过点来访问对应的value:

{{range .slice}}

{{.field}}

{{end}}

在循环内,点是代表遍历的值。原本使用点来访问的变量,那么在循环内部就要用 $. 来访问。下面的例子表示循环内和循环外 ArticleConten 这个变量访问的方式:

{{.ArticleContent}}

{{range .slice}}

{{$.ArticleContent}}

{{end}}

定义变量

模板的参数可以是go中的基本数据类型,如字串,数字,布尔值,数组切片或者一个结构体。在模板中设置变量可以使用 $variable := value。我们在range迭代的过程使用了设置变量的方式。

{{$article := "hello"}}

{{$name := .Name}}

mysql 使用

这里只简单讲了数据的增删改查,所以测试代码前,需要先把数据库准备好。

先创建一个数据库,指定了编码,这样应该可以支持中文:

CREATE DATABASE 库名 CHARSET "utf8";

然后建2张表:

CREATE TABLE person (

    user_id int primary key auto_increment,

    username varchar(260),

    gender varchar(260),

    email varchar(260)

);

CREATE TABLE place (

    country varchar(200),

    city varchar(200),

    telcode int

);

导入数据库驱动

sql 包提供了通用的SQL(或类SQL)数据库接口。

sql 包必须与数据库驱动结合使用。

驱动包需要安装:

go get -u github.com/go-sql-driver/mysql

使用前,先要导入mysql的包:

import (

    "database/sql"

    _ "github.com/go-sql-driver/mysql"

)

上面导入了2个包。第一个是sql包,就是我们调用操作数据库用的。

第二个是驱动包,这里前面加了占位符,所以这个包只是引入,但是不使用它。并且如果要操作别的数据库的话,只需要修改驱动包就行了。其实就是只执行这个包里的初始化init函数

连接数据库

构建连接, 格式是:”用户名:密码@tcp(IP:端口)/数据库?charset=utf8” :

package main

import (

    "fmt"

    "time"

    "database/sql"

    _ "github.com/go-sql-driver/mysql"

)

var DB *sql.DB

func init() {

    database, err := sql.Open("mysql", "admin:admin123@tcp(192.168.3.103:3306)/Golang_week10")

    if err != nil {

        fmt.Println("连接数据库失败:", err)

        return

    }

    DB = database

}

func main() {

    fmt.Println(DB)

    DB.SetMaxIdleConns(16)  //设置闲置连接数

    DB.SetMaxOpenConns(100)  //设置最大连接数

    DB.SetConnMaxLifetime(100*time.Second)  //最大连接周期,超过时间的连接就close

    fmt.Println(DB)

}

插入数据

下面是插入数据,并且再获取id的示例:

// 数据库连接的init函数就省略了

func insert() {

    r, err := DB.Exec("insert into person(username,gender,email) values(?,?,?)", "Barry", "Male", "Barry@go.net")

    if err != nil {

        fmt.Println("插入数据ERROR:", err)

        return

    }

    fmt.Println(r)

    id, err := r.LastInsertId()

    if err != nil {

        fmt.Println("获取id ERROR:", err)

        return

    }

    fmt.Println(id)

}

func main() {

    insert()

}

上面的 values(?,?,?) 里的问号,是占位符,具体的值可以写在后面的参数里。当然如果不用占位符,直接就传1个字符串作为参数也是可以的。

查询

查询单个字段:

func query() {

    row := DB.QueryRow("select username from person where user_id=?", 1)

    var name string  // 创建变量用于存放查询到的数据

    if err := row.Scan(&name); err != nil {

        fmt.Println("Scan Failed:", err)

        return

    }

    fmt.Println(name)

}

func main() {

    query()

}

也可以一次查询多个字段或所有字段,查询之前按照表的类型创建结构体,用查询到的数据为结构体赋值:

type Person struct {

    ID int `db:"user_id"`

    Username sql.NullString `db:"username"`

    Gender sql.NullString `db:"gender"`

    Email sql.NullString `db:"email"`

}

func query() {

    row := DB.QueryRow("select * from person where user_id=?", 6)

    var person = new(Person)

    // row.scan中的字段必须是按照数据库存入字段的顺序,否则报错

    if err := row.Scan(&person.ID, &person.Username, &person.Gender, &person.Email); err != nil {

        fmt.Println("Scan Failed:", err)

        return

    }

    fmt.Println(person)

}

func main() {

    query()

}

数据模型,就是上面定义的结构体。这里的类型可以是Go的标准数据类型。但是如果数据库的字段允许为空,并且该字段的值也为空,那么查询后该字段会返回nil。如果是string类型,则无法接收nil,但sql.NullString则可以接收nil值。

另外,结构体里的tag标签在这里没有意义。不过上面的tag标注了该字段在数据库里对应的字段名,可能在别处会有用。

查询多行

func query() {

    rows, err := DB.Query("select * from person where user_id > ?", 1)

    defer func() {

        if rows != nil {

            rows.Close()

        }

    }()

    if err != nil {

        fmt.Println("Query 查询 ERROR:", err)

        return

    }

    var person = new(Person)

    for rows.Next() {

        if err = rows.Scan(&person.ID, &person.Username, &person.Gender, &person.Email); err != nil {

            fmt.Println("Scan Failed:", err)

            return

        }

        fmt.Println(person)

    }

}

func main() {

    query()

}

查询用起来还是不太方法,不过还可以选择其他第三方库,那里会有一些很好的扩展。后面会举例。

其他操作

由于基本都是用SQL的命令进行操作,所以其他操作就不一个一个举例了 

update 更新数据

result, err := DB.Exec("UPDATE person set email=? where username=?", "Cara", "Cara@catco.org")

delete 删除数据

result,err := DB.Exec("DELETE FROM person where id=?",1)

注意:更新数据不返回LastInsertID,所以result.LastInsertID一直为0。删除数据可以拿到LastInsertID,用法和插入数据里一样。

第三方库 sqlx

sqlx是一个go语言包,在内置database/sql包之上增加了很多扩展,简化数据库操作代码的书写。

由于database/sql接口是sqlx的子集,所有database/sql的用法,在sqlx中一样可以用。不过sqlx还有更多扩展,用起来更方便。

安装:

go get github.com/jmoiron/sqlx

查询 Select() 方法

Select是一个非常省时的扩展。它们把query和非常灵活的scan语法结合起来。Select用来获取结果切片:

// 这里的tag标签就有意义了,下面的Select()方法应该就是根据tag标签对号入座的

type Person struct {

    ID int `db:"user_id"`

    Username sql.NullString `db:"username"`

    Gender sql.NullString `db:"gender"`

    Email sql.NullString `db:"email"`

}

func select() {

    var persons []Person  // 这里创建的是存放结构体的切片

    if err := DB.Select(&person, "select * from person where userid > ?", 1); err != nil {

        fmt.Println("Select ERROR:", err)

        return

    }

    fmt.Println(person)

}

Select可以提高编码效率,还有更多扩展。sqlx 号称 golang 数据库开发神器,这里就提一下,等到真正用的时候再去深入学习了。

mysql 事务

事务的特性:

原子性

一致性

隔离性

持久性

实现事务的步骤

import “github.com/jmoiron/sqlx" :导入第三方库 sqlx

Db.Begin() :声明事务。写在操作的开头

Db.Submit() :所有事务完成后,提交事务。写在操作的结尾

Db.Rollback() :如果有失败,回滚。写在返回err的判断语句里

Db.Begin() 方法返回了 conn 后,之后的操作都是对 conn 对象进行操作:

func main() {

    conn, err := Db.Begin()

    if err != nil {

        return

    }

    r, err := conn.Exec("insert into person(username, sex, email)values(?, ?, ?)", "stu001", "man", "stu01@qq.com")

    if err != nil {

        fmt.Println("exec failed, ", err)

        conn.Rollback()

        return

    }

    id, err := r.LastInsertId()

    if err != nil {

        fmt.Println("exec failed, ", err)

        conn.Rollback()

        return

    }

    r, err := conn.Exec("insert into person(username, sex, email)values(?, ?, ?)", "stu001", "man", "stu01@qq.com")

    if err != nil {

        fmt.Println("exec failed, ", err)

        conn.Rollback()

        return

    }

    fmt.Println("insert succ:", id)

    conn.Commit()

}

©著作权归作者所有:来自51CTO博客作者骑士救兵的原创作品,如需转载,请注明出处,否则将追究法律责任


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消