Go Zero
This is to study go zero
新建proto文件
syntax = "proto3";
package user;
option go_package = "./user";
message IdRequest {
string id = 1;
}
message UserResponse {
string id = 1;
string name = 2;
bool gender = 3;
}
service User{
rpc getUser(IdRequest) returns(UserResponse);
}
// goctl rpc protoc user/rpc/user.proto --go_out=user/rpc/types --go-grpc_out=user/rpc/types --zrpc_out=user/rpc/
// goctl rpc protoc user.proto --go_out=types --go-grpc_out=types --zrpc_out=.
- 轉換proto文件
goctl rpc protoc user/rpc/user.proto --go_out=user/rpc/types --go-grpc_out=user/rpc/types --zrpc_out=user/rpc/
- OR
goctl rpc protoc user.proto --go_out=types --go-grpc_out=types --zrpc_out=.
修改proto文件
- 將要改變地方寫在/internal/logic/getuserlogic 裡面
使用postman發送服務
- 設定好地址 : grpc://localhost:[port]
- 導入proto 後按next再選擇設定地址旁邊的input
新建api文件
type (
VideoReq {
Id string `path:"id"`
}
VideoRes {
Id string `json:"id"`
Name string `json:"name"`
}
)
service video {
@handler getVideo
get /api/videos/:id (VideoReq) returns (VideoRes)
}
- 轉換api文件
goctl api go -api video/api/video.api -dir video/api/
添加grpc服務
- 修改/internal/config/config.go
type Config struct {
rest.RestConf
UserRpc zrpc.RpcClientConf
}
完善grpc依賴
- 修改/internal/svc/servicecontext.go
type ServiceContext struct {
Config config.Config
UserRpc userclient.User
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
UserRpc: userclient.NewUser(zrpc.MustNewClient(c.UserRpc)),
}
}
修改api文件以添加請求服務
- 將要改變地方寫在/internal/logic/getVideologic 裡面
func (l *GetVideoLogic) GetVideo(req *types.VideoReq) (resp *types.VideoRes, err error) {
// todo: add your logic here and delete this line
user1, err := l.svcCtx.UserRpc.GetUser(l.ctx, &user.IdRequest{
Id: "12",
})
if err != nil {
panic(err)
}
return &types.VideoRes{Id: req.Id, Name: user1.Name}, nil
}
啟動服務
- 開啟etcd服務器,終端輸入etcd (在專案根目錄執行)
- 開啟rpc服務 go run user.go
- 開啟api服務 go run api.go
如果遇到明明可以請求rpc但api始終報[rpc服務名稱].rpc未開啟
- 將yaml配置文欓中的地址改為localhost而非etcd
- 重開電腦
封裝response避免每次修改都重複編輯
- 生成模板文件
goctl template init
- 更改模板文件會再生成api文黨時進行變更
{{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}&req{{end}})
// if err != nil {
// httpx.ErrorCtx(r.Context(), w, err)
// } else {
// {{if .HasResp}}httpx.OkJsonCtx(r.Context(), w, resp){{else}}httpx.Ok(w){{end}}
// }
{{if .HasResp}}response.Response(r,w,resp,err){{else}}reponse.Response(r,w,nil,err){{end}}
goctl api go -api user.api -dir .
//入餐,一定要大寫
type LoginRequest {
Username string `json:"username"`
Password string `json:"password"`
}
type UserInfoResponse {
UserId uint `json:"userId"`
Username string `json:"username"`
}
service users{
//試圖函數
@handler login
post /api/users/login (LoginRequest) returns (string )
@handler userinfo
get /api/users/userinfo returns (UserInfoResponse)
}
// goctl api go -api user.api -dir .
resp, err := l.Login(&req)
// if err != nil {
// httpx.ErrorCtx(r.Context(), w, err)
// } else {
// httpx.OkJsonCtx(r.Context(), w, resp)
// }
response.Response(r, w, resp, err)
為api新增路由前綴
//入餐,一定要大寫
type LoginRequest {
Username string `json:"username"`
Password string `json:"password"`
}
type UserInfoResponse {
UserId uint `json:"userId"`
Username string `json:"username"`
}
@server (
prefix : /api/users
)
service users{
//試圖函數
@handler login
post /login (LoginRequest) returns (string )
@handler userinfo
get /userinfo returns (UserInfoResponse)
}
// goctl api go -api user.api -dir .
為api新增JWT
//入餐,一定要大寫
type LoginRequest {
Username string `json:"username"`
Password string `json:"password"`
}
type UserInfoResponse {
UserId uint `json:"userId"`
Username string `json:"username"`
}
@server (
prefix : /api/users
)
service users{
//試圖函數
@handler login
post /login (LoginRequest) returns (string )
}
@server (
prefix : /api/users
jwt : Auth //固定寫法
)
service users{
@handler userinfo
get /userinfo returns (UserInfoResponse)
}
// goctl api go -api user.api -dir .
添加生成jwt操作
Auth中的JWT到etc資料夾裡面的users.yaml配置
對測試api的工具新增Bearer Token請求頭輸入token資料來驗證和解析
package jwt
import (
"errors"
"github.com/golang-jwt/jwt/v4"
"time"
)
type JwtPayload struct {
UserId uint `json:"userId"`
Username string `json:"username"`
Role int `json:"role"`
}
type CustomClaims struct {
JwtPayload
jwt.RegisteredClaims
}
func GetToken(user JwtPayload, accessSecret string, expires int64) (string, error) {
claims := CustomClaims{user, jwt.RegisteredClaims{ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour + time.Duration(expires)))}}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(accessSecret))
}
func ParseToken(tokenStr string, accessSecret string, expires int64) (*CustomClaims, error) {
token, err := jwt.ParseWithClaims(tokenStr, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(accessSecret), nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
return claims, nil
}
return nil, errors.New("invalid token")
}
修改logic/loginlogic.go以獲得jwt
func (l *LoginLogic) Login(req *types.LoginRequest) (resp string, err error) {
// todo: add your logic here and delete this line
//獲取配置文件
auth := l.svcCtx.Config.Auth
token, err := jwt.GetToken(jwt.JwtPayload{
UserId: 1,
Username: "peter",
Role: 1,
}, auth.AccessSecret, auth.AccessExpire)
if err != nil {
return "", err
}
return token, nil
}
修改logic/userinfologic.go以解析jwt並獲得值
func (l *UserinfoLogic) Userinfo() (resp *types.UserInfoResponse, err error) {
// todo: add your logic here and delete this line
//獲取token值
userid := l.ctx.Value("userId").(json.Number)
//遇到不知道的類型錯誤檢查方式
//fmt.Printf("%v %T",userid,userid)
uuid, _ := userid.Int64()
username := l.ctx.Value("username").(string)
return &types.UserInfoResponse{
UserId: uint(uuid),
Username: username,
}, nil
}
修改api_jwt底下的users.go完成驗證jwt失敗返回訊息
func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
server := rest.MustNewServer(c.RestConf, rest.WithUnauthorizedCallback(JwtUnauthorizedResult))
defer server.Stop()
ctx := svc.NewServiceContext(c)
handler.RegisterHandlers(server, ctx)
fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
server.Start()
}
func JwtUnauthorizedResult(w http.ResponseWriter, r *http.Request, err error) {
fmt.Println(err)
httpx.WriteJson(w, http.StatusOK, response.Body{10087, nil, "認證失敗"})
}
操作Mysql
- 使用sqlx
- 編寫model/user.sql檔案
create table user
(
id bigint AUTO_INCREMENT,
username varchar(36) NOT NULL,
password varchar(64) default '',
UNIQUE name_index (username),
PRIMARY KEY (id)
)ENGINE = InnoDB COLLATE utf8mb4_general_ci;
# goctl model mysql ddl --src user.sql --dir .
- 設定Config和依賴
package config
import "github.com/zeromicro/go-zero/rest"
type Config struct {
rest.RestConf
Mysql struct {
Database string
}
}
package svc
import (
"github.com/zeromicro/go-zero/core/stores/sqlx"
"go_zero/study_model/user/api/internal/config"
"go_zero/study_model/user/model"
)
type ServiceContext struct {
Config config.Config
UserModel model.UserModel
}
func NewServiceContext(c config.Config) *ServiceContext {
mysqlConn := sqlx.NewMysql(c.Mysql.Database)
return &ServiceContext{
Config: c,
UserModel: model.NewUserModel(mysqlConn),
}
}
- 編寫測試logic/loginlogic.go
func (l *LoginLogic) Login(req *types.LoginRequest) (resp string, err error) {
// todo: add your logic here and delete this line
res, err := l.svcCtx.UserModel.Insert(l.ctx, &model.User{
Username: "peter",
Password: "63674782",
})
if err != nil {
return "", err
}
fmt.Println(res)
return "success", nil
}
- 編寫etc/users.yaml
Name: users
Host: 0.0.0.0
Port: 8888
Mysql:
Database: root:123456@tcp(127.0.0.1:3306)/zero_db?charset=utf8mb4&parseTime=True&loc=
- 使用gorm
- 編寫common/init_gorm/enter.go來啟動db連接
package init_gorm
import (
"fmt"
"go_zero/study_model/user_gorm/models"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func InitGorm(MysqlDatabase string) *gorm.DB {
db, err := gorm.Open(mysql.Open(MysqlDatabase))
if err != nil {
panic("connect to database error")
}
fmt.Println("success to connect")
db.AutoMigrate(&models.UserModel{})
return db
}
- 編寫models/user_model.go
package models
import "gorm.io/gorm"
type UserModel struct {
gorm.Model
Username string `gorm:"size:32" json:"username"`
Password string `gorm:"size:64" json:"password"`
}
- 編寫svc/servicecontext.go和config/config.go
package svc
import (
"go_zero/common/init_gorm"
"go_zero/study_model/user_gorm/api/internal/config"
"gorm.io/gorm"
)
type ServiceContext struct {
Config config.Config
DB *gorm.DB
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
DB: init_gorm.InitGorm(c.Mysql.Database),
}
}
package config
import "github.com/zeromicro/go-zero/rest"
type Config struct {
rest.RestConf
Mysql struct {
Database string
}
}
- 編寫logic/loginlogic.go測試gorm是否可以正確運行
package logic
import (
"context"
"fmt"
"go_zero/study_model/user_gorm/api/internal/svc"
"go_zero/study_model/user_gorm/api/internal/types"
"go_zero/study_model/user_gorm/models"
"github.com/zeromicro/go-zero/core/logx"
)
type LoginLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic {
return &LoginLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *LoginLogic) Login(req *types.LoginRequest) (resp string, err error) {
// todo: add your logic here and delete this line
//err = l.svcCtx.DB.Create(&models.GUserModel{
// Username: "defer",
// Password: "123456",
//}).Error
user := &models.UserModel{}
err = l.svcCtx.DB.Find(user).Error
if err != nil {
return "", err
}
fmt.Println(user)
return "success", nil
}