Go Kratos framework mall micro service actual user service API

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.

  1. 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))
}

This article is mainly about preparation, and the next one starts to implement the interface

Keywords: Go kratos

Added by PeeJay on Thu, 17 Feb 2022 11:33:39 +0200