Go Kratos framework mall micro service actual user service (5)
This article is mainly about the preparation of services that provide http interfaces to the web. You can see where you don't write clearly Source code , you are welcome to give advice.
Note: vertical... Code is omitted. In order to keep the article concise, I will use vertical for some unnecessary code Instead, when you copy the code block of this article, remember not to Copy it together.
- Creation of shop http service item
- New a new kratos project code
// Create a new shop project in the upper directory of the user service // The overall project directory structure is as follows |-- kratos-shop |-- service |-- user // Original grpc user service |-- shop // Directory code added through kratos new shop
- Delete all files in the shop/api/helloworld/v1 directory
- Modify or delete the shop/api/helloworld/v1 directory in the shop project as shop/api/shop/v1
- Copy service / user / API / user / V1 / user The proto file to the newly created shop/api/service/user/v1 directory
- Create a new shop / API / shop / V1 / shop Proto file
The interface for external access is provided here, which will aggregate data obtained from different services. The interface route defines the corresponding REST API and gRPC API through Protobuf IDL,
Parameter verification uses Validate middleware and proto Gen Validate
You need to install before using validate proto-gen-validate.
syntax = "proto3"; package shop.shop.v1; import "google/api/annotations.proto"; import "validate/validate.proto"; option go_package = "shop/api/shop/v1;v1"; // The Shop service definition. service Shop { rpc Register (RegisterReq) returns (RegisterReply) { option (google.api.http) = { post: "/api/users/register", body: "*", }; } rpc Login (LoginReq) returns (RegisterReply) { option (google.api.http) = { post: "/api/users/login", body: "*", }; } rpc Detail (DetailReq) returns (UserDetailResponse) { option (google.api.http) = { get: "/api/users/detail/{id}", }; } } // Data returned by registration and login message RegisterReply { int64 id = 1; string mobile = 3; string username = 4; string token = 5; int64 expiredAt = 6; } message RegisterReq { string mobile = 1 [(validate.rules).string.len = 11]; string username = 2 [(validate.rules).string = {min_len: 3, max_len: 15}]; string password = 3 [(validate.rules).string = {min_len: 8}]; } message LoginReq { string mobile = 1 [(validate.rules).string.len = 11]; string password = 3 [(validate.rules).string = {min_len: 8}]; } // select user details message DetailReq{ int64 id = 1 [(validate.rules).int64 = {gt: 0}]; } // user Detail returned message UserDetailResponse{ int64 id = 1; string mobile = 2; string nickName = 3; int64 birthday = 4; string gender = 5; int32 role = 6; }
- Execute the make api in the root directory of the shop to generate * Pb Go file
If you think it's unreasonable to design the project directory like this, you can design the directory yourself. As long as you can associate services.
- Modify shop / CMD / shop / main The name in the go file defines name = shop api
Note: the name here is used for service registration and discovery of consumer
- Delete or modify shop / configs / config Yaml profile
When introducing consumer and trace into the project, you need to set the relevant configuration when configuring the file. The service configuration is used by consumer for service discovery
name: shop.api server: http: addr: 0.0.0.0:8097 timeout: 1s grpc: addr: 0.0.0.0:9001 timeout: 1s data: database: driver: mysql source: root:root@tcp(127.0.0.1:3306)/test redis: addr: 127.0.0.1:6379 read_timeout: 0.2s write_timeout: 0.2s trace: endpoint: http://127.0.0.1:14268/api/traces auth: jwt_key: hqFr%3ddt32DGlSTOI5cO6@TH#fFwYnP$S service: user: endpoint: discovery:///shop.user.service goods: endpoint: discovery:///shop.goods.service
- Add shop / configs / registry Yaml file and add:
consul: address: 127.0.0.1:8500 scheme: http
- Modify the shop/internal/conf/conf.proto file:
syntax = "proto3"; package shop.api; option go_package = "shop/internal/conf;conf"; import "google/protobuf/duration.proto"; message Bootstrap { Server server = 1; Data data = 2; Trace trace = 3; // Link tracking Auth auth = 4; // Authentication Service service = 5; // Service registration and discovery } message Server { message HTTP { string network = 1; string addr = 2; google.protobuf.Duration timeout = 3; } message GRPC { string network = 1; string addr = 2; google.protobuf.Duration timeout = 3; } HTTP http = 1; GRPC grpc = 2; } message Data { message Database { string driver = 1; string source = 2; } message Redis { string network = 1; string addr = 2; google.protobuf.Duration read_timeout = 3; google.protobuf.Duration write_timeout = 4; } Database database = 1; Redis redis = 2; } message Service { message User { // User services string endpoint = 1; } message Goods { // Goods and services string endpoint = 1; } User user = 1; Goods goods = 2; } message Trace { string endpoint = 1; } message Registry { message Consul { string address = 1; string scheme = 2; } Consul consul = 1; } message Auth { string jwt_key = 1; }
- Generate a new configuration file conf.proto
shop Root directory execution make config generate shop/internal/conf/conf.pb.go file
Execute make config from the root directory of the shop to generate conf.pb Go file
Modify shop / internal / server / HTTP Go file:
Some of the middleware used here are officially supported by kratos, including jwt, validate and tracing. Please refer to the specific usage file
package server import ( "context" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/middleware/auth/jwt" "github.com/go-kratos/kratos/v2/middleware/logging" "github.com/go-kratos/kratos/v2/middleware/recovery" "github.com/go-kratos/kratos/v2/middleware/selector" "github.com/go-kratos/kratos/v2/middleware/tracing" "github.com/go-kratos/kratos/v2/middleware/validate" "github.com/go-kratos/kratos/v2/transport/http" jwt2 "github.com/golang-jwt/jwt/v4" "github.com/gorilla/handlers" v1 "shop/api/shop/v1" "shop/internal/conf" "shop/internal/service" ) // NewHTTPServer new an HTTP server. func NewHTTPServer(c *conf.Server, ac *conf.Auth, s *service.ShopService, logger log.Logger) *http.Server { var opts = []http.ServerOption{ http.Middleware( recovery.Recovery(), logging.Server(logger), validate.Validator(), // Parameter verification of interface access tracing.Server(), // Link tracking selector.Server( // jwt verification jwt.Server(func(token *jwt2.Token) (interface{}, error) { return []byte(ac.JwtKey), nil }, jwt.WithSigningMethod(jwt2.SigningMethodHS256)), ).Match(NewWhiteListMatcher()).Build(), ), http.Filter(handlers.CORS( // Browser cross domain handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Authorization"}), handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"}), handlers.AllowedOrigins([]string{"*"}), )), } if c.Http.Network != "" { opts = append(opts, http.Network(c.Http.Network)) } if c.Http.Addr != "" { opts = append(opts, http.Address(c.Http.Addr)) } if c.Http.Timeout != nil { opts = append(opts, http.Timeout(c.Http.Timeout.AsDuration())) } srv := http.NewServer(opts...) v1.RegisterShopHTTPServer(srv, s) return srv } // NewWhiteListMatcher whitelist interface that does not require token authentication func NewWhiteListMatcher() selector.MatchFunc { whiteList := make(map[string]struct{}) whiteList["/shop.shop.v1.Shop/Login"] = struct{}{} whiteList["/shop.shop.v1.Shop/Register"] = struct{}{} return func(ctx context.Context, operation string) bool { if _, ok := whiteList[operation]; ok { return false } return true } }
- Modify shop / internal / server / server go
package server import ( "github.com/google/wire" ) // ProviderSet is server providers. var ProviderSet = wire.NewSet(NewHTTPServer)
Since this service only provides http services, grpc files in the same directory can be deleted. In this way, the grpc service will be removed when the sub service is registered.
- Modify or delete shop / internal / service / greeter Go is shop / internal / service / user go
The usecase of ShopService here has not been implemented yet. The editor may have an error prompt. Ignore it first
package service import ( "context" v1 "shop/api/shop/v1" // Pay attention to the file generated by proto introduced in this location, otherwise the corresponding reception and return will not be found ) // User registration interface func (s *ShopService) Register(ctx context.Context, req *v1.RegisterReq) (*v1.RegisterReply, error) { return s.uc.CreateUser(ctx, req) } // User login interface func (s *ShopService) Login(ctx context.Context, req *v1.LoginReq) (*v1.RegisterReply, error) { return s.uc.PassWordLogin(ctx, req) } // Current login user details func (s *ShopService) Detail(ctx context.Context, req *v1.DetailReq) (*v1.UserDetailResponse, error) { return s.uc.UserDetailByID(ctx, req) }
- Modify shop / internal / service / service Go file
package service import ( "github.com/go-kratos/kratos/v2/log" "github.com/google/wire" v1 "shop/api/shop/v1" "shop/internal/biz" ) // ProviderSet is service providers. var ProviderSet = wire.NewSet(NewShopService) // ShopService is a shop service. type ShopService struct { v1.UnimplementedShopServer // This location introduces shop Do not introduce user into the service generated by proto uc *biz.UserUsecase log *log.Helper } // NewShopService new a shop service. func NewShopService(uc *biz.UserUsecase, logger log.Logger) *ShopService { return &ShopService{ uc: uc, log: log.NewHelper(log.With(logger, "module", "service/shop")), } }
- Modify shop / internal / biz / greeter Go is shop / internal / biz / user go
Note: user. In the service directory has not been implemented here Method called by go file
package biz . . . // Defines the structure to return type User struct { ID int64 Mobile string Password string NickName string Birthday int64 Gender string Role int CreatedAt time.Time } type UserRepo interface { CreateUser(c context.Context, u *User) (*User, error) UserByMobile(ctx context.Context, mobile string) (*User, error) UserById(ctx context.Context, Id int64) (*User, error) CheckPassword(ctx context.Context, password, encryptedPassword string) (bool, error) } type UserUsecase struct { uRepo UserRepo log *log.Helper signingKey string // Here is the key config required by jwt to generate token } func NewUserUsecase(repo UserRepo, logger log.Logger, conf *conf.Auth) *UserUsecase { helper := log.NewHelper(log.With(logger, "module", "usecase/shop")) return &UserUsecase{uRepo: repo, log: helper, signingKey: conf.JwtKey} }
- Modify shop / internal / biz / biz Go file
package biz import "github.com/google/wire" // ProviderSet is biz providers. var ProviderSet = wire.NewSet(NewUserUsecase) // Register usercase
- Modify or delete shop / internal / data / greeter Go is shop / internal / data / user go
package data . . . type userRepo struct { data *Data log *log.Helper } // NewUserRepo . func NewUserRepo(data *Data, logger log.Logger) biz.UserRepo { return &userRepo{ data: data, log: log.NewHelper(log.With(logger, "module", "repo/user")), } } func (u *userRepo) CreateUser(c context.Context, user *biz.User) (*biz.User, error) { return nil,nil } func (u *userRepo) UserByMobile(c context.Context, mobile string) (*biz.User, error) { return nil,nil } func (u *userRepo) CheckPassword(c context.Context, password, encryptedPassword string) (bool, error) { return nil,nil } func (u *userRepo) UserById(c context.Context, id int64) (*biz.User, error) { return nil,nil }
Modify shop / internal / data / data go
It is more important here. data is not directly linked to the database, but linked to the service
package data import ( "context" consul "github.com/go-kratos/kratos/contrib/registry/consul/v2" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/middleware/recovery" "github.com/go-kratos/kratos/v2/middleware/tracing" "github.com/go-kratos/kratos/v2/registry" "github.com/go-kratos/kratos/v2/transport/grpc" "github.com/google/wire" consulAPI "github.com/hashicorp/consul/api" grpcx "google.golang.org/grpc" userV1 "shop/api/service/user/v1" "shop/internal/conf" "time" ) // ProviderSet is data providers. var ProviderSet = wire.NewSet(NewData, NewUserRepo, NewUserServiceClient, NewRegistrar, NewDiscovery) // Data . type Data struct { log *log.Helper uc userV1.UserClient } // NewData . func NewData(c *conf.Data, uc userV1.UserClient, logger log.Logger) (*Data, error) { l := log.NewHelper(log.With(logger, "module", "data")) return &Data{log: l, uc: uc}, nil } // NewUserServiceClient link user service grpc // The ac here is not used for the time being. It can be used to verify the authentication calls between services, that is, middleware jwt func NewUserServiceClient(ac *conf.Auth, sr *conf.Service, rr registry.Discovery) userV1.UserClient { conn, err := grpc.DialInsecure( context.Background(), grpc.WithEndpoint(sr.User.Endpoint), // Here is the service name grpc.WithDiscovery(rr),// Service registration and discovery of consumer grpc.WithMiddleware( tracing.Client(), // Link tracking, complete request link recovery.Recovery(), ), grpc.WithTimeout(2*time.Second), grpc.WithOptions(grpcx.WithStatsHandler(&tracing.ClientHandler{})),// Complete request link ) if err != nil { panic(err) } c := userV1.NewUserClient(conn) return c } // NewRegistrar add consul func NewRegistrar(conf *conf.Registry) registry.Registrar { c := consulAPI.DefaultConfig() c.Address = conf.Consul.Address c.Scheme = conf.Consul.Scheme cli, err := consulAPI.NewClient(c) if err != nil { panic(err) } r := consul.New(cli, consul.WithHealthCheck(false)) return r } func NewDiscovery(conf *conf.Registry) registry.Discovery { c := consulAPI.DefaultConfig() c.Address = conf.Consul.Address c.Scheme = conf.Consul.Scheme cli, err := consulAPI.NewClient(c) if err != nil { panic(err) } r := consul.New(cli, consul.WithHealthCheck(false)) return r }
- Modify the entry file main go
package main import ( "flag" "github.com/go-kratos/kratos/v2/middleware/tracing" "github.com/go-kratos/kratos/v2/registry" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/jaeger" "go.opentelemetry.io/otel/sdk/resource" tracesdk "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.7.0" "os" "github.com/go-kratos/kratos/v2" "github.com/go-kratos/kratos/v2/config" "github.com/go-kratos/kratos/v2/config/file" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/transport/grpc" "github.com/go-kratos/kratos/v2/transport/http" "shop/internal/conf" ) // go build -ldflags "-X main.Version=x.y.z" var ( // Name is the name of the compiled software. Name = "shop.api" // Version is the version of the compiled software. Version string // flagconf is the config flag. flagconf string id, _ = os.Hostname() ) func init() { flag.StringVar(&flagconf, "conf", "../../configs", "config path, eg: -conf config.yaml") } func newApp(logger log.Logger, hs *http.Server, rr registry.Registrar) *kratos.App { return kratos.New( kratos.ID(id+"shop.api"), // Note here that if you are on the same computer and start the service at the same time, you need to modify the ID kratos.Name(Name), kratos.Version(Version), kratos.Metadata(map[string]string{}), kratos.Logger(logger), kratos.Server( hs, ), kratos.Registrar(rr), ) } func main() { flag.Parse() logger := log.With(log.NewStdLogger(os.Stdout), "ts", log.DefaultTimestamp, "caller", log.DefaultCaller, "service.id", id, "service.name", Name, "service.version", Version, "trace_id", tracing.TraceID(), "span_id", tracing.SpanID(), ) c := config.New( config.WithSource( file.NewSource(flagconf), ), ) defer c.Close() if err := c.Load(); err != nil { panic(err) } var bc conf.Bootstrap if err := c.Scan(&bc); err != nil { panic(err) } var rc conf.Registry // Consumer initialization if err := c.Scan(&rc); err != nil { panic(err) } // Link tracking initialization err := setTracerProvider(bc.Trace.Endpoint) if err != nil { panic(err) } app, cleanup, err := initApp(bc.Server, bc.Data, bc.Auth, bc.Service, &rc, logger) if err != nil { panic(err) } defer cleanup() // start and wait for stop signal if err := app.Run(); err != nil { panic(err) } } // Link tracking func setTracerProvider(url string) error { // Create the Jaeger exporter exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url))) if err != nil { return err } tp := tracesdk.NewTracerProvider( // Set the sampling rate based on the parent span to 100% tracesdk.WithSampler(tracesdk.ParentBased(tracesdk.TraceIDRatioBased(1.0))), // Always be sure to batch in production. tracesdk.WithBatcher(exp), // Record information about this application in an Resource. tracesdk.WithResource(resource.NewSchemaless( semconv.ServiceNameKey.String(Name), attribute.String("env", "dev"), )), ) otel.SetTracerProvider(tp) return nil }
- Modify shop / CMD / shop / wire go
Note the injection parameters here
// initApp init shop application. func initApp(*conf.Server, *conf.Data, *conf.Auth, *conf.Service, *conf.Registry, log.Logger) (*kratos.App, func(), error) { panic(wire.Build(server.ProviderSet, data.ProviderSet, biz.ProviderSet, service.ProviderSet, newApp)) }