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

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

结束语

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

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

本作品采用《CC 协议》,转载必须注明作者和本文链接
微信搜索:上帝喜爱笨人
本帖由系统于 1年前 自动加精
讨论数量: 11
[root@localhost shop]# make wire
cd cmd/shop/ && wire
wire: /var/goproject/src/kratos-shop/shop/cmd/shop/wire.go:22:1: inject initApp: no provider found for *github.com/go-kratos/kratos/v2/transport/grpc.Server
    needed by *github.com/go-kratos/kratos/v2.App in provider "newApp" (/var/goproject/src/kratos-shop/shop/cmd/shop/main.go:42:6)
wire: shop/cmd/shop: generate failed
wire: at least one generate failure
make: *** [wire] 错误 1

一直卡在这一步,作者是否能帮忙看下?

1年前 评论
Jadedev 1年前
Aliliin (楼主) 1年前
lyao 7个月前

make api 命令执行之后无法生成 validate 的 pb 文件,请问这个该怎么查看问题呢?

➜ make api                                       
protoc --proto_path=./api \
               --proto_path=./third_party \
               --go_out=paths=source_relative:./api \
               --go-http_out=paths=source_relative:./api \
               --go-grpc_out=paths=source_relative:./api \
               --openapi_out=fq_schema_naming=true,default_response=false:. \
               api/shop/v1/shop.proto api/service/user/v1/user.proto

➜ tree api/shop/
api/shop/
└── v1
    ├── shop.pb.go
    ├── shop.proto
    ├── shop_grpc.pb.go
    └── shop_http.pb.go
1年前 评论
Aliliin (楼主) 1年前
BestKind (作者) 1年前

jwt.StandardClaims 方法官方已经弃用了,记得换成jwt.RegisteredClaims 实现

1年前 评论
Aliliin (楼主) 1年前

请问你是完全没有做错误定义吗,我看源码完全没有思考过错误处理的问题

5个月前 评论
Aliliin (楼主) 5个月前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!