kratos微服务商城实战

一.用户微服务

1.1准备工作
1.初始化项目目录

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

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

执行 kratos new user 命令并进入 user 目录,

执行命令 kratos proto add api/user/v1/user.proto ,这时你在 kratos-shop/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

2.完整的命令代码如下
mkdir  -p kratos-shop/service
cd kratos-shop/service

kratos new user
cd 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
3.修改 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;
}
4.生成 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
1.2修改配置文件
1.2.1修改 user/configs/config.yaml 文件,代码如下:

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

server:
  http:
    addr: 0.0.0.0:8000
    timeout: 1s
  grpc:
    addr: 0.0.0.0:9000
    timeout: 1s
data:
  database:
    driver: mysql
    source: root:123456@tcp(127.0.0.1:3306)/shop_user?charset=utf8mb4&parseTime=True&loc=Local
  redis:
    addr: 127.0.0.1:6379
    password: "123456"
    db: 1
    read_timeout: 0.2s
    write_timeout: 0.2s
1.2.2新建 user/configs/registry.yaml 文件,引入consul 服务,代码如下:
# 这里引入了 consul 的服务注册与发现,先把配置加入进去
consul:
    address: 127.0.0.1:8500
    scheme: http
1.2.3修改 user/internal/conf/conf.proto 配置文件
syntax = "proto3";
package kratos.api;

option go_package = "user/internal/conf;conf";

import "google/protobuf/duration.proto";

message Bootstrap {
  Server server = 1;
  Data data = 2;
}

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;
    string password=5;
    int32 db=6;
    google.protobuf.Duration read_timeout = 3;
    google.protobuf.Duration write_timeout = 4;
  }
  Database database = 1;
  Redis redis = 2;
}

//文件底部新增 consul 和 trace 的配置信息
message Trace {
  string endpoint = 1;
}

message Registry {
  message Consul {
    string address = 1;
    string scheme = 2;
  }
  Consul consul = 1;
}
1.2.4新生成 conf.pb.go 文件,执行 make config
# `service/user` 目录下,执行命令
make config
1.2.5安装 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://192.168.66.128:8500/ui/dc1/services 测试是否安装成功

在这里插入图片描述

1.3修改服务代码
1.3.1修改 data.go

修改user/internal/data/ data.go

data.go添加如下内容:

package data

import (
   "github.com/go-redis/redis/v8"
   "gorm.io/driver/mysql"
   "gorm.io/gorm"
   "gorm.io/gorm/logger"
   "gorm.io/gorm/schema"
   "os"
   "time"
   "user/internal/conf"

   "github.com/go-kratos/kratos/v2/log"
   "github.com/google/wire"
   slog "log"
)

// ProviderSet is data providers.
var ProviderSet = wire.NewSet(NewData, NewDB, NewRedis, NewUserRepo)

// Data .
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),
      WriteTimeout: c.Redis.WriteTimeout.AsDuration(),
      ReadTimeout:  c.Redis.ReadTimeout.AsDuration(),
   })
   if err := rdb.Close(); err != nil {
      log.Error(err)
   }
   return rdb
}
1.3.2添加data层user.go文件

user/internal/data/ 增加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 encryption
func 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)
}
1.3.3在service服务层下增加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)}
}

func (u *UserService) CreateUser(ctx context.Context, req *v1.CreateUserRequest) (*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
}

修改 service.go 文件, 代码如下:

package service

import "github.com/google/wire"

// ProviderSet is service providers.
var ProviderSet = wire.NewSet(NewUserService)
1.3.4在biz层增加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)
}

修改 biz.go 文件, 代码如下:

package biz

import "github.com/google/wire"

// ProviderSet is biz providers.
var ProviderSet = wire.NewSet(NewUserUsecase)
1.3.5修改grpc.go

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

这里用不到 http 服务删除 http.go 文件,修改 grpc.go 文件内容如下:

package server

import (
   v1 "user/api/user/v1"
   "user/internal/conf"
   "user/internal/service"

   "github.com/go-kratos/kratos/v2/log"
   "github.com/go-kratos/kratos/v2/middleware/recovery"
   "github.com/go-kratos/kratos/v2/transport/grpc"
)

// 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(),
      ),
   }
   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
}
1.3.6修改 service.go

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

package server

import (
   consul "github.com/go-kratos/kratos/contrib/registry/consul/v2"
   "github.com/go-kratos/kratos/v2/registry"
   "github.com/google/wire"
   consulAPI "github.com/hashicorp/consul/api"
   "user/internal/conf"
)

// ProviderSet is server providers.
var ProviderSet = wire.NewSet(NewGRPCServer, NewRegistrar)

// NewRegistrar 引入 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
}
1.3.7修改wire.go文件

在cmd/user/wire.go

//go:build wireinject
// +build wireinject

// The build tag makes sure the stub is not built in the final build.

package main

import (
   "user/internal/biz"
   "user/internal/conf"
   "user/internal/data"
   "user/internal/server"
   "user/internal/service"

   "github.com/go-kratos/kratos/v2"
   "github.com/go-kratos/kratos/v2/log"
   "github.com/google/wire"
)

// wireApp init kratos application.
func wireApp(*conf.Server, *conf.Registry, *conf.Data, log.Logger) (*kratos.App, func(), error) {
   panic(wire.Build(server.ProviderSet, data.ProviderSet, biz.ProviderSet, service.ProviderSet, newApp))
}
1.3.8修改wire_gen.go文件

在cmd/user/wire_gen.go

// Code generated by Wire. DO NOT EDIT.

//go:generate go run github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject

package main

import (
   "github.com/go-kratos/kratos/v2"
   "github.com/go-kratos/kratos/v2/log"
   "user/internal/biz"
   "user/internal/conf"
   "user/internal/data"
   "user/internal/server"
   "user/internal/service"
)

import (
   _ "go.uber.org/automaxprocs"
)

// Injectors from wire.go:

// wireApp init kratos application.
func wireApp(confServer *conf.Server, registry *conf.Registry, confData *conf.Data, logger log.Logger) (*kratos.App, func(), error) {
   db := data.NewDB(confData)
   client := data.NewRedis(confData)
   dataData, cleanup, err := data.NewData(confData, logger, db, client)
   if err != nil {
      return nil, nil, err
   }
   userRepo := data.NewUserRepo(dataData, logger)
   userUsecase := biz.NewUserUsecase(userRepo, logger)
   userService := service.NewUserService(userUsecase, logger)
   grpcServer := server.NewGRPCServer(confServer, userService, logger)
   registrar := server.NewRegistrar(registry)
   app := newApp(logger, grpcServer, registrar)
   return app, func() {
      cleanup()
   }, nil
}
1.3.9修改main.go文件
package main

import (
   "flag"
   "github.com/go-kratos/kratos/v2/registry"
   "os"

   "user/internal/conf"

   "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/transport/grpc"
   _ "go.uber.org/automaxprocs"
)

// 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 := wireApp(bc.Server, &rc, bc.Data, logger)
   if err != nil {
      panic(err)
   }
   defer cleanup()

   // start and wait for stop signal
   if err := app.Run(); err != nil {
      panic(err)
   }
}
1.4测试
1.4.1启动服务
根目录 service/user 执行命令
    kratos run
1.4.2编写测试代码

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

package main

import (
   "context"
   "fmt"
   "google.golang.org/grpc"
   v1 "user/api/user/v1"
)

var userClient v1.UserClient
var conn *grpc.ClientConn

func Init() {
   var err error
   conn, err = grpc.Dial("192.168.66.128:9000", grpc.WithInsecure())
   if err != nil {
      panic("grpc link err" + err.Error())
   }
   userClient = v1.NewUserClient(conn)
}
func TestCreateUser() {
   rsp, err := userClient.CreateUser(context.Background(), &v1.CreateUserRequest{
      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)
}
func main() {
   Init()

   TestCreateUser() //创建用户

   conn.Close()
}

在该目录下执行:go run main.go

在这里插入图片描述

则在数据库可以看到如下内容

在这里插入图片描述

1.5数据SQL语句
CREATE TABLE `users` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `mobile` varchar(11) DEFAULT NULL COMMENT '手机号',
  `password` varchar(100) NOT NULL COMMENT '用户密码',
  `nick_name` varchar(25) DEFAULT NULL COMMENT '用户昵称',
  `birthday` datetime DEFAULT NULL,
  `gender` varchar(16) DEFAULT NULL COMMENT 'female:女,male:男',
  `role` int(11) DEFAULT NULL COMMENT '1:普通用户,2:管理员''',
  `add_time` datetime DEFAULT NULL COMMENT '添加时间',
  `update_time` datetime DEFAULT NULL,
  `deleted_at` bigint(20) unsigned DEFAULT '0',
  `is_deleted_at` tinyint(4) DEFAULT '0' COMMENT '0未删除 1已删除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

[外链图片转存中…(img-A5HtWsRU-1697091699535)]

则在数据库可以看到如下内容

[外链图片转存中…(img-ApdbTTq4-1697091699535)]

1.5数据SQL语句
CREATE TABLE `users` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `mobile` varchar(11) DEFAULT NULL COMMENT '手机号',
  `password` varchar(100) NOT NULL COMMENT '用户密码',
  `nick_name` varchar(25) DEFAULT NULL COMMENT '用户昵称',
  `birthday` datetime DEFAULT NULL,
  `gender` varchar(16) DEFAULT NULL COMMENT 'female:女,male:男',
  `role` int(11) DEFAULT NULL COMMENT '1:普通用户,2:管理员''',
  `add_time` datetime DEFAULT NULL COMMENT '添加时间',
  `update_time` datetime DEFAULT NULL,
  `deleted_at` bigint(20) unsigned DEFAULT '0',
  `is_deleted_at` tinyint(4) DEFAULT '0' COMMENT '0未删除 1已删除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值