Golang-WEB开发-笔记

Golang http 标准库

http 标准库了 http 客户端和服务器的实现,注意了,客户端实现可以发出 http 请求,并解析响应。服务器可以实现 http server 功能。市面上的所有 golang web 框架都是基于 http 标准库实现的。

http 标准库客户端功能

发出 GET 请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"time"
)

func testGet() {
// https://www.juhe.cn/box/index/id/73
url := "http://apis.juhe.cn/simpleWeather/query?key=087d7d10f700d20e27bb753cd806e40b&city=北京"
r, err := http.Get(url)
if err != nil {
log.Fatal(err)
}
defer r.Body.Close()
b, _ := ioutil.ReadAll(r.Body)
fmt.Printf("b: %v\n", string(b))
}

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
{
"reason": "查询成功!",
"result": {
"city": "北京",
"realtime": {
"temperature": "3",
"humidity": "94",
"info": "阴",
"wid": "02",
"direct": "东北风",
"power": "2级",
"aqi": "117"
},
"future": [
{
"date": "2021-12-09",
"temperature": "-1/7℃",
"weather": "多云转晴",
"wid": {
"day": "01",
"night": "00"
},
"direct": "北风"
},
{
"date": "2021-12-10",
"temperature": "-1/8℃",
"weather": "多云",
"wid": {
"day": "01",
"night": "01"
},
"direct": "北风转西南风"
},
{
"date": "2021-12-11",
"temperature": "-2/10℃",
"weather": "多云转晴",
"wid": {
"day": "01",
"night": "00"
},
"direct": "北风"
},
{
"date": "2021-12-12",
"temperature": "-5/4℃",
"weather": "晴",
"wid": {
"day": "00",
"night": "00"
},
"direct": "西北风转西南风"
},
{
"date": "2021-12-13",
"temperature": "-6/5℃",
"weather": "晴",
"wid": {
"day": "00",
"night": "00"
},
"direct": "西南风"
}
]
},
"error_code": 0
}

本实例我们使用到了:https://www.juhe.cn/box/index/id/73 天气查询 api

GET 请求,把一些参数做成变量而不是直接放到 url

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func testGet2() {
params := url.Values{}
Url, err := url.Parse("http://apis.juhe.cn/simpleWeather/query")
if err != nil {
return
}
params.Set("key", "087d7d10f700d20e27bb753cd806e40b")
params.Set("city", "北京")
//如果参数中有中文参数,这个方法会进行URLEncode
Url.RawQuery = params.Encode()
urlPath := Url.String()
fmt.Println(urlPath)
resp, err := http.Get(urlPath)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}

解析 JSON 类型的返回结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"time"
)

func testParseJson() {
type result struct {
Args string `json:"args"`
Headers map[string]string `json:"headers"`
Origin string `json:"origin"`
Url string `json:"url"`
}

resp, err := http.Get("http://httpbin.org/get")
if err != nil {
return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
var res result
_ = json.Unmarshal(body, &res)
fmt.Printf("%#v", res)
}

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"args": {},
"headers": {
"Accept-Encoding": "gzip",
"Host": "httpbin.org",
"User-Agent": "Go-http-client/1.1",
"X-Amzn-Trace-Id": "Root=1-61b16029-731c99ba4591c9bd3db53edd"
},
"origin": "115.171.25.28",
"url": "http://httpbin.org/get"
}

main.result{Args:"", Headers:map[string]string{"Accept-Encoding":"gzip", "Host":"httpbin.org", "User-Agent":"Go-http-client/1.1", "X-Amzn-Trace-Id":"Root=1-61b16029-731c99ba4591c9bd3db53edd"}, Origin:"115.171.25.28", Url:"http://httpbin.org/get"}

GET 请求添加请求头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"time"
)

func testAddHeader() {
client := &http.Client{}
req, _ := http.NewRequest("GET", "http://httpbin.org/get", nil)
req.Header.Add("name", "老郭")
req.Header.Add("age", "80")
resp, _ := client.Do(req)
body, _ := ioutil.ReadAll(resp.Body)
fmt.Printf(string(body))
}

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"args": {},
"headers": {
"Accept-Encoding": "gzip",
"Age": "3",
"Host": "httpbin.org",
"Name": "zhaofan",
"User-Agent": "Go-http-client/1.1",
"X-Amzn-Trace-Id": "Root=1-61b16107-5814e133649862c20ab1c26f"
},
"origin": "115.171.25.28",
"url": "http://httpbin.org/get"
}

发出 POST 请求

1
2
3
4
5
6
7
8
9
10
11
12
13
func testPost() {
path := "http://apis.juhe.cn/simpleWeather/query"
urlValues := url.Values{}
urlValues.Add("key", "087d7d10f700d20e27bb753cd806e40b")
urlValues.Add("city", "北京")
r, err := http.PostForm(path, urlValues)
if err != nil {
log.Fatal(err)
}
defer r.Body.Close()
b, _ := ioutil.ReadAll(r.Body)
fmt.Printf("b: %v\n", string(b))
}

另外一种方式

1
2
3
4
5
6
7
8
9
10
func testPost2() {
urlValues := url.Values{
"name": {"老郭"},
"age": {"80"},
}
reqBody := urlValues.Encode()
resp, _ := http.Post("http://httpbin.org/post", "text/html", strings.NewReader(reqBody))
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}

发送 JSON 数据的 post 请求

1
2
3
4
5
6
7
8
9
func testPostJson() {
data := make(map[string]interface{})
data["site"] = "www.duoke360.com"
data["name"] = "多课网"
bytesData, _ := json.Marshal(data)
resp, _ := http.Post("http://httpbin.org/post", "application/json", bytes.NewReader(bytesData))
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}

使用 Client 自定义请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func testClient() {
client := http.Client{
Timeout: time.Second * 5,
}
url := "http://apis.juhe.cn/simpleWeather/query?key=087d7d10f700d20e27bb753cd806e40b&city=北京"
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
log.Fatal(err)
}
req.Header.Add("referer", "http://apis.juhe.cn/")
res, err2 := client.Do(req)
if err2 != nil {
log.Fatal(err2)
}
defer res.Body.Close()
b, _ := ioutil.ReadAll(res.Body)
fmt.Printf("b: %v\n", string(b))
}

HTTP Server

使用 golang 实现一个 http server 非常简单,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
func testHttpServer() {
// 请求处理函数
f := func(resp http.ResponseWriter, req *http.Request) {
io.WriteString(resp, "hello world")
}
// 响应路径,注意前面要有斜杠 /
http.HandleFunc("/hello", f)
// 设置监听端口,并监听,注意前面要有冒号:
err := http.ListenAndServe(":9999", nil)
if err != nil {
log.Fatal(err)
}
}

在浏览器输入:

1
http://localhost:9999/hello

运行结果:

1
hello world

使用 Handler 实现并发处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type countHandler struct {
mu sync.Mutex // guards n
n int
}

func (h *countHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.mu.Lock()
defer h.mu.Unlock()
h.n++
fmt.Fprintf(w, "count is %d\n", h.n)
}

func testHttpServer2() {
http.Handle("/count", new(countHandler))
log.Fatal(http.ListenAndServe(":8080", nil))
}

在浏览器输入:http://localhost:8080/count,刷新查看结果

1
count is 8

Golang 标准库 template

templates 包定义了数据驱动的文本输出。生成 html 文件的模板在 html/template包下面。模板使用插值语法 {{.var}}格式,也可以使用一些流程控制,例如判断 if else、循环 range还可以使用一些函数,包括内建函数和自定义函数。

第一个模板实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"os"
"text/template"
)

func main() {
//数据
name := "ghz"
//定义模板
muban := "hello, {{.}}"
//解析模板
tmpl, err := template.New("test").Parse(muban)
if err != nil {
panic(err)
}
//执行模板,输出到终端
err = tmpl.Execute(os.Stdout, name)
if err != nil {
panic(err)
}
}

运行结果

1
hello, ghz

也可以是结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"os"
"text/template"
)

type Person struct {
Name string
Age int
}

func main() {
ghz := Person{"ghz", 80}
muban := "hello, {{.Name}}, Your age {{.Age}}"
tmpl, err := template.New("test").Parse(muban)
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, ghz)
if err != nil {
panic(err)
}
}

运行结果

1
hello, ghz, Your age 80

html 模板

定义一个 HTML 文件

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html lang="en">
<head>
<title>Test golang template</title>
</head>
<body>
{{.}}
</body>
</html>

定义一个 HttpServer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"html/template"
"net/http"
)

func tmpl(w http.ResponseWriter, r *http.Request) {
t1, err := template.ParseFiles("test.html")
if err != nil {
panic(err)
}
t1.Execute(w, "hello world")
}

func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/tmpl", tmpl)
server.ListenAndServe()
}

客户端访问

1
http://localhost:8080/tmpl

运行结果

1
hello,ghz

文本和空格

模板引擎在进行替换的时候,是完全按照文本格式进行替换的。除了需要评估和替换的地方,所有的行分隔符、空格等等空白都原样保留。所以,对于要解析的内容,不要随意缩进、随意换行

例如:

1
2
3
4
{{23}} < {{45}}        -> 23 < 45
{{23}} < {{- 45}} -> 23 <45
{{23 -}} < {{45}} -> 23< 45
{{23 -}} < {{- 45}} -> 23<45

去掉后空格 xxxx -}},去掉前空格 {{- xxxx` ## 模板注释 注释方式:`{{/* a comment */}}

注释后的内容不会被引擎进行替换。但需要注意,注释行在替换的时候也会占用行,所以应该去除前缀和后缀空白,否则会多一空行。

1
2
3
{{- /* a comment without prefix/suffix space */}}
{{/* a comment without prefix/suffix space */ -}}
{{- /* a comment without prefix/suffix space */ -}}

管道 pipeline

管道就是一系列命令的链式调用。当然,也可以是一个命令,例如:计算表达式的值 {{.}}{{.Name}},或者是一个函数调用或者方法调用。

可以使用管道符号 |链接多个命令,用法和 unix 下的管道类似:|前面的命令将运算结果(或返回值)传递给后一个命令的最后一个位置。

需要注意的是,并非只有使用了 |才是 pipeline。Go template 中,pipeline 的概念是传递数据,只要能产生数据的,都是 pipeline。

下面是 Pipeline 的几种示例,它们都输出 "output"

1
2
3
4
5
6
{{`"output"`}}
{{printf "%q" "output"}}
{{"output" | printf "%q"}}
{{printf "%q" (print "out" "put")}}
{{"put" | printf "%s%s" "out" | printf "%q"}}
{{"output" | printf "%s" | printf "%q"}}

可以在 HTML 中测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<title>Test golang template</title>
</head>
<body>
{{`"output"`}} <br />
{{printf "%q" "output"}}<br />
{{"output" | printf "%q"}}<br />
{{printf "%q" (print "out" "put")}}<br />
{{"put" | printf "%s%s" "out" | printf "%q"}}<br />
{{"output" | printf "%s" | printf "%q"}}<br />
</body>
</html>

运行结果

1
2
3
4
5
6
"output"
"output"
"output"
"output"
"output"
"output"

变量

变量的语法

1
2
3
4
// 未定义过的变量
$var := pipeline
// 已定义过的变量
$var = pipeline

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<title>Test golang template</title>
</head>
<body>
{{$Name := "tom"}}
{{$Name = "kite"}}
{{$Name}}<br>

{{$len := (len "hello,ghz")}}

{{$len}}
</body>
</html>

运行结果

1
2
kite
9

条件判断

语法

1
2
3
4
{{if pipeline}} T1 {{end}}
{{if pipeline}} T1 {{else}} T0 {{end}}
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}

pipeline 为 false 的情况是各种数据对象的 0 值:数值 0,指针或接口是 nil,数组、slice、map 或 string 则是 len 为 0。

可以使用如下运算符表达式

1
2
3
4
5
6
7
8
9
10
11
12
eq
Returns the boolean truth of arg1 == arg2
ne
Returns the boolean truth of arg1 != arg2
lt
Returns the boolean truth of arg1 < arg2
le
Returns the boolean truth of arg1 <= arg2
gt
Returns the boolean truth of arg1 > arg2
ge
Returns the boolean truth of arg1 >= arg2

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<title>Test golang template</title>
</head>
<body>
{{$Age := 18}} {{if (ge $Age 18)}}
<h3>你已经成年!</h3>
{{else}}
<h3>你还未成年!</h3>
{{end}}
</body>
</html>

运行结果

1
你已经成年!

循环迭代

语法

1
2
{{range pipeline}} T1 {{end}}
{{range pipeline}} T1 {{else}} T0 {{end}}

range 可以迭代 slice、数组、map 或 channel。迭代的时候,会设置”.”为当前正在迭代的元素。对于第一个表达式,当迭代对象的值为 0 值时,则 range 直接跳过,就像 if 一样。对于第二个表达式,则在迭代到 0 值时执行 else 语句。

实例演示

go 代码

1
2
3
4
5
6
7
8
func tmpl(w http.ResponseWriter, r *http.Request) {
t1, err := template.ParseFiles("test.html")
if err != nil {
panic(err)
}
s := []string{"多课网", "golang 教程", "老郭"}
t1.Execute(w, s)
}

html

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html lang="en">
<head>
<title>Test golang template</title>
</head>
<body>
{{range $x := . -}} {{println $x}} {{- end}}
</body>
</html>

运行结果

1
多课网 golang 教程 老郭

with…end

with 用来设置 "."的值,语法如下:

1
2
{{with pipeline}} T1 {{end}}
{{with pipeline}} T1 {{else}} T0 {{end}}

对于第一种格式,当 pipeline 不为 0 值的时候,点”.”设置为 pipeline 运算的值,否则跳过。对于第二种格式,当 pipeline 为 0 值时,执行 else 语句块,否则”.”设置为 pipeline 运算的值,并执行 T1。

实例演示

1
{{with "多课网-golang-老郭"}}{{println .}}{{end}}

运行结果

1
多课网-golang-老郭

内置函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
and
返回第
一个空参数或最后一个参数返回其参数的布尔值 AND ,即
“and x y”表现为“if x then y else x”。
评估所有参数。
call
返回调用第一个参数的结果,该参数
必须是一个函数,其余参数作为参数。
因此,“call .XY 1 2”在 Go 表示法中是 dot.XY(1, 2),其中
Y 是函数值字段、映射条目等。
第一个参数必须是
产生函数类型值的评估结果(不同于
预定义的函数,如打印)。该函数必须
返回一个或两个结果值,其中第二个
是类型错误。如果参数与函数不匹配
或返回的错误值为非零,则执行停止。
html
返回等效
于其参数文本表示的转义 HTML 。此功能
在 html/template 中不可用,但有一些例外。
index
返回通过
以下参数对其第一个参数进行索引的结果。因此,在 Go 语法中,“索引 x 1 2 3”是
x[1][2][3]。每个索引项必须是映射、切片或数组。
slice
slice 返回其第一个参数被
其余参数切片的结果。因此,"slice x 1 2" 在 Go 语法中是 x[1:2],
"slice x" 是 x[:],"slice x 1" 是 x[1:],
是 x[1:2:3]。第一个参数必须是字符串、切片或数组。
js
返回转义的 JavaScript 等效
于其参数的文本表示。
len
返回其参数的整数长度。
not
返回其单个参数的布尔否定。
or
通过返回第
一个非空参数或最后一个参数来返回其参数的布尔 OR ,即
“or x y”表现为“if x then x else y”。
评估所有参数。
print
fmt.Sprint
的别名
printf fmt.Sprintf
的别名
println fmt.Sprint的别名
urlquery
以适合嵌入 URL 查询的形式
返回其参数的文本表示的转义值。
此功能在 html/template 中不可用,但有一些
例外。

嵌套 template:define 和 template

define 可以直接在待解析内容中定义一个模板,这个模板会加入到 common 结构组中,并关联到关联名称上。

1
2
3
{{template "name"}}
{{template "name" pipeline}}
{{define "name"}}

实例演示

假设我们有一个 header.html、footer.html 和 index.html,index.html 包含 header.html 和 footer.html

header.html

1
2
3
4
5
{{define "header"}}
<head>
<title>{{.Title}}</title>
</head>
{{end}}

footer.html

1
2
3
4
{{define "footer"}}
这是footer

{{end}}

index.html

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
{{template "header" .}}
<h1>首页...</h1>
{{template "footer"}}
</body>
</html>

go code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"html/template"
"net/http"
)

func tmpl(w http.ResponseWriter, r *http.Request) {
t1, err := template.ParseFiles("templates/index.html", "templates/header.html", "templates/footer.html")

if err != nil {
panic(err)
}
t1.Execute(w, nil)
}

func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/tmpl", tmpl)
server.ListenAndServe()
}

运行结果

1
2
3
这是header
首页...
这是footer

HttpRouter

HttpRouter 是一种轻量级高性能的,golang HTTP 请求路由器。

与 Golang 默认路由相比,此路由器支持路由模式中的变量 并匹配请求方法。它还可以更好地扩展。

该路由器针对高性能和小内存占用进行了优化。即使有很长的路径和大量的路线,它也能很好地扩展。压缩动态特里(基数树)结构用于有效匹配。

gin 框架就是以 httprouter 为基础开发的,下面我们来学习一下 httprouter。

下载安装

1
go get github.com/julienschmidt/httprouter

第一个路由实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"fmt"
"log"
"net/http"

"github.com/julienschmidt/httprouter"
)

func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "Welcome!\n")
}

func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
}

func main() {
router := httprouter.New()
router.GET("/", Index)
router.GET("/hello/:name", Hello)

log.Fatal(http.ListenAndServe(":8080", router))
}

在浏览器输入:http://localhost:8080/,输出:Welcome!

在浏览器输入:http://localhost:8080/hello/ghz,输出:hello, ghz!

http router 请求方法

httprouter 为所有的 HTTP Method 提供了快捷的使用方式,只需要调用对应的方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func (r *Router) GET(path string, handle Handle) {
r.Handle("GET", path, handle)
}

func (r *Router) HEAD(path string, handle Handle) {
r.Handle("HEAD", path, handle)
}

func (r *Router) OPTIONS(path string, handle Handle) {
r.Handle("OPTIONS", path, handle)
}

func (r *Router) POST(path string, handle Handle) {
r.Handle("POST", path, handle)
}

func (r *Router) PUT(path string, handle Handle) {
r.Handle("PUT", path, handle)
}

func (r *Router) PATCH(path string, handle Handle) {
r.Handle("PATCH", path, handle)
}

func (r *Router) DELETE(path string, handle Handle) {
r.Handle("DELETE", path, handle)
}

restful 风格路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package main

import (
"fmt"
"log"
"net/http"

"github.com/julienschmidt/httprouter"
)

func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "Welcome!\n")
}

func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
}

func getuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
uid := ps.ByName("uid")
fmt.Fprintf(w, "you are get user %s", uid)
}

func modifyuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
uid := ps.ByName("uid")
fmt.Fprintf(w, "you are modify user %s", uid)
}

func deleteuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
uid := ps.ByName("uid")
fmt.Fprintf(w, "you are delete user %s", uid)
}

func adduser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
// uid := r.FormValue("uid")
uid := ps.ByName("uid")
fmt.Fprintf(w, "you are add user %s", uid)
}

func main() {
router := httprouter.New()
router.GET("/", Index)
router.GET("/hello/:name", Hello)

router.GET("/user/:uid", getuser)
router.POST("/adduser/:uid", adduser)
router.DELETE("/deluser/:uid", deleteuser)
router.PUT("/moduser/:uid", modifyuser)

log.Fatal(http.ListenAndServe(":8080", router))
}

Gin 简介

Gin 官网

1
https://gin-gonic.com/

gin 简介

Gin 是一个 golang 的微框架,基于 httprouter,封装比较优雅,API 友好,源码注释比较明确,具有快速灵活,容错方便等特点。

gin 特征

速度快

基于基数树的路由,内存占用小。没有反射。可预测的 API 性能。

中间件支持

传入的 HTTP 请求可以由中间件链和最终操作处理。例如:Logger、Authorization、GZIP 最后在 DB 中发布一条消息。

Crash-free

Gin 可以捕获 HTTP 请求期间发生的 panic 并恢复它。这样,你的服务器将始终可用。

JSON 验证

Gin 可以解析和验证请求的 JSON - 例如,检查所需值的存在。

路由分组

更好地组织您的路线。需要授权与不需要授权,不同的 API 版本……此外,组可以无限嵌套,而不会降低性能。

错误管理

Gin 提供了一种方便的方法来收集 HTTP 请求期间发生的所有错误。最终,中间件可以将它们写入日志文件、数据库并通过网络发送它们。

内置渲染

Gin 为 JSON、XML 和 HTML 渲染提供了一个易于使用的 API。

可扩展

创建一个新的中间件非常简单,只需查看示例代码即可。

第一个 gin

安装 gin

1
go get -u github.com/gin-gonic/gin

导入项目

1
import "github.com/gin-gonic/gin"

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "github.com/gin-gonic/gin"

func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080
}

运行

浏览器输入:http://localhost:8080/ping

1
{"message":"pong"}

Gin 实现用户登录

实现步骤

创建一个文件 tempates

在项目跟目录下面创建给文件夹 tempates,用来保存静态文件

创建一个登录 html 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Login</title>
</head>
<body>
<form action="/login" method="post">
Username: <input type="text" name="username" /><br />
Password: <input type="password" name="password" /><br />
<input type="submit" value="Login" />
</form>
</body>
</html>

创建一个欢迎 html 页面

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Welcome</title>
</head>
<body>
Welcome, {{.username}}
</body>
</html>

使用 Gin 处理逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import "github.com/gin-gonic/gin"

func MyHandler(c *gin.Context) {
c.JSON(200, gin.H{
"hello": "hello world",
})
}

func Login(c *gin.Context) {
c.HTML(200, "login.html", nil)
}

func DoLogin(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")

c.HTML(200, "welcome.html", gin.H{
"username": username,
"password": password,
})

}

func main() {
e := gin.Default()
e.LoadHTMLGlob("templates/*")

e.GET("/login", Login)
e.POST("/login", DoLogin)
e.Run()
}

Gin 请求参数

Get 请求参数

使用 c.Query("key")、或者 c.DefaultQuery("key")方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "github.com/gin-gonic/gin"

func TestQueryString(c *gin.Context) {
username := c.Query("username")
site := c.DefaultQuery("site", "www.xiaobaibk.com")

c.String(200, "username:%s, site:%s", username, site)
}

func main() {

e := gin.Default()
// url : http://localhost:8080/testQueryString?username=xiaobai&site=baidu.com
e.GET("/testQueryString", TestQueryString)

e.Run()

}

运行结果

1
username:xiaobai, site:baidu.com

Post 参数

使用 c.PostForm("key")、或者 c.DefaultQuery("key")方法

1
2
3
4
5
6
7
8
9
func DoLogin(c *gin.Context) {
username := c.PostForm("username")
password := c.DefaultPostForm("password", "123")

c.HTML(200, "welcome.html", gin.H{
"username": username,
"password": password,
})
}

使用 Postman 或者 Post 表单测试

路劲参数(restful 风格)

使用 c.Param("key")方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "github.com/gin-gonic/gin"

func TestPathParam(c *gin.Context) {
s := c.Param("username")
c.String(200, "Username:%s", s)
// 输出:Username:ghz
}

func main() {
e := gin.Default()
// http://localhost:8080/hello/ghz
e.GET("/hello/:username", TestPathParam)
e.Run()
}

既有 Get 也有 Post

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "github.com/gin-gonic/gin"

func TestGetAndPost(c *gin.Context) {
page := c.DefaultQuery("page", "0")
key := c.PostForm("key")
c.String(200, "Page:%s, Key:%s", page, key)
}

func main() {
e := gin.Default()
// http://localhost:8080/query?page=1
e.POST("/query", TestGetAndPost)
e.Run()
}

使用 Postman 或者 Post 表单测试

Gin 表单处理

创建一个 HTML 表单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>多课网,听老郭讲golang</title>
</head>
<body>
<h1>用户注册页面</h1>

<form action="/register" method="post">
用户名: <input type="text" name="username" /><br />
密码: <input type="password" name="password" /><br />
爱好:
<input type="checkbox" name="hobby" value="swiming" />游泳
<input type="checkbox" name="hobby" value="basketball" />篮球
<br />
性别:<input type="radio" name="gender" id="1" value="m" />
<input type="radio" name="gender" id="2" value="f" />
<br />
城市:
<select name="city">
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
</select>
<br />
<input type="submit" value="注册" />
</form>
</body>
</html>

Go code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import "github.com/gin-gonic/gin"

func Regsiter(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
hobby := c.PostFormArray("hobby")
gender := c.PostForm("gender")
city := c.PostForm("city")

c.String(200, "Username:%s, Password:%s, hobby:%s, gender:%s, city:%s", username, password, hobby, gender, city)

}

func GoRegister(c *gin.Context) {
c.HTML(200, "register.html", nil)
}

func main() {
e := gin.Default()
e.LoadHTMLGlob("templates/*")
e.POST("/register", Regsiter)
e.GET("/register", GoRegister)
e.Run()
}

运行结果

1
Username:ghz, Password:123, hobby:[swiming basketball], gender:m, city:beijing

Gin 数据绑定

绑定 Form 表单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
"github.com/gin-gonic/gin"
)

type User struct {
Username string `form:"username"`
Password string `form:"password"`
Hobby []string `form:"hobby"`
Gender string `form:"gender"`
City string `form:"city"`
}

func Regsiter(c *gin.Context) {
var user User
c.ShouldBind(&user)
c.String(200, "User:%s", user)
}

func GoRegister(c *gin.Context) {
c.HTML(200, "register.html", nil)
}

func main() {
e := gin.Default()
e.LoadHTMLGlob("templates/*")
e.POST("/register", Regsiter)
e.GET("/register", GoRegister)
e.Run()
}

绑定查询参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"log"

"github.com/gin-gonic/gin"
)

type User struct {
Username string `form:"username"`
Password string `form:"password"`
}

func TestGetBind(c *gin.Context) {
var user User
err := c.ShouldBind(&user)
if err != nil {
log.Fatal(err)
}
c.String(200, "User:%s", user)
}

func main() {
e := gin.Default()
// http://localhost:8080/testGetBind?username=ghz&password=123
e.GET("/testGetBind", TestGetBind)
e.Run()
}

路径请求参数绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"log"

"github.com/gin-gonic/gin"
)

type User struct {
Username string `uri:"username"`
Password string `uri:"password"`
}

func TestGetBind(c *gin.Context) {
var user User
err := c.ShouldBindUri(&user)
if err != nil {
log.Fatal(err)
}
c.String(200, "User:%s", user)
}

func main() {
e := gin.Default()
// http://localhost:8080/testGetBind/ghz/123
e.GET("/testGetBind/:username/:password", TestGetBind)
e.Run()
}

注意:结构体和绑定方法的变化

Gin 访问静态文件集成 BootStrap 框架

下载 BootStrap

下载地址:https://getbootstrap.com/

添加 bootstrap css 和 js 文件

创建一个 assets 文件夹,将 css 和 js 文件添加到该文件夹

创建 html 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/assets/css/bootstrap.min.css" />

<title>Login</title>
</head>
<body>
<div class="container">
<form>
<div class="mb-3">
<label for="exampleInputEmail1" class="form-label"
>Email address</label
>
<input
type="email"
class="form-control"
id="exampleInputEmail1"
aria-describedby="emailHelp"
/>
<div id="emailHelp" class="form-text">
We'll never share your email with anyone else.
</div>
</div>
<div class="mb-3">
<label for="exampleInputPassword1" class="form-label">Password</label>
<input
type="password"
class="form-control"
id="exampleInputPassword1"
/>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="exampleCheck1" />
<label class="form-check-label" for="exampleCheck1"
>Check me out</label
>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</body>
</html>

Go Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"net/http"

"github.com/gin-gonic/gin"
)

func Login(c *gin.Context) {
c.HTML(200, "login.html", nil)
}

func main() {
e := gin.Default()
e.LoadHTMLGlob("templates/*")

e.Static("/assets", "./assets")
e.StaticFS("/croot", http.Dir("c:/"))
e.StaticFile("/favicon.ico", "./assets/favicon.ico")

e.GET("/login", Login)
e.POST("/login", DoLogin)
e.Run()
}

Gin 使用中间件

中间件听起来非常高大上的名字,实际非常简单,就是在请求中间起到拦截作用的处理函数。

Gin 默认中间件

如果你使用 Gin.Default()实例化 gin 引擎,默认有两个中间件,LoggerRecovery,分别用来处理日志和处理错误。如果使用 gin.New()需要重新添加。

1
2
3
4
5
6
7
8
9
10
// 新建一个没有任何默认中间件的路由
r := gin.New()

// 全局中间件
// Logger 中间件将日志写入 gin.DefaultWriter,即使你将 GIN_MODE 设置为 release。
// By default gin.DefaultWriter = os.Stdout
r.Use(gin.Logger())

// Recovery 中间件会 recover 任何 panic。如果有 panic 的话,会写入 500。
r.Use(gin.Recovery())

自定义中间件

  1. 自定义中间件非常简单,定义一个符合下面格式的处理函数
1
type HandlerFunc func(*Context)
  1. 使用Use方法调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import (
"fmt"

"github.com/gin-gonic/gin"
)

func TestMW(c *gin.Context) {
c.String(200, "hello,%s", "ghz")
}

func MyMiddleware1(c *gin.Context) {
fmt.Println("我的第一个中间件")
}

func MyMiddleware2(c *gin.Context) {
fmt.Println("我的第二个中间件")
}

func main() {

/* func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
} */
// e := gin.Default()
// e := gin.New()

e := gin.Default()

e.Use(MyMiddleware1, MyMiddleware2)

e.GET("testmw", TestMW)

e.Run()

}

使用 Gin BasicAuth 中间件

Gin 提供了 BasicAuth 中间件,用来对网站资源的访问保护。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package main

import (
"fmt"
"net/http"

"github.com/gin-gonic/gin"
)

// 模拟一些私人数据
var secrets = gin.H{
"foo": gin.H{"email": "[email protected]", "phone": "123433"},
"austin": gin.H{"email": "[email protected]", "phone": "666"},
"lena": gin.H{"email": "[email protected]", "phone": "523443"},
}

func main() {
r := gin.Default()

// 路由组使用 gin.BasicAuth() 中间件
// gin.Accounts 是 map[string]string 的一种快捷方式
authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
"foo": "bar",
"austin": "1234",
"lena": "hello2",
"manu": "4321",
}))

// /admin/secrets 端点
// 触发 "localhost:8080/admin/secrets
authorized.GET("/secrets", func(c *gin.Context) {
// 获取用户,它是由 BasicAuth 中间件设置的
user := c.MustGet(gin.AuthUserKey).(string)
fmt.Println(user)
if secret, ok := secrets[user]; ok {
c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
} else {
c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
}
})

// 监听并在 0.0.0.0:8080 上启动服务
r.Run(":8080")
}

测试

在浏览器输入 localhost:8080/admin/secrets时,会弹出一个对话框,要求输入正确的用户名和密码,才能访问资源。

Gin cookie 的使用

cookie是服务器向客户端写的一些数据,可以实现像自动登录等功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "github.com/gin-gonic/gin"

func Handler(c *gin.Context) {
// 获得cookie
s, err := c.Cookie("username")
if err != nil {
s = "ghz"
// 设置cookie
c.SetCookie("username", s, 60*60, "/", "localhost", false, true)
}

c.String(200, "测试cookie")
}

func main() {
e := gin.Default()
e.GET("/test", Handler)
e.Run()
}

基于安全的考虑,需要给 cookie 加上 SecureHttpOnly属性,HttpOnly比较好理解,设置 HttpOnly=true的 cookie 不能被 js 获取到,无法用 document.cookie打出 cookie 的内容。

Secure属性是说如果一个 cookie 被设置了 Secure=true,那么这个 cookie 只能用 https协议发送给服务器,用 http协议是不发送的。

Gin 使用 Session

因为 http 是无状态、短连接,如何保存客户端和服务器直接的会话状态呢?可以使用 session。

使用 gin session 中间件

gin 本身没有对 session 的支持,可以使用第三方中间件。

1
2
go get github.com/gin-contrib/sessions
import "github.com/gin-contrib/sessions"

该中间件提供了很多后端支持:

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
)

func main() {
r := gin.Default()
store := cookie.NewStore([]byte("secret"))
r.Use(sessions.Sessions("mysession", store))

r.GET("/hello", func(c *gin.Context) {
session := sessions.Default(c)

if session.Get("hello") != "world" {
session.Set("hello", "world")
session.Save()
}

c.JSON(200, gin.H{"hello": session.Get("hello")})
})
r.Run(":8000")
}

更多内容请参考:https://pkg.go.dev/github.com/gin-contrib/sessions#section-readme

Gin 实现 restful 风格的 CRUD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package main

import (
"fmt"
"strconv"
"github.com/gin-gonic/gin"
)

type User struct {
UId int `json:"uid"`
Name string `json:"name"`
Age int `json:"age"`
}

var users = make([]User, 3)

func init() {
u1 := User{1, "tom", 20}
u2 := User{2, "kite", 30}
u3 := User{3, "rose", 40}
users = append(users, u1)
users = append(users, u2)
users = append(users, u3)
fmt.Println(users)
}

func find(uid int) (*User, int) {
for i, u := range users {
if u.UId == uid {
return &u, i
}
}
return nil, -1
}

func AddUser(c *gin.Context) {
u4 := User{4, "Joe", 50}
users = append(users, u4)
c.JSON(200, users)
}
func DelUser(c *gin.Context) {
uid := c.Param("uid")
id, _ := strconv.Atoi(uid)
_, i := find(id)
users = append(users[:i], users[i+1:]...)
c.JSON(200, users)
}

func UpdateUser(c *gin.Context) {
uid := c.Param("uid")
id, _ := strconv.Atoi(uid)
u, _ := find(id)
u.Name = "修改的Name"
c.JSON(200, u)
}

func FindUser(c *gin.Context) {
uid := c.Param("uid")
id, _ := strconv.Atoi(uid)
u, _ := find(id)
c.JSON(200, u)
}
func main() {

e := gin.Default()
e.GET("/user/:uid", FindUser)
e.PUT("/user/:uid", UpdateUser)
e.DELETE("/user/:uid", DelUser)
e.POST("/user/", AddUser)
e.Run()

}

Gin 实现路由分组

假如你的网站有多个模块:博客、教程、视频、问答,每个模块又有多个路由,这样就可以进行路由分组,使用的方法是 router.Group(“分组名称”)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import "github.com/gin-gonic/gin"

func F1(c *gin.Context) {}
func F2(c *gin.Context) {}
func F3(c *gin.Context) {}
func F4(c *gin.Context) {}
func F5(c *gin.Context) {}
func F6(c *gin.Context) {}

func main() {
router := gin.Default()

// 博客
// 访问:http://localhost:8080/blog/list
v1 := router.Group("/blog")
{
v1.POST("/list", F1)
v1.POST("/post", F2)
v1.POST("/add", F3)
}

// 视频
// 访问:http://localhost:8080/video/list
v2 := router.Group("/video")
{
v2.POST("/list", F4)
v2.POST("/post", F5)
v2.POST("/add", F6)
}

router.Run(":8080")
}

Gin 输出渲染

Gin 支持很多种输出渲染,可以是简单的字符串、JSON、XML、HTML、ProtoBuf。使用的方法如下:

1
2
3
4
5
c.JSON(200, nil)
c.XML(200, nil)
c.HTML(200, "", nil)
c.String(200, "")
c.ProtoBuf(200, nil)

这里像字符串、JSON、HTML 我们都用过,这里我们再总结一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import "github.com/gin-gonic/gin"

func TestJson(c *gin.Context) {
c.JSON(200, gin.H{
"name": "多课网",
"site": "www.duoke360.com",
})
}

func TestXML(c *gin.Context) {
c.XML(200, gin.H{
"name": "多课网",
"site": "www.duoke360.com",
})
}

func TestHtml(c *gin.Context) {
c.HTML(200, "login.html", nil)
}

func TestString(c *gin.Context) {
c.String(200, "多课网,老郭讲golang")
}

func main() {
e := gin.Default()

e.GET("/test_json", TestJson)
e.GET("/test_xml", TestXML)
e.LoadHTMLGlob("templates/*")
e.GET("/test_html", TestHtml)
e.GET("/test_string", TestString)

e.Run()
}

Gin 实现文件上传

创建 html 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
请选择上传文件:<input type="file" name="file" id="" /><br />
<input type="submit" value="上传" />
</form>
</body>
</html>

Go Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import (
"fmt"
"log"
"net/http"

"github.com/gin-gonic/gin"
)

func Upload(c *gin.Context) {
// 单文件
file, _ := c.FormFile("file")
log.Println(file.Filename)

// 上传文件到项目根目录,使用原文件名
c.SaveUploadedFile(file, file.Filename)

c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
}

func GoUpload(c *gin.Context) {
c.HTML(200, "upload.html", nil)
}

func main() {
router := gin.Default()
// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.LoadHTMLGlob("templates/*")
router.GET("/upload", GoUpload)
router.POST("/upload", Upload)
router.Run(":8080")
}

Gin 博客项目-项目架构

规范包及目录

1
2
3
4
5
6
7
8
9
10
11
|-controller

|-dao

|-router

|-model

|-assets

|-templates

初始化项目

1
go mod init blog

Gin 博客项目-集成 gorm

下载包

1
2
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql

创建模型

1
2
3
4
5
6
7
8
9
package models

import "github.com/jinzhu/gorm"

type User struct {
gorm.Model
Username string `json:"username"`
Password string `json:"passowrd"`
}

创建 Dao

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package dao

import (
"log"
"pro04/models"

"gorm.io/driver/mysql"
"gorm.io/gorm"
)

type Manager interface {
AddUser(user *models.User)
}

type manager struct {
db *gorm.DB
}

var Mgr Manager

func init() {
dsn := "root:123456@tcp(127.0.0.1:3306)/golang_db?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("Failed to init db:", err)
}
Mgr = &manager{db: db}
db.AutoMigrate(&models.User{})
}

func (mgr *manager) AddUser(user *models.User) {
mgr.db.Create(user)
}

测试

1
2
3
4
5
6
user := models.User{
Username: username,
Password: password,
}

dao.Mgr.AddUser(&user)

Gin 博客项目-集成 Bootstrap 创建用户表单

下载 bootstrap

1
https://getbootstrap.com/

添加 js 和 css 到 assets 目录下面

创建添加用户 html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/assets/css/bootstrap.min.css">

<title>用户管理</title>
</head>
<body>
<div class="container">
<div class="row mt-3 justify-content-center">
<div class="col-md-4">
<form method="post" action="/users">
<div class="mb-3">
<label for="exampleInputEmail1" class="form-label">用户名称</label>
<input type="username" class="form-control"
id="exampleInputEmail1"
aria-describedby="emailHelp">
<div id="emailHelp" class="form-text">We'll never
share your
email with anyone else.</div>
</div>
<div class="mb-3">
<label for="exampleInputPassword1"
class="form-label">密码</label>
<input type="password" class="form-control"
name="password"
id="exampleInputPassword1">
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input"
id="exampleCheck1">
<label class="form-check-label" for="exampleCheck1">Check
me
out</label>
</div>

<textarea name="test" id="test" cols="30" rows="10"></textarea>
<button type="submit" class="btn btn-primary">添加</button>
</form>

<hr>
<ul class="list-group list-group-flush">
<li class="list-group-item">tom 123</li>
<li class="list-group-item">kite 456</li>
</ul>

</div>
</div>
</div>
</body>
</html>

Gin 博客项目-实现控制器和路由

控制器 controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package controller

import (
"pro04/dao"
"pro04/models"
"github.com/gin-gonic/gin"
)

func AddUser(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")

user := models.User{
Username: username,
Password: password,
}

dao.Mgr.AddUser(&user)
}

func ListUser(c *gin.Context) {
c.HTML(200, "user.html", nil)
}

路由 router

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package routers

import (
"pro04/controller"

"github.com/gin-gonic/gin"
)

func Start() {
e := gin.Default()
e.LoadHTMLGlob("templates/*")
e.Static("/assets", "./assets")

e.GET("/users", controller.ListUser)
e.POST("/users", controller.AddUser)
e.Run()
}

Gin 博客项目-设计静态页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{{define "header"}}
<header class="p-3 bg-dark text-white">
<div class="container">
<div
class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start"
>
<a class="navbar-brand" href="#">多课网</a>

<ul
class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0"
>
<li><a href="#" class="nav-link px-2 text-secondary">首页</a></li>
<li><a href="#" class="nav-link px-2 text-white">博客</a></li>
</ul>

<form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3">
<input
type="search"
class="form-control form-control-dark"
placeholder="Search..."
aria-label="Search"
/>
</form>

<div class="text-end">
<button type="button" class="btn btn-outline-light me-2">登录</button>
<a type="button" class="btn btn-warning" href="/register">注册</a>
</div>
</div>
</div>
</header>
{{end}}

注册 register.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<link rel="stylesheet" href="/assets/css/bootstrap.min.css" />

<title>用户注册</title>
</head>
<body>
<div class="container">
{{template "header"}}
<div class="row justify-content-center mt-3">
<div class="col-md-4">
<form>
<div class="mb-3">
<label for="exampleInputEmail1" class="form-label"
>用户名称</label
>
<input
type="email"
class="form-control"
id="exampleInputEmail1"
aria-describedby="emailHelp"
/>
</div>
<div class="mb-3">
<label for="exampleInputPassword1" class="form-label"
>用户密码</label
>
<input
type="password"
class="form-control"
id="exampleInputPassword1"
/>
</div>
<div class="mb-3 form-check">
<input
type="checkbox"
class="form-check-input"
id="exampleCheck1"
/>
<label class="form-check-label" for="exampleCheck1">记住我</label>
</div>
<button type="submit" class="btn btn-primary">添加</button>
</form>
</div>
</div>
</div>
</body>
</html>

登录 login.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<link rel="stylesheet" href="/assets/css/bootstrap.min.css" />

<title>用户注册</title>
</head>
<body>
<div class="container">
{{template "header"}}

<div class="row justify-content-center mt-3">
<div class="col-md-4">
<form>
<div class="mb-3">
<label for="exampleInputEmail1" class="form-label"
>用户名称</label
>
<input
type="email"
class="form-control"
id="exampleInputEmail1"
aria-describedby="emailHelp"
/>
</div>
<div class="mb-3">
<label for="exampleInputPassword1" class="form-label"
>用户密码</label
>
<input
type="password"
class="form-control"
id="exampleInputPassword1"
/>
</div>
<div class="mb-3 form-check">
<input
type="checkbox"
class="form-check-input"
id="exampleCheck1"
/>
<label class="form-check-label" for="exampleCheck1">记住我</label>
</div>
<button type="submit" class="btn btn-primary">添加</button>
</form>
</div>
</div>
</div>
</body>
</html>

首页 index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<link rel="stylesheet" href="/assets/css/bootstrap.min.css" />

<title>Document</title>
</head>
<body>
<div class="container">
{{template "header"}}

<div class="row justify-content-center mt-3">
<div class="col-md-4">
<p>首页...</p>
</div>
</div>
</div>
</body>
</html>

Gin 博客项目-用户注册

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func RegisterUser(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")

user := model.User{
Username: username,
Password: password,
}

dao.Mgr.AddUser(&user)

c.Redirect(200, "/")

}

func GoRegister(c *gin.Context) {
c.HTML(200, "register.html", nil)
}

router

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package router

import (
"blog/controller"

"github.com/gin-gonic/gin"
)

func Start() {
e := gin.Default()
e.LoadHTMLGlob("templates/*")
e.Static("/assets", "./assets")
e.GET("/", controller.Index)
e.POST("/register", controller.RegisterUser)
e.GET("/register", controller.GoRegister)
e.Run()
}

Gin 博客项目-用户登录

dao

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package dao

import (
"blog/model"
"log"

"gorm.io/driver/mysql"
"gorm.io/gorm"
)

type Manager interface {
AddUser(user *model.User)
Login(username string) model.User
}

type manager struct {
db *gorm.DB
}

var Mgr Manager

func init() {
dsn := "root:123456@tcp(127.0.0.1:3306)/golang_db?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("Failed to init db:", err)
}
Mgr = &manager{db: db}
db.AutoMigrate(&model.User{})
}

func (mgr *manager) AddUser(user *model.User) {
mgr.db.Create(user)
}

func (mgr *manager) Login(username string) model.User {
var user model.User
mgr.db.Where("username=?", username).First(&user)
return user
}

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package controller

import (
"blog/dao"
"blog/model"
"fmt"

"github.com/gin-gonic/gin"
)

func RegisterUser(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")

user := model.User{
Username: username,
Password: password,
}

dao.Mgr.AddUser(&user)

c.Redirect(200, "/")

}

func GoRegister(c *gin.Context) {
c.HTML(200, "register.html", nil)
}

func GoLogin(c *gin.Context) {
c.HTML(200, "login.html", nil)
}

func Login(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
fmt.Println(username)
u := dao.Mgr.Login(username)

if u.Username == "" {
c.HTML(200, "login.html", "用户名不存在!")
fmt.Println("用户名不存在!")
} else {
if u.Password != password {
fmt.Println("密码错误")
c.HTML(200, "login.html", "密码错误")
} else {
fmt.Println("登录成功")
c.Redirect(301, "/")
}
}

}

func Index(c *gin.Context) {
c.HTML(200, "index.html", nil)
}

func ListUser(c *gin.Context) {
c.HTML(200, "index.html", nil)
}

router

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package router

import (
"blog/controller"

"github.com/gin-gonic/gin"
)

func Start() {
e := gin.Default()
e.LoadHTMLGlob("templates/*")
e.Static("/assets", "./assets")

e.GET("/login", controller.GoLogin)
e.POST("/login", controller.Login)

e.GET("/", controller.Index)
e.POST("/register", controller.RegisterUser)
e.GET("/register", controller.GoRegister)
e.Run()
}

login.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<link rel="stylesheet" href="/assets/css/bootstrap.min.css" />

<title>用户注册</title>
</head>
<body>
<div class="container">
{{template "header"}}

<div class="row justify-content-center mt-3">
<div class="col-md-4">
<p style="background-color: red;">{{.}}</p>
<form method="post" action="/login">
<div class="mb-3">
<label for="exampleInputEmail1" class="form-label"
>用户名称</label
>
<input
type="text"
name="username"
class="form-control"
id="exampleInputEmail1"
aria-describedby="emailHelp"
/>
</div>
<div class="mb-3">
<label for="exampleInputPassword1" class="form-label"
>用户密码</label
>
<input
type="password"
name="password"
class="form-control"
id="exampleInputPassword1"
/>
</div>

<button type="submit" class="btn btn-primary">登录</button>
</form>
</div>
</div>
</div>
</body>
</html>

Gin 博客项目-集成 markdown 编辑器

下载 mdeditor

1
https://pandao.github.io/editor.md/

集成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<link rel="stylesheet" href="/assets/css/bootstrap.min.css" />
<link rel="stylesheet" href="/assets/editormd/css/editormd.css" />

<title>Document</title>
</head>
<body>
<div class="container">
{{template "header"}}
<div id="test-editormd">
<textarea style="display:none;"></textarea>
</div>
</div>

var testEditor; $(function() { testEditor = editormd("test-editormd", {
width : "100%", height : 640, syncScrolling : "single", path :
"assets/editormd/lib/" }); });
</body>
</html>

Gin 博客项目-创建博客模型和 DAO

创建模型

1
2
3
4
5
6
type Post struct {
gorm.Model
Title string
Content string `gorm:"type:text"`
Tag string
}

创建 DAO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package dao

import (
"blog/model"
"log"

"gorm.io/driver/mysql"
"gorm.io/gorm"
)

type Manager interface {
AddUser(user *model.User)
Login(username string) model.User

// 博客操作
AddPost(post *model.Post)
GetAllPost() []model.Post
getPost(pid int) model.Post
}

type manager struct {
db *gorm.DB
}

var Mgr Manager

func init() {
dsn := "root:123456@tcp(127.0.0.1:3306)/golang_db?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("Failed to init db:", err)
}
Mgr = &manager{db: db}
db.AutoMigrate(&model.User{})
db.AutoMigrate(&model.Post{})
}

func (mgr *manager) AddUser(user *model.User) {
mgr.db.Create(user)
}

func (mgr *manager) Login(username string) model.User {
var user model.User
mgr.db.Where("username=?", username).First(&user)
return user
}

// 博客操作
func (mgr *manager) AddPost(post *model.Post) {
mgr.db.Create(post)
}
func (mgr *manager) GetAllPost() []model.Post {
var posts = make([]model.Post, 10)
mgr.db.Find(&posts)
return posts
}
func (mgr *manager) getPost(pid int) model.Post {
var post model.Post
mgr.db.First(&post, pid)
return post
}

Gin 博客项目-创建博客控制器和路由

创建控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package controller

import (
"blog/dao"
"blog/model"
"fmt"

"github.com/gin-gonic/gin"
)

func RegisterUser(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")

user := model.User{
Username: username,
Password: password,
}

dao.Mgr.AddUser(&user)

c.Redirect(200, "/")

}

func GoRegister(c *gin.Context) {
c.HTML(200, "register.html", nil)
}

func GoLogin(c *gin.Context) {
c.HTML(200, "login.html", nil)
}

func Login(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
fmt.Println(username)
u := dao.Mgr.Login(username)

if u.Username == "" {
c.HTML(200, "login.html", "用户名不存在!")
fmt.Println("用户名不存在!")
} else {
if u.Password != password {
fmt.Println("密码错误")
c.HTML(200, "login.html", "密码错误")
} else {
fmt.Println("登录成功")
c.Redirect(301, "/")
}
}
}

func Index(c *gin.Context) {
c.HTML(200, "index.html", nil)
}

func ListUser(c *gin.Context) {
c.HTML(200, "index.html", nil)
}

func GetPostIndex(c *gin.Context) {
posts := dao.Mgr.GetAllPost()
c.HTML(200, "postIndex.html", posts)
}

func AddPost(c *gin.Context) {
title := c.PostForm("title")
tag := c.PostForm("tag")
content := c.PostForm("content")

post := model.Post{
Title: title,
Tag: tag,
Content: content,
}

dao.Mgr.AddPost(&post)

c.Redirect(302, "/post_index")
}

func GoAddPost(c *gin.Context) {
c.HTML(200, "post.html", nil)
}

创建路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package router

import (
"blog/controller"

"github.com/gin-gonic/gin"
)

func Start() {
e := gin.Default()
e.LoadHTMLGlob("templates/*")
e.Static("/assets", "./assets")
e.GET("/login", controller.GoLogin)
e.POST("/login", controller.Login)

e.GET("/", controller.Index)
e.POST("/register", controller.RegisterUser)
e.GET("/register", controller.GoRegister)
e.GET("/post_index", controller.GetPostIndex)
e.POST("/post", controller.AddPost)
e.GET("/post", controller.GoAddPost)
e.Run()
}

Gin 博客项目-添加博客

添加博客页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<link rel="stylesheet" href="/assets/css/bootstrap.min.css">
<link rel="stylesheet" href="/assets/editormd/css/editormd.css">


<title>添加博客</title>
</head>
<body>

<div class="container">
{{template "header"}}


<form action="/post" method="post">
<div class="row">

<div class="col-md-8">
<div id="test-editormd">
<textarea style="display:none;" name="content"></textarea>
</div>
</div>

<div class="col-md-4 mt-3">
<label for="title" class="form-label">请输入标题</label>
<input type="text" name="title" class="form-control" id="title"><br>

<label for="tag" class="form-label">请输入标签</label>
<input type="text" name="tag" class="form-control" id="tag"><br>

<button type="submit" class="btn btn-primary">添加</button>
</div>

</div>
</form>

</div>



var testEditor;
$(function() {
testEditor = editormd("test-editormd", {
width : "100%",
height : 450,
syncScrolling : "single",
path : "assets/editormd/lib/",
watch : false,
});
});

</body>
</html>

Gin 博客项目-实现博客列表

博客列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<link rel="stylesheet" href="/assets/css/bootstrap.min.css">

<title>博客列表</title>
</head>
<body>
<div class="container">
{{template "header"}}
<div class="row mt-3">
{{range $post := . -}}
<div class="col-md-6">
<div class="row g-0 border rounded overflow-hidden flex-md-row mb-4
shadow-sm h-md-250 position-relative">
<div class="col p-4 d-flex flex-column position-static">
<strong class="d-inline-block mb-2 text-primary">分类</strong>
<h3 class="mb-0">{{$post.Title}}</h3>
<div class="mb-1 text-muted">Nov 12</div>
<p class="card-text mb-auto">{{$post.Content}}</p>
<a href="#" class="stretched-link">阅读更多...</a>
</div>
<div class="col-auto d-none d-lg-block">
<svg class="bd-placeholder-img" width="200" height="250"
xmlns="http://www.w3.org/2000/svg" role="img"
aria-label="Placeholder: Thumbnail"
preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect
width="100%" height="100%" fill="#55595c"></rect><text x="50%"
y="50%" fill="#eceeef" dy=".3em">博客封面</text></svg>
</div>
</div>
</div>
{{- end}}
</div>
</div>
</body>
</html>

Gin 博客项目-实现博客详细

博客详细页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<link rel="stylesheet" href="/assets/css/bootstrap.min.css" />
<link rel="stylesheet" href="/assets/editormd/css/editormd.css" />

<title>博客详细</title>
</head>
<body>
<div class="container">
{{template "header"}}

<div class="row">
<div class="col-md-12">
<h1>{{.Title}}</h1>
{{.Content}}
</div>
</div>
</div>
</body>
</html>

博客详细控制器

1
2
3
4
5
6
7
8
9
10
11
12
func PostDetail(c *gin.Context) {
s := c.Query("pid")
pid, _ := strconv.Atoi(s)
p := dao.Mgr.GetPost(pid)

content := blackfriday.Run([]byte(p.Content))

c.HTML(200, "detail.html", gin.H{
"Title": p.Title,
"Content": template.HTML(content),
})
}

beego 起步

下载安装 beego

1
go get github.com/astaxie/beego

beego 命令行工具 bee

1
go get github.com/beego/bee

第一个 beego 项目

  1. 使用 bee 命令创建项目
    1
    bee new myproject
  2. 项目目录结构
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    myproject
    ├── conf
    │ └── app.conf
    ├── controllers
    │ └── default.go
    ├── main.go
    ├── models
    ├── routers
    │ └── router.go
    ├── static
    │ ├── css
    │ ├── img
    │ └── js
    ├── tests
    │ └── default_test.go
    └── views
    └── index.tpl
  3. 运行项目
    1
    bee run

beego restful api

创建 restful api 项目

1
bee api apiproject

创建模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package models

import (
"errors"
"strconv"
"time"
)

var (
UserList map[string]*User
)

func init() {
UserList = make(map[string]*User)
u := User{"user_11111", "astaxie", "11111", Profile{"male", 20, "Singapore", "[email protected]"}}
UserList["user_11111"] = &u
}

type User struct {
Id string
Username string
Password string
Profile Profile
}

type Profile struct {
Gender string
Age int
Address string
Email string
}

func AddUser(u User) string {
u.Id = "user_" + strconv.FormatInt(time.Now().UnixNano(), 10)
UserList[u.Id] = &u
return u.Id
}

func GetUser(uid string) (u *User, err error) {
if u, ok := UserList[uid]; ok {
return u, nil
}
return nil, errors.New("User not exists")
}

func GetAllUsers() map[string]*User {
return UserList
}

func UpdateUser(uid string, uu *User) (a *User, err error) {
if u, ok := UserList[uid]; ok {
if uu.Username != "" {
u.Username = uu.Username
}
if uu.Password != "" {
u.Password = uu.Password
}
if uu.Profile.Age != 0 {
u.Profile.Age = uu.Profile.Age
}
if uu.Profile.Address != "" {
u.Profile.Address = uu.Profile.Address
}
if uu.Profile.Gender != "" {
u.Profile.Gender = uu.Profile.Gender
}
if uu.Profile.Email != "" {
u.Profile.Email = uu.Profile.Email
}
return u, nil
}
return nil, errors.New("User Not Exist")
}

func Login(username, password string) bool {
for _, u := range UserList {
if u.Username == username && u.Password == password {
return true
}
}
return false
}

func DeleteUser(uid string) {
delete(UserList, uid)
}

创建控制器 controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package controllers

import (
"apiproject/models"
"encoding/json"

"github.com/astaxie/beego"
)

// Operations about Users
type UserController struct {
beego.Controller
}

// @Title CreateUser
// @Description create users
// @Param body body models.User true "body for user content"
// @Success 200 {int} models.User.Id
// @Failure 403 body is empty
// @router / [post]
func (u *UserController) Post() {
var user models.User
json.Unmarshal(u.Ctx.Input.RequestBody, &user)
uid := models.AddUser(user)
u.Data["json"] = map[string]string{"uid": uid}
u.ServeJSON()
}

// @Title GetAll
// @Description get all Users
// @Success 200 {object} models.User
// @router / [get]
func (u *UserController) GetAll() {
users := models.GetAllUsers()
u.Data["json"] = users
u.ServeJSON()
}

// @Title Get
// @Description get user by uid
// @Param uid path string true "The key for staticblock"
// @Success 200 {object} models.User
// @Failure 403 :uid is empty
// @router /:uid [get]
func (u *UserController) Get() {
uid := u.GetString(":uid")
if uid != "" {
user, err := models.GetUser(uid)
if err != nil {
u.Data["json"] = err.Error()
} else {
u.Data["json"] = user
}
}
u.ServeJSON()
}

// @Title Update
// @Description update the user
// @Param uid path string true "The uid you want to update"
// @Param body body models.User true "body for user content"
// @Success 200 {object} models.User
// @Failure 403 :uid is not int
// @router /:uid [put]
func (u *UserController) Put() {
uid := u.GetString(":uid")
if uid != "" {
var user models.User
json.Unmarshal(u.Ctx.Input.RequestBody, &user)
uu, err := models.UpdateUser(uid, &user)
if err != nil {
u.Data["json"] = err.Error()
} else {
u.Data["json"] = uu
}
}
u.ServeJSON()
}

// @Title Delete
// @Description delete the user
// @Param uid path string true "The uid you want to delete"
// @Success 200 {string} delete success!
// @Failure 403 uid is empty
// @router /:uid [delete]
func (u *UserController) Delete() {
uid := u.GetString(":uid")
models.DeleteUser(uid)
u.Data["json"] = "delete success!"
u.ServeJSON()
}

// @Title Login
// @Description Logs user into the system
// @Param username query string true "The username for login"
// @Param password query string true "The password for login"
// @Success 200 {string} login success
// @Failure 403 user not exist
// @router /login [get]
func (u *UserController) Login() {
username := u.GetString("username")
password := u.GetString("password")
if models.Login(username, password) {
u.Data["json"] = "login success"
} else {
u.Data["json"] = "user not exist"
}
u.ServeJSON()
}

// @Title logout
// @Description Logs out current logged in user session
// @Success 200 {string} logout success
// @router /logout [get]
func (u *UserController) Logout() {
u.Data["json"] = "logout success"
u.ServeJSON()
}

创建路由

路由在控制器方法中有注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// @APIVersion 1.0.0
// @Title beego Test API
// @Description beego has a very cool tools to autogenerate documents for your API
// @Contact [email protected]
// @TermsOfServiceUrl http://beego.me/
// @License Apache 2.0
// @LicenseUrl http://www.apache.org/licenses/LICENSE-2.0.html
package routers

import (
// "apiproject/controllers"

"apiproject/controllers"

"github.com/astaxie/beego"
)

func init() {
ns := beego.NewNamespace("/v1",
beego.NSNamespace("/object",
beego.NSInclude(
&controllers.ObjectController{},
),
),
beego.NSNamespace("/user",
beego.NSInclude(
&controllers.UserController{},
),
),
)
beego.AddNamespace(ns)
}

运行并生成 swagger

1
bee run -gendoc=true -downdoc=true

测试

1
http://localhost:8080/swagger/

beego 从零开始写一个 beego 项目

为了更好的理解 beego 框架,我们不使用 bee 工具,从零开始来写一个项目,实现步骤如下:

1. 创建项目结构

创建一个文件夹,例如:beegopro01,并初始化。

1
go mod init beegopro01

项目结构如下:

1
2
3
|-controller
|-router
|-views

2. 创建 controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package controller

import (
"github.com/astaxie/beego"
)

type UserControler struct {
beego.Controller
}

func (c *UserControler) Get() {
c.Data["name"] = "老郭"
c.Data["site"] = "多课网"
c.TplName = "index.tpl"
}

3. 创建 router 路由

1
2
3
4
5
6
7
8
9
10
package routers

import (
"myproject/controllers"
"github.com/astaxie/beego"
)

func init() {
beego.Router("/", &controllers.MainController{})
}

4. 创建模板文件 index.tpl

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<title>beego 项目</title>
</head>
<body>
Name: {{.name}} <br />
Site:{{.site}}
</body>
</html>

5. main.go

1
2
3
4
5
6
7
8
9
10
11
package main

import (
_ "beegopro01/router"

"github.com/astaxie/beego"
)

func main() {
beego.Run()
}

beego orm 起步

安装库

1
go get github.com/astaxie/beego/orm

简单示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package main

import (
"fmt"

"github.com/astaxie/beego/orm"

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

// 隐射表结构
type User struct {
Id int
Name string `orm:"size(100)"`
}

func init() {
// 连接到数据库
orm.RegisterDataBase("default", "mysql", "root:123456@/beego_db?charset=utf8")
// 注册模型
orm.RegisterModel(new(User))
// 创建表
orm.RunSyncdb("default", false, true)
}

// 添加
func add() {
o := orm.NewOrm()
o.Insert(&User{Name: "老郭"})
}

// 查找
func find() {
o := orm.NewOrm()
user := User{Id: 101}
o.Read(&user)
fmt.Printf("user: %v\n", user)
}

// 修改
func update() {
o := orm.NewOrm()
user := User{Id: 101}
o.Read(&user)
user.Name = "多课网-老郭"
o.Update(&user)
}

// 删除
func del() {
o := orm.NewOrm()
user := User{Id: 101}
o.Read(&user)
o.Delete(&user)
}

func main() {
// add()
// update()
// find()
// update()
del()
}