Go-kratos 框架商城微服务实战之商城服务 (四) BFF 层

Golang
408
0
0
2022-08-28
标签   微服务

Go-kratos 框架商城微服务实战之用户服务 (四)

这篇主要编写 HTTP API 端的服务,跟前几篇写的用户服务对接上,主要还是项目的初始化准备工作。写的不清晰的地方可看GitHub 源码 , 也感谢您指出不足之处。
注:竖排 … 代码省略,为了保持文章的篇幅简洁,我会将一些不必要的代码使用竖排的 . 来代替,你在复制本文代码块的时候,切记不要将 . 也一同复制进去。这里所有 import 引入的包都没有特殊说明,自己写的时候要注意包的引入。

shop Api 准备工作

new 一个新的 kratos 项目

在 kratos-shop 目录下新建一个 kratos new shop shop 项目

// 整体的项目 目录结构如下
|-- kratos-shop
    |-- service
        |-- user // 原先的用户服务 grpc
    |-- shop //  刚刚通过 kratos new shop 新增的项目代码
  • 进入新建的 shop 目录下
  • 删除目录下的所有文件 rm -rf api/helloworld
  • 复制 service/user/api/user/v1/user.proto 文件到新建的 shop/api/service/user/v1 目录下
  • 执行命令 kratos proto add api/shop/v1/user.proto 创建 api user.proto
  • 执行 kratos proto server api/user/v1/user.proto -t internal/service 命令生成对应的 service 文件。
  • 删除不需要的 service 文件 rm internal/service/greeter.go
  • 完整执行命令如下:
cd kratos-shop
kratos new shop
cd shop
rm -rf api/helloworld
rm internal/service/greeter.go
kratos proto add api/shop/v1/user.proto
kratos proto server api/shop/v1/user.proto -t internal/service
mkdir -p api/service/user/v1
cp ../service/user/api/user/v1/user.proto api/service/user/v1

proto 的目录结构如下:

├── api
│   ├── service
│   │   └── user
│   │       └── v1
│   │           └── user.proto
│   └── shop
│       └── v1
│           └── user.proto

修改 shop 下的 user.proto

这里提供对外访问的接口,会聚合从不同的服务之间获取数据,接口路由通过 Protobuf IDL 定义对应的 REST API 和 gRPC API,

参数校验使用 Validate 中间件,使用 proto-gen-validate 生成
在使用 validate 之前首先需要安装 proto-gen-validate
syntax = "proto3";

package shop.shop.v1;
// 这里可以把 proto 文件下载下来,放到项目的 third_party 目录下
import "google/api/annotations.proto";
import "google/protobuf/empty.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 Captcha (google.protobuf.Empty) returns (CaptchaReply) {
    option (google.api.http) = {
      get: "/api/users/captcha",
    };
  }
  rpc Detail (google.protobuf.Empty) returns (UserDetailResponse) {
    option (google.api.http) = {
      get: "/api/users/detail",
    };
  }
}

// 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 = 2 [(validate.rules).string = {min_len: 8}];
  string captcha = 3 [(validate.rules).string = {min_len: 5,max_len:5}];
  string captchaId = 4  [(validate.rules).string ={min_len: 1}];
}

// user Detail returned
message UserDetailResponse{
  int64 id = 1;
  string mobile = 2;
  string nickName = 3;
  int64 birthday = 4;
  string gender = 5;
  int32 role = 6;
}

message CaptchaReply{
  string captchaId = 1;
  string picPath = 2;
}

这里一共定义了 4 个 rpc 方法,其中三个是需要跟之前写的用户服务交互的,还有个获取图片验证码的接口,自己内部实现。

  • shop 根目录执行 make api ,生成对应的 *pb.go 文件

修改配置文件

  • 修改 shop/configs/config.yaml 文件
项目中引入了 consul 配置需要把相关的配置设置好,service 就是 consul 用来服务发现的。这里的 trace 并没有用到呢先在这里定义了,之后会专门拿一篇来说说。auth 是用来 jwt 验证。
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
  • 新增 shop/configs/registry.yaml 文件
consul: 
  address: 127.0.0.1:8500 
  scheme: http
  • 修改 internal/conf/conf.proto 文件:
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; // 链路追踪 
  Auth auth = 4; // 认证鉴权 
  Service service = 5; // 服务注册与发现
}

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 { // 用户服务 
    string endpoint = 1;
  }
  message Goods { // 商品服务 
    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;
}
  • 生成新的配置文件
shop 根目录执行
make config
生成 shop/internal/conf/conf.pb.go  文件

修改 HTTP 服务

  • 修改 internal/server/http.go 文件
这里用到的一些 middleware 中间件都是 kratos 官方支持的,jwt、validate、tracing,具体使用方式可参考 kratos 的middleware 文档
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/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(),
            validate.Validator(), // 接口访问的参数校验
            selector.Server( // jwt 验证
                jwt.Server(func(token *jwt2.Token) (interface{}, error) {
                    return []byte(ac.JwtKey), nil
                }, jwt.WithSigningMethod(jwt2.SigningMethodHS256)),
            ).Match(NewWhiteListMatcher()).Build(),
            logging.Server(logger),
        ),
        http.Filter(handlers.CORS( // 浏览器跨域
            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 设置白名单,不需要 token 验证的接口
func NewWhiteListMatcher() selector.MatchFunc {
    whiteList := make(map[string]struct{})
    whiteList["/shop.shop.v1.Shop/Captcha"] = 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
    }
}
  • 修改 internal/server/server.go
由于此服务只对外提供 http 服务,所以同目录下 grpc 文件可以删除,这样子注册服务的时候也把 grpc 服务去掉。
package server

import (
    "github.com/google/wire"
)

// ProviderSet is server providers.
var ProviderSet = wire.NewSet(NewHTTPServer)

实现接口

  • 修改 shop/internal/service/service.go 文件
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

    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")),
    }
}
  • 修改 shop/internal/service/user.go
这里的 ShopService 的 usecase 还没实现,编辑器可能会有错误提示,先忽略
package service

import (
    "context" 
    "google.golang.org/protobuf/types/known/emptypb"

    v1 "shop/api/shop/v1"
)

func (s *ShopService) Register(ctx context.Context, req *v1.RegisterReq) (*v1.RegisterReply, error) {
    return s.uc.CreateUser(ctx, req)
}

func (s *ShopService) Login(ctx context.Context, req *v1.LoginReq) (*v1.RegisterReply, error) {
    return s.uc.PassWordLogin(ctx, req)
}

func (s *ShopService) Captcha(ctx context.Context, r *emptypb.Empty) (*v1.CaptchaReply, error) {
    return s.uc.GetCaptcha(ctx)
}

func (s *ShopService) Detail(ctx context.Context, r *emptypb.Empty) (*v1.UserDetailResponse, error) {
    return s.uc.UserDetailByID(ctx)
}

新增 jwt 验证的 Middleware

  • 新建文件 internal/pkg/middleware/auth/auth.go
package auth

import (
    "errors" 
    "github.com/golang-jwt/jwt/v4"
)

type CustomClaims struct {
    ID          int64
    NickName    string
    AuthorityId int
    jwt.StandardClaims
}

// CreateToken generate token
func CreateToken(c CustomClaims, key string) (string, error) {
    claims := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
    signedString, err := claims.SignedString([]byte(key))
    if err != nil {
        return "", errors.New("generate token failed" + err.Error())
    }
    return signedString, nil
}

新增生成验证码的文件

  • 新建文件 internal/pkg/captcha/captcha.go
package captcha

import (
    "context" 
    "github.com/mojocn/base64Captcha"
)

var Store = base64Captcha.DefaultMemStore

type CaptchaInfo struct {
    CaptchaId string
    PicPath   string
}

// GetCaptcha 生成验证码
func GetCaptcha(ctx context.Context) (*CaptchaInfo, error) {
    driver := base64Captcha.NewDriverDigit(80, 250, 5, 0.7, 80)
    cp := base64Captcha.NewCaptcha(driver, Store)
    id, b64s, err := cp.Generate()
    if err != nil {
        return nil, err
    }

    return &CaptchaInfo{
        CaptchaId: id,
        PicPath:   b64s,
    }, nil
}
  • 修改 shop/internal/biz/user.go
package biz

import (
    "context" 
    "errors" 
    "github.com/go-kratos/kratos/v2/log" 
    "github.com/go-kratos/kratos/v2/middleware/auth/jwt"
    jwt2 "github.com/golang-jwt/jwt/v4"
    v1 "shop/api/shop/v1" 
    "shop/internal/conf" 
    "shop/internal/pkg/captcha" 
    "shop/internal/pkg/middleware/auth" 
    "time"
)

// 定义错误信息
var (
    ErrPasswordInvalid     = errors.New("password invalid")
    ErrUsernameInvalid     = errors.New("username invalid")
    ErrCaptchaInvalid      = errors.New("verification code error")
    ErrMobileInvalid       = errors.New("mobile invalid")
    ErrUserNotFound        = errors.New("user not found")
    ErrLoginFailed         = errors.New("login failed")
    ErrGenerateTokenFailed = errors.New("generate token failed")
    ErrAuthFailed          = errors.New("authentication failed")
)

// 定义返回的数据的结构体
type User struct {
    ID        int64
    Mobile    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 // 这里是为了生存 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}
}

// GetCaptcha 验证码
func (uc *UserUsecase) GetCaptcha(ctx context.Context) (*v1.CaptchaReply, error) {
    captchaInfo, err := captcha.GetCaptcha(ctx)
    if err != nil {
        return nil, err
    }

    return &v1.CaptchaReply{
        CaptchaId: captchaInfo.CaptchaId,
        PicPath:   captchaInfo.PicPath,
    }, nil
}

func (uc *UserUsecase) UserDetailByID(ctx context.Context) (*v1.UserDetailResponse, error) {
    // 在上下文 context 中取出 claims 对象 
    var uId int64 
    if claims, ok := jwt.FromContext(ctx); ok {
        c := claims.(jwt2.MapClaims)
        if c["ID"] == nil {
            return nil, ErrAuthFailed
        }
        uId = int64(c["ID"].(float64))
    }

    user, err := uc.uRepo.UserById(ctx, uId)
    if err != nil {
        return nil, err
    }
    return &v1.UserDetailResponse{
        Id:       user.ID,
        NickName: user.NickName,
        Mobile:   user.Mobile,
    }, nil
}

func (uc *UserUsecase) PassWordLogin(ctx context.Context, req *v1.LoginReq) (*v1.RegisterReply, error) {
    // 表单验证 
    if len(req.Mobile) <= 0 {
        return nil, ErrMobileInvalid
    }
    if len(req.Password) <= 0 {
        return nil, ErrUsernameInvalid
    }
    // 验证验证码是否正确 
    if !captcha.Store.Verify(req.CaptchaId, req.Captcha, true) {
        return nil, ErrCaptchaInvalid
    }

    if user, err := uc.uRepo.UserByMobile(ctx, req.Mobile); err != nil {
        return nil, ErrUserNotFound
    } else {
        // 用户存在检查密码 
        if passRsp, pasErr := uc.uRepo.CheckPassword(ctx, req.Password, user.Password); pasErr != nil {
            return nil, ErrPasswordInvalid
        } else {
            if passRsp {
                claims := auth.CustomClaims{
                    ID:          user.ID,
                    NickName:    user.NickName,
                    AuthorityId: user.Role,
                    StandardClaims: jwt2.StandardClaims{
                        NotBefore: time.Now().Unix(),               // 签名的生效时间
                        ExpiresAt: time.Now().Unix() + 60*60*24*30, // 30天过期
                        Issuer:    "Gyl",
                    },
                }

                token, err := auth.CreateToken(claims, uc.signingKey)
                if err != nil {
                    return nil, ErrGenerateTokenFailed
                }
                return &v1.RegisterReply{
                    Id:        user.ID,
                    Mobile:    user.Mobile,
                    Username:  user.NickName,
                    Token:     token,
                    ExpiredAt: time.Now().Unix() + 60*60*24*30,
                }, nil
            } else {
                return nil, ErrLoginFailed
            }
        }
    }
}

func (uc *UserUsecase) CreateUser(ctx context.Context, req *v1.RegisterReq) (*v1.RegisterReply, error) {
    newUser, err := NewUser(req.Mobile, req.Username, req.Password)
    if err != nil {
        return nil, err
    }
    createUser, err := uc.uRepo.CreateUser(ctx, &newUser)
    if err != nil {
        return nil, err
    }
    claims := auth.CustomClaims{
        ID:          createUser.ID,
        NickName:    createUser.NickName,
        AuthorityId: createUser.Role,
        StandardClaims: jwt2.StandardClaims{
            NotBefore: time.Now().Unix(),               // 签名的生效时间
            ExpiresAt: time.Now().Unix() + 60*60*24*30, // 30天过期
            Issuer:    "Gyl",
        },
    }
    token, err := auth.CreateToken(claims, uc.signingKey)
    if err != nil {
        return nil, err
    }

    return &v1.RegisterReply{
        Id:        createUser.ID,
        Mobile:    createUser.Mobile,
        Username:  createUser.NickName,
        Token:     token,
        ExpiredAt: time.Now().Unix() + 60*60*24*30,
    }, nil
}

func NewUser(mobile, username, password string) (User, error) {
    // check mobile 
    if len(mobile) <= 0 {
        return User{}, ErrMobileInvalid
    }
    // check username 
    if len(username) <= 0 {
        return User{}, ErrUsernameInvalid
    }
    // check password 
    if len(password) <= 0 {
        return User{}, ErrPasswordInvalid
    }
    return User{
        Mobile:   mobile,
        NickName: username,
        Password: password,
    }, nil
}
  • 修改 shop/internal/biz/biz.go 文件
package biz

import "github.com/google/wire"

// ProviderSet is biz providers.
var ProviderSet = wire.NewSet(NewUserUsecase)
  • 修改 internal/data/data.go
这里比较重要,data 不是直接链接本机器配置的数据库的,而是链接各个服务,通过服务提供的 rpc 接口去获取数据的。
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/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 链接用户服务
func NewUserServiceClient(ac *conf.Auth, sr *conf.Service, rr registry.Discovery) userV1.UserClient {
    conn, err := grpc.DialInsecure(
        context.Background(),
        grpc.WithEndpoint(sr.User.Endpoint),// consul
        grpc.WithDiscovery(rr),// consul
        grpc.WithMiddleware(
            recovery.Recovery(),
        ),
        grpc.WithTimeout(2*time.Second),
    )
    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
}
  • 修改 shop/internal/data/user.go
package data

import (
    "context" 
    "github.com/go-kratos/kratos/v2/log"
    userService "shop/api/service/user/v1" 
    "shop/internal/biz"
)

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) {
    createUser, err := u.data.uc.CreateUser(c, &userService.CreateUserInfo{
        NickName: user.NickName,
        Password: user.Password,
        Mobile:   user.Mobile,
    })
    if err != nil {
        return nil, err
    }
    return &biz.User{
        ID:       createUser.Id,
        Mobile:   createUser.Mobile,
        NickName: createUser.NickName,
    }, nil
}

func (u *userRepo) UserByMobile(c context.Context, mobile string) (*biz.User, error) {
    byMobile, err := u.data.uc.GetUserByMobile(c, &userService.MobileRequest{Mobile: mobile})
    if err != nil {
        return nil, err
    }
    return &biz.User{
        Mobile:   byMobile.Mobile,
        ID:       byMobile.Id,
        NickName: byMobile.NickName,
    }, nil
}

func (u *userRepo) CheckPassword(c context.Context, password, encryptedPassword string) (bool, error) {
    if byMobile, err := u.data.uc.CheckPassword(c, &userService.PasswordCheckInfo{Password: password, EncryptedPassword: encryptedPassword}); err != nil {
        return false, err
    } else {
        return byMobile.Success, nil
    }
}

func (u *userRepo) UserById(c context.Context, id int64) (*biz.User, error) {
    user, err := u.data.uc.GetUserById(c, &userService.IdRequest{Id: id})
    if err != nil {
        return nil, err
    }
    return &biz.User{
        ID:       user.Id,
        Mobile:   user.Mobile,
        NickName: user.NickName,
        Gender:   user.Gender,
        Role:     int(user.Role),
    }, nil
}

修改启动服务

  • 修改 wire.go
一定要注意这里的注入的参数,多了少了都会报错的
package main

...

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))
}
  • 重新生成依赖注入关系
根目录执行
make wire
  • 修改入口文件 main.go
package main

import (
    "flag" 
    "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/registry" 
    "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 = "shop.api.v1" 
    // 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, gs *grpc.Server, rr registry.Registrar) *kratos.App {
    return kratos.New(
        kratos.ID(id+"shop.api"),
        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
    if err := c.Scan(&rc); 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)
    }
}

结束语

这一篇主要是前期的准备工作,下一篇开始测试接口、测试服务注册与发现、加入链路追踪并测试。

感谢您的耐心阅读,动动手指点个赞吧。