想了解下区块链相关的东西,从头开始学习go 语法实在是耐不下心,稍微看了下
还是直接做web来学吧,主要材料如下
web应用的流程如图所示,goweb使用默认的多路服用去转发请求到处理器,如果要使用模板,处理器解析并渲染返回响应,和数据交互通过模型完成
不论怎样,首先写个hello world的demo,跑起来再说
package main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/", hello) http.ListenAndServe(":8888", nil) } func hello(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "hello wolrd") }
一个简单的web页面就启动起来了,相对于javaee的web工程简直太棒了
go语言的标准库放在这里:go标准库
上面的handlerFunc实际上是一个适配器,将func转换为handler
handler是一个接口,实现了handler的ServeHTTP方法,就能够为特定路径提供服务
上面传入的nil实际上是指定了默认的ServeMux,可以自己实现但是没有太大必要
下面自己实现一个handler
func main() { http.HandleFunc("/", hello) myhandler := Myhandler{} http.Handle("/demo", &myhandler) http.ListenAndServe(":8888", nil) } func hello(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "hello wolrd", r.URL.Path) } type Myhandler struct{} func (h *Myhandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "demo") }
上面的Myhandler struct时间了Handler接口,因此可以注册到Mux中处理请求了
此外可以通过定义Server结构体自动逸server的各种参数
func main() { myhandler := Myhandler{} server := http.Server{ Addr: ":8888", Handler: &myhandler, ReadTimeout: 2 * time.Second, } server.ListenAndServe() } type Myhandler struct{} func (h *Myhandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "server") }
这个时候还是使用的默认mux,可以创建自己的mux
func main() { mux := http.NewServeMux() mux.HandleFunc("/", hello) myhandler := Myhandler{} mux.Handle("/demo", &myhandler) server := http.Server{ Addr: ":8888", Handler: mux, ReadTimeout: 2 * time.Second, } server.ListenAndServe() } func hello(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "hello") } type Myhandler struct{} func (h *Myhandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "server") }
如果做过web应用那么以上的应该没啥问题,大同小异
这里使用vscode 的rest client插件进行http请求,so easy
接下来调试下go链接数据库的的后端
和java一样,golang中的database/sql定义了database的操作,安装相应的驱动即可(这些驱动是按照go的接口标准开发的),sql包统一了database的操作
这里用最常用的mysql数据库来实验,在本地安装好mariadb数据库即可(驱动和mysql貌似都一样,毕竟是亲兄弟)
文档:https://studygolang.com/static/pkgdoc/pkg/database_sql.htm
驱动下载:https://github.com/golang/go/wiki/SQLDrivers
go get github.com/go-sql-driver/mysql
然后再MySQL中创建数据库和表
CREATE DATABASE webdemo; use webdemo; CREATE TABLE IF NOT EXISTS `student`( `id` INT UNSIGNED AUTO_INCREMENT, `name` VARCHAR(100) NOT NULL, `age` INT, `tele` VARCHAR(40), `birth` DATE, PRIMARY KEY ( `id` ) )ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO student (name, age, tele, birth) VALUES ("sam", 22, "12304772837",NOW()); INSERT INTO student (name, age, tele, birth) VALUES ("bob", 16, "321314431321",NOW()); INSERT INTO student(name, age, tele, birth) VALUES ("mary", 22, "7389217391",NOW());
数据库链接工具使用vsvode插件database client,表结构如下
尝试链接并查询studnet表
文档中sql模块中对curd的接口介绍的比较简洁,但是也足够了
Query 查询多行 QueryRow 查询一行 Exec 增删改 Prepare 预编译语句
这里首先获取Db链接
var ( Db *sql.DB err error ) func init() { Db, err = sql.Open("mysql", "root:zhaojie@tcp(localhost:3306)/webdemo") if err != nil { panic(err.Error()) } }
然后创建User结构,实现crud方法
package model type User struct { Id int Name string Age int Tele string Birth time.Time } func (u *User) Adduser() error { sqlStr := "insert into student (name,age,tele,birth) values(?,?,?,?)" inStmt, err := dbutils.Db.Prepare(sqlStr) if err != nil { log.Fatal("prepare is wrong", err) return err } u.Birth = time.Now() _, inerr := inStmt.Exec(u.Name, u.Age, u.Tele, u.Birth) if inerr != nil { log.Fatal("insert is wrong", err) return err } return nil }
为了方便进行测试,看一下go的单元测试方法,参考testing包
测试文件以test_开头,和被测在一个包中,测试函数以Test开头并且参数按照不同的用途进行了分类。
此外如果不以Test开头则默认不执行,可以设置成子测试函数
TestMain可以在测试方法之前执行一段逻辑
package model func TestMain(t *testing.M) { fmt.Println("start testing") } func TestUser(t *testing.T) { fmt.Println("start test user func") t.Run("test add user", TestAdduser) } func TestAdduser(t *testing.T) { fmt.Println("start test add user:") user := &User{ Name: "test1", Age: 12, Tele: "12313213123", Birth: time.Now(), } user.Adduser() }
在model模块打开控制台运行go test即可,运行之后显示数据库插入成功
$ go test start testing start test user func start test add user: PASS ok beegodemo/model 0.005s
查询操作如下
func (u *User) GetUserByID() (*User, error) { sqlStr := "select * from student where id = ?" row := dbutils.Db.QueryRow(sqlStr, u.Id) var ( id int name string age int tele string birth_str string ) err := row.Scan(&id, &name, &age, &tele, &birth_str) if err != nil { return nil, err } DefaultTimeLoc := time.Local birth, err := time.ParseInLocation("2006-01-02", birth_str, DefaultTimeLoc) user := &User{ Id: id, Age: age, Name: name, Tele: tele, Birth: birth, } return user, err }
测试方法
func TestGetUserByID(t *testing.T) { fmt.Println("start test get user by id:") user := &User{ Id: 2, } u, err := user.GetUserByID() if err != nil { fmt.Println("wrong get user", err) } json, _ := json.Marshal(u) fmt.Print(string(json)) }
测试结果如下
start testing === RUN TestGetUserByID start test get user by id: {"id":2,"name":"bob","age":16,"tele":"321314431321","Birth":"2022-01-29T00:00:00Z"} --- PASS: TestGetUserByID (0.00s) PASS ok beegodemo/model 0.004s
后面的写法大同小异,参考godoc即可
只不过Query方法得到rows,需要便利rows进行scan到每个user对象中
for rows.Next(){ rows.Scan(....) user := &User(....) append(users,user) }
直接去看net/http包
requesu是一个struct,根据字段写获取就行
func hello(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, r.Header["Accept-Encoding"]) fmt.Fprintln(w, r.UserAgent()) }
解析request请求
看一下post请求的解析
request中的Body字段是 io.ReadCloser类型
readcloser有两个子接口reader和closer实现了read和lclose方法
read方法将数据读入byte切片
func (h *Myhandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { length := r.ContentLength body := make([]byte, length) r.Body.Read(body) fmt.Fprintln(w, string(body)) }
不知道为什么rest client没有codelens,有点麻烦,懒得查了将就用吧
当然form表单中的数据有专门的获取方式
func form(w http.ResponseWriter, r *http.Request) { r.ParseForm() fmt.Fprintln(w, r.Form) fmt.Fprintln(w, r.PostForm) }
form字段只有在调用parsefrom之后才有效,并且post请求优先于url查询字符串
注意:postform只支持x-www-form-urlencoded,如果是mutilpart/form-data,需要使用mutilpartform
如果想快速获得某个请求参数那就用formvalue和postformvalue,好处是会自动parseform
上传文件用解析mutilpartform即可,得到一个file指针读取就行了,大同小异
这部分也比较简单,大体上就是设置响应头和响应体
func resp(w http.ResponseWriter, r *http.Request) { str := `<html> <head><title>go web</title></head> <body><h1>hello world</h1></body> </html>` w.Write([]byte(str)) }
wtiteheader能够设置响应header code,但是调用之后就不能继续修改header了
func resp(w http.ResponseWriter, r *http.Request) { w.WriteHeader(404) fmt.Fprintln(w, "not found") }
重定向
func resp(w http.ResponseWriter, r *http.Request) { w.Header().Set("Location", "http://bing.com") w.WriteHeader(302) }
返回json
type Post struct { User string `json:"user"` Threads []string `json:"threads"` } func resp(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") post := &Post{ User: "zhaojie", Threads: []string{"first", "second", "third"}, } json, _ := json.Marshal(post) w.Write(json) }
go内置了很多response还是很方便的
模板引擎能够将模板和数据结合起来生成网页
模板引擎有两种
go模板引擎使用了text/template库,介于两种引擎特性之间
简单的模板 demo
<!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> {{ . }} </body> </html>
替换占位符
func tmpl(w http.ResponseWriter, r *http.Request) { t, _ := template.ParseFiles("html/index.html") t.Execute(w, "hello world") }
也可以创建模板集,然后搜寻和使用
写一个模板的demo
func main() { mux := http.NewServeMux() templates := loadTemplates() mux.HandleFunc("/", newtemp(templates)) server := http.Server{ Addr: ":8888", Handler: mux, ReadTimeout: 2 * time.Second, } mux.Handle("/css/", http.FileServer(http.Dir("html/wwwroot"))) mux.Handle("/img/", http.FileServer(http.Dir("html/wwwroot"))) server.ListenAndServe() } //创建模板集 func loadTemplates() *template.Template { result := template.New("templates") template.Must(result.ParseGlob("html/template/*.html")) return result } //handler函数 func newtemp(templates *template.Template) func(w http.ResponseWriter, r *http.Request) { temp := func(w http.ResponseWriter, r *http.Request) { filename := r.URL.Path[1:] t := templates.Lookup(filename) if t != nil { err := t.Execute(w, nil) if err != nil { log.Fatalln(err.Error()) } } else { w.WriteHeader(http.StatusNotFound) } } return temp } 请求结果和项目结构在下面,注意
有图片和css,效果在网页看比较好
这里有个坑点,注意自己使用的多路复用器是自定义的还是默认的,加载静态文件的时候一定不要弄错了
搭建好了基本的股价,下面来看模板中的action,一共五种
其实就是大同小异的模板语法,做过web的没啥难度
这里随便写几个demo
func tmplaction(w http.ResponseWriter, r *http.Request) { t, _ := template.ParseFiles("html/template/tmpl.html") rand.Seed(time.Now().Unix()) t.Execute(w, rand.Intn(10) > 5) }
gotemplate-syntax插件
<body> {{ if . }} number os greater than 5 {{ else }} number is 5 or less {{ end }} </body>
模板中还能设置传入的参数,就是让模板的逻辑更加复杂,不过现在都前后端分离了
其他的掠过用到在查,nothing
前面的逻辑处理放在了main函数当中,但是实际上main函数应该只负责设置类的工作,说白了就是要用MVC