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框架简介与基本使用 - Abel Chan的文章 - 知乎
get -u github.com/zeromicro/go-zero
go 1.15及以前版本:
go get -u github.com/zeromicro/go-zero/tools/goctl@latest
go 1.16 及以后版本:
go install github.com/zeromicro/go-zero/tools/goctl@latest
查看goctl版本
goctl -v
// 创建API服务
goctl api new greet
cd greet
go mod init
go mod tidy
// 启动服务
go run greet.go -f etc/greet-api.yaml
访问: http://127.0.0.1:8888/from/you
或者linux
curl -i http://127.0.0.1:8888/from/you
备注:
默认启动端口号:8888
修改配置文件/etc/greet-api.yaml 可以修改端口号
|____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 入口
使用proto文件自动生成
方式1:创建rpc服务
goctl rpc new greet
生成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);
}
方式2:
通过定义好的proto文件,生成相应的rpc服务
goctl rpc template -o=userinfo.proto
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
修改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"`
}
自动生成代码
goctl api go -api greet.api -dir .
或者
goctl api go -api greet.api -dir . -style go_zero
备注:
-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:通过ddl生成
goctl mysql goctl model mysql ddl -src="./*.sql" -dir="../" -c=true
备注:
-c : 是否加缓存
方式2:通过datasource生成
goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="*" -dir="./"
生成文件示例
.
|____tdfsubscriptionmodel.go
|____vars.go
|____diversionrulesmodel.go
|____sql
| |____sub.sql
goctl docker -go greet.go
.
|____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
goctl kube deploy -name redis -namespace adhoc -image redis:6-alpine -o redis.yaml -port 6379
生成的redis.yaml文件
golang常用的几种orm框架,主流框架包括:
这块内容基于B站实战视频学习所得
【【项目实战】基于Go-zero、Xorm的网盘系统】
/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
/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
}
}
/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),
}
}
/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
})
}
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
}
这节内容参考了博客
【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
/internal/config/config.go增加DataSourceName
type Config struct {
rest.RestConf
DataSourceName string
}
/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,
}
}
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)
}
使用代码生成
goctl api go -api greet.api -dir .
生成/logic/registerlogic.go
/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
}
/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
}
/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
}
运行服务
go mod tidy
go run greet.go -f etc/greet-api.yaml
postman发送请求
http://127.0.0.1:8888/register
参数
{
"mobile": "123233123123",
"password":"111234311"
}
原视频链接:
【#101 晓黑板 go-zero 微服务框架的架构设计】
1、进程内限流
2、分布式限流
3、基于优先级进行分级降载保护
4、路径级别的自适应熔断
1、多重防护,保障高可用
请求–》并发控制、限流–》自适应降载、K8S弹性伸缩 --》自适应熔断、负载均衡
2、重试机制
不自动重试
重试注意事项:
3、缓存设计三要点
4、缓存解决办法
5、可观测性
(1)链路跟踪
(2)Logging
(3)监控报警
6、数据上报
7、其他