本文将介绍如何基于grpc-go实现服务的远程调用
不使用其他rpc框架的原因是grpc-go的实现相对更简单(而且在github ;go语言rpc框架榜单排名较高)
database部分代码就不写了,需要的可以看我的上一篇文章。
proto文件
// user.proto
syntax = "proto3";
option go_package = "/userpb";
package user;
import "google/protobuf/any.proto";
service UserService {
rpc CreateUser(CreateUserRequest) returns (BaseResponse) {}
rpc GetUser(GetUserRequest) returns (BaseResponse) {}
}
message CreateUserRequest {
string username = 1;
string account = 2;
string password = 3;
}
message GetUserRequest {
uint32 id = 1;
}
message BaseResponse {
google.protobuf.Any data = 1;
}
message User {
uint32 id = 1;
string username = 2;
string account = 3;
string password = 4;
}
这段代码是一个使用 Protocol Buffers (protobuf) 定义的用户服务接口和数据结构。
首先,syntax = "proto3"; 表示该文件使用的是 proto3 语法。
option go_package = "/userpb"; 表示生成的 Go 语言代码的包名为 userpb。
package user; 定义了当前文件的包名为 user,这个包名用于在生成的代码中进行组织。
import "google/protobuf/any.proto"; 引入了 google/protobuf/any.proto 文件,这个文件定义了 Any 类型,用于在消息中存储任意类型的数据。
service UserService 定义了一个名为 UserService 的服务,该服务包含两个 RPC 方法:CreateUser 和 GetUser。每个方法都有输入参数和返回结果。
message CreateUserRequest 定义了一个消息类型,用于在创建用户时传递参数。它包含三个字段:username、account 和 password,这些字段分别表示用户名、账号和密码。
message GetUserRequest 定义了一个消息类型,用于在获取用户信息时传递参数。它只包含一个字段 id,表示用户的ID。
message BaseResponse 定义了一个消息类型,用于作为所有方法的返回结果。它含有一个 data 字段,类型为 google.protobuf.Any,用于存储任意类型的数据。
message User 定义了一个消息类型,表示用户的信息。它包含四个字段:id、username、account 和 password,分别表示用户的ID、用户名、账号和密码。
service文件
package services
import (
"errors"
"gorm.io/gorm"
"user-center-go/database"
pb "user-center-go/proto/userpb"
)
type UserService interface {
CreateUser(user *pb.User) (*pb.User, error)
GetUserByID(id uint) (*pb.User, error)
GetUserByUsername(username string) (*pb.User, error)
UpdateUser(user *pb.User) (*pb.User, error)
DeleteUser(id uint) error
}
type userService struct{}
func NewUserService() UserService {
return &userService{}
}
func (s *userService) CreateUser(user *pb.User) (*pb.User, error) {
err := database.DB.Create(user).Error
if err != nil {
return nil, err
}
return user, nil
}
func (s *userService) GetUserByID(id uint) (*pb.User, error) {
var user pb.User
err := database.DB.First(&user, id).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil // 用户不存在
}
return nil, err
}
return &user, nil
}
func (s *userService) GetUserByUsername(username string) (*pb.User, error) {
var user pb.User
err := database.DB.Where("username = ?", username).First(&user).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil // 用户不存在
}
return nil, err
}
return &user, nil
}
func (s *userService) UpdateUser(user *pb.User) (*pb.User, error) {
err := database.DB.Save(user).Error
if err != nil {
return nil, err
}
return user, nil
}
func (s *userService) DeleteUser(id uint) error {
err := database.DB.Delete(&pb.User{}, id).Error
if err != nil {
return err
}
return nil
}
这段代码定义了一个名为 UserService 的接口,并提供了一些实现了该接口的方法。
首先,import 语句导入了所需的包。
UserService 是一个接口,它定义了一些操作用户的方法,包括创建用户、根据 ID 获取用户、根据用户名获取用户、更新用户和删除用户。
userService 是一个结构体类型,实现了 UserService 接口。它没有任何字段或状态,只是用来组织和提供实现接口方法的功能。
NewUserService() 是一个工厂函数,用于创建一个新的 UserService 实例。
接下来是各个方法的实现:
CreateUser方法用于创建用户,它接收一个pb.User类型的指针作为参数,将该用户存储到数据库中并返回创建的用户对象。GetUserByID方法用于根据用户 ID 获取用户信息,它接收一个id参数,通过查询数据库获取对应的用户信息,并返回用户对象。GetUserByUsername方法用于根据用户名获取用户信息,它接收一个username参数,通过查询数据库获取对应的用户信息,并返回用户对象。UpdateUser方法用于更新用户信息,它接收一个pb.User类型的指针作为参数,将更新后的用户信息存储到数据库中,并返回更新后的用户对象。DeleteUser方法用于删除用户,它接收一个id参数,根据用户的 ID 删除对应的用户信息。
在这些方法中,database.DB 是一个数据库连接对象,通过该对象来执行数据库操作。gorm 是一个流行的 Go 语言 ORM 库,用于操作关系型数据库。
这段代码实现了用户服务的基本功能,可以用于创建、获取、更新和删除用户信息。
handler
package handlers
import (
"context"
"fmt"
"google.golang.org/protobuf/types/known/anypb"
pb "user-center-go/proto/userpb"
"user-center-go/services" // 替换为你的service文件所在的包路径
)
// 只提供grpc服务
type Server struct {
UserService services.UserService
}
func (s *Server) CreateUser(_ context.Context, req *pb.CreateUserRequest) (*pb.BaseResponse, error) {
user := &pb.User{
Username: req.GetUsername(),
Account: req.GetAccount(),
Password: req.GetPassword(),
}
createdUser, err := s.UserService.CreateUser(user)
if err != nil {
return nil, err
}
userResult, _ := anypb.New(createdUser)
res := &pb.BaseResponse{
Data: userResult,
}
return res, nil
}
func (s *Server) GetUser(_ context.Context, req *pb.GetUserRequest) (*pb.BaseResponse, error) {
userID := req.GetId()
user, err := s.UserService.GetUserByID(uint(userID))
if err != nil {
return nil, err
}
if user == nil {
return nil, fmt.Errorf("user not found")
}
userResult, _ := anypb.New(user)
res := &pb.BaseResponse{
Data: userResult,
}
return res, nil
}
这段代码定义了一个名为 Server 的结构体类型,并提供了一些方法用于处理 gRPC 请求。
首先,import 语句导入了所需的包。
Server 是一个结构体类型,其中包含一个 UserService 类型的字段 UserService,用于处理用户相关的服务。
接下来是两个方法的实现:
CreateUser方法接收一个context.Context对象和一个pb.CreateUserRequest对象作为参数,用于创建用户。它从请求对象中获取用户名、账号和密码,然后调用UserService的CreateUser方法创建用户,并将返回的结果存储在createdUser变量中。如果创建用户的过程中发生错误,将返回错误;否则,将创建的用户对象转换为anypb.Any类型,并将其作为数据存储在pb.BaseResponse结构体中,并将该结构体作为响应返回。GetUser方法接收一个context.Context对象和一个pb.GetUserRequest对象作为参数,用于获取用户信息。它从请求对象中获取用户 ID,然后调用UserService的GetUserByID方法根据用户 ID 获取用户信息,并将返回的结果存储在user变量中。如果获取用户信息的过程中发生错误,将返回错误;如果用户不存在,将返回一个自定义的错误;否则,将用户对象转换为anypb.Any类型,并将其作为数据存储在pb.BaseResponse结构体中,并将该结构体作为响应返回。
这段代码实现了一个 gRPC 服务,通过处理不同的请求,调用相应的 UserService 方法来执行用户相关的操作,并将结果封装到 pb.BaseResponse 结构体中作为响应返回。
服务端启动类
package main
import (
"google.golang.org/grpc"
"log"
"net"
"user-center-go/database"
"user-center-go/handlers"
pb "user-center-go/proto/userpb"
"user-center-go/services"
)
func main() {
// 初始化数据库连接
database.InitSqliteDB()
// 创建UserService实例
userService := services.NewUserService()
// 创建gRPC服务器
grpcServer := grpc.NewServer()
// 注册UserService服务
pb.RegisterUserServiceServer(grpcServer, &handlers.Server{UserService: userService})
// 监听指定端口
listener, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
// 启动gRPC服务器
if err := grpcServer.Serve(listener); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}
这段代码是一个服务端的主程序代码,它完成以下几个主要任务:
-
调用
database.InitSqliteDB()来初始化数据库连接,确保数据库可用。 -
创建一个
UserService实例,即services.NewUserService(),用于处理用户相关的服务。 -
创建一个 gRPC 服务器实例
grpcServer,通过调用grpc.NewServer()来创建。 -
注册
UserService服务,使得客户端可以通过 gRPC 调用相应的方法。通过调用pb.RegisterUserServiceServer(grpcServer, &handlers.Server{UserService: userService})将userService注册到grpcServer上。 -
通过调用
net.Listen("tcp", ":50051")监听指定的端口(这里是 50051),并返回一个net.Listener对象。 -
最后,通过调用
grpcServer.Serve(listener)启动 gRPC 服务器,并开始监听客户端的请求。
整个过程可以概括为:初始化数据库连接,创建实例和服务器,注册服务,监听端口,并启动服务器等待客户端请求的到来。
客户端启动类
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"log"
"time"
pb "user-center-go/proto/userpb" // 替换为你的 proto 文件的包路径
)
func main() {
// 设置 gRPC 服务器的地址和端口
address := "localhost:50051"
// 创建与 gRPC 服务器的连接
conn, err := grpc.Dial(address, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("Failed to connect to gRPC server: %v", err)
}
defer conn.Close()
// 创建 gRPC 客户端
client := pb.NewUserServiceClient(conn)
// 循环获取用户输入的 ID
for {
// 获取用户输入的 ID
var id int64
fmt.Print("Enter the user ID (0 to exit): ")
_, err = fmt.Scan(&id)
if err != nil {
log.Fatalf("Failed to read user ID: %v", err)
}
if id == 0 {
// 输入 0 表示退出循环
break
}
// 发起 gRPC 调用
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
// 调用 GetUserByID 方法
getUserByIDRequest := &pb.GetUserRequest{
Id: uint32(id),
}
getUserByIDResponse, err := client.GetUser(ctx, getUserByIDRequest)
if err != nil {
log.Fatalf("Failed to get user by ID: %v", err)
}
// 处理返回结果...
log.Printf("User by ID: %v", getUserByIDResponse)
}
}
这段代码是一个 gRPC 客户端程序,用于向服务端发送请求并获取返回结果。
首先,import 语句导入了所需的包。
main 函数是程序的入口函数,其中包含了以下几个步骤:
-
设置 gRPC 服务器的地址和端口,这里设置为
localhost:50051。 -
创建与 gRPC 服务器的连接,使用
grpc.Dial方法建立连接。这里使用grpc.WithTransportCredentials(insecure.NewCredentials())来设置连接的安全凭证,其中insecure.NewCredentials()表示使用不安全的连接。在实际生产环境中,应该使用合适的安全凭证来保护连接。 -
创建 gRPC 客户端,使用
pb.NewUserServiceClient(conn)方法创建一个新的 gRPC 客户端实例,其中conn是前面建立的连接。 -
进入一个循环,获取用户输入的 ID。用户可以不断输入 ID 来查询用户信息,当输入 0 时表示退出循环。
-
发起 gRPC 调用。首先使用
context.WithTimeout方法创建一个带有超时时间的上下文,这里设置超时时间为 1 秒。然后调用client.GetUser方法来向服务端发起GetUserByID请求,传入用户输入的 ID。 -
处理返回结果,通过
getUserByIDResponse获取服务端返回的数据,并进行处理。这里只是简单地使用log.Printf打印出来,实际应用中可以根据业务需求进行相应的处理。
总之,这段代码创建了一个 gRPC 客户端,与服务端建立连接后,通过循环获取用户输入的 ID,并向服务端发起 GetUserByID 请求,然后处理返回结果。