当前位置: 首页 > 工具软件 > Kratos > 使用案例 >

kratos 微服务框架商城实战初识 kratos

严知
2023-12-01

准备工作

本机器这里已经安装好了 go、kratos、proto、wire、make 等所需的工具。

初始化项目目录

进入自己电脑中存放 Go 项目的目录

新建 kratos-shop/service 目录,并进入到新建的目录中

执行 kratos new user 命令并进入 user 目录,执行命令 kratos proto add api/user/v1/user.proto ,

这时你在 /service/user/api/user/v1 目录下会看到新的 user.proto 文件已经创建好了

接下来执行 kratos proto server api/user/v1/user.proto -t internal/service 命令生成对应的 service 文件。

删除不需要的 proto 文件 rm -rf api/helloworld/

删除不需要的 service 文件 rm internal/service/greeter.go

  • 完整的命令代码如下

mkdir  -p kratos-shop/servicecd kratos-shop/service
kratos new usercd user
kratos proto add api/user/v1/user.proto
kratos proto server api/user/v1/user.proto -t internal/service
rm -rf api/helloworld/
rm internal/service/greeter.go

复制代码

  • 修改 user.proto 文件,内容如下:

proto 基本的语法请自行学习,目前这里的只先提供了一个创建用户的 rpc 接口,后续会逐步添加其他 rpc 接口

syntax = "proto3";package user.v1;option go_package = "user/api/user/v1;v1";
service User{  rpc CreateUser(CreateUserInfo) returns (UserInfoResponse); // 创建用户}
// 创建用户所需字段message  CreateUserInfo{  string nickName = 1;  string password = 2;  string mobile = 3;}
// 返回用户信息message UserInfoResponse{  int64 id = 1;  string password = 2;  string mobile = 3;  string nickName = 4;  int64 birthday = 5;  string gender = 6;  int32 role = 7;}

复制代码

  • 生成 user.proto 定义的接口信息

进入到 service/user 目录下,执行 make api 命令,

这时可以看到 user/api/user/v1/ 目录下多出了 proto 创建的文件

cd user
make api
# 目录结构如下:├── api│   └── user│       └── v1│           ├── user.pb.go│           ├── user.proto│           └── user_grpc.pb.go

复制代码

修改配置文件

  • 修改 user/configs/config.yaml 文件,代码如下:

具体链接 mysql、redis 的参数填写自己本机的,本项目用到的是 gorm 。trace 是以后要用到的链路追踪的参数,先定义了。

server:  http:    addr: 0.0.0.0:8000    timeout: 1s  grpc:    addr: 0.0.0.0:50051    timeout: 1sdata:  database:    driver: mysql    source: root:root@tcp(127.0.0.1:3306)/shop_user?charset=utf8mb4&parseTime=True&loc=Local  redis:    addr: 127.0.0.1:6379    dial_timeout: 1s    read_timeout: 0.2s    write_timeout: 0.2strace:  endpoint: http://127.0.0.1:14268/api/traces

复制代码

新建 user/configs/registry.yaml 文件,代码如下:

# 这里引入了 consul 的服务注册与发现,先把配置加入进去consul:    address: 127.0.0.1:8500    scheme: http

复制代码

  • 修改 user/internal/conf/conf.proto 配置文件

# 文件底部新增 consul 和 trace 的配置信息message Trace {  string endpoint = 1;}
message Registry {  message Consul {    string address = 1;    string scheme = 2;  }  Consul consul = 1;}

复制代码

  • 新生成 conf.pb.go 文件,执行 make config

# `service/user` 目录下,执行命令make config

复制代码

安装 consul 服务工具

# 这里使用的是 docker 工具进行创建的docker run -d -p 8500:8500 -p 8300:8300 -p 8301:8301 -p 8302:8302 -p 8600:8600/udp consul consul agent -dev -client=0.0.0.0
# 浏览器访问 http://127.0.0.1:8500/ui/dc1/services 测试是否安装成功

复制代码

修改 internal 服务目录

修改 user/internal/data/ 目录下的文件

  • 修改 data.go 添加如下内容:

package data
import (    "github.com/go-kratos/kratos/v2/log"    "github.com/go-redis/redis/extra/redisotel"    "github.com/go-redis/redis/v8"    "github.com/google/wire"    "gorm.io/driver/mysql"    "gorm.io/gorm"    "gorm.io/gorm/logger"    "gorm.io/gorm/schema"    slog "log"    "os"    "time"    "user/internal/conf")
// ProviderSet is data providers.var ProviderSet = wire.NewSet(NewData, NewDB, NewRedis, NewUserRepo)
type Data struct {    db  *gorm.DB    rdb *redis.Client}
// NewData .func NewData(c *conf.Data, logger log.Logger, db *gorm.DB, rdb *redis.Client) (*Data, func(), error) {    cleanup := func() {        log.NewHelper(logger).Info("closing the data resources")    }    return &Data{db: db, rdb: rdb}, cleanup, nil}
// NewDB .func NewDB(c *conf.Data) *gorm.DB {    // 终端打印输入 sql 执行记录    newLogger := logger.New(        slog.New(os.Stdout, "\r\n", slog.LstdFlags), // io writer        logger.Config{            SlowThreshold: time.Second, // 慢查询 SQL 阈值            Colorful:      true,        // 禁用彩色打印            //IgnoreRecordNotFoundError: false,            LogLevel: logger.Info, // Log lever        },    )
    db, err := gorm.Open(mysql.Open(c.Database.Source), &gorm.Config{        Logger:                                   newLogger,        DisableForeignKeyConstraintWhenMigrating: true,        NamingStrategy:                           schema.NamingStrategy{            //SingularTable: true, // 表名是否加 s        },    })
    if err != nil {        log.Errorf("failed opening connection to sqlite: %v", err)        panic("failed to connect database")    }
    return db}
func NewRedis(c *conf.Data) *redis.Client {    rdb := redis.NewClient(&redis.Options{        Addr:         c.Redis.Addr,        Password:     c.Redis.Password,        DB:           int(c.Redis.Db),        DialTimeout:  c.Redis.DialTimeout.AsDuration(),        WriteTimeout: c.Redis.WriteTimeout.AsDuration(),        ReadTimeout:  c.Redis.ReadTimeout.AsDuration(),    })    rdb.AddHook(redisotel.TracingHook{})    if err := rdb.Close(); err != nil {        log.Error(err)    }    return rdb}

复制代码

这里的 wire 概念如果不熟悉的话,请参看 Wire 依赖注入

修改 user/internal/service/ 目录下的文件

  • 修改或者删除 user/internal/service/greeter.go 为 user.go , 添加代码如下:

package service
import (    "context"    "github.com/go-kratos/kratos/v2/log"    v1 "user/api/user/v1"    "user/internal/biz")
type UserService struct {    v1.UnimplementedUserServer
    uc  *biz.UserUsecase    log *log.Helper}
// NewUserService new a greeter service.func NewUserService(uc *biz.UserUsecase, logger log.Logger) *UserService {    return &UserService{uc: uc, log: log.NewHelper(logger)}}
// CreateUser create a userfunc (u *UserService) CreateUser(ctx context.Context, req *v1.CreateUserInfo) (*v1.UserInfoResponse, error) {    user, err := u.uc.Create(ctx, &biz.User{        Mobile:   req.Mobile,        Password: req.Password,        NickName: req.NickName,    })    if err != nil {        return nil, err    }
    userInfoRsp := v1.UserInfoResponse{        Id:       user.ID,        Mobile:   user.Mobile,        Password: user.Password,        NickName: user.NickName,        Gender:   user.Gender,        Role:     int32(user.Role),        Birthday: user.Birthday,    }
    return &userInfoRsp, nil}

复制代码

  • 修改 ser/internal/service/service.go 文件, 代码如下:

package service
import "github.com/google/wire"
// ProviderSet is service providers.var ProviderSet = wire.NewSet(NewUserService)

复制代码

  • 修改或删除 user/internal/biz/greeter.go 为 user.go 添加如下内容:

package biz
import (    "context"    "github.com/go-kratos/kratos/v2/log")
// 定义返回数据结构体type User struct {    ID       int64    Mobile   string    Password string    NickName string    Birthday int64    Gender   string    Role     int}
type UserRepo interface {    CreateUser(context.Context, *User) (*User, error)}
type UserUsecase struct {    repo UserRepo    log  *log.Helper}
func NewUserUsecase(repo UserRepo, logger log.Logger) *UserUsecase {    return &UserUsecase{repo: repo, log: log.NewHelper(logger)}}
func (uc *UserUsecase) Create(ctx context.Context, u *User) (*User, error) {    return uc.repo.CreateUser(ctx, u)}

复制代码

  • 修改 user/internal/biz/biz.go 文件,内容如下:

package biz
import "github.com/google/wire"
// ProviderSet is biz providers.var ProviderSet = wire.NewSet(NewUserUsecase)

复制代码

  • 修改或删除 user/internal/data/greeter.go 为 user.go 添加如下内容:

package data
import (    "context"    "crypto/sha512"    "fmt"    "github.com/anaskhan96/go-password-encoder"    "github.com/go-kratos/kratos/v2/log"    "google.golang.org/grpc/codes"    "google.golang.org/grpc/status"    "gorm.io/gorm"    "time"    "user/internal/biz")// 定义数据表结构体type User struct {    ID          int64      `gorm:"primarykey"`    Mobile      string     `gorm:"index:idx_mobile;unique;type:varchar(11) comment '手机号码,用户唯一标识';not null"`    Password    string     `gorm:"type:varchar(100);not null "` // 用户密码的保存需要注意是否加密    NickName    string     `gorm:"type:varchar(25) comment '用户昵称'"`    Birthday    *time.Time `gorm:"type:datetime comment '出生日日期'"`    Gender      string     `gorm:"column:gender;default:male;type:varchar(16) comment 'female:女,male:男'"`    Role        int        `gorm:"column:role;default:1;type:int comment '1:普通用户,2:管理员'"`    CreatedAt   time.Time  `gorm:"column:add_time"`    UpdatedAt   time.Time  `gorm:"column:update_time"`    DeletedAt   gorm.DeletedAt    IsDeletedAt bool}type userRepo struct {    data *Data    log  *log.Helper}
// NewUserRepo . 这里需要注意,上面 data 文件 wire 注入的是此方法,方法名不要写错了func NewUserRepo(data *Data, logger log.Logger) biz.UserRepo {    return &userRepo{        data: data,        log:  log.NewHelper(logger),    }}
// CreateUser .func (r *userRepo) CreateUser(ctx context.Context, u *biz.User) (*biz.User, error) {    var user User    // 验证是否已经创建    result := r.data.db.Where(&biz.User{Mobile: u.Mobile}).First(&user)    if result.RowsAffected == 1 {        return nil, status.Errorf(codes.AlreadyExists, "用户已存在")    }
    user.Mobile = u.Mobile    user.NickName = u.NickName    user.Password = encrypt(u.Password) // 密码加密    res := r.data.db.Create(&user)    if res.Error != nil {        return nil, status.Errorf(codes.Internal, res.Error.Error())    }    return &biz.User{        ID:       user.ID,        Mobile:   user.Mobile,        Password: user.Password,        NickName: user.NickName,        Gender:   user.Gender,        Role:     user.Role,    }, nil}
// Password encryptionfunc encrypt(psd string) string {    options := &password.Options{SaltLen: 16, Iterations: 10000, KeyLen: 32, HashFunction: sha512.New}    salt, encodedPwd := password.Encode(psd, options)    return fmt.Sprintf("$pbkdf2-sha512$%s$%s", salt, encodedPwd)}

复制代码

修改 user/internal/server/ 目录下的文件

这里用不到 http 服务,所以删除了 http.go 文件,

  • 修改 grpc.go 文件内容如下:

 package server
import (    "github.com/go-kratos/kratos/v2/log"    "github.com/go-kratos/kratos/v2/middleware/logging"    "github.com/go-kratos/kratos/v2/middleware/recovery"    "github.com/go-kratos/kratos/v2/transport/grpc"    v1 "user/api/user/v1"    "user/internal/conf"    "user/internal/service")
// NewGRPCServer new a gRPC server.func NewGRPCServer(c *conf.Server, greeter *service.UserService, logger log.Logger) *grpc.Server {    var opts = []grpc.ServerOption{        grpc.Middleware(            recovery.Recovery(),            logging.Server(logger),        ),    }    if c.Grpc.Network != "" {        opts = append(opts, grpc.Network(c.Grpc.Network))    }    if c.Grpc.Addr != "" {        opts = append(opts, grpc.Address(c.Grpc.Addr))    }    if c.Grpc.Timeout != nil {        opts = append(opts, grpc.Timeout(c.Grpc.Timeout.AsDuration()))    }    srv := grpc.NewServer(opts...)    v1.RegisterUserServer(srv, greeter)    return srv}

复制代码

  • 修改 server.go 文件,这里加入了 consul 的服务,内容如下:

package server
import (    "github.com/go-kratos/kratos/v2/registry"    "github.com/google/wire"    "user/internal/conf"
    consul "github.com/go-kratos/kratos/contrib/registry/consul/v2"    consulAPI "github.com/hashicorp/consul/api")
// ProviderSet is server providers.var ProviderSet = wire.NewSet(NewGRPCServer, NewRegistrar)
// NewRegistrar 引入 consulfunc 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}

复制代码

修改启动程序

  • 修改 user/cmd/wire.go 文件

这里注入了 consul 需要的配置,需要添加进来

func initApp(*conf.Server, *conf.Data, *conf.Registry, log.Logger) (*kratos.App, func(), error) {    panic(wire.Build(server.ProviderSet, data.ProviderSet, biz.ProviderSet, service.ProviderSet, newApp))}

复制代码

  • 修改 user/cmd/user/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/middleware/tracing"    "github.com/go-kratos/kratos/v2/registry"    "github.com/go-kratos/kratos/v2/transport/grpc"    "user/internal/conf")
// go build -ldflags "-X main.Version=x.y.z"var (    // Name is the name of the compiled software.    Name = "shop.users.service"    // Version is the version of the compiled software.    Version = "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, gs *grpc.Server, rr registry.Registrar) *kratos.App {    return kratos.New(        kratos.ID(id+"shop.user.service"),        kratos.Name(Name),        kratos.Version(Version),        kratos.Metadata(map[string]string{}),        kratos.Logger(logger),        kratos.Server(            gs,        ),        kratos.Registrar(rr), // consul 的引入    )}
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)    }    // consul 的引入    var rc conf.Registry    if err := c.Scan(&rc); err != nil {        panic(err)    }    app, cleanup, err := initApp(bc.Server, bc.Data, &rc, logger)    if err != nil {        panic(err)    }    defer cleanup()
    // start and wait for stop signal    if err := app.Run(); err != nil {        panic(err)    }}

复制代码

  • 修改根目录 user/makefile 文件

    在 go generate ./... 下面添加代码
    wire:        cd cmd/user/ && wire

复制代码

  • 根目录执行 make wire 命令

# service/usermake wire

复制代码

启动程序

别忘记根据 data 里面的 user struct 创建对应的数据库表,这里也可以写一个 gorm 创建表的文件进行创建。

启动程序 kratos run

根目录 service/user 执行命令    kratos run

复制代码

简单测试

由于没写对外访问的 http 服务,这里还没有加入单元测试,所以先创建个文件链接启动过的 grpc 服务简单测试一下。

  • 根目录新建 user/test/user.go 文件,添加如下内容:

package main
import (    "context"    "fmt"    "google.golang.org/grpc"    v1 "user/api/user/v1")
var userClient v1.UserClientvar conn *grpc.ClientConn
func main() {    Init()
    TestCreateUser() // 创建用户
    conn.Close()}
// Init 初始化 grpc 链接 注意这里链接的 端口func Init() {    var err error    conn, err = grpc.Dial("127.0.0.1:50051", grpc.WithInsecure())    if err != nil {        panic("grpc link err" + err.Error())    }    userClient = v1.NewUserClient(conn)}
func TestCreateUser() {
    rsp, err := userClient.CreateUser(context.Background(), &v1.CreateUserInfo{        Mobile:   fmt.Sprintf("1388888888%d", 1),        Password: "admin123",        NickName: fmt.Sprintf("YWWW%d", 1),    })    if err != nil {        panic("grpc 创建用户失败" + err.Error())    }    fmt.Println(rsp.Id)}

复制代码

这里别忘记启动 kratos user 服务之后,再执行 test/user.go 文件,查询执行结果,是否有个 ID 输出查询自己的数据库,看看是否有插入的数据了。

结束语

Kratos-shop 源码 已经上传到 GitHub 上了,如果有疑问,请点击源码进行查看。也感谢您指出错误。

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

如果你觉得这篇文章对你有帮助  点赞关注,然后私信回复【888】即可免费获取Java进阶全套视频以及源码学习资料

 类似资料: