go web编程入门教程(待更新)

2022/1/31 14:35:56

本文主要是介绍go web编程入门教程(待更新),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

想了解下区块链相关的东西,从头开始学习go 语法实在是耐不下心,稍微看了下
还是直接做web来学吧,主要材料如下

  • 尚硅谷GoWeb教程
  • go web编程快速入门
  • go语言标准库

web应用的流程如图所示,goweb使用默认的多路服用去转发请求到处理器,如果要使用模板,处理器解析并渲染返回响应,和数据交互通过模型完成

1、简单的hello world应用

不论怎样,首先写个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

在这里插入图片描述

2、链接数据库

接下来调试下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)
}

处理request请求

直接去看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指针读取就行了,大同小异

3、给客户端response响应

这部分也比较简单,大体上就是设置响应头和响应体

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还是很方便的

  • NotFound
  • ServeFile
  • ServeContent
  • Redirect

4、web模板和模板引擎

模板引擎能够将模板和数据结合起来生成网页

模板引擎有两种

  • 无逻辑,不做逻辑处理,仅仅是占位符替换,完全由handler完成,目标是表示层和罗技完全分离
  • 逻辑嵌入,编程语言嵌入,难以维护

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

5、更合理的项目结构

前面的逻辑处理放在了main函数当中,但是实际上main函数应该只负责设置类的工作,说白了就是要用MVC



这篇关于go web编程入门教程(待更新)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程