Skip to content

项目开发

  • 具体使用 kratos 命令行工具进行开发

安装 kratos

  • 安装 kratos 命令行工具
bash
# 开启 go module
go env -w GO111MODULE=on
# 安装 kratos 命令行工具
go install github.com/go-kratos/kratos/cmd/kratos/v2@latest

安装 Protobuf

bash
# mac 安装
brew install protobuf

创建项目

  • 使用 kratos 命令行工具创建一个项目
bash
# 创建一个项目
kratos new <project-name>

# cd 项目目录
cd <project-name>

# 下载依赖
go mod tidy

# 安装 wire 依赖注入工具
go get github.com/google/wire/cmd/wire@latest

# 生成所有proto源码、wire等等
go generate ./...

# 运行项目
kratos run

项目结构

kratos-1.png

移除示例模块

移除 api/helloworld 模块

bash
# 移除示例模块
rm -rf api/helloworld

移除helloworld 模块的业务代码

bash
rm -rf internal/biz/greeter.go
rm -rf internal/data/greeter.go
rm -rf internal/service/greeter.go

清除注入代码

  • 编辑 internal/biz/biz.go 文件
bash
# 删除 ProviderSet 中的 NewGreeterUsecase 依赖注入代码
var ProviderSet = wire.NewSet()
  • 编辑 internal/data/data.go 文件
bash
# 删除 ProviderSet 中的 NewGreeterRepo 依赖注入代码
var ProviderSet = wire.NewSet(NewData)
  • 编辑 internal/service/service.go 文件
bash
# 删除 ProviderSet 中的 NewGreeterService 依赖注入代码
var ProviderSet = wire.NewSet()

清除服务注入代码

  • 编辑 internal/server/grpc.go 文件,移除标红代码即可

  • 编辑 internal/server/http.go 文件, 按照上面同样操作即可

添加 Proto 文件

  • 根据业务需求创建admin模块,并创建login业务接口
bash
# 创建 admin 模块 login 业务接口
kratos proto add api/login/login.proto
# 创建 admin 模块 admin 业务接口
kratos proto add api/admin/admin.proto
  • 示例login.proto
proto
syntax = "proto3";

package api.login;

import "google/api/annotations.proto";

option go_package = "yellow/api/login;login";
option java_multiple_files = true;
option java_package = "api.login";

service Login {
  // 获取登录env信息
  rpc Env (EnvRequest) returns (EnvReply){
    option (google.api.http) = {
      get: "/login/env"
    };
  };

  // 账号登录
  rpc LoginAccount (LoginAccountRequest) returns (LoginAccountReply){
    option (google.api.http) = {
        post: "/login/account",
        body: "*",
    };
  };
}

message EnvRequest {}
message EnvReply {
  string uid = 1;     // 自动生成的ID
  string server = 2;  // 当前服务器
  // ... 其他参数,自由定制
}

message LoginAccountRequest{
  string username = 1;  // 登录账号
  string password = 2;  // 登录密码
}

message LoginAccountReply{
  string token = 1; // 登录token
  string refresh_token = 2; // 刷新token
}
  • 示例admin.proto
proto
syntax = "proto3";

package api.admin;

import "google/api/annotations.proto";
option go_package = "yellow/api/admin;admin";
option java_multiple_files = true;
option java_package = "api.admin";

service Admin {
  // 获取登录信息
  rpc AdminInfo(AdminInfoRequest) returns (AdminReply){
    option (google.api.http) = {
      get: "/admin/info"
    };
  };

  // 创建管理员
	rpc CreateAdmin (CreateAdminRequest) returns (CreateAdminReply){
    option (google.api.http) = {
        post: "/admin",
        body: "*",
    };
  };

  // 更新管理员
	rpc UpdateAdmin (UpdateAdminRequest) returns (UpdateAdminReply){
    option (google.api.http) = {
        put: "/admin/{id}",
        body: "*",
    };
  };

  // 删除管理员
	rpc DeleteAdmin (DeleteAdminRequest) returns (DeleteAdminReply){
    option (google.api.http) = {
        delete: "/admin",
        // delete请求不需要 body
    };
  };

  // 获取管理员信息
	rpc GetAdmin (GetAdminRequest) returns (AdminReply){
    option (google.api.http) = {
      get: "/admin/{id}"
    };
  };

  // 管理员信息
	rpc ListAdmin (ListAdminRequest) returns (ListAdminReply){
    option (google.api.http) = {
      get: "/admins"
    };
  };
}

message AdminInfoRequest{}

message AdminReply{
  int64 id = 1; // 用户ID
  string name = 2; // 用户名称
  string password = 3; // 用户密码
  string avatar = 4; // 用户头像
  int32  is_dis = 5; // 是否禁用
  int32  is_del = 6; // 是否删除
  string created_at = 7; // 创建时间
  string update_at = 8; // 更新时间
  int32  deleted = 9; //删除时间
}

message CreateAdminRequest {
  string name = 1;  // 用户姓名
  string password = 2; // 用户密码
  string avatar = 3; // 用户头像
}
message CreateAdminReply{}

message UpdateAdminRequest {
  string id = 1;
  string name = 2; // 用户名称
  string password = 3; // 用户密码
  string avatar = 4; // 用户头像
}
message UpdateAdminReply{}

message DeleteAdminRequest {
  repeated int64 ids = 1; // 设置复数,批量删除
}

message DeleteAdminReply{}
message GetAdminRequest {
  int64 id = 1; // 用户ID
}

message ListAdminRequest {
  int32 current_page = 1; // 当前页数
  string keyword = 2; // 关键字搜索
}

message ListAdminReply {
  int32 total = 1; // 总条数
  int32 current_page =  2; // 当前页数
  repeated Admin list = 3; // 用户列表
}

生成 Proto 代码

bash
# 全部生成
make api

# 生成 admin 模块 login 业务接口的代码
kratos proto client api/login/login.proto
# 生成 admin 模块 admin 业务接口的代码
kratos proto client api/admin/admin.proto
  • 会在 api/admin 目录下生成 login.pb.gologin_grpc.pb.gologin_http.pb.go 文件
  • 会在 api/admin 目录下生成 admin.pb.goadmin_grpc.pb.goadmin_http.pb.go 文件

生成 Service 代码

bash
# 生成 admin 模块 login 业务接口的代码
kratos proto server api/login/login.proto
# 生成 admin 模块 admin 业务接口的代码
kratos proto server api/admin/admin.proto
  • 会在 internal/service 目录下生成 login.goadmin.go 文件

代码规范

internal/biz 目录

  • 主要用户定义业务逻辑接口,并提供操作实例

  • 创建internal/biz/login.go文件

go
package biz

import (
	"context"
	"yellow/api/login"

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

// LoginRepo 定义接口清单
type LoginRepo interface {
	Env(ctx context.Context, request login.EnvRequest) (login.EnvReply, error)
	LoginAccount(ctx context.Context, request login.LoginAccountRequest) (login.LoginAccountRequest, error)
}

// LoginBiz 定义注入实体
type LoginBiz struct {
	repo LoginRepo
	log  *log.Helper
}

// NewLoginBiz 创建实体
func NewLoginBiz(repo LoginRepo, logger log.Logger) *LoginBiz {
	return &LoginBiz{repo: repo, log: log.NewHelper(logger)}
}
  • internal/biz/biz.go 中注入 LoginBiz 实体
go
package biz

import "github.com/google/wire"

// ProviderSet is biz providers.
var ProviderSet = wire.NewSet(NewLoginBiz)

internal/data 目录

  • 完成各种数据连接操作,例如mysqlredis

  • 完成数据库数据操作,例如定义表实体、CURD 操作

  • 完成缓存操作,例如存储缓存

  • 完成 oss,cos 操作方法

  • 完成 文件 操作方法

  • 示例:在internal/data/data.go文件,完成数据库,redis 连接操作

go
package data

import (
	"yellow/internal/conf"

	"github.com/go-kratos/kratos/v2/log"
	"github.com/google/wire"
	"github.com/redis/go-redis/v9"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

// ProviderSet is data providers.
var ProviderSet = wire.NewSet(NewData)

// Data .
type Data struct {
	db    *gorm.DB // gorm 操作数据库
	cache *redis.Client // redis 作为缓存
}

// NewData .
func NewData(c *conf.Data, logger log.Logger) (*Data, func(), error) {
	cleanup := func() {
		log.NewHelper(logger).Info("closing the data resources")
	}
	return &Data{db: NewMysql(c), cache: NewRedis(c)}, cleanup, nil
}

func NewMysql(c *conf.Data) *gorm.DB {
	db, err := gorm.Open(mysql.Open(c.Database.Source), &gorm.Config{
		DisableForeignKeyConstraintWhenMigrating: true,
	})

	if err != nil {
		log.Errorf("failed opening connection to mysql: %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,
		WriteTimeout: c.Redis.WriteTimeout.AsDuration(),
		ReadTimeout:  c.Redis.ReadTimeout.AsDuration(),
	})
	if err := rdb.Close(); err != nil {
		log.Error(err)
		panic("failed to connect redis")
	}
	return rdb
}
  • 示例:创建internal/data/login.go文件,完成数据库操作方法
go
package data

import (
	"context"
	"yellow/api/login"
	"yellow/internal/biz"

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

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

func NewLoginData(data *Data, logger log.Logger) biz.LoginRepo {
	return &LoginData{
		data: data,
		log:  log.NewHelper(logger),
	}
}

func (d *LoginData) Env(ctx context.Context, req *login.EnvRequest) (*login.EnvReply, error) {
	return &login.EnvReply{}, nil
}

func (d *LoginData) LoginAccount(ctx context.Context, req *login.LoginAccountRequest) (*login.LoginAccountReply, error) {
	return &login.LoginAccountReply{}, nil
}

internal/service 目录

  • 实现具体业务
  • 编辑internal/service/login.go文件
go
package service

import (
	"context"
	"yellow/internal/biz"

	pb "yellow/api/login"

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

type LoginService struct {
	pb.UnimplementedLoginServer

  // 重点 这里必须 实例化 biz.LoginBiz
	biz *biz.LoginBiz
	log *log.Helper
}

func NewLoginService(biz *biz.LoginBiz, logger log.Logger) *LoginService {
	return &LoginService{
    // 重点 这里必须 实例化 biz.LoginBiz
		biz: biz,
		log: log.NewHelper(logger),
	}
}

func (s *LoginService) Env(ctx context.Context, req *pb.EnvRequest) (*pb.EnvReply, error) {
	return &pb.EnvReply{}, nil
}
func (s *LoginService) LoginAccount(ctx context.Context, req *pb.LoginAccountRequest) (*pb.LoginAccountReply, error) {
	return &pb.LoginAccountReply{}, nil
}
go
package service

import (
	"context"
	"yellow/api/login"
	"yellow/internal/biz"

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

type LoginService struct {
	biz biz.LoginBiz
	log *log.Helper
}

func NewLoginService(biz biz.LoginBiz, logger log.Logger) *LoginService {
	return &LoginService{
		biz: biz,
		log: log.NewHelper(logger),
	}
}

func (s *LoginService) Env(ctx context.Context, req *login.EnvRequest) (*login.EnvReply, error) {
	return s.biz.Env(ctx, req)
}

func (s *LoginService) LoginAccount(ctx context.Context, req *login.LoginAccountRequest) (*login.LoginAccountReply, error) {
	return s.biz.LoginAccount(ctx, req)
}

internal/server 目录

  • 注册服务

  • http.go中注册http服务,在grpc.go中注册grpc服务,2 个地方代码基本一样

go
package server

import (
	apiLogin "yellow/api/login"
	"yellow/internal/conf"
	"yellow/internal/service"

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

// NewHTTPServer new an HTTP server.
func NewHTTPServer(c *conf.Server, login *service.LoginService, logger log.Logger) *http.Server {
	var opts = []http.ServerOption{
		http.Middleware(
			recovery.Recovery(),
		),
	}
	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...)
  // 注册login模块
	apiLogin.RegisterLoginHTTPServer(srv, login)
	return srv
}

验证 wire

go
cd <project_name>/cmd

// wire 注入没有报错,即完成成功
wire
// 运行项目
kratos run