go-zero tutorial - Пользователь rpc - Войти

Go

Создание пользователя rpc

cdприбытьFoodGuidesПод содержанием. Создайтеrpcпапка

mkdir -p usermanage/rpc/user && cd usermanage/rpc/user

существуетrpc/userнаписать в каталогuser.protoдокумент

goctl rpc template -o user.proto

записыватьuser.protoдокумент

syntax = "proto3";

package user;

message LoginRequest {
  string email = 1;
  string password = 2;
}

message RegisterRequest {
  string Username = 1;
  string Email = 2;
  string Password = 3;
}

message UserinfoRequest {
  string Userid = 1;
  string Token = 2;
}

message Response {
  int64 id = 1;
  string email = 2;
  string accessToken = 3;
  int64 accessExpire = 4;
  int64 refreshAfter = 5;
}

service User {
  rpc Login(LoginRequest) returns(Response);
  rpc Register(RegisterRequest) returns(Response);
  rpc Userinfo(UserinfoRequest) returns(Response);
}

Мы определяем три интерфейса:Login Register UserInfo

генерироватьuser-rpcСлужить

goctl rpc proto -src user.proto -dir .

Проверьте этоrpc/userсодержание

➜  user git:(master) ✗ tree
.
├── etc
│   └── user.yaml
├── internal
│   ├── config
│   │   └── config.go
│   ├── logic
│   │   ├── loginlogic.go
│   │   ├── registerlogic.go
│   │   └── userinfologic.go
│   ├── server
│   │   └── userserver.go
│   └── svc
│       └── servicecontext.go
├── user
│   └── user.pb.go
├── user.go
├── user.proto
└── userclient
    └── user.go

8 directories, 11 files
➜  user git:(master) ✗

API Gatewayкод вызоваuser rpcСлужить

редактироватьapi/etcвнизuser-api.yamlфайл, добавитьuser.rpcнастроить

Name: user-api
Host: 0.0.0.0
Port: 8888

User:
  Etcd:
    Hosts:
      - localhost:2379
    Key: user.rpc

редактироватьapi/internal/configвнизconfig.goфайл, добавитьUserПеременная

type Config struct {
	rest.RestConf
	User zrpc.RpcClientConf
}

редактироватьapi/internal/svcвнизservicecontext.goфайл, добавитьUserVariable , добавьте код создания экземпляра.

type ServiceContext struct {
	Config config.Config
	User  userclient.User
}

func NewServiceContext(c config.Config) *ServiceContext {
	return &ServiceContext{
		Config: c,
		User: userclient.NewUser(zrpc.MustNewClient(c.User)),
	}
}

редактироватьapi/internal/logicвнизloginlogic.goфайл, добавить вызовuser rpcизloginметод

func (l *LoginLogic) Login(req types.LoginRequest) (*types.LoginResponse, error) {
	// todo: add your logic here and delete this line
	resp,err := l.svcCtx.User.Login(l.ctx, &user.LoginRequest{
		Email: req.Email,
		Password: req.Password,
	});
	if err != nil {
		return nil, err
	}

	token := types.JwtToken{
		AccessToken: resp.AccessToken,
		AccessExpire: resp.AccessExpire,
		RefreshAfter: resp.RefreshAfter,
	}

	response := types.UserReply{
		Id: resp.Id,
		Email: resp.Email,
		JwtToken: token,
	}

	return &types.LoginResponse{
		response,
	},nil
}

Определить структуру таблицы базы данных и сгенерировать код CRUD+cache

существуетusermanageСоздать подmodelпапка.

mkdir -p model & cd model

существуетmodelпод новымuser.sqlфайл и напишите следующее.

CREATE TABLE `user` (
                        `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户Id',
                        `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名称',
                        `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户密码',
                        `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户邮箱',
                        `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
                        `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                        PRIMARY KEY (`id`),
                        UNIQUE KEY `name_index` (`name`),
                        UNIQUE KEY `email_index` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

существуетmodelВыполните следующую команду в каталоге для созданияCRUD+cacheкод,-cуказать использованиеredis cache

goctl model mysql ddl -c -src user.sql -dir .

ПроверитьmodelСтруктура каталогов

➜  model git:(master) ✗ tree
.
├── user.sql
├── usermodel.go // CRUD+cache代码
└── vars.go      // 定义常量和变量

на этой машинеmysqlсоздан вfoodguidesбазу данных и создать новуюuserповерхность.

добавить часть данных

INSERT INTO `foodguides`.`user`(`id`, `name`, `password`, `email`) VALUES (1, 'Ningxi', 'd89617870c6f8a028f5728be69cc09d4cd3585b4651b7f206f1cd674bb4351ec', 'ningxi@ningxi.com');

rpcкод вызоваcrud+cacheкод

редактироватьrpc/user/etcвнизuser.yamlфайл, добавьте следующее содержимое.

Обратите внимание здесьmysqlиспользуетningxi-composeБегdockerконтейнер, поэтому параметры требуют внимания.

DataSource: root:2e70F5E6@(localhost:13306)/foodguides?parseTime=true
Table: user
Cache:
  - Host: localhost:16379
  
AccessSecret: ad879037-d3fd-tghj-112d-6bfc35d54b7d
AccessExpire: 86400
Salt: ^&yh

редактироватьrpc/user/internal/configвнизconfig.goфайл, добавитьDataSource Cache AccessSecret AccessExpireПеременная

type Config struct {
	zrpc.RpcServerConf
	DataSource string
	Cache cache.CacheConf
    AccessSecret string
	AccessExpire int64
    Salt string
}

редактироватьrpc/user/internal/svcвнизserviceContext.goфайл, добавитьModelVariable , добавьте код создания экземпляра.

type ServiceContext struct {
	Config config.Config
	Model model.UserModel
}

func NewServiceContext(c config.Config) *ServiceContext {
	return &ServiceContext{
		Config: c,
		Model: model.NewUserModel(sqlx.NewMysql(c.DataSource),c.Cache),
	}
}

редактироватьrpc/user/internal/logicвнизloginlogic.goфайл, добавьте следующий код

func (l *LoginLogic) Login(in *user.LoginRequest) (*user.Response, error) {
	res, err := l.svcCtx.Model.FindOneByEmail(in.Email)
	if err == nil {
		passwords := ningxi.PasswordEncrypt(l.svcCtx.Config.Salt,in.Password)
		if passwords == res.Password {
			now := time.Now().Unix()
			accessExpire := l.svcCtx.Config.AccessExpire
			jwtToken, err := l.getJwtToken(l.svcCtx.Config.AccessSecret, now, accessExpire, res.Id)
			if err != nil {
				return nil, err
			}
			response := user.Response{
				Email: res.Email,
				Id: res.Id,
				AccessToken: jwtToken,
				AccessExpire: now + accessExpire,
				RefreshAfter: now + accessExpire/2,
			}
			return &response, nil
		} else {
			return nil, errors.New("密码错误")
		}

	}
	return nil, err
}

я здесьloginlogicдобавлено вtokenСгенерируйте соответствующий код для шифрования и проверки пароля, чтобы, когда пользователь успешно войдет в систему, мы сгенерировалиtokenвозвращен клиенту.

Исправлятьapi responseОбратный формат

Я надеюсь, что формат возврата данных запроса клиентского интерфейса будет таким

{
    "code": 1,
    "message": "",
    "result": {
        "id": 1,
        "username": "",
        "email": "ningxi@ningxi.com",
        "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MTE0NzAzOTQsImlhdCI6MTYxMTM4Mzk5NCwidXNlcklkIjoxfQ.8EJU0XDZ535NZvtCPgyOg9RVw3FAdG5AJktHYcjEGo0",
        "accessExpire": 1611470394,
        "refreshAfter": 1611427194
    }
}

существуетfoodguidesдобавить в папкуningxiпапку и создатьningxi.goфайл, добавьте следующий код

import (
	"fmt"
	"golang.org/x/crypto/scrypt"
)

type HttpResponse struct {
	Code    int         `json:"code"`
	Message string      `json:"message"`
	Result interface{} `json:"result"`
}

func SuccessResponse(resData interface{},message string) HttpResponse {
	return HttpResponse{Code:1,Message: message,Result: resData}
}

func FailureResponse(resData interface{},message string,code int) HttpResponse {
	return HttpResponse{Code:code,Message: message,Result: resData}
}

func PasswordEncrypt(salt,password string) string {
	dk,_ := scrypt.Key([]byte(password), []byte(salt), 32768, 8, 1, 32)
	return fmt.Sprintf("%x",string(dk))
}

Отредактируйте loginhandler.go в разделе api/internal/handler и добавьте следующий код.

func LoginHandler(ctx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var req types.LoginRequest
		if err := httpx.Parse(r, &req); err != nil {
			httpx.OkJson(w, ningxi.FailureResponse(nil,err.Error(),1000))
			return
		}

		l := logic.NewLoginLogic(r.Context(), ctx)
		resp, err := l.Login(req)
		if err != nil {
			httpx.OkJson(w, ningxi.FailureResponse(nil,err.Error(),1000))
		} else {
			httpx.OkJson(w, ningxi.SuccessResponse(resp,""))
		}
	}
}

запустить службу

Запустите службу, обратите внимание, что перед запуском службы необходимо убедиться, что используется та, которая использовалась в предыдущей статье.ningxi-composeбеги нормально.

запускатьuser rpcслужба, после успешного запуска,user rpcбегать по локалке8080порт

➜  FoodGuides git:(master) ✗ go run usermanage/rpc/user/user.go -f usermanage/rpc/user/etc/user.yaml
Starting rpc server at 127.0.0.1:8080...

запускатьuser apiслужба, после успешного запуска,user apiбегать по локалке8888порт

➜  FoodGuides git:(master) ✗ go run usermanage/api/user.go -f usermanage/api/etc/user-api.yaml
Starting server at 0.0.0.0:8888...

apiПроверка Если получены следующие данные, служба работает нормально

➜  ~ curl http://localhost:8888/users/login -X POST -d '{"email": "ningxi@ningxi.com","password": "809161"}' --header "Content-Type: application/json"
{"code":1,"message":"","result":{"id":1,"username":"","email":"ningxi@ningxi.com","accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MTE0OTU3MjAsImlhdCI6MTYxMTQwOTMyMCwidXNlcklkIjoxfQ.EN9og9owK5eW--qUIJCf0UvMKNaeenVac1lmMQFcHSM","accessExpire":1611495720,"refreshAfter":1611452520}}%
➜  ~

Понять, как работает сервис

Старый способ — понять процесс выполнения сервиса

  • user/etcвнизuser.yamlдокумент. Этот файл настраиваетuser rpcНекоторые переменные, требуемые службой, это то же самое, что иapiОбслуживание такое же.
  • userвнизuser.protoдокумент. Этот файл определяетuser rpcИнформация об интерфейсе, предоставляемая службой, и последующие добавления интерфейса также обрабатываются здесь. тогда позвониgoctlВосстановите службу.
  • userвнизuser.goдокумент. Файлuser rpcВходной файл для службы, здесь все начинается.
  • user/userвнизuser.pb.goдокумент. Файлuser.protoгенерироватьrpcслужебный файл, содержащийrpcсерединаclientа такжеserverреализация.

папка пользователя-клиента

userclientвнизuser.goфайлrpcобслуживаниеclientКонкретный код реализации файла реализуетuser.pb.goтри интерфейса в

User interface {
	Login(ctx context.Context, in *LoginRequest) (*Response, error)
	Register(ctx context.Context, in *RegisterRequest) (*Response, error)
	Userinfo(ctx context.Context, in *UserinfoRequest) (*Response, error)
}

когдаapiв сервисеserviceContext.goв инициализацииuserclientчас,apiСервис имеет возможность вызова этих трех интерфейсов

type ServiceContext struct {
	Config config.Config
	User  userclient.User
}

func NewServiceContext(c config.Config) *ServiceContext {
	return &ServiceContext{
		Config: c,
		User: userclient.NewUser(zrpc.MustNewClient(c.User)),
	}
}

внутренняя папка

rpcВнутренний код реализации в сервисе находится в этой папке.

internal/configвнизconfig.goдокумент. Вы обнаружите, что файл определяет иuser.yamlопределение аналогичное. да.user.yamlсуществуетuser.goВходной файл находится вmainметод, он анализируется какconfigобъект. Так что их значения взаимно однозначно соответствуют.

internal/logicвнизxxxlogic.goфайл, здесь реализована окончательная бизнес-логика.

internal/svcвнизservicecontext.goдокумент. Файл сохраненrpcУслугиconfigОбъект, объект подключения к базе данных Модель. пройти черезUserServerобъект переданlogicв объекте.

internal/sercerвнизuserserver.goдокумент. Файлuser rpcобслуживаниеserverКонкретный код реализации ,userserverсохраненsvc.ServiceContextобъект и реализует три интерфейса

func (s *UserServer) Login(ctx context.Context, in *user.LoginRequest) (*user.Response, error) {
	l := logic.NewLoginLogic(ctx, s.svcCtx)
	return l.Login(in)
}

func (s *UserServer) Register(ctx context.Context, in *user.RegisterRequest) (*user.Response, error) {
	l := logic.NewRegisterLogic(ctx, s.svcCtx)
	return l.Register(in)
}

func (s *UserServer) Userinfo(ctx context.Context, in *user.UserinfoRequest) (*user.Response, error) {
	l := logic.NewUserinfoLogic(ctx, s.svcCtx)
	return l.Userinfo(in)
}

когдаapiобслуживание черезuserclientперечислитьrpcПри подаче,userserverЗапустит соответствующий метод и, наконец, вызовет соответствующийlogicметод.

Подведение итогов процесса вызова

В качестве примера возьмем клиент, вызывающий интерфейс входа в систему.

clientчасть

apiКогда служба запускается, она инициализируетсяuserclientобъект, поэтомуapiСервис имеет звонокuser rpc способность служить

type ServiceContext struct {
	Config config.Config
	User  userclient.User
}

func NewServiceContext(c config.Config) *ServiceContext {
	return &ServiceContext{
		Config: c,
		User: userclient.NewUser(zrpc.MustNewClient(c.User)),
	}
}

serverчасть

user.goвходной файл черезyamlконфигурационный файл, экземплярconfigобъект.

var configFile = flag.String("f", "etc/user.yaml", "the config file")

func main() {
	flag.Parse()

	var c config.Config
	conf.MustLoad(*configFile, &c)
}

создавать экземплярServiceContextобъект

ctx := svc.NewServiceContext(c)

создавать экземплярUserServerобъект

srv := server.NewUserServer(ctx)

создавать экземплярrpcСлужить

s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
	user.RegisterUserServer(grpcServer, srv)
})

user.RegisterUserServer(grpcServer, srv)сделал что-то вродеapiФункцию реализует маршрут в сервисе.

запускатьrpcСлужить

s.Start()

когдаapiобслуживание черезclientперечислитьloginспособ,rpc serverвызоветuserserveсерединаloginметод.

func (s *UserServer) Login(ctx context.Context, in *user.LoginRequest) (*user.Response, error) {
	l := logic.NewLoginLogic(ctx, s.svcCtx)
	return l.Login(in)
}

loginвызов методаlogicсерединаloginметод

После обработки данных интерфейс отвечает слой за слоем и, наконец, завершает вызов клиентского интерфейса.

Содержание этого фильма относительно длинное, в которомJwtToken,rpcсерединаclientа такжеserverДругие связанные знания, вы можете проверить информацию самостоятельно, чтобы лучше понять содержание учебника.

Последняя статья "go-zero tutorial - User API Gateway"

Далее "go-zero tutorial - User rpc - Register"