一.创建项目

1.1创建项目

在Linuxshare/cloud_center/目录下创建module项目

kratos new module -r https://gitee.com/go-kratos/kratos-layout.git

kratos 架构 B_kratos 架构 B

 

进入到module删除多余的文件

kratos 架构 B_包名_02

 

 

 internal/data/greeter.go,internal/biz/greeter.go这个也删除掉

1.2创建module的proto

syntax = "proto3";

package api.module.v1;
import "google/protobuf/empty.proto";

option go_package = "module/api/module/v1;v1";


service Module {
rpc CreateModule (CreateModuleRequest) returns (CreateModuleReply);
 rpc UpdateModule (UpdateModuleRequest) returns (google.protobuf.Empty);
 rpc DeleteModule (DeleteModuleRequest) returns (google.protobuf.Empty);
 rpc GetModule (GetModuleRequest) returns (CreateModuleReply);
 rpc ListModule (PageInfo) returns (ListModuleReply);
}
// 分页
message PageInfo{
uint32 pn = 1;
 uint32 pSize = 2;
}

message CreateModuleRequest {
int32 modCompanyid=1;
 int32 modDepartid=2;
 int32 modProid=3;
 int32 modParentsid=4;
 string modName=5;
 string modAliasname=6;
 string modUrl=7;
 int32 modState=8;
 string modIco=9;
 int32 modOptid=10;
 int32 modSourceId=11;
 int32 authLevel=12;
}
message CreateModuleReply {
int32 modId=1;
 int32 modCompanyid=2;
 int32 modDepartid=3;
 int32 modProid=4;
 int32 modParentsid=5;
 string modName=6;
 string modAliasname=7;
 string modUrl=8;
 int32 modState=9;
 string modIco=10;
 int32 modOptid=11;
 int32 modSourceId=12;
 int32 authLevel=13;
}

message UpdateModuleRequest {
int32 modId=1;
 int32 modCompanyid=2;
 int32 modDepartid=3;
 int32 modProid=4;
 int32 modParentsid=5;
 string modName=6;
 string modAliasname=7;
 string modUrl=8;
 int32 modState=9;
 string modIco=10;
 int32 modOptid=11;
 int32 modSourceId=12;
 int32 authLevel=13;
}
message UpdateModuleReply {}

message DeleteModuleRequest {
int32 modId=1;
}

message GetModuleRequest {
int32 modId=1;
}
message GetModuleReply {}

message ListModuleRequest {}
message ListModuleReply {
int32 total = 1;
 repeated CreateModuleReply data = 2;
}

创建完后,使用make api 生成proto

kratos 架构 B_包名_03

 

 

 1.2.3生成错误的error_reason.proto

代码如下:

syntax = "proto3";

// 定义包名
package api.kratos.v1;
import "errors/errors.proto";

// 多语言特定包名,用于源代码引用
option go_package = "module/api/module/v1;v1";

enum ErrorReason {
// 设置缺省错误码
option (errors.default_code) = 500;

// 为某个枚举单独设置错误码
USER_NOT_FOUND = 0 [(errors.code) = 404];

CONTENT_MISSING = 1 [(errors.code) = 400];
}

kratos 架构 B_包名_04

使用make errors 生成错误的proto

使用之前先在makefile 添加下代码

.PHONY: errors
# generate api proto
errors:
   protoc --proto_path=. \
             --proto_path=./third_party \
             --go_out=paths=source_relative:. \
             --go-errors_out=paths=source_relative:. \
             $(API_PROTO_FILES)
然后就可以使用make errors 了
二.修改配置文件
修改configs下的config.yaml修改后的内容如下

kratos 架构 B_github_05

 

建立注册配置文件registry.yaml

kratos 架构 B_创建项目_06

 

 

三.具体实现

3.1创建biz层代码

kratos 架构 B_github_07

 

 

代码如下:

package biz

import (
   "context"
   "github.com/go-kratos/kratos/v2/log"
   "module/internal/domain"
)

type ModuleRepo interface {
   CreateModule(context.Context, *domain.Module) (*domain.Module, error)
   UpdateModule(ctx context.Context, module *domain.Module) (bool, error)
   DeleteModule(ctx context.Context, id int32) (bool, error)
   GetModule(ctx context.Context, id int32) (*domain.Module, error)
   ListModule(ctx context.Context, pageNum, pageSize int) ([]*domain.Module, int, error)
}

type ModuleUsecase struct {
   repo ModuleRepo
   log  *log.Helper
}

// NewModuleUsecase .
func NewModuleUsecase(repo ModuleRepo, logger log.Logger) *ModuleUsecase {
   return &ModuleUsecase{
      repo: repo,
      log:  log.NewHelper(logger),
   }
}

// Create .
func (m *ModuleUsecase) Create(ctx context.Context, module *domain.Module) (*domain.Module, error) {
   return m.repo.CreateModule(ctx, module)
}

// UpdateModule .
func (m *ModuleUsecase) UpdateModule(ctx context.Context, module *domain.Module) (bool, error) {
   return m.repo.UpdateModule(ctx, module)
}

// DeleteModule .
func (m *ModuleUsecase) DeleteModule(ctx context.Context, id int32) (bool, error) {
   return m.repo.DeleteModule(ctx, id)
}

//GetModule .
func (m *ModuleUsecase) GetModule(ctx context.Context, id int32) (*domain.Module, error) {
   return m.repo.GetModule(ctx, id)
}

// ListModule.
func (m *ModuleUsecase) ListModule(ctx context.Context, pageNum, pageSize int) ([]*domain.Module, int, error) {
   return m.repo.ListModule(ctx, pageNum, pageSize)
}
在biz.go添加如下配置

 

 3.2创建service层

kratos 架构 B_kratos 架构 B_08

 

 service层的代码如下:

package biz

import (
   "context"
   "github.com/go-kratos/kratos/v2/log"
   "module/internal/domain"
)

type ModuleRepo interface {
   CreateModule(context.Context, *domain.Module) (*domain.Module, error)
   UpdateModule(ctx context.Context, module *domain.Module) (bool, error)
   DeleteModule(ctx context.Context, id int32) (bool, error)
   GetModule(ctx context.Context, id int32) (*domain.Module, error)
   ListModule(ctx context.Context, pageNum, pageSize int) ([]*domain.Module, int, error)
}

type ModuleUsecase struct {
   repo ModuleRepo
   log  *log.Helper
}

// NewModuleUsecase .
func NewModuleUsecase(repo ModuleRepo, logger log.Logger) *ModuleUsecase {
   return &ModuleUsecase{
      repo: repo,
      log:  log.NewHelper(logger),
   }
}

// Create .
func (m *ModuleUsecase) Create(ctx context.Context, module *domain.Module) (*domain.Module, error) {
   return m.repo.CreateModule(ctx, module)
}

// UpdateModule .
func (m *ModuleUsecase) UpdateModule(ctx context.Context, module *domain.Module) (bool, error) {
   return m.repo.UpdateModule(ctx, module)
}

// DeleteModule .
func (m *ModuleUsecase) DeleteModule(ctx context.Context, id int32) (bool, error) {
   return m.repo.DeleteModule(ctx, id)
}

//GetModule .
func (m *ModuleUsecase) GetModule(ctx context.Context, id int32) (*domain.Module, error) {
   return m.repo.GetModule(ctx, id)
}

// ListModule.
func (m *ModuleUsecase) ListModule(ctx context.Context, pageNum, pageSize int) ([]*domain.Module, int, error) {
   return m.repo.ListModule(ctx, pageNum, pageSize)
}
在servcie.go中修改代码,修改后的代码如下:

 

 3.3创建data层

 

 module.go的代码如下:

package data

import (
   "context"
   "github.com/go-kratos/kratos/v2/errors"
   "github.com/go-kratos/kratos/v2/log"
   "gorm.io/gorm"
   "module/internal/biz"
   "module/internal/domain"
)

type Module struct {
   MroId        int64  `gorm:"primarykey"`
   ModCompanyid int64  `gorm:"type:int;column:mod_companyid;comment '公司ID';not null;"`
   ModDepartid  int64  `gorm:"type:int;column:mod_departid;comment '部门ID';not null;"`
   ModProid     int64  `gorm:"type:int;column:mod_proid;comment '项目ID';not null;"`
   ModParentsid int64  `gorm:"type:int;column:mod_parentsid;comment '父级模块ID';not null;"`
   ModName      string `gorm:"type:varchar(100);column:mod_name; comment '模块名称';not null;"`
   ModAliasname string `gorm:"type:varchar(100);column:mod_aliasname; comment '模块别名';not null;"`
   ModUrl       string `gorm:"type:varchar(256);column:mod_url;comment '模块URL';not null; "`
   ModState     int32  `gorm:"type:int;column:mod_state; comment '模块状态';default:1;"`
   ModIco       string `gorm:"type:varchar(200);column:mod_ico; comment '图标链接地址';default:'';"`
   ModOptid     int32  `gorm:"type:int;column:mod_optid; comment '后台操作人用户ID'"`
   ModSourceId  string `gorm:"type:int;column:mod_source_id; comment '原平台的ID';not null;"`
   AuthLevel    int32  `gorm:"type:int;column:auth_level; comment '菜单等级';not null;"`
}

func (Module) TableName() string {
   return "module"
}

type moduleRepo struct {
   data *Data
   log  *log.Helper
}

// NewModuleRepo .
func NewModuleRepo(data *Data, logger log.Logger) biz.ModuleRepo {
   return &moduleRepo{
      data: data,
      log:  log.NewHelper(logger),
   }
}

// CreateModule .
func (m *moduleRepo) CreateModule(ctx context.Context, mod *domain.Module) (*domain.Module, error) {
   var module domain.Module
   result := m.data.db.Where(&domain.Module{ModName: mod.ModName}).First(&module)
   if result.RowsAffected == 1 {
      return nil, errors.New(500, "MODULE_EXIST", "模块已存在")
   }
   module.ModCompanyid = mod.ModCompanyid
   module.ModProid = mod.ModProid
   module.ModParentsid = mod.ModParentsid
   module.ModName = mod.ModName
   module.ModAliasname = mod.ModAliasname
   module.ModUrl = mod.ModUrl
   module.ModState = mod.ModState
   module.ModIco = mod.ModIco
   module.ModOptid = mod.ModOptid
   module.ModSourceId = mod.ModSourceId
   module.AuthLevel = mod.AuthLevel
   res := m.data.db.Create(&module)
   if res.Error != nil {
      return nil, errors.New(500, "CREATE_MODULE_FAILED", "模块创建失败")
   }
   companyInfoRes := modelToResponse(module)
   return &companyInfoRes, nil
}

// UpdateModule .
func (m *moduleRepo) UpdateModule(ctx context.Context, mod *domain.Module) (bool, error) {
   var moduleInfo domain.Module
   result := m.data.db.Where(&domain.Module{ModId: mod.ModId}).First(&moduleInfo)
   if errors.Is(result.Error, gorm.ErrRecordNotFound) {
      return false, errors.NotFound("MODULE_NOT_FOUND", "模块未找到")
   }
   if result.RowsAffected == 0 {
      return false, errors.NotFound("MODULE_NOT_FOUND", "模块未找到")
   }
   moduleInfo.ModCompanyid = mod.ModCompanyid
   moduleInfo.ModProid = mod.ModProid
   moduleInfo.ModParentsid = mod.ModParentsid
   moduleInfo.ModName = mod.ModName
   moduleInfo.ModAliasname = mod.ModAliasname
   moduleInfo.ModUrl = mod.ModUrl
   moduleInfo.ModState = mod.ModState
   moduleInfo.ModIco = mod.ModIco
   moduleInfo.ModOptid = mod.ModOptid
   moduleInfo.ModSourceId = mod.ModSourceId
   moduleInfo.AuthLevel = mod.AuthLevel

   if err := m.data.db.Save(&moduleInfo).Error; err != nil {
      return false, errors.New(500, "MODULE_NOT_FOUND", err.Error())
   }
   return true, nil
}

// DeleteModule .
func (m *moduleRepo) DeleteModule(ctx context.Context, id int32) (bool, error) {
   var module domain.Module
   result := m.data.db.Where(&domain.Module{ModId: id}).First(&module)
   if result.RowsAffected == 0 {
      return false, errors.NotFound("MODULE_NOT_FOUND", "模块不存在")
   }
   res := m.data.db.Model(&domain.Module{}).Delete("mod_id=?", id)
   if res.Error != nil {
      return false, errors.New(500, "DELETE_MODULE_FAILED", "删除模块失败")
   }
   return true, nil
}

// GetModule .
func (m *moduleRepo) GetModule(ctx context.Context, id int32) (*domain.Module, error) {
   var moduleInfo domain.Module
   result := m.data.db.Where(&domain.Module{ModId: id}).First(&moduleInfo)
   if errors.Is(result.Error, gorm.ErrRecordNotFound) {
      return nil, errors.NotFound("MODULE_NOT_FOUND", "模块未找到")
   }
   re := modelToResponse(moduleInfo)
   return &re, nil
}

func (m *moduleRepo) ListModule(ctx context.Context, pageNum, pageSize int) ([]*domain.Module, int, error) {
   var moduleList []domain.Module
   result := m.data.db.Find(&moduleList)
   if errors.Is(result.Error, gorm.ErrRecordNotFound) {
      return nil, 0, errors.NotFound("MODULE_NOT_FOUND", "暂无数据")
   }
   if result.Error != nil {
      return nil, 0, errors.New(500, "MODULE_NOT_FOUND", "暂无数据")
   }
   total := int(result.RowsAffected)
   m.data.db.Scopes(paginate(pageNum, pageSize)).Find(&moduleList)
   rv := make([]*domain.Module, 0)
   for _, mod := range moduleList {
      rv = append(rv, &domain.Module{
         ModId:        mod.ModId,
         ModCompanyid: mod.ModCompanyid,
         ModDepartid:  mod.ModDepartid,
         ModProid:     mod.ModProid,
         ModParentsid: mod.ModParentsid,
         ModName:      mod.ModName,
         ModAliasname: mod.ModAliasname,
         ModUrl:       mod.ModUrl,
         ModState:     mod.ModState,
         ModIco:       mod.ModIco,
         ModOptid:     mod.ModOptid,
         ModSourceId:  mod.ModSourceId,
         AuthLevel:    mod.AuthLevel,
      })
   }
   return rv, total, nil
}

// ModelToResponse 转换 user 表中所有字段的值
func modelToResponse(module domain.Module) domain.Module {
   moduleInfoRsp := domain.Module{
      ModId:        module.ModId,
      ModCompanyid: module.ModCompanyid,
      ModDepartid:  module.ModDepartid,
      ModProid:     module.ModProid,
      ModParentsid: module.ModParentsid,
      ModName:      module.ModName,
      ModAliasname: module.ModAliasname,
      ModUrl:       module.ModUrl,
      ModState:     module.ModState,
      ModIco:       module.ModIco,
      ModOptid:     module.ModOptid,
      ModSourceId:  module.ModSourceId,
      AuthLevel:    module.AuthLevel,
   }
   return moduleInfoRsp
}

// paginate 分页
func paginate(page, pageSize int) func(db *gorm.DB) *gorm.DB {
   return func(db *gorm.DB) *gorm.DB {
      if page == 0 {
         page = 1
      }

      switch {
      case pageSize > 100:
         pageSize = 100
      case pageSize <= 0:
         pageSize = 10
      }

      offset := (page - 1) * pageSize
      return db.Offset(offset).Limit(pageSize)
   }
}
data.go的代码如下:
package data

import (
   "context"
   "github.com/go-redis/redis/extra/redisotel"
   "github.com/go-redis/redis/v8"
   "gorm.io/driver/mysql"
   "gorm.io/gorm"
   "gorm.io/gorm/logger"
   "gorm.io/gorm/schema"
   slog "log"
   "module/internal/biz"
   "module/internal/conf"
   "os"
   "time"

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

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

// Data .
type Data struct {
   db  *gorm.DB
   rdb *redis.Client
}
type contextTxKey struct{}

// 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
}
func NewTransaction(d *Data) biz.Transaction {
   return d
}

func (d *Data) DB(ctx context.Context) *gorm.DB {
   tx, ok := ctx.Value(contextTxKey{}).(*gorm.DB)
   if ok {
      return tx
   }
   return d.db
}

func (d *Data) ExecTx(ctx context.Context, fn func(ctx context.Context) error) error {
   return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
      ctx = context.WithValue(ctx, contextTxKey{}, tx)
      return fn(ctx)
   })
}

// 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
      },
   )
   log.Info("failed opening connection to ")
   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
}

3.4修改server.go代码

kratos 架构 B_创建项目_09

 

 删掉http.go这个文件,修改grpc.go 这个文件,修改后的代码如下

package server

import (
   v1 "module/api/module/v1"
   "module/internal/conf"
   "module/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.ModuleService, 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.RegisterModuleServer(srv, greeter)
   return srv
}
四.修改cmd/main.go,wire.go文件
4.1 修改main.go修改后的代码如下:
package main

import (
   "flag"
   "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.4.0"
   "os"

   "module/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 = "cloud.module.service"
   // 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, gs *grpc.Server, rr registry.Registrar) *kratos.App {
   return kratos.New(
      kratos.ID(id),
      kratos.Name(Name),
      kratos.Version(Version),
      kratos.Metadata(map[string]string{}),
      kratos.Logger(logger),
      kratos.Server(
         gs,
      ),
      kratos.Registrar(rr),
   )
}

// Set global trace provider
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
}
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)
   }
   if err := setTracerProvider(bc.Trace.Endpoint); err != nil {
      panic(err)
   }

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

 

 这边是修改的地方

kratos 架构 B_包名_10

 

 这个是新增的

kratos 架构 B_github_11

 

 这个也是修改的地方

4.2修改wire.go

kratos 架构 B_包名_12

 

 4.3运行wire命令生成配置

到这个目录下

kratos 架构 B_kratos 架构 B_13

 

 运行wire

kratos 架构 B_创建项目_14

 

 五.启动测试

5.1启动项目

kratos run启动项目,启动成功 显示如下

kratos 架构 B_创建项目_15

 

 这是也可以consul看到服务已经注册了

kratos 架构 B_kratos 架构 B_16

 

 注:启动的时候要把consul 先启动起来

kratos 架构 B_github_17

 

 5.2测试

运用BloomRPC这个工具来测试

kratos 架构 B_包名_18

 

 点周CreateModule输入相应的参数,然后点击运行,运行成功 后,会返回相应的数据,如上图所示,至些kratos简单的项目实战已经完成,后面会接入ES