Kratos 项目实战(模块管理)

一.创建项目

1.1创建项目

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

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

 

进入到module删除多余的文件

 

 

 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

 

 

 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];
使用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修改后的内容如下

 


建立注册配置文件registry.yaml

 

 

三.具体实现

3.1创建biz层代码

 

 

代码如下:

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层

 

 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代码

 

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

 


 这边是修改的地方

 

 这个是新增的

 

 这个也是修改的地方

4.2修改wire.go

 

 4.3运行wire命令生成配置

到这个目录下

 

 运行wire

 

 五.启动测试

5.1启动项目

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

 

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

 

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

 

 5.2测试

运用BloomRPC这个工具来测试

 

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



posted @ 2023-01-30 16:32  lisus2000  阅读(673)  评论(0编辑  收藏  举报