三大框架 | 青训营笔记

130 阅读12分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 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

image-20230120114128001.png

出现下图即安装成功:

image-20230120114022911.png

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

image-20230120115202929.png

RESTful API

以前:

get /user

post /create_user

post /update_user

post /delete_user

restful:

get /user

post /user

put /user

delete /user

image-20230120121315259.png

开发网页

前端页面

<!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")
}

结果:

image-20230120122330451.png

渲染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")
}

结果:

image-20230120123836422.png

restful风格:Param()

image-20230120124447333.png

解析前端传递的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")
}

image-20230120130125375.png

提交表单

前端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")
}

image-20230120130810559.png

image-20230120130816401.png

路由

重定向:

//路由 重定向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)
    })

image-20230120132121157.png

路由组

//路由组
    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/

中文文档:gitea.com/xorm/xorm/s…

特性:

  • 支持 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

客户端与服务端沟通的过程

  1. 客户端发送数据(字节流)
  2. 服务端解析数据,根据约定要执行什么,再返回结果给的客户RPC

gPRC概念:

高性能、开源的RPC框架

调用方为client,被调用方为server

gRPC会屏蔽底层的细节,client只需要调用定义好的方法就能拿到预期的返回结果,对于server来说,话需要实现定义的方法

gRPC使用了Protocol Buffers:谷歌开源的数据结果序列化机制

  • 将定义的方法转换为特定语言的代码,如定义一种类型的参数,会转换为Go中的struct结构体,定义的方法会转换为func函数

序列化:将数据结构或对象转换为二进制串的过程

反序列化:将二进制串转换为数据结构或者对象

优势:

  • 序列化后体积比json和xml更小
  • 支持跨平台语言
  • 消息格式升级和兼容性还不错
  • 序列化反序列化速度很块

Protocol Buffers

下载:github.com/protocolbuf…

下载完成后配置环境变量到PATH路径下

测试:protoc

image-20230120160002492.png

安装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路径下有两个文件:

image-20230120161013396.png

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生成证书和密钥

  1. 官网下载:www.openss;.org/source
  2. 便捷版安装包:slproweb.com/products/Wi…
  3. 使用便捷版
  4. 配置环境变量 D:\Environment\OpenSSL-Win64\bin
  5. 命令行测试 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