这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天
三大框架
Gin框架
Gin:Go语言编写的web框架,更好的性能实现类似Martini框架的API
Beego:开源的高性能go语言web框架
Iris:最快的go语言web框架,完备MVC支持
Gin安装
在终端输入命令:
go get -u github.com/gin-gonic/gin
如果下载访问不了,在cmd中修改代理地址,因为长城防火墙给屏蔽了
go env -w GOPROXY=https://goproxy.cn
在设置里配置go Modules
出现下图即安装成功:
package main
import "github.com/gin-gonic/gin"
func main() {
//创建一个服务
ginServer := gin.Default()
//访问地址 处理请求 request response
ginServer.GET("/hello", func(context *gin.Context) {
context.JSON(200, gin.H{"msg": "hello world"})
})
ginServer.POST("/user", func(context *gin.Context) {
context.JSON(200, gin.H{"msg": "post,user"})
})
ginServer.PUT("/user")
ginServer.DELETE("/user")
//服务器端口
ginServer.Run(":8082")
}
网页访问:localhost:8082/hello
RESTful API
以前:
get /user
post /create_user
post /update_user
post /delete_user
restful:
get /user
post /user
put /user
delete /user
开发网页
前端页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>我的第一个go web页面</title>
</head>
<body>
<h1>你真好看</h1>
获取的后端数据为:
{{.msg}}
</body>
</html>
func main() {
//创建一个服务
ginServer := gin.Default() //生成实例
//ginServer.Use(favicon.New("./favicon.ico"))
//加载静态页面
ginServer.LoadHTMLGlob("templetes/*")
//响应一个页面给前端 定义url可以访问函数
ginServer.GET("/index", func(context *gin.Context) {
context.HTML(http.StatusOK, "index.html", gin.H{
"msg": "这是go后台传递数据",
})
})
//服务器端口
ginServer.Run(":8082")
}
结果:
渲染js和css
body{
background: red;
}
alert(1)
前端页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>我的第一个go web页面</title>
<link rel="stylesheet" href="/static/css/style.css">
<script src="/static/js/common.js"></script>
</head>
<body>
<h1>你真好看</h1>
获取的后端数据为:
{{.msg}}
</body>
</html>
加载资源文件
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
//创建一个服务
ginServer := gin.Default() //生成实例
//加载静态页面
ginServer.LoadHTMLGlob("templetes/*")
//加载资源文件
ginServer.Static("/static", "./static")
//响应一个页面给前端
ginServer.GET("/index", func(context *gin.Context) {
context.HTML(http.StatusOK, "index.html", gin.H{
"msg": "这是go后台传递数据",
})
})
//服务器端口
ginServer.Run(":8082")
}
接收前端传递的参数
func main() {
//创建一个服务
ginServer := gin.Default() //生成实例
//加载静态页面
ginServer.LoadHTMLGlob("templetes/*")
//加载资源文件
ginServer.Static("/static", "./static")
//响应一个页面给前端
ginServer.GET("/index", func(context *gin.Context) {
context.HTML(http.StatusOK, "index.html", gin.H{
"msg": "这是go后台传递数据",
})
})
//usl?userid=xxx&username=liuxiang
ginServer.GET("/user/info", func(context *gin.Context) {
userid := context.Query("userid")
username := context.Query("username")
//后端返回数据
context.JSON(http.StatusOK, gin.H{
"userid": userid,
"username": username,
})
})
// /user/info/1/liuxiang restful风格
ginServer.GET("/user/info/:userid/:username", func(context *gin.Context) {
userid := context.Param("userid")
username := context.Param("username")
//后端返回数据
context.JSON(http.StatusOK, gin.H{
"userid": userid,
"username": username,
})
})
//服务器端口
ginServer.Run(":8082")
}
结果:
restful风格: 用Param()
解析前端传递的json
func main() {
//创建一个服务
ginServer := gin.Default() //生成实例
//加载静态页面
ginServer.LoadHTMLGlob("templetes/*")
//加载资源文件
ginServer.Static("/static", "./static")
//响应一个页面给前端
ginServer.GET("/index", func(context *gin.Context) {
context.HTML(http.StatusOK, "index.html", gin.H{
"msg": "这是go后台传递数据",
})
})
//前端给后端传递json 后端解析json
ginServer.POST("/json", func(context *gin.Context) {
//request.body
data, _ := context.GetRawData()
//go语言的object用空接口表示
var m map[string]interface{} // string object
_ = json.Unmarshal(data, &m) //指针赋值
context.JSON(http.StatusOK, m)
})
//服务器端口
ginServer.Run(":8082")
}
提交表单
前端html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>我的第一个go web页面</title>
<link rel="stylesheet" href="/static/css/style.css">
<script src="/static/js/common.js"></script>
</head>
<body>
<h1>你真好看</h1>
<form action="/user/add" method="post">
<p>username: <input type="text" name="username"></p>
<p>password: <input type="text" name="password"></p>
<button type="submit">提交</button>
</form>
获取的后端数据为:
{{.msg}}
</body>
</html>
后端
func main() {
//创建一个服务
ginServer := gin.Default() //生成实例
//加载静态页面
ginServer.LoadHTMLGlob("templetes/*")
//加载资源文件
ginServer.Static("/static", "./static")
//响应一个页面给前端
ginServer.GET("/index", func(context *gin.Context) {
context.HTML(http.StatusOK, "index.html", gin.H{
"msg": "这是go后台传递数据",
})
})
// go 支持函数式编程 函数作为参数传递
ginServer.POST("user/add", func(context *gin.Context) {
username := context.PostForm("username")
password := context.PostForm("password")
context.JSON(http.StatusOK, gin.H{
"msg": "ok",
"username": username,
"password": password,
})
})
//服务器端口
ginServer.Run(":8082")
}
路由
重定向:
//路由 重定向301
ginServer.GET("/test", func(context *gin.Context) {
context.Redirect(http.StatusMovedPermanently,"https://www.baidu.com")
})
404
//404
ginServer.NoRoute(func(context *gin.Context) {
context.HTML(http.StatusNotFound, "404.html", nil)
})
路由组
//路由组
userGroup := ginServer.Group("/user")
{
userGroup.GET("/add")
userGroup.GET("/login")
userGroup.GET("/logout")
}
order := ginServer.Group("/order")
{
order.GET("/add")
order.DELETE("/delete")
}
中间件(java:拦截器)
定义:
// 自定义Go中间件 拦截器
func myHandler() gin.HandlerFunc {
return func(context *gin.Context) {
//通过自定义的中间件设置的值,只要调用了中间件就可以拿到参数
context.Set("usersession","userid-1")
context.Next() //放过
context.Abort() //阻断
}
}
注册中间件:
//注册中间件
ginServer.Use(myHandler())
取值
ginServer.GET("/user/info", myHandler(), func(context *gin.Context) {
//取出中间件的值
usersession := context.MustGet("usersession").(string)
log.Println("hhh>>>>>", usersession)
userid := context.Query("userid")
username := context.Query("username")
//后端返回数据
context.JSON(http.StatusOK, gin.H{
"userid": userid,
"username": username,
})
})
Xorm框架
解决面向对象与关系数据库存在的互不匹配的现象
安装:
go get xorm.io/xorm
官网:xorm.io/
特性:
- 支持 Struct 和数据库表之间的灵活映射,并支持自动同步
- 事务支持
- 同时支持原始SQL语句和ORM操作的混合执行
- 使用连写来简化调用
- 支持使用ID, In, Where, Limit, Join, Having, Table, SQL, Cols等函数和结构体等方式作为条件
- 支持级联加载Struct
- Schema支持(仅Postgres)
- 支持缓存
- 通过 xorm.io/reverse 支持根据数据库自动生成 xorm 结构体
- 支持记录版本(即乐观锁)
- 通过 xorm.io/builder 内置 SQL Builder 支持
- 上下文缓存支持
- 支持日志上下文
创建引擎:
engine, err := xorm.NewEngine(driverName, dataSourceName)
创建表同步的结构体
type User struct {
Id int64
Name string
Salt string
Age int
Passwd string `xorm:"varchar(200)"`
Created time.Time `xorm:"created"`
Updated time.Time `xorm:"updated"`
}
err := engine.Sync(new(User)) //同步
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"time"
"xorm.io/xorm"
)
func main() {
//数据库连接基本信息
var (
userName string = "root"
password string = "123456789"
ipAddress string = "127.0.0.1"
port int = 3306
dbName string = "go_test"
charset string = "utf8mb4"
)
//构建数据库连接信息
dataSourceName := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddress, port, dbName, charset)
//创建引擎
engine, err := xorm.NewEngine("mysql", dataSourceName)
if err != nil {
fmt.Println("数据库连接失败")
}
type User struct {
Id int64
Name string
Age int
Passwd string `xorm:"varchar(200)"`
Created time.Time `xorm:"created"`
Updated time.Time `xorm:"updated"`
}
err1 := engine.Sync(new(User))
if err1 != nil {
fmt.Println("同步失败")
}
}
插入数据
//数据插入 对象 返回值:受影响的行数 n
user := User{Id: 100, Name: "liuxiang", Age: 17, Passwd: "1234567"}
n, _ := engine.Insert(&user)
if n > 1 {
fmt.Println("数据插入成功")
}
//插入多条数据
user1 := User{Id: 1001, Name: "liuxiang1", Age: 15, Passwd: "12344567"}
user2 := User{Id: 1002, Name: "liuxiang2", Age: 18, Passwd: "12342567"}
n, _ = engine.Insert(&user1, &user2)
fmt.Println(n)
if n >= 1 {
fmt.Println("数据插入成功")
}
//user切片
var users []User
users = append(users, User{Id: 1003, Name: "liuxiang3", Age: 11, Passwd: "12344567"})
users = append(users, User{Id: 1004, Name: "liuxiang4", Age: 10, Passwd: "12344567"})
n, _ = engine.Insert(users)
更新与删除
//更新
user := User{Name: "duyao"}
n, _ := engine.ID(100).Update(&user)
fmt.Println(n)
//删除
user1 := User{Name: "duyao"}
n1, _ := engine.ID(100).Delete(&user1)
fmt.Println(n1)
//执行一个sql语句
engine.Exec("update user set age = ? where id = ?", 10, 1001)
查询
//查询
results, err := engine.Query("select * from user")
fmt.Println(results)
results2, err := engine.QueryString("select * from user")
fmt.Println(results2)
results3, err := engine.QueryInterface("select * from user")
fmt.Println(results3)
//Get 只能查询单条数据
user := User{}
engine.Get(&user)
fmt.Println(user)
//指定条件查询
user1 := User{Name: "liuxiang1"}
engine.Where("name=?", user1.Name).Asc("id").Get(&user1)
fmt.Println(user1)
//获取指定字段的值
var name string
engine.Table(&user).Where("id = 1001").Cols("name").Get(&name)
fmt.Println(name)
//查询多条记录 find
var users []User //定义切片
engine.Where("passwd=12344567").And("age=10").Limit(10, 0).Find(&users)
fmt.Println(users)
//Count 获取记录条数
user2 := User{Passwd: "12344567"}
count, _ := engine.Count(&user2)
fmt.Println(count)
//Iterate 和 Rows根据条件遍历数据
engine.Iterate(&User{Passwd: "12344567"}, func(idx int, bean interface{}) error {
user := bean.(*User)
fmt.Println(user)
return nil
})
rows, _ := engine.Rows(&User{Passwd: "12344567"})
defer rows.Close()
userBean := new(User)
for rows.Next() {
rows.Scan(userBean)
fmt.Println(userBean)
}
事务处理
//开启事务会话
session := engine.NewSession()
//关闭
defer session.Close()
//开启事务
session.Begin()
defer func() {
err := recover()
if err != nil {
//回滚
fmt.Println(err)
fmt.Println("Rollback")
session.Rollback()
} else {
session.Commit() //提交
}
}()
//插入
user := User{Id: 1004, Name: "liuxiang51", Passwd: "1234", Age: 11}
if _, err := session.Insert(user); err != nil {
panic(err)
}
//修改
user1 := User{Name: "liuxiang22", Age: 3}
if _, err := session.Where("id=1001").Update(user1); err != nil {
panic(err)
}
//删除
if _, err := session.Exec("delete from user where id = 1004"); err != nil {
panic(err)
}
Gorm
文档:gorm.cn
// 定义gorm model
type Product struct {
Code string
Price uint
}
// 为model定义表名
func (p Product) TableName() string {
return "product"
}
func main() {
db, err := gorm.Open(
mysql.Open("user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"),
&gorm.Config{})
if err != nil {
panic("failed to connect database")
}
//创建数据
db.Create(&product{Code: "23", Price: 100})
//查询数据
var product Product
db.First(&product, 1) //根据主键查找
db.First(&product, "code=?", "23") //查找code字段值为23的记录
//更新数据
db.Model(&product).Update("Price", 200)
//更新多个字段
db.Model(&product).Updates(Product{Price: 200, Code: "F42"})
//删除数据
db.Delete(&product,1)
}
GRPC
背景:
微服务架构的弊端:服务与服务之间存在调用关系,需要发起网络调用,http性能低,需要用到远程过程调用,通过自定义协议发起TCP调用加快传输效率。
Remote Procedure Call:远程过程调用,一种协议,本地调用远程函数
官网:grpc.io
官方文档:doc.oschina.net/grpc
客户端与服务端沟通的过程
- 客户端发送数据(字节流)
- 服务端解析数据,根据约定要执行什么,再返回结果给的客户RPC
gPRC概念:
高性能、开源的RPC框架
调用方为client,被调用方为server
gRPC会屏蔽底层的细节,client只需要调用定义好的方法就能拿到预期的返回结果,对于server来说,话需要实现定义的方法
gRPC使用了Protocol Buffers:谷歌开源的数据结果序列化机制
- 将定义的方法转换为特定语言的代码,如定义一种类型的参数,会转换为Go中的struct结构体,定义的方法会转换为func函数
序列化:将数据结构或对象转换为二进制串的过程
反序列化:将二进制串转换为数据结构或者对象
优势:
- 序列化后体积比json和xml更小
- 支持跨平台语言
- 消息格式升级和兼容性还不错
- 序列化反序列化速度很块
Protocol Buffers
下载完成后配置环境变量到PATH路径下
测试:protoc
安装grpc的核心库
go get google.golang.org/grpc
上面安装的protocol的编译器,开源生成不同语言的代码。
go语言的代码生成工具:protoc-gen-go
安装两个库:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
这些文件再安装grpc的时候已经下载下来了,所以用install命令,而不是get命令
在goworks/bin路径下有两个文件:
Proto文件编写
相当于一个约束
//说明使用的是proto3语法
syntax = "proto3";
//最后生成的go文件在哪个目录哪个包,代码在当前目录生成。service代表生成的go文件的包名
option go_package = ".;service";
//定义一个服务,需要一个方法,这个方法开源接收客户端的参数,再返回服务端的响应
//定义一个service 称为SayHello 这个服务有一个rpc方法名为SayHello
//这个方法会发送一个HelloRequest,返回一个HelloResponse
service SayHello{
rpc SayHello(HelloRequest) returns(HelloResponse){}
}
//message关键字 理解为golang中的结构体
//变量后面的“赋值”,不是赋值,是定义这个变量在message中的位置
message HelloRequest{
string requestName = 1;
// int64 age = 2;
}
message HelloResponse{
string responseMsg = 1;
}
编写完后在hello/proto目录下执行命令:
protoc --go_out=. hello.proto
protoc --go-grpc_out=. hello.proto
proto文件介绍
message
protobuf中定义一个消息类型是通过关键字message字段指定的,消息就是需要传输的数据格式的定义
message关键字类似于c++中的class,java中的class,go中的struct
在消息中承载的数据分别对应于每一个字段,其中每个字段都有一个名字和一种类型
一个proto文件开源定义多个消息类型
字段规则
required消息体中必填字段,不设置会导致编码异常,在protobuf2中使用
optional消息体中可选字段,protobuf3没有了required
repeate消息中可重复字段,重复的值的吮吸会保留在go中重复的会被定义为切片
消息号
在消息体中的定义,每个字段都必须有一个唯一的标识号,标识号是[1,2^29-1]范围内的一个整数
嵌套消息
可以在其他消息类型中定义,使用消息类型,在下面的例子中,person消息就定义在PersonInfo消息内
message PersinOnfo{
message Person{
string name =1;
int32 height = 2;
repeated int32 weight = 3;
}
repeated Person info = 1;
}
服务定义
rpc服务接口
service SearchService{
#rpc 服务函数名(参数)返回(返回参数)
rpc Search(SearchRequest) returns (SearchResponse)
}
服务端编写
- 创建grpc Server对象
- 将server(其中包含需要被调用的服务端接口)注册到gRPC Server的内部注册中心,接收到请求时通过内部的服务发现发现该服务接口并转接 进行逻辑处理
- 创建Listen,监听TCP端口
- gRPC Server开始list Accept,直到Stop
客户端编写
- 创建与给定目标(服务端)的连接交互
- 创建server的客户端对象
- 发送RPC请求等待同步响应,得到回调后返回响应结果
- 输出响应结果
认证-安全传输
客户端与服务端的调用,通过加入证书的方式实现调用的安全性
TLS(安全传输层),是建立在传输层TCP协议上的协议,服务于应用层,前身是SSL,实现了将应用层的保温进行加密后再由TCP进行传输的功能
解决三个问题:
- 保密,通过加密encryption实现,所有信息加密传输,第三方无法查探
- 完整性:通过MAC校验机制,一旦被改,通信双方会立刻发现
- 认证:双方认证,双方都可以配备证书,防止身份被冒充
生成环境可购买的证书和免费证书
key:服务器上的私钥文件,用于发送给客户端数据的加密和客户端收到数据的解密
csr:证书签名请求文件,用于提交给证书颁发机构对证书签名
crt:由证书颁发机构签名后的证书
pem:基于Base64编码的证书格式,扩展名包括PEM\CRT和CER
SSL/TSL认证方式
通过openssl生成证书和密钥
- 官网下载:www.openss;.org/source
- 便捷版安装包:slproweb.com/products/Wi…
- 使用便捷版
- 配置环境变量 D:\Environment\OpenSSL-Win64\bin
- 命令行测试 openssl
1.生成证书
# 1.生成密钥
openssl genrsa -out server.key 2048
# 2.生成证书
openssl req -new -x509 -key server.key -out server.crt -days 36500
# 3.生成csr
opnessl req -new -key server.key -out server.csr
#更改openssl.cnf(linux是openssl.cfg)
1.赋值一份安装的openssl的bin目录的openssl.cnf文件到项目所在目录
2.找到CA_default,打开copy_extensions = copy(去#号)
3.找到req,打开req_extensions = v3_req
4.找到v3_req,调价subjectAltName = @alt_names
5.添加新的标签[alt_names]和标签字段
DNS.1 = *.liuxiang.com
# 生成私钥证书
openssl genpkey -algorithm RSA -out test.key
# 通过私钥test.key生成证书请求文件test.csr
openssl req -new -nodes -key test.key -out test.csr -days 3650 -subj "/C=cn/OU=myorg/O=mycomg/CN=myname" -config ./openssl.cnf -extensions v3_req
# 生成SAN证书 pem
openssl x509 -req -days 365 -in test.csr -out test.pem -CA server.crt -CAkey server.key -CAcreateserial -extfile ./openssl.cnf -extensions v3_req
Token认证
给RPC提供一个接口,由两个方法,接口位于credentials包下
type PerRPCCredentials interface {
GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
RequireTransportSecurity() bool
}
第一个方法作用获取元数据信息,也就是客户端提供的key,value,context用于控制超时和取消,uri是请求入口的uri
第二个方法的作用是否需要基于TLS认证进行安全传输,如果返回值是ture,则必须TLS认证
Kitex代码生成工具
rpc工具
安装:
go install github.com/cloudwego/kitex/tool/cmd/kitex@latex
go install github.com/cloudwego/thriftgo@latex
测试cmd
kitex -version