02:开发简单的登录功能
创建 proto
Kratos CLI工具 提供了创建proto文件的命令 kratos proto add ;使用下面命令添加 user.proto;当然也可以手动创建user.proto 文件;
kratos proto add api/user/v1/user.proto
先写一个登录rpc服务练习一下;默认的内容用不到的可以删除;写入一下内容;
syntax = "proto3";
package api.user.v1;
option go_package = "sys_api/api/user/v1;v1";
option java_multiple_files = true;
option java_package = "api.user.v1";
import "validate/validate.proto";
import "google/api/annotations.proto";
service UserService {
// 登陆
rpc Login (LoginRequest) returns (LoginResponse) {
option (google.api.http) = {
post: "/v1/login"
body: "*"
};
}
}
// 登录 -入参
message LoginRequest {
optional string username = 1;
optional string password = 2;
}
// 登录 - 返回
message LoginResponse {
string username = 1;
string token = 2;
string expires = 3;
string refresh_token = 4;
}
关于 Protocol Buffers 相关的语法可以参考官方文档
生成 Proto 代码
1、kratos cli 生成、make 生成 如果系统支持 make 命令官方layout中提供了默认的Makefile 可以直接通过 make 命令生成
make api
kratos cli 也提供了 相应的命令
kratos proto client api/user/v1/user.proto
2、 使用buf 管理 proto
Buf CLI是实现现代、快速、高效Protobuf API管理的终极工具。Buf具有格式化、linting、中断更改检测和代码生成等功能,为Protobuf的开发和维护提供了全面的解决方案;
buf 使用文档 链接 本项目我们使用buf 管理 proto文件;进到api目录,初始化buf.yaml
cd api
buf mod init
在buf.yaml 中添加依赖;如果使用buf生成proto,将依赖buf仓库中的proto文件,不再使用项目third_party目录中的文件;
version: v1
deps:
- buf.build/googleapis/googleapis
- buf.build/envoyproxy/protoc-gen-validate
- buf.build/kratos/apis
breaking:
use:
- FILE
lint:
use:
- DEFAULT
创建 buf.gen.yaml文件;指定生成文件及配置等;为了方便proto文件管理,把生成的文件与proto文件分离;统一放到了 gen 目录下
# 配置protoc生成规则
version: v1
plugins:
# Use protoc-gen-go at v1.28.1
- plugin: buf.build/protocolbuffers/go:v1.28.1
out: gen
opt: paths=source_relative
# Use the latest version of protoc-gen-go-grpc
- plugin: buf.build/grpc/go
out: gen
opt:
- paths=source_relative
- plugin: go-http
out: gen
opt:
- paths=source_relative
- plugin: openapi
out: gen
opt:
- paths=source_relative
# Use the latest version of protoc-gen-validate
- plugin: buf.build/bufbuild/validate-go
out: gen
opt:
- paths=source_relative
- plugin: go-errors
out: gen
opt:
- paths=source_relative
#使用openapi生成文件
- plugin: openapi
out: ./
opt:
- naming=proto
执行生成命令
buf build
buf generate
生成文件:
业务代码框架
1、service 定义 UserService 结构体, 依赖biz层实例实现 grpc service 接口;实现 Login 方法;同时也提供一个创建service对象的方法
ype UserService struct {
v1.UnimplementedUserServiceServer
log *log.Helper
uc *biz.UserUseCase
}
var _ (v1.UserServiceServer) = (*UserService)(nil)
func NewUserService(uc *biz.UserUseCase, logger log.Logger) *UserService {
l := log.NewHelper(log.With(logger, "module", "UserUseCase"))
return &UserService{
log: l,
uc: uc,
}
}
func (u *UserService) Login(ctx context.Context, request *v1.LoginRequest) (*v1.LoginResponse, error) {
login, err := u.uc.Login(ctx, &biz.LoginRequest{
Username: request.GetUsername(),
Password: request.GetPassword(),
})
return &v1.LoginResponse{
Username: login.Username,
Token: login.Token,
Expires: login.Expires,
RefreshToken: login.RefreshToken,
}, err
}
2、biz层代码;实现业务逻辑的组装层;定义数据层接口并依赖其实现具体业务逻辑;同时为了更好的管理事务,使data层业务代码与事务分离;biz层同时定义一个 Transaction 接口;参考官网示例:事务管理
type Transaction interface {
InTx(context.Context, func(ctx context.Context) error) error
}
一下为biz 主要业务逻辑代码:
type UserRepo interface {
GetUidByUsername(ctx context.Context, username string) (int64, error)
GetUserById(ctx context.Context, id int64) (*UserFields, error)
CheckPassword(ctx context.Context, id int64, password string) bool
GenerateToken(ctx context.Context, id int64) (*JWTToken, error)
}
type UserUseCase struct {
log *log.Helper
repo UserRepo
tm Transaction
}
func NewUserUseCase(repo UserRepo, tm Transaction, logger log.Logger) *UserUseCase {
l := log.NewHelper(log.With(logger, "module", "UserUseCase"))
return &UserUseCase{
repo: repo,
log: l,
tm: tm,
}
}
func (uc *UserUseCase) Login(ctx context.Context, request *LoginRequest) (*LoginResponse, error) {
//验证用户名
uid, err := uc.repo.GetUidByUsername(ctx, request.Username)
if err != nil {
uc.log.Error("username not found ", err)
return &LoginResponse{}, REASON_PWD_OR_UNAME_ERROR
}
//验证密码
if ok := uc.repo.CheckPassword(ctx, uid, request.Password); !ok {
uc.log.Error("password error", err)
return &LoginResponse{}, REASON_PWD_OR_UNAME_ERROR
}
// 生成Tjw token
token, err := uc.repo.GenerateToken(ctx, uid)
if err != nil {
return &LoginResponse{}, REASON_GENKEY_ERROR
}
return &LoginResponse{
Username: request.Username,
JWTToken: JWTToken{
Token: token.Token,
Expires: token.Expires,
RefreshToken: token.RefreshToken,
},
}, nil
}
3、data层可以专注于实现biz层的接口;biz层复制事务管理为了提高biz层的可复用性,降低耦合;biz层建议单独定义 个方法参数使用的结构体;使service层到biz层时做一次拷贝。 一下为data层 接口实现(啥用Gorm操作数据库):
package data
import (
"context"
"time"
"github.com/go-kratos/kratos/v2/log"
"sys_api/internal/biz"
"sys_api/internal/data/mini_sys"
"sys_api/pkg/app"
"sys_api/pkg/util/md5"
)
var _ biz.UserRepo = (*UserRepo)(nil)
type UserRepo struct {
data *Data
log *log.Helper
}
func NewUserRepo(data *Data, logger log.Logger) biz.UserRepo {
l := log.NewHelper(log.With(logger, "module", "user/repo/user-service"))
return &UserRepo{
data: data,
log: l,
}
}
func (u *UserRepo) GetUidByUsername(ctx context.Context, username string) (int64, error) {
var id int64
err := u.data.DB(ctx).Model(&mini_sys.SysUsers{}).Where("username = ?", username).
Pluck("id", &id).Error
if err != nil {
return 0, err
}
return id, err
}
func (u *UserRepo) CheckPassword(ctx context.Context, id int64, password string) bool {
var pwd string
err := u.data.DB(ctx).Model(&mini_sys.SysUserPassword{}).
Where("id = ?", id).Pluck("password", &pwd).Error
if err != nil {
return false
}
if pwd != md5.PasswordMD5(password) {
return false
}
return true
}
func (u *UserRepo) GenerateToken(ctx context.Context, id int64) (*biz.JWTToken, error) {
token, err := app.GenerateToken(u.data.jwt, id)
if err != nil {
u.log.Error(err)
return &biz.JWTToken{}, err
}
u.log.Info(u.data.jwt, token)
expires := u.data.jwt.Expires.AsDuration()
return &biz.JWTToken{
Token: token,
Expires: time.Now().Add(expires).Unix(),
RefreshToken: "",
}, nil
}
func (u *UserRepo) GetUserById(ctx context.Context, id int64) (*biz.UserFields, error) {
var user *mini_sys.SysUsers
err := u.data.DB(ctx).Model(&mini_sys.SysUsers{}).Where("id = ?", id).
First(&user).Error
if err != nil {
u.log.Error(err)
return &biz.UserFields{}, err
}
return &biz.UserFields{
Id: user.Id,
Username: user.Username,
Avatar: user.Avatar,
Gender: user.Gender,
Email: user.Email,
Roles: []int32{user.Role},
}, nil
}