1. 程式人生 > 資料庫 >Go語言中http和mysql的實現程式碼

Go語言中http和mysql的實現程式碼

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",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",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包,就是我們呼叫操作資料庫用的。

第二個是驅動包,這裡前面加了佔位符,所以這個包只是引入,但是不使用它。並且如果要操作別的資料庫的話,只需要修改驅動包就行了。

連線資料庫

構建連線,格式是:”使用者名稱:密碼@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","[email protected]")
  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.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",[email protected])

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 資料庫開發神器,這裡就提一下,等到真正用的時候再去深入學習了。