wx

关注公众号领取Go学习资料

wx

洋葱小店

wx

LUCA

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

生成文件: image.png

业务代码框架

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
}
如有疑问关注公众号给我留言
wx

关注公众号领取Go学习资料

wx

洋葱小店

wx

LUCA

©2017-2023 鲁ICP备17023316号-1 Powered by Hugo