• Go-zero框架学习+xorm+gorm配置


    Go-zero框架学习+xorm+gorm配置


    一、框架介绍

    go-zero 包含极简的 API 定义和生成工具 goctl,可以根据定义的 api 文件一键生成 Go, iOS, Android, Kotlin, Dart, TypeScript, JavaScript 代码,并可直接运行。

    goctl是go-zero微服务框架下的代码生成工具。使用 goctl 可显著提升开发效率,让开发人员将时间重点放在业务开发上,其功能有:api服务生成,rpc服务生成,model代码生成,模板管理等。

    protoc是一款用C++编写的工具,其可以将proto文件翻译为指定语言的代码。在go-zero的微服务中,我们采用grpc进行服务间的通信,而grpc的编写就需要用到protoc和翻译成go语言rpc stub代码的插件protoc-gen-go。

    二、go-zero快速搭建

    这节内容部分参考资料
    go-zero框架简介与基本使用 - Abel Chan的文章 - 知乎

    1.下载go-zero

    go get -u github.com/zeromicro/go-zero
    
    • 1

    2.安装goctl

    go 1.15及以前版本:

    go get -u github.com/zeromicro/go-zero/tools/goctl@latest
    
    • 1

    go 1.16 及以后版本:

    go install github.com/zeromicro/go-zero/tools/goctl@latest
    
    • 1

    查看goctl版本

    goctl -v
    
    • 1

    3.项目初始化

    // 创建API服务
    goctl api new greet
    cd greet
    go mod init
    go mod tidy
    // 启动服务
    go run greet.go -f etc/greet-api.yaml
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.测试

    访问: http://127.0.0.1:8888/from/you

    或者linux
    curl -i http://127.0.0.1:8888/from/you

    备注:
    默认启动端口号:8888
    修改配置文件/etc/greet-api.yaml 可以修改端口号

    5.项目结构

    |____go.mod
    |____etc //存放配置文件
    | |____greet-api.yaml
    |____internal
    | |____handler // 路由与处理器
    | | |____routes.go
    | | |____greethandler.go
    | |____types //中间类型
    | | |____types.go
    | |____config // 配置-对应etc下配置文件
    | | |____config.go
    | |____logic //逻辑处理
    | | |____greetlogic.go
    | |____svc //依赖资源
    | | |____servicecontext.go
    |____go.sum
    |____greet.api //api接口与类型定义
    |____greet.go //main.go 入口
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    6.快速生成rpc服务

    使用proto文件自动生成
    方式1:创建rpc服务

    goctl rpc new greet
    
    • 1

    生成greet.proto文件如下

    syntax = "proto3";
    
    package rpc-greet;
    option go_package="./rpc-greet";
    
    message Request {
      string ping = 1;
    }
    
    message Response {
      string pong = 1;
    }
    
    service Rpc-Greet {
      rpc Ping(Request) returns(Response);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    方式2:
    通过定义好的proto文件,生成相应的rpc服务

    goctl rpc template -o=userinfo.proto
    
    • 1

    userinfo.proto内容如下

    syntax = "proto3";  //指定版本信息,不指定会报错,默认proto2
    //option go_package = "./proto;userinfo";  // 分号后面是包名
    option go_package = "./userService";
    
    message userinfo{
        string username = 1;
        int32 age = 2;
        repeated string hobby = 3; //数组,golang对应string切片
    }
    // 转成go文件protoc --go_out=./ *.proto 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    7.快速添加api接口

    修改greet.api文件

    service greet-api {
        @handler UserLogin
    	post /user/login(LoginRequest) returns (LoginReply)
    }
    @server (
    	middleware: Auth
    )
    service core-api{
    	// 文件上传
    	@handler FileUpload
    	post /file/upload(FileUploadRequest) returns (FileUploadReply)	
    }
    type LoginRequest {
    	Name     string `json:"name"`
    	Password string `json:"password"`
    }
    	
    type LoginReply {
    	Token string `json:"token"`
    }
    type FileUploadRequest {
    	Hash string `json:"hash,optional"`
    	Name string `json:"name,optional"`
    	Ext string `json:"ext,optional"`
    	Size int64 `json:"size,optional"`
    	Path string `json:"path,optional"`
    }
    	
    type FileUploadReply {
    	Identity string `json:"identity"`
    	Ext string `json:"ext"`
    	Name string `json:"name"`
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    自动生成代码

    goctl api go -api greet.api -dir . 
    
    或者
    goctl api go -api greet.api -dir . -style go_zero
    
    • 1
    • 2
    • 3
    • 4

    备注:
    -style go_zero:自动生成的文件名使用下划线进行分隔,如:
    service_context.go
    user_file_delete_logic.go

    生成代码后的文件结构如下:

    自动生成handler文件和logic文件
    |____go.mod
    |____etc
    | |____greet-api.yaml
    |____internal
    | |____handler
    | | |____userhandler.go // 新增handler
    | | |____routes.go
    | | |____greethandler.go
    | |____types
    | | |____types.go // 新增type都在这里
    | |____config
    | | |____config.go
    | |____logic
    | | |____userlogic.go //新增的logic文件,处理具体业务逻辑
    | | |____greetlogic.go
    | |____svc
    | | |____servicecontext.go
    |____go.sum
    |____greet.api
    |____greet.go
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    8.快速生成model服务

    方式1:通过ddl生成

    goctl mysql goctl model mysql ddl -src="./*.sql" -dir="../" -c=true
    
    • 1

    备注:
    -c : 是否加缓存

    方式2:通过datasource生成

    goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="*" -dir="./"
    
    • 1

    生成文件示例
    .
    |____tdfsubscriptionmodel.go
    |____vars.go
    |____diversionrulesmodel.go
    |____sql
    | |____sub.sql

    8.快速生成Dockerfile

    goctl docker -go greet.go
    
    • 1
    .
    |____go.mod
    |____Dockerfile // 当前服务的Dockerfile文件
    |____etc
    | |____greet-api.yaml
    |____internal
    | |____handler
    | | |____userhandler.go
    | | |____routes.go
    | | |____greethandler.go
    | |____types
    | | |____types.go
    | |____config
    | | |____config.go
    | |____logic
    | | |____userlogic.go
    | | |____greetlogic.go
    | |____svc
    | | |____servicecontext.go
    |____go.sum
    |____greet.api
    |____greet.go
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    9.快速生成K8s部署文件

    goctl kube deploy -name redis -namespace adhoc -image redis:6-alpine -o redis.yaml -port 6379
    
    • 1

    生成的redis.yaml文件

    三.golang的ORM框架

    golang常用的几种orm框架,主流框架包括:

    • gorm
    • xorm
    • upper/db
    • gorose
    • worm
      接下来将针对如何配置xorm和gorm进行说明,新手入门项目,可以选择xorm或者gorm其中的一个作为选型;

    四. go-zero配置xorm和redis

    这块内容基于B站实战视频学习所得
    【【项目实战】基于Go-zero、Xorm的网盘系统】

    1.配置文件

    /etc/core-api.yaml
    增加mysql和redis的配置

    Name: core-api
    Host: 0.0.0.0
    Port: 8888
    
    Mysql:
      DataSource: root:password@tcp(127.0.0.1:3306)/cloud_disk?charset=utf8mb4&parseTime=True&loc=Local
    
    Redis:
      Addr: 127.0.0.1:6379
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.修改config

    /internal/config/config.go

    package config
    
    import "github.com/zeromicro/go-zero/rest"
    
    type Config struct {
    	rest.RestConf
    	Mysql struct {
    		DataSource string
    	}
    	Redis struct {
    		Addr string
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    3.修改serviceContext文件

    /internal/svc/service_context.go
    增加Engine和RDB

    package svc
    
    import (
    	"cloud-disk/core/internal/config"
    	"cloud-disk/core/models"
    	"github.com/go-redis/redis/v8"
    	"github.com/zeromicro/go-zero/rest"
    	"xorm.io/xorm"
    )
    
    type ServiceContext struct {
    	Config config.Config
    	Engine *xorm.Engine
    	RDB    *redis.Client
    }
    
    func NewServiceContext(c config.Config) *ServiceContext {
    	return &ServiceContext{
    		Config: c,
    		Engine: models.Init(c.Mysql.DataSource),
    		RDB:    models.InitRedis(c),
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    4.初始化Init方法

    /models/init.go

    package models
    
    import (
    	"cloud-disk/core/internal/config"
    	"log"
    
    	"github.com/go-redis/redis/v8"
    	_ "github.com/go-sql-driver/mysql"
    	"xorm.io/xorm"
    )
    
    func Init(dataSource string) *xorm.Engine {
    	engine, err := xorm.NewEngine("mysql", dataSource)
    	if err != nil {
    		log.Printf("Xorm New Engine Error : %v", err)
    		return nil
    	}
    	return engine
    }
    
    func InitRedis(c config.Config) *redis.Client {
    	return redis.NewClient(&redis.Options{
    		Addr:     c.Redis.Addr,
    		Password: "", // no password set
    		DB:       0,  // use default DB
    	})
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    5.logic业务逻辑调用

    func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp *types.UserRegisterReply, err error) {
    	// 调用redis获取验证码
    	// 判断code是否一致
    	code, err := l.svcCtx.RDB.Get(l.ctx, req.Email).Result()
    	if err != nil {
    		err = errors.New("该邮箱验证码为空")
    		return nil, err
    	}
    
    	if code != req.Code {
    		err = errors.New("验证码错误")
    		return
    	}
    
        // 使用xorm查询数据库
    	// 判断用户名是否存在
    	cnt, err := l.svcCtx.Engine.Where("name = ?", req.Name).Count(new(models.UserBasic))
    	if err != nil {
    		return nil, err
    	}
    
    	if cnt > 0 {
    		err = errors.New("用户名已存在")
    		return nil, err
    	}
    
    	user := &models.UserBasic{
    		Identity: helper.UUID(),
    		Name:     req.Name,
    		Password: helper.Md5(req.Password),
    		Email:    req.Email,
    	}
    
    	n, err := l.svcCtx.Engine.Insert(user)
    	if err != nil {
    		return nil, err
    	}
    	log.Println("insert user row:", n)
    	res := &types.UserRegisterReply{}
    	res.Message = "注册成功"
    	return
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    五.go-zero配置gorm

    这节内容参考了博客
    【go-zero之gozero+gorm】

    1、配置文件
    /etc/greet-api.yaml增加数据库连接配置DataSourceName

    Name: greet-api
    Host: 0.0.0.0
    Port: 8888
    
    DataSourceName: root:password@tcp(127.0.0.1:3306)/cloud_disk?charset=utf8mb4&parseTime=True&loc=Local
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    /internal/config/config.go增加DataSourceName

    type Config struct {
    	rest.RestConf
    	DataSourceName string
    }
    
    • 1
    • 2
    • 3
    • 4

    2.修改servicecontext.go文件

    /internal/svc/servicecontext.go

    package svc
    
    import (
    	"greet/internal/config"
    	"greet/internal/models"
    
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"gorm.io/gorm/schema"
    )
    
    type ServiceContext struct {
    	Config  config.Config
    	DbEngine *gorm.DB
    }
    
    func NewServiceContext(c config.Config) *ServiceContext {
    	db, err := gorm.Open(mysql.Open(c.DataSourceName), &gorm.Config{
    		NamingStrategy: schema.NamingStrategy{
    			//TablePrefix:   "tech_", // 表名前缀,`User` 的表名应该是 `t_users`
    			SingularTable: true,    // 使用单数表名,启用该选项,此时,`User` 的表名应该是 `t_user`
    		},
    	})
    	//如果出错就GameOver了
    	if err != nil {
    		panic(err)
    	}
    	//自动同步更新表结构,不要建表了O(∩_∩)O哈哈~
    	db.AutoMigrate(&models.User{})
    
    	return &ServiceContext{
    		Config:  c,
    		DbEngine: db,
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    3.修改greet.api文件

    type RegisterRequest {
    	Mobile string `json:"mobile"`
    	Password string `json:"password"`
    }
    type RegisterResponse {
    	Id int `json:"id"`
    }
    
    service greet-api {
    	@handler Register
    	post /register(RegisterRequest) returns (RegisterResponse)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    使用代码生成

    goctl api go -api greet.api -dir .
    
    • 1

    生成/logic/registerlogic.go

    4.修改logic文件

    /logic/registerlogic.go

    func (l *RegisterLogic) Register(req *types.RegisterRequest) (resp *types.RegisterResponse, err error) {
    	user := models.User{
    		Mobile:   req.Mobile,
    		Password: req.Password,
    	}
    	result := l.svcCtx.DbEngine.Create(&user)
    	return &types.RegisterResponse{
    		Id: user.Id,
    	}, result.Error
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    /internal/models/user.go

    package models
    
    import (
    	"errors"
    	"greet/internal/utils"
    
    	"gorm.io/gorm"
    )
    
    type User struct {
    	gorm.Model
    	Id       int `gorm:"type:int(64)"`
    	Mobile   string `gorm:"index:mobile;type:varchar(13)"`
    	Password string `gorm:"type:varchar(64)"`
    }
    
    // 在创建前检验验证一下密码的有效性
    func (u *User) BeforeCreate(db *gorm.DB) error {
    	if len(u.Password) < 6 {
    		return errors.New("密码太简单了")
    	}
    	//对密码进行加密存储
    	u.Password = utils.Password(u.Password)
    	return nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    /internal/utils/password.go 工具方法

    package utils
    
    import (
    	"fmt"
    
    	"golang.org/x/crypto/bcrypt"
    )
    
    // 密码加密
    func Password(plainpwd string) string {
    	//谷歌的加密包
    	hash, err := bcrypt.GenerateFromPassword([]byte(plainpwd), bcrypt.DefaultCost) //加密处理
    	if err != nil {
    		fmt.Println(err)
    	}
    	encodePWD := string(hash) // 保存在数据库的密码,虽然每次生成都不同,只需保存一份即可
    	return encodePWD
    }
    
    // 密码校验
    func CheckPassword(plainpwd, cryptedpwd string) bool {
    	err := bcrypt.CompareHashAndPassword([]byte(cryptedpwd), []byte(plainpwd)) //验证(对比)
    	return err == nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    数据表设计如下
    user表
    在这里插入图片描述

    5.运行测试

    运行服务

    go mod tidy
    go run greet.go -f etc/greet-api.yaml
    
    • 1
    • 2

    postman发送请求
    http://127.0.0.1:8888/register
    参数

    {
        "mobile": "123233123123",
        "password":"111234311"
    }
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    查看数据表
    在这里插入图片描述
    数据添加成功


    六、go-zero架构设计-作者万俊峰的讲解

    原视频链接:
    【#101 晓黑板 go-zero 微服务框架的架构设计】

    go-zero架构特点

    1、进程内限流

    • 控制并发请求
    • 简单高效
    • 可配置,有默认值
    • 有效防止突发恶意流量
    • 第一道防护(WAF等除外)

    2、分布式限流

    • 基于redis/lua
    • 令牌桶
    • 漏桶
    • 广泛使用,短信、推送等

    3、基于优先级进行分级降载保护

    • K8S的HPA 80%触发
    • CPU>90%开始拒绝低优先级请求
    • CPU>95%开始拒绝高优先级请求
    • http/rpc框架内建
    • 基于滑动窗口,防止毛刺
    • 有冷却时间,防止抖动
    • 实践检验,配合K8S弹性伸缩
    • 第二道防护

    4、路径级别的自适应熔断

    • 自动触发,自动恢复
    • http/rpc框架内建
    • Google SRE算法
    • 放弃了Netfix Hystrix算法
    • 基于滑动窗口(10秒/40秒窗口)
    • 支持自定义触发条件
    • 支持自定义fallback
    • 第三道防护

    其他说明

    1、多重防护,保障高可用
    请求–》并发控制、限流–》自适应降载、K8S弹性伸缩 --》自适应熔断、负载均衡

    2、重试机制
    不自动重试

    重试注意事项:

    • 指数退避
    • 流量quota
    • 超时相关性

    3、缓存设计三要点

    • 缓存穿透,不存在的数据
    • 缓存击穿,热点key过期
    • 缓存雪崩,大量缓存同时过期

    4、缓存解决办法

    • 缓存穿透:即使查不到,也自动缓存,短过期时间,1分钟
    • 缓存击穿:确保一个进程同时只拿一次数据,并共享结果
    • 缓存雪崩:针对缓存过期时间设置随机偏差,确保分散过期
    • 缓存基于非主键查询:转换为主键做缓存;获取复杂,查询->主键->缓存
    • 分布式缓存:多虚拟节点一致性hash,避免降级后过多cache miss

    5、可观测性

    • 链路跟踪
    • Logging
    • Metrics
    • 监控报警

    (1)链路跟踪

    • 框架自建,context传递
    • Trace id, 贯穿整个调用链
    • Span id, 有层级和时序关系
    • 记录起止时间
    • 记录调用关系,client/server

    (2)Logging

    • 自动rotate
    • 多模式支持,console,file,volume
    • 自动压缩
    • 自动删除过期日志

    (3)监控报警

    • 自动聚合汇报异常,比如http code 5xx
    • 自动控制频率并汇总异常

    6、数据上报

    • 上报到控制台服务
    • 上报到prometheus

    7、其他

    • 基于JWT的自动鉴权
    • MapReduce
    • Graceful shutdown
    • 并发控制工具箱
    • 资源控制工具箱,比如多个线程同时创建同一个数据库链接
    • 分布式高可用延迟任务框架
    • 极简Kafka Pub/Sub框架
    • Logstash 5倍性能的go-stash框架
  • 相关阅读:
    无痛理解傅里叶变换
    python入门基础(7)--字典及相关操作
    Weblogic SSRF漏洞
    网络安全高级工具软件100套
    天锐绿盾 | 如何防止开发部门源代码泄露、外泄?
    ARMv8架构简介
    电子产品量产工具-软件架构-显示系统
    2.7 二叉搜索树
    java毕业设计房产置购门户网站Mybatis+系统+数据库+调试部署
    《Java并发编程的艺术》读书笔记 - 第八章 - Java中的并发工具类
  • 原文地址:https://blog.csdn.net/MissEel/article/details/128062915