前言

本文主要介绍在Go中内置的包如何使用

 

time

时间相关的包

package main

import (
	"fmt"
	"time"
)

func main() {
	//格式化时间
	now := time.Now()
	fmt.Println(now)
	fmt.Println(now.Year())
	fmt.Println(now.Month())
	fmt.Println(now.Day())
	fmt.Println(now.Hour())
	fmt.Println(now.Minute())
	fmt.Println(now.Second())
	//时间戳
	fmt.Println(now.Unix())
	fmt.Println(now.UnixNano()) //纳秒时间戳
	//time.Unix()时间戳转时间格式
	ret := time.Unix(1587084700, 0)
	fmt.Print(ret.Year(), ret.Month())
	//获取时间间隔 Duration类型
	/*
			Golang的时间间隔是定义在time包中的常量
			const (
			//1纳秒
			Nanosecond  Duration = 1
			//1微妙
			Microsecond          = 1000 * Nanosecond
			//1毫秒
			Millisecond          = 1000 * Microsecond
			//1秒
			Second               = 1000 * Millisecond
			//1分钟
			Minute               = 60 * Second
			//1小时
			Hour                 = 60 * Minute
		)

	*/
	// time.Sleep(time.Millisecond)
	// time.Sleep(5 * time.Second)
	// time.Sleep(time.Hour)

	//时间操作
	//1天后
	fmt.Println(now.Add(24 * time.Hour))
	//一小时之后
	fmt.Println(now.Add(time.Hour))
	//一小时前
	h, _ := time.ParseDuration("-1h")
	h1 := now.Add(8 * h)
	fmt.Println(h1)
	//2天前
	d, _ := time.ParseDuration("-48h")
	DayBeforYesterday := now.Add(d)
	fmt.Println(DayBeforYesterday)
	//计算2个时间的时间差,返回duration
	subDay := now.Sub(DayBeforYesterday)
	fmt.Println(subDay)
	//判断2个时间是否相等,返回bool
	fmt.Println(now.Equal(DayBeforYesterday))
	//判断2个时间点前后顺序,返回bool
	fmt.Println(now.Before(DayBeforYesterday))
	fmt.Println(now.After(DayBeforYesterday))

	//定时器
	//1秒钟执行1次
	// timer:=time.Tick(time.Second)
	// for t :=range timer{
	// 	fmt.Println(t)
	// }

	//Go中有趣的时间格式
	//以Golang诞生的时间2006 1 2 3 4 5 000替代%Y-%m-%d %H:%i:%S
	fmt.Println(DayBeforYesterday.Format("2006-1-2|3:4:5"))
	fmt.Println(now.Format("2006年1月2日3时4分5秒"))
	//时间格式化成字符串 精确到毫秒
	fmt.Println(DayBeforYesterday.Format("2006-1-2|3:4:5.000"))
	fmt.Println(DayBeforYesterday.Format("2006-1-2|3:4:5.999"))
	stringNow := DayBeforYesterday.Format("2006-1-2|3:4:5.999")
	//字符串格式化成时间格式
	d2, _ := time.Parse("2006-1-2|3:4:5.999", stringNow)
	fmt.Println(d2.Unix())
	fmt.Println(d2.Year())
	fmt.Println(d2.Month())
	fmt.Println(d2.Day())
	//ParseInLocation以指定的时区解析时间

	location, err := time.LoadLocation("Asia/Shanghai")
	if err != nil {
		fmt.Println("时区解析失败")
		return
	}
	timeObj,err:=time.ParseInLocation("2006-01-02 15:04:05","2016-05-02 15:04:05",location)
	if err != nil {
		fmt.Println("按照时区解析时间失败",err)
		return
	}
	timeObj1,err:=time.Parse("2006-01-02 15:04:05","2016-05-02 15:04:05")
	//为什么我Parse和ParseInLocation出来的同一时间还差了8小时呢?
	subValue:=timeObj1.Sub(timeObj)
	fmt.Println(subValue)
	fmt.Println(timeObj.Zone())	//CST 28800
	fmt.Println(timeObj1.Zone())//UTC 0
	

}

  

ParseInLocation和Parse的区别
学个单词
absent:ver /adj
Golang工具包
    




SQLAlchemy
I was absent from the APEC In Beijing。//我缺席了北京的APEC会议
His wife often complains about his.  //他老婆经常抱怨她缺少他的陪伴
 
package main

import (
	"fmt"
	"runtime"
)

//runtime.Caller()
//可传可以不传...可变长,可以没有,可以有1个
func f1(n ...interface{}) {
	pc, file, line, ok := runtime.Caller(3)
	//runtime.Caller(skip)
	//0层: main.f1
	//1层:main.f2
	//2层:main.f3
	if !ok {
		fmt.Printf("runtime.Caller faild.error\n")
		return
	}
	fmt.Println(runtime.FuncForPC(pc).Name()) //函数名
	fmt.Println(file)                         //哪个文件调用了我
	fmt.Println(line)                         //文件的哪1行
}

func f2() {
	f1()
}

func f3() {
	f2()
}

func main() {
	f3()

}
package main

import (
	"encoding/json"
	"fmt"
)

//结构体
type person struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

//字符串
func main() {
	str:=`{"name":"Martin","age":18}`
	var p person
	//Unmarshal做了什么?让1个字符串的值,赋值给了p结构体
	json.Unmarshal([]byte(str),&p)
	//
	fmt.Println(p.Name,p.Age)//Martin 18

}
package main

import (
	"fmt"
	"reflect"
)


type person struct{
	name string
	age int16
}

func reflectType(arg interface{}) {
	v := reflect.TypeOf(arg)
	fmt.Printf("%v %v\n", v.Kind(),v.Name())
}

func main() {
	var name string
	name="Martin"
	reflectType(name)//string string
	var age int16
	age=19
	//如果是struct类型如何拿到 struct种类的Person类型名称?v.Name(
	reflectType(age)//int16 int16
	p1:=person{
		name:"Martin",
		age:20,
	}
	reflectType(p1)//struct person
	//func函数类型
	reflectType(reflectType)//func

}
package main

import (
	"fmt"
	"reflect"
)

type person struct {
	name string
	age  int16
}

func reflectValue(arg interface{}) {
	//获取变量的值
	reflectValue:= reflect.ValueOf(arg)
	//判断变量值的早reflect包中的类型
	argkind := reflectValue.Kind()
	switch argkind {
	//如果在reflect包中值的类型为reflect.Int64
	case reflect.Int16:
		//就转换为Go内置的int64类型
		fmt.Printf("type is int16, value is %d\n", int64(reflectValue.Int()))
	}
}

func main() {
	var name string
	name = "Martin"
	reflectValue(name) //string string
	var age int16
	age = 19
	//如果是struct类型如何拿到 struct种类的Person类型名称?v.Name(
	reflectValue(age) //int16 int16
	p1 := person{
		name: "Martin",
		age:  20,
	}
	reflectValue(p1) //struct person
	//func函数类型
	reflectValue(reflectValue) //func

}

 

2.1获取到变量的值之后修改值

reflectValue.Elem().SetInt(200)
想要在函数中通过反射修改变量的值,需要注意函数参数传递的是值拷贝,必须传递变量地址才能修改变量值。
Elem()
Elem()通过指针获取变量对应的值相当于*pointer
package main

import (
	"fmt"
	"reflect"
)

//通过返回设置 变量的值
func setValue(arg interface{}) {
	//获取变量的值
	reflectValue := reflect.ValueOf(arg)
	//设置变量的值
	if reflectValue.Kind() == reflect.Int16 {
		//找到指针类型的变量内存地址,把变量的值设置为200
		reflectValue.Elem().SetInt(200)
	}

}

func main() {
	var age int16
	age = 19
	setValue(&age)
	fmt.Println(age)

}

  

 

2.2.isNil()和isValid()

获取到值之后,判断值里面   是否包含某个方法 / 字段(是否存在)

类似于Python中 getattr(obj,"method_name " )

package main

import (
	"fmt"
	"reflect"
)

type person struct {
	name string
	age  uint8
}

//注意要struct的方法要大写否则找不到!哈哈哈
func(p person)Walk(){
	fmt.Printf("%s is walking!",p.name)
}

func main() {
	p1 := person{
		name: "Martin",
		age:  19,
	}
	fmt.Println("不存在的结构体成员:",reflect.ValueOf(p1).FieldByName("name").IsValid())
	if reflect.ValueOf(p1).MethodByName("Walk").IsValid() {
		method :=reflect.ValueOf(p1).MethodByName("Walk")
		method.Call([]reflect.Value{})//调用方法
	}
}

  

loadini文件

根据ini文件配置生成go中的结构体

package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"reflect"
	"strconv"
	"strings"
)

//Mysqlconfig 结构体
type Mysqlconfig struct {
	Address  string `ini:"address"`
	Port     int    `ini:"port"`
	Username string `ini:"username"`
	Password string `ini:"password"`
	Debug    bool   `ini:"debug"`
}

//Redisconfig 结构体
type Redisconfig struct {
	Host     string `ini:"host"`
	Port     int    `ini:"port"`
	Username string `ini:"username"`
	Database string `ini:"database"`
}

//配置文件
type config struct {
	Mysqlconfig `ini:"mysql"`
	Redisconfig `ini:"redis"`
}

func loadIni(fileName string, config interface{}) (err error) {
	//判断config是否为指针类型的struct
	t := reflect.TypeOf(config)
	if t.Kind() != reflect.Ptr || t.Elem().Kind() == reflect.Struct {
		err = fmt.Errorf("请输入1个指针类型的 config struct参数!")
	}
	fileBytes, err := ioutil.ReadFile(fileName)
	if err != nil {
		err = errors.New("请检查配置文件是否存在?")
	}
	lineSlice := strings.Split(string(fileBytes), "\r\n")
	var sectionName string
	for lineNuber, line := range lineSlice {
		line = strings.TrimSpace(line)
		//空行
		if len(line) < 1 {
			continue
		}
		//注释
		if strings.HasPrefix(line, "#") || strings.HasPrefix(line, "//") {
			continue
		}
		//加载section
		if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
			sectionName = line[1 : len(line)-1]
			if len(sectionName) < 1 {
				err = fmt.Errorf("select:%s %d 语法错误", sectionName, lineNuber)
				return
			}

			//变量配置struct的字段
			for i := 0; i < t.Elem().NumField(); i++ {
				field := t.Elem().Field(i)
				//获取config中的ini信息
				if sectionName == field.Tag.Get("ini") {
					sectionName = field.Name //拿到config结构体中的 嵌套结构体信息
					break
				}
			}
		} else { //secetion中的内容
			if strings.Index(line, "=") == -1 || strings.HasPrefix(line, "=") {
				err = fmt.Errorf("%d语法错误", lineNuber)
				return
			}
			//从文件中获取==的index
			index := strings.Index(line, "=")
			//从文件中 获取属性
			key := strings.TrimSpace(line[:index])
			//从文件中获取值
			value := strings.TrimSpace(line[index+1:])

			v := reflect.ValueOf(config)
			structValue := v.Elem().FieldByName(sectionName)
			structType := structValue.Type()
			//config中嵌套的结构体
			if structType.Kind() != reflect.Struct {
				err = fmt.Errorf("请设置config中的%s字段为结构体", sectionName)
				return
			}
			//嵌套结构体里面具体的tag获取到字段
			var StructFiledName string
			var StructFiledType reflect.StructField
			for i := 0; i < structValue.NumField(); i++ {
				filed := structType.Field(i)
				StructFiledType = filed
				//mysql =mysql
				if filed.Tag.Get("ini") == key {
					StructFiledName = filed.Name
					break
				}

			}
			//根据StructFiledName给嵌套结构体里面具体的field获取到值
			structObj := structValue.FieldByName(StructFiledName)
			if len(StructFiledName) < 1 {
				//在嵌套结构体中找不到对应的字段
				err = fmt.Errorf("Warning在嵌套结构体%s中找不到对应字段%s", StructFiledName, StructFiledName)
				continue
			}
			switch StructFiledType.Type.Kind() {
			case reflect.String:
				structObj.SetString(value)
			case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
				var valueInt int64
				valueInt, err = strconv.ParseInt(value, 10, 64)
				if err != nil {
					err = fmt.Errorf("第%d init类型解析错误", lineNuber)
					return
				}
				structObj.SetInt(valueInt)
			case reflect.Bool:
				var valueBool bool
				valueBool, err = strconv.ParseBool(value)
				if err != nil {
					err = fmt.Errorf("第%d bool类型解析错误", lineNuber)
					return
				}

				structObj.SetBool(valueBool)
			case reflect.Float32, reflect.Float64:
				var valuefloat float64
				//字符串解析成其他类型数据
				valuefloat, err = strconv.ParseFloat(value, 64)
				if err != nil {
					err = fmt.Errorf("第%d bool类型解析错误", lineNuber)
					return
				}
				structObj.SetFloat(valuefloat)
			}
		}
	}
	return
}

func main() {
	var lC config
	err := loadIni("./log.ini", &lC)
	if err != nil {
		fmt.Println("加载配置文件失败", err)
	}
	fmt.Printf("%#v\n", lC.Mysqlconfig)
	fmt.Printf("%#v\n", lC.Redisconfig)
}

  

 

根据字符串面值 转换为Go中的数据类型

Go语言是强类型的语言:如果Go本身不支持2种数据类型直接的转换,是不能强制转换的。

package main

import (
	"fmt"
	"strconv"
)

var n1 int
var n2 int32
var name string

func main() {
	//支持相互强制转换的类型
	name = "MAEJH"
	n1 = 250
	n2 = 1280000000
	//int32可以转换为init64
	fmt.Println(int64(n2))
	//字符串可以转换为byte类型的切片
	fmt.Println([]byte(name))
	//把数字转换为字符串
	s1 := fmt.Sprintf("%d", 250)
	fmt.Println(s1)
	name = "250"
	//strconv把字符串对应的数据:
	//转换为10进制的int64位数字
	ret, _ := strconv.ParseInt(name, 10, 64)
	fmt.Println(ret)
	//Atoi()arry(c中的字符串)to int
	retInt, _ := strconv.Atoi(name)
	fmt.Println(retInt)
	//Itoa:把int转换为字符串
	reSrting:= strconv.Itoa(int(100))
	fmt.Println(reSrting)
	//从字符串中解析 相应的数据类型
	name="true"
	fmt.Println(strconv.ParseBool(name))
	name="1.3456"
	fmt.Println(strconv.ParseFloat(name,32))


}

   

net网络相关

 

Golang工具包
    




SQLAlchemy

 

golang的net包封装了实现传输层、应用层协议的API 。

我们使用net包可以写1个遵循TCP/UPD协议的socket程序,也可以写1个遵循http协议的 web应用程序。

 

1.TCP服务端和客户端实现

TCP server

package main

import (
	"fmt"
	"net"
)

//tcp server
func processConn(conn net.Conn) {
	defer conn.Close() // 关闭连接
	//有人来连接我!就与客户通信
	var data [1024]byte
	//持续接收 来自每1个客户端连接通信
	for {
		lengh, err := conn.Read(data[:])
		if err != nil {
			fmt.Println("客户端发送数据失败", err)
			return
		}
		fmt.Println(string(data[:lengh]))
	}

}

func main() {
	//0.本地端口启动服务
	listener, err := net.Listen("tcp", "127.0.0.1:8001")
	if err != nil {
		fmt.Println("start server faild", err)
		return
	}

	for {
		//2.server 接收到 来着client的 syn包,回复 syn+ack
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("客户端连接失败", err)
		}
		go processConn(conn) //轻松实现大并发啊 哈哈!
	}

}

TCP client

package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
	"strings"
)

//tcp client

func main() {
	//1.与server(127.0.0.1:8001)端建立连接(1.client 发送 syn包给 server)
	conn, err := net.Dial("tcp", "127.0.0.1:8001")
	if err != nil {
		fmt.Println("The socket you dialed is bussy now,pleale redial agin later!", err)
	}

	//2.发送数据
	reader := bufio.NewReader(os.Stdin)
	for {
		fmt.Print("请输入内容: ")
		msg, _ := reader.ReadString('\n')
		msg = strings.TrimSpace(msg)
		if msg == "exit" {
			break
		}
		conn.Write([]byte(msg))
	}
	conn.Close()

}

 

TCP粘包问题

TCP tcp数据传递模式是流模式(水流)。

“粘包”可发生在发送端也可发生在接收端:

  1. 由Nagle算法造成的发送端的粘包:Nagle算法是一种改善网络传输效率的算法。简单来说就是当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。
  2. 接收端接收不及时造成的接收端粘包:TCP会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据。当应用层由于某些原因不能及时的把TCP的数据取出来,就会造成TCP缓冲区中存放了几段数据。

 

客户端发送数据

我们可以自己定义一个协议,比如数据包的前4个字节为包头(固定大小),用来存储要发送数据的长度。

如何保证数据包的包头为固定大小呢?

golang中int32数据类型的变量为4个字节!

在网络上传输的都是字节数据,golangGo里面内建仅支持UTF8字符串编码,所以我们不需要考虑编码的问题,直接使用   bytes.buffer把数据发送到网络!

 

服务端接收到数据

先获取4个字节的信息(也就是要接收数据的元数据)

根据元数据获取指定长度的数据。

package proto

import (
	"bufio"
	"bytes"
	"encoding/binary"
)

// Encode 将消息编码
func Encode(message string) ([]byte, error) {
	// 读取消息的长度,转换成int32类型(占4个字节)
	var length = int32(len(message))
	var pkg = new(bytes.Buffer)
	// 写入消息头
	err := binary.Write(pkg, binary.LittleEndian, length)
	if err != nil {
		return nil, err
	}
	// 写入消息实体
	err = binary.Write(pkg, binary.LittleEndian, []byte(message))
	if err != nil {
		return nil, err
	}
	return pkg.Bytes(), nil
}

// Decode 解码消息
func Decode(reader *bufio.Reader) (string, error) {
	// 读取消息的长度
	lengthByte, _ := reader.Peek(4) // 读取前4个字节的数据
	lengthBuff := bytes.NewBuffer(lengthByte)
	var length int32
	err := binary.Read(lengthBuff, binary.LittleEndian, &length)
	if err != nil {
		return "", err
	}
	// Buffered返回缓冲中现有的可读取的字节数。
	if int32(reader.Buffered()) < length+4 {
		return "", err
	}

	// 读取真正的消息数据
	pack := make([]byte, int(4+length))
	_, err = reader.Read(pack)
	if err != nil {
		return "", err
	}
	return string(pack[4:]), nil
}

  

2.UPD服务端和客户端

UDP server

UDP协议用户数据报协议(UDP,User Datagram Protocol)

UDP和TCP最大的区别就是UDP 为应用程序提供了一种无需建立连接(3次握手)就可以发送封装的 IP 数据包的方法。

// UDP/server/main.go

// UDP server端
func main() {
	listen, err := net.ListenUDP("udp", &net.UDPAddr{
		IP:   net.IPv4(0, 0, 0, 0),
		Port: 30000,
	})
	if err != nil {
		fmt.Println("listen failed, err:", err)
		return
	}
	defer listen.Close()
	for {
		var data [1024]byte
		n, addr, err := listen.ReadFromUDP(data[:]) // 接收数据
		if err != nil {
			fmt.Println("read udp failed, err:", err)
			continue
		}
		fmt.Printf("data:%v addr:%v count:%v\n", string(data[:n]), addr, n)
		_, err = listen.WriteToUDP(data[:n], addr) // 发送数据
		if err != nil {
			fmt.Println("write to udp failed, err:", err)
			continue
		}
	}
}

  

UDP client

 UDP 客户端
func main() {
	socket, err := net.DialUDP("udp", nil, &net.UDPAddr{
		IP:   net.IPv4(0, 0, 0, 0),
		Port: 30000,
	})
	if err != nil {
		fmt.Println("连接服务端失败,err:", err)
		return
	}
	defer socket.Close()
	sendData := []byte("Hello server")
	_, err = socket.Write(sendData) // 发送数据
	if err != nil {
		fmt.Println("发送数据失败,err:", err)
		return
	}
	data := make([]byte, 4096)
	n, remoteAddr, err := socket.ReadFromUDP(data) // 接收数据
	if err != nil {
		fmt.Println("接收数据失败,err:", err)
		return
	}
	fmt.Printf("recv:%v addr:%v count:%v\n", string(data[:n]), remoteAddr, n)
}

 

web聊天室

server

package main

import (
	"fmt"
	"net"
	"time"
)

//用户信息结构体
type client struct {
	name string
	c    chan string
	addr string
}

//所有在线用户
var onlineMap map[string]client

//全局消息
var massage = make(chan string)

//广播msg
func msgManager() {
	//初始化online map
	onlineMap = make(map[string]client)
	//循环全局消息channe是否有数据
	for {
		msg := <-massage
		//循环所有在线用户广播消息
		for _, clen := range onlineMap {
			clen.c <- msg
		}

	}

}

//处理单个request的消息
func writeMsgToClent(c client, conn net.Conn) {
	for msg := range c.c {
		conn.Write([]byte(msg + "\n"))
	}
}

//制作用户消息
func makeMsg(clnt client, msg string) (buf string) {
	buf = "【" + clnt.addr + "】" + ": " + clnt.name + msg
	return
}

//处理每1个request
func handlerRequest(conn net.Conn) {
	defer conn.Close()
	netAddr := conn.RemoteAddr().String()
	c := client{
		name: netAddr,
		c:    make(chan string),
		addr: netAddr,
	}
	onlineMap[netAddr] = c
	//创建【小管家】专门从全局消息中接收数据回复客户端
	go writeMsgToClent(c, conn)
	//向全局消息推送消息
	massage <- makeMsg(c, "上线了")
	//判断用户退出状态
	isQuit := make(chan bool)
	//判断用户是否活跃
	isActive := make(chan bool)
	
	//创建1个匿名go程 专门处理用户发送的消息
	go func() {
		buf := make([]byte, 4096)
		for {
			n, err := conn.Read(buf)
			if n == 0 {
				// fmt.Printf("检查到客户端:%s退出", c.name)
				isQuit <- true
				return
			}
			if err != nil {
				fmt.Printf("客户端%s连接出错", c.name)
				return
			}
			msg := string(buf[:n])
			//查看在线用户
			if msg == "who" {
				for _, user := range onlineMap {
					conn.Write([]byte(user.name))
				}
				//用户输入了exit
			} else if msg == "exit" {
				isQuit <- true
			} else { //将读到的消息广播给在线用户
				fmt.Println(msg)
				massage <- makeMsg(c, msg)
			}
			//keep alive 
			isActive <- true

		}
	}()
	
	//循环检查request conn的状态
	for {
		select {
		//用户退出
		case <-isQuit:
			//为了关闭子gorutine也要关闭
			close(c.c)
			delete(onlineMap, c.name)
			massage <- makeMsg(c, "退出")
			return
		//用户活跃
		case <-isActive:
		//5钟不输入超时
		case <-time.After(time.Minute * 5):
			close(c.c)
			delete(onlineMap, c.name)
			massage <- makeMsg(c, "退出")
			return
		}

	}

}

func main() {
	listener, err := net.Listen("tcp", "127.0.0.1:8001")
	if err != nil {
		fmt.Println("server listen err", err)
		return
	}
	defer listener.Close()
	//创建【大管家】msgManager负责向全局map向所有在线用户广播消息
	go msgManager()
	//循环监控客户端请求
	for {
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("accept error", err)
			return
		}
		go handlerRequest(conn)
	}
}

  

 

client

package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
	"strings"
)

//tcp client

func main() {
	//1.与server(127.0.0.1:8001)端建立连接(1.client 发送 syn包给 server)
	conn, err := net.Dial("tcp", "127.0.0.1:8001")
	defer conn.Close()
	if err != nil {
		fmt.Println("The socket you dialed is bussy now,pleale redial agin later!", err)
	}

	//2.发送和接收数据
	var serverMsg [4096]byte
	reader := bufio.NewReader(os.Stdin)
	for {
		n, _ := conn.Read(serverMsg[:])
		fmt.Println(string(serverMsg[:n]))
		fmt.Print("----->: ")
		msg, _ := reader.ReadString('\n')
		msg = strings.TrimSpace(msg)
		if msg == "exit" {
			break
		}
		conn.Write([]byte(msg))
	}

}

  

 

3.HTTP协议的服务端和客户端

Golang工具包
    




SQLAlchemy

 

 

HTTP服务端

ps:net/http源码内部使用了gorutine,每个request都会有对应gorutine去处理,所以性能比较强悍。

做web开发关注这块,什么Django/Flashk/Tornado(写接口)

 

package main

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

//page
func indexView(response http.ResponseWriter, request *http.Request) {
	b, err := ioutil.ReadFile("./templates/index.html")
	if err != nil {
		response.Write([]byte(fmt.Sprintf("%v\n", err)))
	}
	response.Write(b)

}

//api
func indexAPIView(response http.ResponseWriter, request *http.Request) {
	//获取请求的url
	fmt.Println(request.URL)
	contentType := request.Header.Get("Content-Type")
	switch request.Method {
	case "GET": //http GET请求参数都放在请求头中,请求体里面没有数据!
		//获取请求方法
		fmt.Println(request.Method) //GET

		//获取Get请求参数
		quaryParam := request.URL.Query()
		fmt.Println(quaryParam.Get("dataType"))
		//文件
		//request.MultipartForm
		//在服务端获取request请求的body
		fmt.Println(ioutil.ReadAll(request.Body))
		response.Write([]byte("string oK"))

	case "POST":
		switch contentType {
		//处理form提交
		case "application/x-www-form-urlencoded":
			if err := request.ParseForm(); err != nil {
				fmt.Fprintf(response, "ParseForm() err: %v", err)
				return
			}
			name := request.FormValue("name")
			age := request.FormValue("age")
			fmt.Println(name, age)
			response.Write([]byte("form oK"))
		//处理josn提交
		case "application/json":
			//设置1个映射json的struct
			type person struct {
				Name string `json:"name" db:"name" ini:"name"`
				Age  uint8  `json:"age" db:"name" ini:"name"`
			}
			var p1 person
			//获取[]byte
			body, err := ioutil.ReadAll(request.Body)
			if err != nil {
				fmt.Printf("read body err, %v\n", err)
				return
			}
			//解析
			json.Unmarshal(body, &p1)
			fmt.Println(p1.Name)

			//
			type data struct {
				Status  bool   `json:"status"`
				Message string `json:"message"`
			}
			d := data{
				Status:  true,
				Message: "application/json",
			}
			b, _ := json.Marshal(d)
			response.Write(b)

		}

	}

}

func main() {
	http.HandleFunc("/index/", indexView)
	http.HandleFunc("/api/index/", indexAPIView)
	//放在路由的下面
	http.ListenAndServe("127.0.0.1:8001", nil)
}

    

  

HTTP客户端

做运维、测试、爬虫关注这块(接口测试、调用个API、写个爬虫)

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
)

func main() {
	//1.模拟浏览器向server端发起GET请求
	response, err := http.Get("http://127.0.0.1:8001/api/index/?dataType=string")
	if err != nil {
		fmt.Println("请求错误")
		return
	}
	//把返回的数据读出来
	//在Golang中不管是文件、网络IO都实现了Read方法都可以使用ioutill读取数据
	b, err := ioutil.ReadAll(response.Body)
	if err != nil {
		fmt.Println("Read faild")
	}
	fmt.Println(string(b))

	//2.模拟浏览器发起form Post请求
	formData := make(url.Values)
	formData.Add("name", "张根")
	formData.Add("age", "18")
	formPayLoad := formData.Encode()

	formRequest, _ := http.Post(
		"http://127.0.0.1:8001/api/index/",
		"application/x-www-form-urlencoded",
		strings.NewReader(formPayLoad),
	)

	formResponse, _ := ioutil.ReadAll(formRequest.Body)
	fmt.Println(string(formResponse))
	formRequest.Body.Close()

	//发 json数据
	josnData := struct {
		Name string `json:"name"`
		Age  int    `json:"age"`
	}{
		Name: "张根",
		Age:  18,
	}
	josnPayLoad, _ := json.Marshal(josnData)
	josnRequest, _ := http.Post(
		"http://127.0.0.1:8001/api/index/",
		"application/json",
		bytes.NewReader(josnPayLoad),
	)

	josnResponse, _ := ioutil.ReadAll(josnRequest.Body)
	fmt.Println(string(josnResponse))
	josnRequest.Body.Close()

}

  

设置http客户端head参数

在爬虫请求次数频繁的业务场景下:       使用共用1个固定的HTTP客户端设置keepalive, 限制服务器开启socket的数量和每次建立TCP连接的开销。

在爬虫请求次数不频繁的业务场景下: 使用多个HTTP客户端禁用 keepalive保证数据获取完毕之后socket资源及时的释放掉。

package main

import (
	// "crypto/tls"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io/ioutil"
	"net/http"
)

func main() {

	//创建1个保存所有网站CA证书的pool
	pool := x509.NewCertPool()
	//CA证书的路径
	caCertPath := "ca.crt"
	//获取CA证书
	caCrt, err := ioutil.ReadFile(caCertPath)
	//CA证书获取失败
	if err != nil {
		fmt.Println("ReadFile err:", err)
		return
	}
	//把证书添加到证书pool
	pool.AppendCertsFromPEM(caCrt)

	tr := &http.Transport{
		//支持TLS(https)
		TLSClientConfig: &tls.Config{RootCAs: pool},
		//支持压缩包
		DisableCompression: true,
		//禁用keepalive:根据情况
		DisableKeepAlives: true,
	}

	client := &http.Client{Transport: tr}

	resp, _ := client.Get("https://example.com")
	formResponse, _ := ioutil.ReadAll(resp.Body)
	fmt.Println(string(formResponse))
	//记得关闭客户端连接
	resp.Body.Close()
}

  

4.WebSockt协议的服务端和客户端

Golang工具包
    




SQLAlchemy

 

websocket是1种在单个TCP连接上利用http协议发送握手信息后,客户端和服务端捂手建立全双工通信的协议。

特点:服务端也可以主动向客户端推送消息。

get -u -v github.com/gorilla/websocket

 

 

 

flag包可以帮助我们获取解析命令行参数(用户调用执行go程序时后面指定的参数),比Python中的sys包中的sys.argv功能强大些 。写一些go脚本的时候蛮合适的!

os.Args

等同于Python中的sys.args
package main

import (
	"fmt"
	"os"
)

func main() {
	//os.Args()获取命令行用户输入,是1个切片类型的变量
	fmt.Println(os.Args)
	//和Python和shell一致:第一个参数是程序本身
	fmt.Println(os.Args[0])
	//其他参数以此类推
}

  

flag包基本使用

etct start --gotuineCount=10000

如果我想在支持程序时 支持这种调用方式,os.Args将会不容易实现。

boolintint64uintuint64floatfloat64stringduration

 

定义命令行flag参数

 flag.String(解析为指针类型)

package main

import (
	"flag"
	"fmt"
	"time"
)

//flag获取并解析命令行参数
func main() {
	//创建1个标志位参数:参数名为name,Martin为默认值,然后是提示帮助信息
	name := flag.String("name", "Martin", "请输入姓名")
	age := flag.Int("age", 18, "请输入年龄")
	married := flag.Bool("married", false, "请输入婚否?")
	timeOfmarriage := flag.Duration("tofm", time.Hour*1, "结婚多久了?")
	flag.Parse()
	//注意 flag返回的是指针类型的变量
	fmt.Println(*name)
	fmt.Println(*age)
	fmt.Println(*married)
	fmt.Println(*timeOfmarriage)
	fmt.Printf("%T\n", *timeOfmarriage)

}

 

 flag.StringVar(非指针类型)

如果以上面的方式解析命令行参数,返回的是指针类型的变量,不方便你在程序你使用它!

package main

import (
	"flag"
	"fmt"
	"time"
)

//flag获取并解析命令行参数
func main() {
	var (
		name           string
		age            int
		married        bool
		timeOfmarriage time.Duration
	)
	//flag.StringVar
	flag.StringVar(&name, "name", "Martin", "请输入姓名")
	flag.IntVar(&age,"age", 18, "请输入年龄")
	flag.BoolVar(&married,"married", false, "请输入婚否?")
	flag.DurationVar(&timeOfmarriage,"tofm", time.Hour*1, "结婚多久了?")
	flag.Parse()

	fmt.Println(name)
	fmt.Println(age)
	fmt.Println(married)
	fmt.Println(timeOfmarriage)
	fmt.Printf("%T\n", timeOfmarriage)
	//其他方法
	fmt.Println(flag.Args())//获取没有设置了命令标志(-或--)的参数
	fmt.Println(flag.NArg())//获取所有参数的个数
	fmt.Println(flag.NFlag())//获取设置了命令标志(-或--)的参数 使用前有-的参数个数

}

  

 

结果

D:\goproject\src\hello\os.args>main.exe -name=tom -tofm=100h s
tom
18
false
100h0m0s
time.Duration
[s]
1
2

  

 

  

Go语言操作MySQL

 

database/sql标准库

我们要连接数据库需要使用database/sql标准库,但是golang内置的标准库databse/sql没有具体连接数据库,只是列出 第三方库需要实现连接的内容。

所以需要下载第三方的驱动。

func init() {
	sql.Register("mysql", &MySQLDriver{})
}

  

go-sql-driver/mysql驱动下载

        update 所有依赖到最新
go get -u github.com/go-sql-driver/mysql

database/sql包原生实现了数据库连接池,并且并发安全。

 

创建连接池

go-sql-driver原生支持连接池并支持并发,所有可以同时使用多个gorutine从连接池中获取数据库连接。

package main

import (
	"database/sql"
	"fmt"

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

//全局连接池对象
var db *sql.DB

func initDB() (err error) {
	//数据库信息
	dsn := "zhanggen:123.com@tcp(192.168.56.133:3306)/golang"
	//验证数据库信息
	db, err = sql.Open("mysql", dsn)
	if err != nil {
		return
	}
	//尝试连接数据库
	err = db.Ping()
	if err != nil {
		return
	}
	//最大连接
	db.SetMaxOpenConns(20)
	//最大空闲连接数(至少保留几个连接)
	db.SetMaxIdleConns(5)
	return

}

  

QueryRow(查询单条记录)

 

package main

import (
	"database/sql"
	"fmt"

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

//全局连接池对象
var db *sql.DB

func initDB() (err error) {
	//数据库信息
	dsn := "zhanggen:123.com@tcp(192.168.56.133:3306)/golang"
	//验证数据库信息
	db, err = sql.Open("mysql", dsn)
	if err != nil {
		return
	}
	//尝试连接数据库
	err = db.Ping()
	if err != nil {
		return
	}
	//最大连接
	db.SetMaxOpenConns(20)
	//最大空闲连接数(至少保留几个连接)
	db.SetMaxIdleConns(5)
	return

}

type user struct {
	name string
	sex  string
	age  int
}

func insertOne() {
	sqlStr := "insert into studentds_info(name,sex,age) values (?,?,?)"
	ret, err := db.Exec(sqlStr, "王五", "男", 38)
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return
	}
	theID, err := ret.LastInsertId() // 新插入数据的id
	if err != nil {
		fmt.Printf("get lastinsert ID failed, err:%v\n", err)
		return
	}
	fmt.Printf("insert success, the id is %d.\n", theID)
}

//QueryRow查询1条记录
func queryOne(sql string) {
	//从连接池中获取1个连接
	rowObj := db.QueryRow(sql)
	//获取结果
	var user1 user
	//获取数据库对象并释放连接
	rowObj.Scan(&user1.name, &user1.sex, &user1.age)
	//打印结果
	fmt.Printf("%#v", user1)

}

func main() {
	err := initDB()
	if err != nil {
		fmt.Println("数据库连接失败", err)
		return
	}
	fmt.Println("数据库连接成功!")
	// insertOne()
	sql := "select name,sex,age from studentds_info where id=1"
	queryOne(sql)
}

 

Query(查询多条记录)

query一定要手动释放持有的数据库连接到连接池(否则会导致连接池连接资源马上枯竭!)
func queryMore(sql string) {
	ret := make([]user, 0, 2)
	//0sql的参数
	rows, err := db.Query(sql, 0)
	if err != nil {
		fmt.Println("query 查询失败", err)
		return
	}
	//query一定要手动释放持有的数据库连接到连接池(否则连接池资源枯竭了!)
	defer rows.Close()
	//循环读取(只要有下一条)
	for rows.Next() {
		var u user
		err := rows.Scan(&u.name, &u.sex, &u.age)
		if err != nil {
			fmt.Println("scan失败", err)
			return
		}
		//封装到slince [user1,user2....]
		ret = append(ret, u)

	}
	fmt.Printf("%#v\n", ret)
}

func main() {
	err := initDB()
	if err != nil {
		fmt.Println("数据库连接失败", err)
		return
	}
	fmt.Println("数据库连接成功!")
	sql := "select name,sex,age from studentds_info where age >?;"
	queryMore(sql)

}

 

Exec(insert/update/remove) 

 在更新数据时一定要记得 rows.RowsAffected()使update sql语句生效。跟pymysql的commit()一样!

package main

import (
	"database/sql"
	"fmt"

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

//全局连接池对象
var db *sql.DB

func initDB() (err error) {
	//数据库信息
	dsn := "zhanggen:123.com@tcp(192.168.56.133:3306)/golang"
	//验证数据库信息
	db, err = sql.Open("mysql", dsn)
	if err != nil {
		return
	}
	//尝试连接数据库
	err = db.Ping()
	if err != nil {
		return
	}
	//最大连接
	db.SetMaxOpenConns(20)
	//最大空闲连接数(至少保留几个连接)
	db.SetMaxIdleConns(5)
	return

}

type user struct {
	name string
	sex  string
	age  int
}

//插入数据
func insertRow(sql string) {
	ret, err := db.Exec(sql, "王五1", "男", 38)
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return
	}
	// 新插入数据的id
	theID, err := ret.LastInsertId() 
	if err != nil {
		fmt.Printf("get lastinsert ID failed, err:%v\n", err)
		return
	}
	fmt.Printf("insert success, the id is %d.\n", theID)
}


//update数据
func updateRow(sql string){
	rows, err :=db.Exec(sql,18,"王五1")
	if err != nil {
		fmt.Printf("更新数据failed, err:%v\n", err)
		return
	}
	//使update sql语句生效
	n, err := rows.RowsAffected() 
	if err != nil {
		fmt.Printf("没有查询到 err:%v\n", err)
		return
	}
	fmt.Printf("更新完成:受影响的行 %d.\n", n)


}


//QueryRow查询1条记录
func queryOne(sql string) {
	//从连接池中获取1个连接
	rowObj := db.QueryRow(sql)
	//获取结果
	var user1 user
	//获取数据库对象并释放连接
	rowObj.Scan(&user1.name, &user1.sex, &user1.age)
	//打印结果
	fmt.Printf("%#v", user1)

}

func queryMore(sql string) {
	ret := make([]user, 0, 2)
	//0是sql占位符?需要替换的参数
	rows, err := db.Query(sql, 0)
	if err != nil {
		fmt.Println("query 查询失败", err)
		return
	}
	//query一定要手动释放持有的数据库连接到连接池(否则连接池资源枯竭了!)
	defer rows.Close()
	//循环读取(只要有下一条)
	for rows.Next() {
		var u user
		err := rows.Scan(&u.name, &u.sex, &u.age)
		if err != nil {
			fmt.Println("scan失败", err)
			return
		}
		//封装到slince [user1,user2....]
		ret = append(ret, u)

	}
	fmt.Printf("%#v\n", ret)
}

func main() {
	err := initDB()
	if err != nil {
		fmt.Println("数据库连接失败", err)
		return
	}
	fmt.Println("数据库连接成功!")
	sql:="insert into studentds_info(name,sex,age) values (?,?,?)"
	insertRow(sql)
	sql="update studentds_info set age=? where name=?"
	updateRow(sql)
	sql= "select name,sex,age from studentds_info where age >?;"
	queryMore(sql)

}

  

事物操作

事物就是保证 N个SQL组合成1组:它们要么全部执行! 要么全部不执行!它们之间是1个不可分割的工作单位。

func transaction(){
	//开始事物操作
	tx,err:=db.Begin()
	if err!=nil{
		fmt.Println("事物开启失败")
		return
	}
	//执行1组sql操作
	addSQL:="update studentds_info set age=age+1 where name=?"
	subSQL:="update studentds_info set age=age-1 where name=?"
	//执行给王五加钱操作
	_,err=tx.Exec(addSQL,"王五")
	if err!=nil{
		//加钱时断电了要回滚
		tx.Rollback()
		fmt.Println("加钱时断电了,已经回滚")
		return
	}
	//执行给二狗减钱操作
	_,err=tx.Exec(subSQL,"二狗")
	if err!=nil{
		//减钱时断电了要回滚
		tx.Rollback()
		fmt.Println("减钱时断电了,已经回滚")
	}
	//提交事物操作
	tx.Commit()
	fmt.Println("二狗赠送王五大宝剑成功!")

}

  

 

 

sqlx使用

sqlx使用了反射机制,在变量赋值的时候

 

安装

go get github.com/jmoiron/sqlx

  

连接池

package main

import (
	"fmt"

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

var db *sqlx.DB

func initDB() (err error) {
	dbinfo := "zhanggen:123.com@tcp(192.168.56.133:3306)/golang"
	db, err = sqlx.Connect("mysql", dbinfo)
	if err != nil {
		fmt.Println("数据库连接错误!")
		return
	}
	db.SetMaxOpenConns(10)
	db.SetMaxIdleConns(5)
	return
}

  

Get和Select查询

sqlx和go-sql-driver的差别主要体现在这里!
//go-sql
var u user
err := rows.Scan(&u.name, &u.sex, &u.age)
//sqlx
var u user
db.Get(&u, sql, 38)
package main

import (
	"fmt"

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

var db *sqlx.DB

func initDB() (err error) {
	dbinfo := "zhanggen:123.com@tcp(192.168.56.133:3306)/golang"
	db, err = sqlx.Connect("mysql", dbinfo)
	if err != nil {
		fmt.Println("数据库连接错误!")
		return
	}
	db.SetMaxOpenConns(10)
	db.SetMaxIdleConns(5)
	return
}

type user struct {
	Name string
	Sex  string
	Age  int
}

func main() {
	err := initDB()
	if err != nil {
		fmt.Println("sqlx初始化失败", err)
		return
	}
	sql := "select name,sex,age from studentds_info where age=?"
	/*
	sqlx和go-sql-driver的差别主要体现在这里!
	err := rows.Scan(&u.name, &u.sex, &u.age)
	db.Get(&u, sql, 38)
	由于内部源码是 reflect实现所以,我们无需scan 结构体的所有字段
	但是为了给结构体赋值,我们必须传递指结构体针保、字段大写,才能被起码包调用到!
	*/
	var u user
	//注意Get()必须接受指针类型struct 的变量,而且字段大写!
	db.Get(&u, sql, 38)
	fmt.Printf("%#v\n", u)

	var userList []user
	sql = "select name,sex,age from studentds_info where age>?"
	db.Select(&userList, sql, 18)
	fmt.Printf("%#v\n", userList)

}

 

Exec(insert/update/remove) 

package main

import (
	"fmt"

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

var db *sqlx.DB

type user struct {
	Name string
	Sex  string
	Age  int
}

func initDB() (err error) {
	dbinfo := "zhanggen:123.com@tcp(192.168.56.133:3306)/golang"
	db, err = sqlx.Connect("mysql", dbinfo)
	if err != nil {
		fmt.Println("数据库连接错误!")
		return
	}
	db.SetMaxOpenConns(10)
	db.SetMaxIdleConns(5)
	return
}

func insert(SQL string) {
	row, err := db.Exec(SQL, "sss", "男", 19)
	if err != nil {
		fmt.Printf("插入行失败,err:%v\n", err)
		return
	}
	theID, err := row.LastInsertId() // 新插入数据的id
	if err != nil {
		fmt.Println("获取插入数据id失败", err)
		return
	}
	fmt.Println("插入数据成功id:", theID)
}

func update(SQL string) {
	ret, err := db.Exec(SQL, "Tom","男",19,"Martin")
	if err != nil {
		fmt.Printf("update failed, err:%v\n", err)
		return
	}
	n, err := ret.RowsAffected() // 操作影响的行数
	if err != nil {
		fmt.Printf("get RowsAffected failed, err:%v\n", err)
		return
	}
	fmt.Printf("update success, affected rows:%d\n", n)
}

// 删除数据
func deleteRow(SQL string) {
	ret, err := db.Exec(SQL, "Tom")
	if err != nil {
		fmt.Printf("delete failed, err:%v\n", err)
		return
	}
	n, err := ret.RowsAffected() // 操作影响的行数
	if err != nil {
		fmt.Printf("get RowsAffected failed, err:%v\n", err)
		return
	}
	fmt.Printf("delete success, affected rows:%d\n", n)
}

func main() {
	err := initDB()
	if err != nil {
		fmt.Println("sqlx初始化失败", err)
		return
	}
	sql := "insert into studentds_info(name,sex,age)value(?,?,?)"
	insert(sql)
	sql= "update studentds_info set name=?,sex=?,age=? where name=?"
	update(sql)
	sql= "delete from studentds_info  where name = ?"
	deleteRow(sql)

}

  

 namedExec

通过结构体修改数据库行

//name exec
func namedExec(SQL string) (err error) {
	_, err = db.NamedExec(SQL,
		map[string]interface{}{
			"name": "张根",
			"sex":  "男",
			"age":  26,
		})
	return
}

func main() {
	err := initDB()
	if err != nil {
		fmt.Println("sqlx初始化失败", err)
		return
	}
	sql := "insert into studentds_info (name,sex,age) values (:name,:sex,:age)"
	namedExec(sql)


}

 

sqlx事物操作

unc transactionDemo2()(err error) {
	tx, err := db.Beginx() // 开启事务
	if err != nil {
		fmt.Printf("begin trans failed, err:%v\n", err)
		return err
	}
	defer func() {
		if p := recover(); p != nil {
			tx.Rollback()
			panic(p) // re-throw panic after Rollback
		} else if err != nil {
			fmt.Println("rollback")
			tx.Rollback() // err is non-nil; don't change it
		} else {
			err = tx.Commit() // err is nil; if Commit returns error update err
			fmt.Println("commit")
		}
	}()

	sqlStr1 := "Update user set age=20 where id=?"

	rs, err := tx.Exec(sqlStr1, 1)
	if err!= nil{
		return err
	}
	n, err := rs.RowsAffected()
	if err != nil {
		return err
	}
	if n != 1 {
		return errors.New("exec sqlStr1 failed")
	}
	sqlStr2 := "Update user set age=50 where i=?"
	rs, err = tx.Exec(sqlStr2, 5)
	if err!=nil{
		return err
	}
	n, err = rs.RowsAffected()
	if err != nil {
		return err
	}
	if n != 1 {
		return errors.New("exec sqlStr1 failed")
	}
	return err
}

  

sql注入

sql注入产生的root cause 是字符串拼接。

select name,sex,age from studentds_info where name='xxx' or 1=1 #别拼了

如果SQL是拼接而成的就意味着这条SQL出现了缝隙,有了这个缝隙hack就可以乘虚而入: 迎合sql前一部分【干自己想干的】注释掉后一部分。

真佩服第1个搞这个事情的人!~~~~~方法总比困难多!!!

func sqlInject(name string){
	//自己拼接sql语句
	sql:=fmt.Sprintf("select name,sex,age from studentds_info where name='%s'",name)
	//
	fmt.Println(sql)
	var users []user
	err:=db.Select(&users,sql)
	if err!=nil{
		fmt.Println("查询失败",err)
		return
	}
	fmt.Println(users)

	

}
func main() {
	err:=initDB()
	if err!=nil{
		fmt.Println("初始化数据库失败")
	}
	//哈哈:这个比较绝脱裤了
	name:="xxx' or 1=1 # "
	//select name,sex,age from studentds_info where name='xxx' or 1=1 # '
	//这个不好实现因为你不知道对方的数据名称
	name="xxx' and (select count(*) from studentds_info) <10 #"
	sqlInject(name)


}

  

sql预处理

一条完整的sql语句=MySQL规定的sql语法部分+用户参数部分

sql预处理:就是把一条完整的sql语句划分为2部分 sql语法部分、用户参数部分,sql语句部分先发送到mysqld保存, 后期每次用户发送数据,在mysqld(服务端)完成字符串和sql语句的拼接!

在重复执行多个相同sql逻辑的sql语句时,无非就是sql要替换的字符串不同而已,所有sql预处理在这种场景下既可以提供sql的执行效率,也可以 防止sql 注入。

普通SQL语句执行过程:

  1. 客户端对SQL语句进行占位符替换得到完整的SQL语句。
  2. 客户端发送完整SQL语句到MySQL服务端
  3. MySQL服务端执行完整的SQL语句并将结果返回给客户端。 

预处理执行过程:

  1. 把SQL语句分成两部分,命令部分与数据部分。
  2. 先把命令部分发送给MySQL服务端,MySQL服务端进行SQL预处理。
  3. 然后把数据部分发送给MySQL服务端,MySQL服务端对SQL语句进行占位符替换。
  4. MySQL服务端执行完整的SQL语句并将结果返回给客户端。

 

批量插入(预处理)

//插入多条记录
func insertBatch(sql string, age int) {
	defer wg.Done()
	//先把没有字符串拼接的sql发到mysql端
	stmt, err := db.Prepare(sql)
	if err != nil {
		fmt.Printf("sql预处理 failed, err:%v\n", err)
		return
	}
	defer stmt.Close()
	//再把用户输入的参数发生到mysqld
	ret, err := stmt.Exec("二狗", "男", age)
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return
	}
	fmt.Printf("insert success, the id is %d.\n", ret)
}

  

批量查询 

func queryBatch(sql string) {
	ret := make([]user, 0, 2)
	//准备sql部分
	stmt, err := db.Prepare(sql)
	if err != nil {
		fmt.Println("预处理失败", err)
		return
	}
	defer stmt.Close()
	//准备用户输入部分
	rows, err := stmt.Query(0)
	if err != nil {
		fmt.Printf("query failed, err:%v\n", err)
		return
	}
	//query一定要手动释放持有的数据库连接到连接池(否则连接池资源枯竭了!)
	defer rows.Close()
	//循环读取(只要有下一条)
	for rows.Next() {
		var u user
		err := rows.Scan(&u.name, &u.sex, &u.age)
		if err != nil {
			fmt.Println("scan失败", err)
			return
		}
		//封装到slince [user1,user2....]
		ret = append(ret, u)

	}
	fmt.Printf("%#v\n", ret)
} 

 

 go操作Redis

 

安装

go get -u github.com/go-redis/redis

  

连接

1.普通连接

func initRedis() (err error) {
	//给全局变量赋值!!注意不要 :=
	redisdb = redis.NewClient(&redis.Options{
		Addr:     "192.168.56.133:6379",
		Password: "",
		DB:       0,
	})
	_, err = redisdb.Ping().Result()
	
	return

}

  

 

2.连接Redis哨兵模式

func initClient()(err error){
	rdb := redis.NewFailoverClient(&redis.FailoverOptions{
		MasterName:    "master",
		SentinelAddrs: []string{"x.x.x.x:26379", "xx.xx.xx.xx:26379", "xxx.xxx.xxx.xxx:26379"},
	})
	_, err = rdb.Ping().Result()
	if err != nil {
		return err
	}
	return nil
}

  

 

3.连接Redis集群

func initClient()(err error){
	rdb := redis.NewClusterClient(&redis.ClusterOptions{
		Addrs: []string{":7000", ":7001", ":7002", ":7003", ":7004", ":7005"},
	})
	_, err = rdb.Ping().Result()
	if err != nil {
		return err
	}
	return nil
}

  

3.set和get数据

Redis支持诸如字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)、带范围查询的排序集合(sorted sets)、位图(bitmaps)、hyperloglogs、带半径查询和流的地理空间索引等数据结构(geospatial indexes)。

package main

import (
	// "encoding/json"
	"encoding/json"
	"fmt"

	"github.com/go-redis/redis"
)

//全局连接
var redisdb *redis.Client

//初始化Redis
func initRedis() (err error) {
	//给全局变量赋值!!注意不要 :=
	redisdb = redis.NewClient(&redis.Options{
		Addr:     "192.168.56.133:6379",
		Password: "",
		DB:       0,
	})
	_, err = redisdb.Ping().Result()
	
	return

}



type person struct {
	Name string `json:"name"`
	Age  uint8  `json:"age"`
}

//制造json数据
func newPerson(name string, age uint8) []byte {
	p1 := person{
		Name: name,
		Age:  age,
	}
	datas, _ := json.Marshal(p1)
	return datas
}

func main() {
	err := initRedis()
	//ZsetKey redis中数据类型有序集合使用于排行榜、轮班啊
	var ZsetKey = "Ranking11"
	var items = []*redis.Z{
		&redis.Z{Score: 90, Member: "William1"},
		&redis.Z{Score: 91, Member: "Martin2"},
		&redis.Z{Score: 92, Member: "Chris1"},
		&redis.Z{Score: 93, Member: "Tom1"}}
	if err != nil {
		fmt.Println("Redis连接失败", err)
		return
	}

	//把所有值班人员都追加到key
	redisdb.ZAdd(ZsetKey, items...)
	redisdb.ZRem(ZsetKey)
	// 给某个运维值班加10分
	newScore, err := redisdb.ZIncrBy(ZsetKey, 10, "Martin1").Result()
	if err != nil {
		fmt.Printf("加分失败, err:%v\n", err)
		return
	}
	fmt.Printf("添加成功,最新分数%v\n", newScore)

	//获取最高的分(2个)
	ret, err := redisdb.ZRevRangeWithScores(ZsetKey, 0, 0).Result()
	if err != nil {
		fmt.Printf("zrevrange failed, err:%v\n", err)
		return
	}
	for _, z := range ret {
		fmt.Println(z.Member, z.Score)
	}

	//获取95~100分的
	op := &redis.ZRangeBy{
		Min: "80",
		Max: "100",
	}
	ret, err = redisdb.ZRangeByScoreWithScores(ZsetKey, op).Result()
	if err != nil {
		fmt.Printf("zrangebyscore failed, err:%v\n", err)
		return
	}
	for _, z := range ret {
		fmt.Println(z.Member, z.Score)
	}

	for _, z := range ret {
		fmt.Println(z.Member, z.Score)
	}
	//存储json
	redisdb.Set("name", newPerson("Sally", 24), 0)
	//获取json
	val, err := redisdb.Get("name").Result()
	var p2 person
	json.Unmarshal([]byte(string(val)),&p2)
	fmt.Printf("%#v",p2)

}


redigo包

如果你感觉go-redis的API不支持直接输入redis 命令,可以使用redigo。

Features

package main

import (
	"fmt"
	"github.com/gomodule/redigo/redis"
	"time"
)

//创建Redis 连接池
func NewRdisPool(addr, pwd string) (pool *redis.Pool) {
	return &redis.Pool{
		MaxIdle:     60,
		MaxActive:   200,
		IdleTimeout: time.Second,
		Dial: func() (redis.Conn, error) {
			conn, err := redis.Dial("tcp", addr)
			if err != nil {
				fmt.Println("连接redis数据库失败", err)
				_ = conn.Close()
				return nil, err
			}
			//如果需要密码!
			if len(pwd) > 1 {
				if _, err := conn.Do("AUTH", pwd); err != nil {
					_ = conn.Close()
					fmt.Println("密码错误", err)
					return conn, err
				}
			}
			return conn, err
		},
		//连接redis 连接池测试
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			_, err := c.Do("ping")
			return err
		},
	}
}

func main() {
	//初始化1个连接池
	pool := NewRdisPool("192.168.56.18:6379", "123.com")
	//从连接池中获取连接
	conn := pool.Get()
	//最后关闭连接、关闭连接池
	defer conn.Close()
	pool.Close()
	//set值
	_, err := conn.Do("set", "name", "zhanggen")
	if err != nil {
		fmt.Println("set值失败")
	}
	//获取值
	value, err := conn.Do("get", "name")
	if err != nil {
		fmt.Println("获取值失败")
	}
	fmt.Println(value)

}

  

 

 

 

sarama

sarama是go连接kafka的模块,由于v1.19.0之后需要gcc环境如果是windown平台要下载指定版本。

 

下载

D:\goproject\src\go相关模块\kafka>SET GO111MODULE=on
D:\goproject\src\go相关模块\kafka>SET GOPROXY=http://goproxy.cn
D:\goproject\src\go相关模块\kafka>go mod init
go: creating new go.mod: module go相关模块/kafka
D:\goproject\src\go相关模块\kafka>go mod download
go: finding github.com/Shopify/sarama v1.19.0
D:\goproject\src\go相关模块\kafka>go build
go: finding github.com/rcrowley/go-metrics latest
go: finding github.com/eapache/go-xerial-snappy latest
go: finding github.com/golang/snappy v0.0.1
go: downloading github.com/golang/snappy v0.0.1
D:\goproject\src\go相关模块\kafka>kafka.exe
连接成功!
分区ID:0, offset:0

 

使用

package main

import (
	"fmt"
	"github.com/Shopify/sarama"
)

func main() {
	config := sarama.NewConfig()
	config.Producer.RequiredAcks = sarama.WaitForAll          //赋值为-1:这意味着producer在follower副本确认接收到数据后才算一次发送完成。
	config.Producer.Partitioner = sarama.NewRandomPartitioner //写到随机分区中,默认设置8个分区
	config.Producer.Return.Successes = true
	msg := &sarama.ProducerMessage{}
	msg.Topic = `nginx_log`
	msg.Value = sarama.StringEncoder("this is a good test")
	client, err := sarama.NewSyncProducer([]string{"192.168.56.133:9092"}, config)
	if err != nil {
		fmt.Println("producer close err, ", err)
		return
	}
	fmt.Println("Kafka连接成功!")
	defer client.Close()
	pid, offset, err := client.SendMessage(msg)
	if err != nil {
		fmt.Println("send message failed, ", err)
		return
	}
	fmt.Printf("分区ID:%v, offset:%v \n", pid, offset)
}

  

 

 

 

 

 taill模块

类似于Linux中的taill命令可以检测文件内容的变化,并支持文件重新打开。

什么是文件重新打开呢?

一般日志文件切割的策略是当文件内容到达1个size阀值之后,把当前的文件(nginx.log)重命名(nginx1),然后重新打开一个新的文件(nginx.log).......。

下载

go get -u github.com/hpcloud/tail

 

基本使用

package main

import (
	"fmt"
	"time"

	"github.com/hpcloud/tail"
)

func main() {
	fileName :="mylog.txt"
	config := tail.Config{
		ReOpen:    true,                                 //重新打开文件
		Follow:    true,                                 //跟随文件
		Location:  &tail.SeekInfo{Offset: 0, Whence: 2}, //从文件的哪个地方开始读
		MustExist: false,                                //文件不存在不报错
		Poll:      true,
	}
	tails, err := tail.TailFile(fileName, config)
	if err != nil {
		fmt.Println("文件打开失败", err)
	}
	var (
		line *tail.Line
		ok   bool
	)
	for {
		line, ok = <-tails.Lines
		if !ok {
			fmt.Printf("文件:%s关闭重新打开\n", fileName)
			time.Sleep(time.Second)
			continue
		}
		fmt.Println(line.Text)
	}

}

                                                   

我们在写项目的时候使用配置文件是常见的事情,那么如何把1个配置文件转换成go程序可以识别的数据类型呢?

总不能自己reflect.TypeOf用反射实现一个,unkown实现了1个第三方包。

$ go get gopkg.in/ini.v1

 

conf.ini配置文件

#服务端设置
[gin]
mode=debug
port=8002
#mysql相关配置
[mysql]
database=web
host=192.168.56.18
port=3306
username=zhanggen
password=123.com

 

定义需要映射的结构体

package config

import (
    "fmt"
    "gopkg.in/ini.v1"
)

//mysql配置 tag和ini文件保持一致
type MysqlConf struct {
    Database string `ini:"database"`
    Host     string `ini:"host"`
    Port     int    `ini:"port"`
    Username string `ini:"username"`
    Password string `ini:"password"`
}

//gin的配置 tag和ini文件保持一致
type ServerConf struct {
    Mode string `ini:"mode"`
    Port  int   `ini:"post"`
}
//一定要配置全局对象去反射ini文件里的全部session
type AllConf struct {
    MysqlConf MysqlConf `ini:"mysql"`
    GinConf  ServerConf  `ini:"gin"`
}
//单例模式
var ConfogObj = new(AllConf)

//开干
func init() {
    err := ini.MapTo(ConfogObj, "./config/conf.ini")
    if err!=nil{
        fmt.Println("ini配置文件解析错误",err)
        return
    }
}

 

  

     

使用orm可以提升我们的开发效率,他是对不同数据库sql的一层完美封装。

有了orm之后作为开发人员,并不需要去专注于不同sql的编写。

 Python的orm是

当然还有Django web框架中也有自己的orm,而Gang中也有1个使用起来很方便的orm工具gorm,它限于哪个web框架。

package main

import (
    "fmt"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "time"
)

type User struct {
    gorm.Model //自动增加 id、created_at、updated_at、deleted_at列
    Username   string
    Birthday   *time.Time
    Password   string
    Salary     int
}

func main() {
    //配置数据连接:注意设置parseTime=true否则会报错!unsupported Scan 时间类型的字段
    dbinfo := "YourUserName:YourPassWord@tcp(192.168.56.18:3306)/web?charset=utf8&parseTime=true"
    db, err := gorm.Open(mysql.Open(dbinfo), &gorm.Config{})
    if err != nil {
        fmt.Println(err)
        //panic("failed to connect database")
    }
    //配置一下数据连接参数!
    mySQL, err := db.DB()
    if err != nil {
        fmt.Println(err)
    }
    defer mySQL.Close()
    mySQL.SetMaxIdleConns(10)
    mySQL.SetMaxOpenConns(100)
    mySQL.SetConnMaxLifetime(time.Hour)

    // 迁移 schema表结构,指定表
    db.AutoMigrate(&User{})
    //删除表
    //db.Migrator().DropTable(&User{})

    ////Create 1条记录
    //birthday:=time.Now()
    //db.Create(&User{Username: "Bob",Password: "2018",Birthday:&birthday,Salary: 2018})
    //var userList = []User{
    //    {Username: "Saly",Password: "2019",Birthday:&birthday,Salary: 2019},
    //    {Username: "Jinzhu",Password: "2020",Birthday:&birthday,Salary: 2020},
    //    {Username: "WuMa",Password: "2021",Birthday:&birthday,Salary: 2021},
    //}
    ////创建多条记录
    //db.CreateInBatches(userList, len(userList))

    // First查看1条记录
    var person User
    db.First(&person) // 根据整形主键查找
    fmt.Println("----------------------", person.Username)
    //Find 查看全部记录
    var people []User
    db.Find(&people)
    fmt.Printf("本次查询到%d人----------\n", db.Find(&people).RowsAffected)
    for i, user := range people {
        fmt.Printf("%d----%s\n", i, user.Username)
    }
    //查看部分用户= SELECT * FROM users WHERE id IN (1,2,3)
    db.Find(&people, []int{1, 3})
    fmt.Printf("本次查询到%d人----------\n", db.Find(&people, []int{1, 3}).RowsAffected)
    for _, user := range people {
        fmt.Printf("%d----%s\n", user.ID, user.Username)
    }

    // Update
    var changPeson = User{}
    db.Model(&changPeson).Where("username = ?", "Bob").Update("Username", "Jacob")
    fmt.Println(changPeson.Username)

    // Update - 更新多个字段
    db.Model(User{}).Where("id in ?", []int{1, 4}).Updates(User{Salary: 200, Password: "xxx"})

    //Delete - 删除
    db.Delete(&User{}, 5)
}

 

html/template模板渲染包

都是web开发是数据驱动视图(前端页面显示),我们除了可以在前段JavaScript中发送ajax或者vue的axios从后端获取数据,然后赋值给前端变量。(前后、端配合完成数据驱动)

还可以在后端使用模板渲染也就是字符串替换的方式,把后端的数据赋值给前端的变量,然后将视图(html标签)和数据一起打包返回给浏览器。(在后端完成数据驱动视图)

Python的Flask使用Jia2,Django使用自带的模板引擎而Golang中的html/template包实现了数据驱动的模板。

 

 

{{.}}语法

模板语法都包含在{{  }}中间,其中{{ .}}中的点表示当前对象。

当我们把golang中的1个结构体传到模板之后就可以.字段名来获取值了.

前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1>您好</h1>
<h1>{{.Name }}</h1>
<h1>{{.Age }}</h1>
</body>
</html>

后端

package main

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

type Person struct {
    Name string
    Age  int
}

func handleBook(w http.ResponseWriter, r *http.Request) {
    method := r.Method
    if method == "GET" {
        //fmt.Fprintf(w,"hellow") //凡是实现了file接口的类型都可以fmt.Fprintf()写入数据
        //w.Write([]byte("您好!"))
        //data, err := ioutil.ReadFile("./home.html")
        //1.定义模板:{{.Age }}
        //2.解析模板:不要刻舟求剑,在goland中Run 'go build main.go否则找不到模板路径'
        t, err := template.ParseFiles("./home.html")
        if err != nil {
            fmt.Println("模板解析失败!")
        }
        userInfo := Person{Name: "张根", Age: 27}
        //3.渲染模板:字符串替换
        t.Execute(w, userInfo)
    }
}

func main() {
    http.HandleFunc("/book", handleBook)
    err := http.ListenAndServe(":9001", nil)
    if err != nil {
        fmt.Println(err)
        panic("服务启动失败!")
    }
}

 

后端传多个变量到模板

Django支持同时把多个变量传入模板也就是把所有变量组合成1个字典。

return render(request, "myapp/index.html", {"foo": "bar"})

html/templatet是把多个变量组合成map,需要注意的是map的key不用大写,而结构体的字段需要大小。

    data:=map[string]interface{}{
            "u1":person1,
            "u2":person2,
        }
        //3.渲染模板:字符串替换
        t.Execute(w, data)

代码

package main

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

//结构体渲染进模板时要大小写这种属性的可见性
type Person struct {
    Name string
    Age  int
}

func handleBook(w http.ResponseWriter, r *http.Request) {
    method := r.Method
    if method == "GET" {
        //fmt.Fprintf(w,"hellow") //凡是实现了file接口的类型都可以fmt.Fprintf()写入数据
        //w.Write([]byte("您好!"))
        //data, err := ioutil.ReadFile("./home.html")
        //1.定义模板:{{.Age }}
        //2.解析模板:不要刻舟求剑,在goland中Run 'go build main.go否则找不到模板路径'
        t, err := template.ParseFiles("./home.html")
        if err != nil {
            fmt.Println("模板解析失败!")
        }
        //map的渲染到模板语言时不需要把key进行大写!
        person1:= map[string]interface{}{"name": "Martin", "age": 18}
        person2:=Person{Name: "Bob",Age: 25}
        //Django的模板语言可以同时传多个参数,而template/html只能传1个,所以我对2个变量进行了打包装!
        data:=map[string]interface{}{
            "u1":person1,
            "u2":person2,
        }
        //3.渲染模板:字符串替换
        t.Execute(w, data)
    }
}

func main() {
    http.HandleFunc("/book", handleBook)
    err := http.ListenAndServe(":9001", nil)
    if err != nil {
        fmt.Println(err)
        panic("服务启动失败!")
    }
}

 

{{/* 注释内容 */}}语法

注意/*和*/一定要紧贴着{{ }}

{{/*
 注释内容
 */}}

 

定义和使用变量

<h1>{{$name:=.u2.Age}}</h1>
<h2>{{$name}}</h2>

 

去除左右两侧的空格

<h2>{{- $name -}}</h2>

 

管道符(pipeline)

<a href="/search?q={{. | urlquery}}">{{. | html}}</a>

 

比较函数

布尔函数会将任何类型的零值视为假,其余视为真。

下面是定义为函数的二元比较运算的集合:

eq      如果arg1 == arg2则返回真
ne      如果arg1 != arg2则返回真
lt      如果arg1 < arg2则返回真
le      如果arg1 <= arg2则返回真
gt      如果arg1 > arg2则返回真
ge      如果arg1 >= arg2则返回真

 

逻辑判断

{{$age:=.u2.Age}}
{{if lt $age 18 }}
    好好学习!
{{else if and (ge $age 18) (lt $age 30)}}
    好好谈对象!
{{else if and (ge $age 30) (lt $age 50)}}
    努力干活!
{{else}}
    享受人生!
{{end}}

 

rang循环

后端

people:=[]Person{{Name: "张三",Age: 18},{Name: "李四",Age: 19},{Name: "王武",Age: 20} 

前端

range 数组

{{ range .people}}
        {{/*注意 当前rang代码块中的点,就是遍历的item*/}}
        <li>{{.Name}}:{{.Age}}</li>
    {{end}}

rang 字典

<ul>
    {{ range $k,$v :=. }}
    <li>{{$k}}----{{$v}}</li>
    {{end}}
</ul>

 

with

with可以开辟1个函数作用域。如果pipeline为empty不产生输出,否则将dot(点)设为pipeline的值并执行,但是不修改外面的dot。
<ul>
    {{with .people}}
        <li>{{index . 0}}</li>
        <li>{{index . 1}}</li>
        <li>{{index . 2}}</li>
    {{end}}
</ul>

 

模板语言内置函数

and
    函数返回它的第一个empty参数或者最后一个参数;
    就是说"and x y"等价于"if x then y else x";所有参数都会执行;
or
    返回第一个非empty参数或者最后一个参数;
    亦即"or x y"等价于"if x then x else y";所有参数都会执行;
not
    返回它的单个参数的布尔值的否定
len
    返回它的参数的整数类型长度
index
    执行结果为第一个参数以剩下的参数为索引/键指向的值;
    如"index x 1 2 3"返回x[1][2][3]的值;每个被索引的主体必须是数组、切片或者字典。
print
    即fmt.Sprint
printf
    即fmt.Sprintf
println
    即fmt.Sprintln
html
    返回与其参数的文本表示形式等效的转义HTML。
    这个函数在html/template中不可用。
urlquery
    以适合嵌入到网址查询中的形式返回其参数的文本表示的转义值。
    这个函数在html/template中不可用。
js
    返回与其参数的文本表示形式等效的转义JavaScript。
call
    执行结果是调用第一个参数的返回值,该参数必须是函数类型,其余参数作为调用该函数的参数;
    如"call .X.Y 1 2"等价于go语言里的dot.X.Y(1, 2);
    其中Y是函数类型的字段或者字典的值,或者其他类似情况;
    call的第一个参数的执行结果必须是函数类型的值(和预定义函数如print明显不同);
    该函数类型值必须有1到2个返回值,如果有2个则后一个必须是error接口类型;
    如果有2个返回值的方法返回的error非nil,模板执行会中断并返回给调用模板执行者该错误;

 

自定义函数

如果模板语言中内置的函数无法满足我们渲染数据的需求时,我们可以在后端定义函数传到前端使用。-------Django的simple tag 和filter

 

package main

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

//结构体渲染进模板时要大小写这种属性的可见性
type Person struct {
    Name string
    Age  int
}

//1.定义1个函数:必须包含2个返回值,1和是结果、1个是error类型
func PraiseSomebody(name string) (string, error) {
    return name + "您真是帅!", nil

}

func handleBook(w http.ResponseWriter, r *http.Request) {
    method := r.Method
    if method == "GET" {
        //fmt.Fprintf(w,"hellow") //凡是实现了file接口的类型都可以fmt.Fprintf()写入数据
        //w.Write([]byte("您好!"))
        //data, err := ioutil.ReadFile("./home.html")
        //2.定义模板home.html注意不要加./:
        t := template.New("home.html")
        //3.告诉模板引擎 我扩展了自己的自定义的函数!
        t.Funcs(template.FuncMap{"praise": PraiseSomebody})
        //4.解析模板:不要刻舟求剑,在goland中Run 'go build main.go否则找不到模板路径'
        _, err := t.ParseFiles("./home.html")
        if err != nil {
            fmt.Println("模板解析失败!")
            fmt.Println(err.Error())
        }
        //map的渲染到模板语言时不需要把key进行大写!
        people := []Person{{Name: "张三", Age: 18}, {Name: "李四", Age: 19}, {Name: "王武", Age: 20}}
        //Django的模板语言可以同时传多个参数,而template/html只能传1个,所以我对2个变量进行了打包装!
        data := map[string]interface{}{
            "people": people,
        }
        //5.渲染模板:字符串替换
        t.Execute(w, data)
    }
}

func main() {
    http.HandleFunc("/book", handleBook)
    err := http.ListenAndServe(":9001", nil)
    if err != nil {
        fmt.Println(err)
        panic("服务启动失败!")
    }
}

--------------

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<div>
    <h1>您好</h1>
    <ul>
    {{range .people}}
        <p>{{praise .Name}}</p>
    {{end}}
    </ul>
</div>
</body>
</html>

 

 

模板嵌套

为了使我们的模板语言代码更加具有灵活扩展性,我们可在template中嵌套其他的子template。

define

1.通过define在当前template中声明子template

<!DOCTYPE html>
<html lang="en">
{{/* 1.声明header子模板*/}}
{{define "heade.html"}}
    <head>
        <meta charset="UTF-8">
        <title>嵌套模板</title>
    </head>
{{end}}
{{/*2.声明body子模板*/}}
{{define "body.html"}}
    <body>
    <h1>您好!</h1>
    </body>
{{end}}
{{/* 3.声明food子模板*/}}
{{define "foot.html"}}
    <script>
        alert("您好!")
    </script>
{{end}}
{{/* 4.使用header子模板*/}}
{{template "heade.html"}}
{{/* 5.使用body子模板*/}}
{{template "body.html"}}
{{/*6.使用foot子模板*/}}
{{template "foot.html"}}
</html>

 

 2.在其他文件声明子template

 Golang工具包
    




SQLAlchemy

 

 

子template

./template/------

{{/* 1.声明header子模板*/}}
{{define "heade.html"}}
    <head>
        <meta charset="UTF-8">
        <title>嵌套模板</title>
    </head>
{{end}}

./template/------

{{/*2.声明body子模板*/}}
{{define "body.html"}}
<body>
<h1>您好!</h1>
</body>
{{end}}

./template/------

{{/* 3.声明food子模板*/}}
{{define "foot.html"}}
<script>
    alert("您好!")
</script>
{{end}}

./template/------

<!DOCTYPE html>
<html lang="en">
{{/* 4.使用header子模板*/}}
{{template "heade.html"}}
{{/* 5.使用body子模板*/}}
{{template "body.html"}}
{{/*6.使用foot子模板*/}}
{{template "foot.html"}}
</html>

后端

_, err := t.ParseFiles("./templates/home.html","./templates/header.html","./templates/body.html","./templates/foot.html")
        if err != nil {
            fmt.Println("模板解析失败!")
            fmt.Println(err.Error())
        }

 

模板继承

既然前面已经有了模板嵌套,为什么现在还需要模板继承呢?

取代模板继承?适应更多的开发场景?

想这个问题的时候我也深刻思考了.........面向对象的3大特性:封装、继承、多态,又想到了为什么会Python了还要学Golang呢?

其实这都是为了能够适应更多、更复杂、更丰富的开发场景!

当我们需要使用一些公用的组件时,去嵌套这些公共的template是1种不错的代码复用方案。

通常情况我们的web站点会有很多URL、很多HTML文件,它们除了有共性之外还会有一些差别。

而且这1堆HTML文件之间的差异部分远远大于相同部分。

这时我们仅仅使用模板嵌套的模式,就会陷入以下情景。

遇到新功能---》创建子template-------》组装出新页面----------》

遇到新功能---》创建子template-------》组装出新页面----------》

遇到新功能---》创建子template-------》组装出新页面.---------》

遇到新功能---》创建子template-------》组装出新页面.---------》

........................................................................................

有没有1种方法可以把这些差异部分抽象出来?把不断得组装子template的环节省略掉?

模板继承是针对一大堆HTML的差异部分远远大于相同部分的一种灵活得新功能扩展方案!

所以模板嵌套可以把公用的模板组件嵌套进来,模板继承可以支持我们更快速得进行新功能迭代。

模板嵌套和模板继承需要相互结合。

 

templates--------------

{{define "layout"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>模板的继承</title>
    <style>
        * {
            margin: 0;
        }

        .nav {
            position: fixed;
            background-color: aqua;
            width: 100%;
            height: 50px;
            top: 0;

        }

        .main {
            margin-top: 50px;
        }

        .menu {
            position: fixed;
            background-color: blue;
            width: 20%;
            height: 100%;
            left: 0;

        }

        .center {
            text-align: center;
        }

    </style>
</head>
<body>
<div class="nav">
</div>
<div class="main">
    <div class="menu"></div>
    <div class="content center">
        {{/*注意content后面有个点!*/}}
        {{block "content" .}}
        {{end}}
    </div>
</div>

</body>
</html>
{{end}}

templates--------------

{{/*继承根模板*/}}
{{template "layout".}}
{{/*重写根模板中的content块*/}}
{{define "content" }}
    <h1>这是index页面1</h1>
    <h1>{{.}}</h1>
{{end}}

templates--------------

{{/*继承根模板*/}}
{{ template "layout" .}}
{{/*重写根模板中的content块*/}}
{{ define "content"}}
    <h1>这是home页面</h1>
    <h1>{{.}}</h1>
{{end}}

后端

package main

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

func index(w http.ResponseWriter, r *http.Request, ) {
    //1.定义模板
    //2.解析模板
    //indexTemplate, err := template.ParseGlob("templates/*.html")
    indexTemplate,err:= template.ParseFiles("./templates/layout.html","./templates/index.html")
    if err != nil {
        fmt.Println(err)
        return
    }
    //3.渲染模板
    data := "Index"
    //指定我具体要渲染哪1个模板?
    err = indexTemplate.ExecuteTemplate(w, "index.html", data)
    if err != nil {
        fmt.Println("渲染模板失败, err:", err)
        return
    }

}

func home(w http.ResponseWriter, r *http.Request) {
    //1.解析模板
    homeTemplate,err:= template.ParseFiles("./templates/layout.html","./templates/home.html")
    if err != nil {
        fmt.Println(err)
    }

    //2.渲染模板
    //template.Execute(w, data)
    data := "Home"
    err = homeTemplate.ExecuteTemplate(w,"home.html", data)
    if err != nil {
        fmt.Println("渲染模板失败", err)
    }

}

func main() {
    http.HandleFunc("/index", index)
    http.HandleFunc("/home", home)
    err := http.ListenAndServe(":8002", nil)
    if err != nil {
        fmt.Println(err)
        return
    }
}

 

 

修改默认的标识符

{{}}VueAngularJS{{}}
    //模板解析之前,定义自己的标识符 {[  ]}
    t, err := template.New("index.html").Delims("{[", "]}").ParseFiles("./templates/index.html")

 

 模板语言防止xss跨站请求攻击

html/template和Django的模板语言一样默认情况下是对前端代码形式的字符串进行转义的!

Django的模板语言中有1个safe内置函数,可以控制后端的字符串<a href="//"></a>直接渲染到前端。

func xss(w http.ResponseWriter, r *http.Request){
    tmpl,err := template.New("xss.tmpl").Funcs(template.FuncMap{
        "safe": func(s string)template.HTML {
            return template.HTML(s)
        },
    }).ParseFiles("./xss.tmpl")
    if err != nil {
        fmt.Println("create template failed, err:", err)
        return
    }
    jsStr := `<script>alert('嘿嘿嘿')</script>`
    err = tmpl.Execute(w, jsStr)
    if err != nil {
        fmt.Println(err)
    }
}

模板语言使用

{{ . | safe }}

 

Sonyflake

在单机架构模式下我们可以使用mysql的ID或者UUID作为唯一标识(不能计算和排序)。

snowflake是一种雪花算法,如果是分布式集群环境如何在整个集群中1个全局自增的唯一数字呢?

Golang工具包
    




SQLAlchemy

 

 

 

 

注意:

这种算法就是基于时间戳来生成的唯一自增ID,请一定确保你集群中的服务器全部做了NTP(时间同步服务)且时间无法后置!!!!

如果你服务器时间滞后到了最近1次生成ID的时候,这个算法会一直等待你服务器时间超过最近1次生成ID时间!

package main

import (
    "fmt"
    "github.com/sony/sonyflake"
)

var (
    snoyFlake *sonyflake.Sonyflake
    machineID uint16
)

//获取机器ID的回调函数
func GetMachineID() (uint16, error) {
    //真正的分布式环境必须从zookeeper或者etcd去获取机器的唯一ID
    return machineID, nil
}

//初始化snowflake
func Init(mID uint16) (err error) {
    machineID = mID
    //配置项
    setings := sonyflake.Settings{}
    setings.MachineID = GetMachineID
    snoyFlake = sonyflake.NewSonyflake(setings)
    return
}

//获取1个全局的ID
func GetID() (id uint64, err error) {
    if snoyFlake == nil {
        err = fmt.Errorf("必须调用Init函数进行初始化!")
        return
    }
    id, err = snoyFlake.NextID()
    return

}

func main() {
    //输入1个集群内机器的ID
    err := Init(3)
    if err != nil {
        fmt.Println(err)
        return
    }
    id, err := GetID()
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(id)
}

 

 

 

 

 

 

 

 

 

ini.v1