go-zero,etcd,gorm完成一次简单的微服务操作
go-zero 是一个集成了各种工程实践的 web 和 rpc 框架,etcd 是一个开源的分布式键值存储系统,用于可靠地存储分布式系统中的关键数据
1.环境准备
#官方脚手架
go install github.com/zeromicro/go-zero/tools/goctl@latest 安装go-zero脚手架,根据配置文件自动生成配置文件
#protobuf 工具
goctl env check --install --verbose --force
#框架
go get -u github.com/zeromicro/go-zero@latest
2.rpc服务端
- 编写proto配置文件user.proto文件
syntax = "proto3";
package user;
option go_package = "../proto/user";
service UserService {
rpc GetUser (UserRequest) returns (Response) {}
rpc AddUserInfo (UserInfo) returns (Response) {}
}
message UserInfo {
string name = 1;
string nickname = 2;
int64 iphone = 3;
string password = 4;
int32 status = 5;
bool is_admin = 6;
}
message UserRequest {
int64 id = 1;
}
message Response {
int64 result = 1;
bytes data = 2;
}
- 通过goctl命令生成文件
goctl rpc protoc user.proto --go_out=. --go-grpc_out=. --zrpc_out=../
- 修改yaml配置文件,并添加mysql等配置信息
Name: user.rpc
ListenOn: 0.0.0.0:8080
Etcd:
Hosts:
- 127.0.0.1:2379
Key: user.rpc
MySQLConf:
Enable: true
User: root
Password: '123456'
Host: 127.0.0.1
Port: 3306
Database: test
CharSet: utf8
ParseTime: true
TimeZOne: Local
AutoMigrate: true
Gorm:
TablePrefix: zero_
SingularTable: true
MaxOpenConns: 100
MaxIdleConns: 5
ConnMaxLifetime: 600
- 修改配置config.go文件,添加mysql配置读取结构体
package config
import "github.com/zeromicro/go-zero/zrpc"
type Config struct {
zrpc.RpcServerConf
MySQLConf MySQLConf
}
// Mysql config
type MySQLConf struct {
Host string `json:"" yaml:"Host"`
Port int64 `json:"" yaml:"Port"`
User string `json:"" yaml:"User"`
Password string `json:"" yaml:"Password"`
Database string `json:"" yaml:"Database"`
CharSet string `json:"" yaml:"CharSet"`
TimeZone string `json:"" yaml:"TimeZone"`
ParseTime bool `json:"" yaml:"ParseTime"`
Enable bool `json:"" yaml:"Enable"` // use mysql or not
AutoMigrate bool `json:"" yaml:"AutoMigrate"`
Gorm GormConf `json:"" yaml:"Gorm"`
}
// gorm config
type GormConf struct {
SingularTable bool `json:"" yaml:"SingularTable"` // 是否使用单数表名(默认复数),启用后,User结构体表将是user
TablePrefix string `json:"" yaml:"TablePrefix"` // 表前缀
MaxOpenConns int `json:"" yaml:"MaxOpenConns"`
MaxIdleConns int `json:"" yaml:"MaxIdleConns"`
ConnMaxLifetime int `json:"" yaml:"ConnMaxLifetime"`
}
- 修改userserviceserver.go文件
package svc
import (
"fmt"
"github.com/zeromicro/go-zero/core/logx"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"study/go-zero-gorm/user-srv/internal/config"
"study/go-zero-gorm/user-srv/models"
"time"
)
type ServiceContext struct {
Config config.Config
DBEngine *gorm.DB
}
func NewServiceContext(c config.Config) *ServiceContext {
mysqlConf := c.MySQLConf
var db *gorm.DB
var err error
if mysqlConf.Enable {
db, err = creteDbClient(mysqlConf)
if err != nil {
db = nil
}
}
return &ServiceContext{
Config: c,
DBEngine: db,
}
}
func creteDbClient(mysqlConf config.MySQLConf) (*gorm.DB, error) {
datasource := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=%t&loc=%s",
mysqlConf.User,
mysqlConf.Password,
mysqlConf.Host,
mysqlConf.Port,
mysqlConf.Database,
mysqlConf.CharSet,
mysqlConf.ParseTime,
mysqlConf.TimeZone)
fmt.Printf(datasource)
db, err := gorm.Open(mysql.Open(datasource), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: mysqlConf.Gorm.TablePrefix, // such as: prefix_tableName
SingularTable: mysqlConf.Gorm.SingularTable, // such as zero_user, not zero_users
},
})
if err != nil {
logx.Errorf("create mysql db failed, err: %v", err)
return nil, err
}
// auto sync table structure, no need to create table
err = db.AutoMigrate(&models.User{})
if err != nil {
logx.Errorf("automigrate table failed, err: %v", err)
}
logx.Info("init mysql client instance success.")
sqlDB, err := db.DB()
if err != nil {
logx.Errorf("mysql set connection pool failed, err: %v.", err)
return nil, err
}
sqlDB.SetMaxOpenConns(mysqlConf.Gorm.MaxOpenConns)
sqlDB.SetMaxIdleConns(mysqlConf.Gorm.MaxIdleConns)
sqlDB.SetConnMaxLifetime(time.Duration(mysqlConf.Gorm.ConnMaxLifetime) * time.Second)
return db, nil
}
- 业务代码的编写操作数据库,函数goctl已经生成了,只需要编写业务代码
func (l *AddUserInfoLogic) AddUserInfo(in *user.UserInfo) (*user.Response, error) {
// todo: add your logic here and delete this line
userInfo := models.User{
Phone: int(in.Iphone),
Username: in.Name,
NickName: in.Nickname,
Password: in.Password,
Status: int8(in.Status),
IsAdmin: in.IsAdmin,
BaseModel: models.BaseModel{
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
DeletedAt: time.Now(),
},
}
err := l.svcCtx.DBEngine.Table("zero_user").Create(&userInfo).Error
if err != nil {
return &user.Response{Result: 500}, err
}
return &user.Response{Result: 200}, nil
}
2.网关业务代码编写
- 编写user.api文件
syntax = "v1"
type Request {
ID int `path:"id"`
}
type Response {
Message string `json:"message"`
}
type UserResponse {
Message string `json:"message"`
Data string `json:"data"`
Code int `json:"code"`
}
type UserRequest {
Username string `json:"username"` // 用户名
Nickname string `json:"nickname"` // 昵称
Phone int64 `json:"phone"` // 手机号
Password string `json:"password"` // 密码
Status int8 `json:"status"` // 状态 1:正常 2:白名单 3:黑名单
IsAdmin bool `json:"is_admin"`
}
service web-api {
@handler UserInfoHandler
get /user/:id (Request) returns (UserResponse)
@handler UserAddHandler
post /user/add (UserRequest) returns (Response)
}
- 通过goctl命令将api文件生成代码
goctl api go -api user.api -dir ../
- 修改web-api.yaml文件,将rpc服务添加到web-api.yaml文件中
Name: web-api
Host: 0.0.0.0
Port: 8888
UserRpcConf:
Etcd:
Hosts:
- 127.0.0.1:2379
Key: user.rpc
- 修改config.go配置文件
package config
import (
"github.com/zeromicro/go-zero/rest"
"github.com/zeromicro/go-zero/zrpc"
)
type Config struct {
rest.RestConf
UserRpcConf zrpc.RpcClientConf
}
- 修改servicecontext.go文件
package svc
import (
"github.com/zeromicro/go-zero/zrpc"
"study/go-zero-gorm/api-gateway/internal/config"
"study/go-zero-gorm/user-srv/userservice"
)
type ServiceContext struct {
Config config.Config
UserRpc userservice.UserService
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
UserRpc: userservice.NewUserService(zrpc.MustNewClient(c.UserRpcConf)),
}
}
- 编写业务代码,函数goctl已经生成了,只需要编写业务代码
func (l *UserAddLogic) UserAdd(req *types.UserRequest) (resp *types.Response, err error) {
// todo: add your logic here and delete this line
userInfo := user.UserInfo{
Name: req.Username,
Iphone: req.Phone,
Nickname: req.Nickname,
Password: req.Password,
Status: int32(req.Status),
IsAdmin: req.IsAdmin,
}
info, err := l.svcCtx.UserRpc.AddUserInfo(context.Background(), &userInfo)
if err != nil {
return nil, err
}
resp = &types.Response{
Message: strconv.Itoa(int(info.GetResult())),
}
return
}