编写微服务博客-第3部分“用户”

在我们的系列文章“在微服务上写博客”的第二部分中,我们描述了API Gateway

在这里,我们描述了用户微服务的实现。

我们的微服务应该能够:

  • 记录服务调用和指示TraceId的中间状态(由api-gw发出的相同状态,请参见第2部分“ API网关”
  • 实现登录(SignIN)和注册(SignUp)功能
  • 实现CRUD功能(在数据库中创建,读取,编辑,删除记录)。 使用MongoDB作为数据库。

首先,我们在原始文件(./services/user/protobuf/user.proto)中描述我们的服务。
指定使用的语法-proto3。 我们指定了protobuf软件包的名称,在该软件包中,将实现服务器和客户端部件的自动生成的代码。

我们导入了注释库google / api / annotations.proto,将需要用它来描述用于生成REST API的指令。

syntax = "proto3"; package protobuf; import "google/api/annotations.proto"; 

用户服务的描述,直接是它应具有的接口(方法)。 例如,SignUp接口(注册):它接收包含Username,Password,FirstName和LastName属性的SignUpRequest消息,并用包含Slug(UserID),Username,Role属性的SignUpResponse消息作为响应。 同样,在接口说明的“选项”部分中,指定post指令:“ / api / v1 / user / signup。基于此,代码生成器将创建一个REST接口,该接口将在http:{{api_gw_host}} / api / v1 / user处接收POST请求。 /注册。

 //-------------------------------------------------- //   User //-------------------------------------------------- service UserService { //  rpc SignUp (SignUpRequest) returns (SignUpResponse) { option (google.api.http) = { post: "/api/v1/user/signup" }; } … } //-------------------------------------------------- // SignUp //-------------------------------------------------- message SignUpRequest { string Username = 1; string Password = 2; string FirstName = 3; string LastName = 4; } message SignUpResponse { string Slug = 1; string Username = 2; string Role = 3; } 

它将在请求正文中包含以下结构:

 { Username: 'username_value', Password: 'password_value', FirstName: 'firstname_value', LastName: 'lastname_value', } 

因此,如果成功,它将给出以下结构:

 { Slug: 'user_id_value', Username: 'username_value', Role: 'role_value', } 

还是一个错误。 在稍后描述实现原型文件中描述的接口的函数的部分中,我们将在稍后再告诉您更多有关错误的信息。

其他接口(登录,创建,更新,删除,获取,查找)的声明方式相同。

现在我们有了一个现成的原型文件。 我们转到项目的根目录,并执行sh ./bin/protogen.sh命令。 该脚本将生成主要代码。
接下来,转到./services/user目录,并在functions.go文件中编写声明的接口的实现。

首先,我们实现中间件。 在对服务的每个请求中,我们从请求上下文中提取TraceId,UserId,UserRole参数,并将它们写入日志文件。 您可以在此处实施请求授权。

 //-------------------------------------------------- // Midelware //-------------------------------------------------- func AccessLogInterceptor(ctx context.Context,req interface{},info *grpc.UnaryServerInfo,handler grpc.UnaryHandler,) (interface{}, error) { start:=time.Now() md,_:=metadata.FromIncomingContext(ctx) // Calls the handler reply, err := handler(ctx, req) var traceId,userId,userRole string if len(md["trace-id"])>0{ traceId=md["trace-id"][0] } if len(md["user-id"])>0{ userId=md["user-id"][0] } if len(md["user-role"])>0{ userRole=md["user-role"][0] } msg:=fmt.Sprintf("Call:%v, traceId: %v, userId: %v, userRole: %v, time: %v", info.FullMethod,traceId,userId,userRole,time.Since(start)) app.AccesLog(msg) return reply, err } 

在SignUp方法中,我们确定响应结构。

 //,   STATUS_FAIL out:=&SignUpResponse{} 

接下来,检查请求参数。

 //     // Username err:=checkUserName(in.Username) if err!=nil{ log.Printf("[ERR] %s.SignUp, %v", app.SERVICE_NAME,err) return out,err } // Username   err=o.checkUserNameExist(in.Username) if err!=nil{ log.Printf("[ERR] %s.SignUp, %v", app.SERVICE_NAME,err) return out,err } // Password err=checkPassword(in.Password) if err!=nil{ log.Printf("[ERR] %s.SignUp, %v", app.SERVICE_NAME,err) return out,err } 

如果一切正常,请填写用户结构,写入数据库并返回答案。

 user:=&User{ Username:in.Username, FirstName:in.FirstName, LastName:in.LastName, Password:getMD5(in.Password), } var slug string collection:= o.DbClient.Database("blog").Collection("users") insertResult, err := collection.InsertOne(context.TODO(), user) if err != nil { return out,err } if oid, ok := insertResult.InsertedID.(primitive.ObjectID); ok { slug=fmt.Sprintf("%s",oid.Hex()) }else { err:=app.ErrInsert return out,err } out.Slug=slug out.Username=in.Username out.Role=app.ROLE_USER return out,nil 

另外,我们注意错误返回,例如:

 err:=app.ErrInsert 

由于最终该错误将返回到我们的api-wg(在其REST部分),因此将其转换为标准HTTP响应代码很酷。 为了不编写其他代码,您不应使用标准的go错误,而应使用google.golang.org/grpc/status包中的status.error。

文件/ Services / user / app / errors.go中描述了User微服务的所有典型错误以及如何将它们转换为HTTP响应代码。

 package app import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) //    var ( //  ErrEmailIncorrect = status.Error(codes.InvalidArgument, " E-mail") ErrPasswordIsEmpty = status.Error(codes.InvalidArgument, "Password  ") ErrUserNameIsEmpty = status.Error(codes.InvalidArgument, "E-mail  ") ErrUserNameIsExist = status.Error(codes.AlreadyExists, "  ") ErrNotFound = status.Error(codes.NotFound, "  ") ErrIncorrectLoginOrPassword = status.Error(codes.Unauthenticated,"   ") // CRUD ErrInsert = status.Error(codes.Internal, "  ") ErrUpdate = status.Error(codes.Internal, "  ") ) //================================================== // All gRPC err codes //================================================== // codes.OK - http.StatusOK // codes.Canceled - http.StatusRequestTimeout // codes.Unknown - http.StatusInternalServerError // codes.InvalidArgument - http.StatusBadRequest // codes.DeadlineExceeded - http.StatusGatewayTimeout // codes.NotFound - http.StatusNotFound // codes.AlreadyExists - http.StatusConflict // codes.PermissionDenied - http.StatusForbidden // codes.Unauthenticated - http.StatusUnauthorized // codes.ResourceExhausted - http.StatusTooManyRequests // codes.FailedPrecondition - http.StatusBadRequest // codes.Aborted - http.StatusConflict // codes.OutOfRange - http.StatusBadRequest // codes.Unimplemented - http.StatusNotImplemented // codes.Internal - http.StatusInternalServerError // codes.Unavailable - http.StatusServiceUnavailable // codes.DataLoss - http.StatusInternalServerError 

关于用户微服务,我想讲的最后一件事是它如何启动并连接到数据库。 这些操作在文件./services/user/main.go中执行。

服务启动:

 lis,err:= net.Listen("tcp", fmt.Sprintf(":%s", Port)) if err != nil { log.Fatalf("failed to listen: %v", err) } grpcServer:= grpc.NewServer( grpc.UnaryInterceptor(protobuf.AccessLogInterceptor), ) s:=&protobuf.Server{} … // attach the user service to the server protobuf.RegisterUserServiceServer(grpcServer, s) 

连接到数据库(main.go):

 //   s.DbConnect() defer s.DbDisconnect() 

DbConnect功能的实现(./services/user/functions.go):

 //-------------------------------------------------- // /   //-------------------------------------------------- func (o *Server) DbConnect() error { var client *mongo.Client // Create client strURI:=fmt.Sprintf("mongodb://%s:%s@%s:%s",os.Getenv("MONGO_USER"),os.Getenv("MONGO_PASS"),os.Getenv("MONGO_HOST"),os.Getenv("MONGO_PORT")) client, err:= mongo.NewClient(options.Client().ApplyURI(strURI)) if err != nil { return err } // Create connect err = client.Connect(context.TODO()) if err != nil { return err } o.DbClient=client return nil } 

Source: https://habr.com/ru/post/zh-CN482002/


All Articles