• Go微服务框架gokratos学习03:使用 gorm 实现增删改查操作


    一、简介

    在上一篇文章 go-kratos学习02 (https://www.cnblogs.com/jiujuan/p/16331967.html)中,详细介绍了用 kratos 编写项目代码的步骤。这篇就在上篇基础上,再结合 Go 数据库操作库 gorm 一步一步来实现一个简单的增删改查操作。

    首先假定你已经会使用 gorm 的基本操作。

    安装 gorm:

    $ go get -u gorm.io/gorm
    go: downloading gorm.io/gorm v1.23.5
    
    ... ...
    

    GORM 文档:https://gorm.io/zh_CN/docs/

    Go,gorm 和 go-kratos 版本:

    go v1.17.10 windows/amd64

    go-kratos v2.2.1

    gorm v1.23.5

    二、新建 student 项目

    在前面文章中,我们知道可以使用 kratos new 命令,用 kratos-layout 这个模板快速新建出一个项目。

    $ kratos new student
     Creating service student, layout repo is https://github.com/go-kratos/kratos-layout.git, please wait a moment.
    
    From https://github.com/go-kratos/kratos-layout
       cc5192f..6715fbc  main       -> origin/main
     * [new tag]         v2.3.0     -> v2.3.0
     * [new tag]         v2.2.2     -> v2.2.2
     
    ... ... 
    

    发现 kratos new 命令每次创建新项目都会使用最新版 go-kratos。看上面的信息 kratos 版本到了 v2.3.0。

    前面项目用的还是 v2.2.1,为了和前面项目版本保持一致,把 go.mod 里的 kratos 改成 v2.2.1 ,然后

    运行 go mod tidy 命令,重新下载依赖包。

    因为使用 kratos-layout 模板新建的 student 项目,为了使项目看起来干净点,需要修改和删除里面的文件。

    比如 proto 文件:

    image-20220601182607229

    三、整理 student 项目

    这时候项目里的很多文件,变量名等都是以 greeter 为名字的,因为这个是模板自带的。先简单整理下。

    1. 删掉 helloworld/v1 文件夹,新建 student/v1 文件夹

    2. 在 internal 目录下的 greeter.go 文件都可以修改为 student.go ,里面的内容后面在逐一修改,或者直接删掉文件后在添加 student.go 文件。我这里直接修改好了,它是一个参考模板。

    四、编写项目代码

    4.1 用命令新建 student.proto

    kratos proto add api/student/v1/student.proto
    

    4.2 通过 student.proto 生成代码

    第一步,给 student.proto 添加如下代码:

    // 先引入 google/api/annotations.proto
    import "google/api/annotations.proto";
    
    // 在 service Student{} 增加如下代码:
    rpc GetStudent (GetStudentRequest) returns (GetStudentReply) {
    		option (google.api.http) = {
    			get: "/student/{id}",
    		};
    }
    
    message GetStudentRequest {
    	int32 id = 1;
    }
    
    message GetStudentReply {
    	string name   = 1;
    	int32  status = 2;
    	int32  id     = 3;
    }
    

    第二步,通过 kratos proto client 生成 pb 相关代码:

    kratos proto client api/student/v1/student.proto
    

    image-20220601204724113

    第三步,通过 student.proto 生成 Service(服务) 代码:

    $ kratos proto server api/student/v1/student.proto -t internal/service
    internal/service/student.go
    

    修改 internal/service/service.go 里依赖注入部分:

    var ProviderSet = wire.NewSet(NewStudentService)
    

    4.3 实例化 HTTP 和 gRPC

    在 internal/server 目录下,修改 http.go, grpc.go, server.go。

    http.go:

    // 上面 import 中引入的 greeter
    import (
    	v1 "student/api/student/v1"
    	
        ... ...
    )
    
    // NewHTTPServer new a HTTP server.
    func NewHTTPServer(c *conf.Server, student *service.StudentService, logger log.Logger) *http.Server {
        ... ...
        
    	srv := http.NewServer(opts...)
    	v1.RegisterStudentHTTPServer(srv, student)
    	return srv
    }
    

    grpc.go:

    import (
    	v1 "student/api/student/v1"
    	
        ... ...
    )
    
    // NewGRPCServer new a gRPC server.
    func NewGRPCServer(c *conf.Server, student *service.StudentService, logger log.Logger) *grpc.Server {
    	... ...
        
    	srv := grpc.NewServer(opts...)
    	v1.RegisterStudentServer(srv, student)
    	return srv
    }
    

    4.4 编写获取学生信息代码

    下面编写用学生 id 来获取学生信息。

    第一步:在 internal/biz/student.go 里编写代码

    前面第一篇文章讲过 biz 目录作用,起到业务组装作用,定义了 biz 的 repo 接口。

    如果没有这个文件就新建一个,student.go 中代码如下:

    package biz
    
    import (
    	"context"
    	"time"
    
    	"github.com/go-kratos/kratos/v2/log"
    )
    
    // Student is a Student model.
    type Student struct {
    	ID        int32
    	Name      string
    	Info      string
    	Status    int32
    	UpdatedAt time.Time
    	CreatedAt time.Time
    }
    
    // 定义 Student 的操作接口
    type StudentRepo interface {
    	GetStudent(context.Context, int32) (*Student, error) // 根据 id 获取学生信息
    }
    
    type StudentUsecase struct {
    	repo StudentRepo
    	log  *log.Helper
    }
    
    // 初始化 StudentUsecase
    func NewStudentUsecase(repo StudentRepo, logger log.Logger) *StudentUsecase {
    	return &StudentUsecase{repo: repo, log: log.NewHelper(logger)}
    }
    
    // 通过 id 获取 student 信息
    func (uc *StudentUsecase) Get(ctx context.Context, id int32) (*Student, error) {
    	uc.log.WithContext(ctx).Infof("biz.Get: %d", id)
    	return uc.repo.GetStudent(ctx, id)
    }
    

    用 wire 注入代码,修改 internal/biz/biz.go :

    var ProviderSet = wire.NewSet(NewStudentUsecase)
    

    第二步:在 internal/data/student.go 里编写代码

    前面第一篇文章已经讲过 data 目录作用,对数据持久化的操作,业务数据访问,包含 DB、redis 等封装,实现了 biz 的 repo interface。biz 里定义了 repo interface。

    如果没有这个文件就新建一个,student.go 代码如下:

    package data
    
    import (
    	"context"
    
    	"student/internal/biz"
    
    	"github.com/go-kratos/kratos/v2/log"
    )
    
    type studentRepo struct {
    	data *Data
    	log  *log.Helper
    }
    
    // 初始化 studentRepo
    func NewStudentRepo(data *Data, logger log.Logger) biz.StudentRepo {
    	return &studentRepo{
    		data: data,
    		log:  log.NewHelper(logger),
    	}
    }
    
    func (r *studentRepo) GetStudent(ctx context.Context, id int32) (*biz.Student, error) {
    	var stu biz.Student
    	r.data.gormDB.Where("id = ?", id).First(&stu) // 这里使用了 gorm
            r.log.WithContext(ctx).Info("gormDB: GetStudent, id: ", id)
    	return &biz.Student{
    		ID:        stu.ID,
    		Name:      stu.Name,
    		Status:    stu.Status,
    		Info:      stu.Info,
    		UpdatedAt: stu.UpdatedAt,
    		CreatedAt: stu.CreatedAt,
    	}, nil
    }
    

    上面代码里有个 r.data.gormDB, gormDB 这个东东从哪里来?就是下面要编写的 data/data.go,连接数据库。

    第三步:编写 internal/data/data.go:

    数据库的封装操作代码。

    // 第 1 步引入 *gorm.DB
    type Data struct {
    	// TODO wrapped database client
    	gormDB *gorm.DB
    }
    
    // 第 2 步初始化 gorm
    func NewGormDB(c *conf.Data) (*gorm.DB, error) {
    	dsn := c.Database.Source
    	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    	if err != nil {
    		return nil, err
    	}
    
    	sqlDB, err := db.DB()
    	if err != nil {
    		return nil, err
    	}
    	sqlDB.SetMaxIdleConns(50)
    	sqlDB.SetMaxOpenConns(150)
    	sqlDB.SetConnMaxLifetime(time.Second * 25)
    	return db, err
    }
    
    // 第 3 步,初始化 Data
    func NewData(logger log.Logger, db *gorm.DB) (*Data, func(), error) {
    	cleanup := func() {
    		log.NewHelper(logger).Info("closing the data resources")
    	}
    
    	return &Data{gormDB: db}, cleanup, nil
    }
    
    // 第 4 步,用 wire 注入代码,修改 原来的 NewSet
    var ProviderSet = wire.NewSet(NewData, NewGormDB, NewStudentRepo)
    

    生成的模板代码是在 NewData 里初始化 db,这里把 gormDB 独立封装,然后用 wire 注入。

    第四步,编写 internal/service/student.go 代码

    上面通过 student.proto 文件生成了一份 service/student.go 代码模板,具体代码还没有编写,下面就来编写 service 代码。

    // 引入 biz.StudentUsecase
    type StudentService struct {
    	pb.UnimplementedStudentServer
    
    	student *biz.StudentUsecase
    	log     *log.Helper
    }
    // 初始化
    func NewStudentService(stu *biz.StudentUsecase, logger log.Logger) *StudentService {
    	return &StudentService{
    		student: stu,
    		log:     log.NewHelper(logger),
    	}
    }
    // 获取学生信息
    func (s *StudentService) GetStudent(ctx context.Context, req *pb.GetStudentRequest) (*pb.GetStudentReply, error) {
    	stu, err := s.student.Get(ctx, req.Id)
    
    	if err != nil {
    		return nil, err
    	}
    	return &pb.GetStudentReply{
    		Id:     stu.ID,
    		Status: stu.Status,
    		Name:   stu.Name,
    	}, nil
    }
    

    4.5 修改配置文件

    配置文件 student/configs/config.yaml。

    修改 mysql 配置项 source,这里 source 要修改成 gorm 的 dsn 数据格式,driver 不变,

    // https://gorm.io/zh_CN/docs/connecting_to_the_database.html#MySQL
    dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local
    
    data:
      database:
        driver: mysql
        source: root:root@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local
    

    我使用的数据库名就是 test,所以就不用修改数据库名。

    把 server.http.addr 端口修改为 8000 -> 8080。

    如果修改了 conf.proto,请使用 make config 命令重新生成 conf.pb.go 文件。我这里没有修改,就不需要重新生成。

    4.6 重新生成 wire_gen.go 文件

    进入到 cmd/student 目录,然后用 wire 命令重新生成 wire_gen.go,

    $ cd ./cmd/student
    $ wire
    wire: student/cmd/student: wrote D:\mygo\go-kratos-demos\student\cmd\student\wire_gen.go
    

    五、数据库

    在 mysql 里创建一个名为 test 的数据库,然后运行下面的 sql,创建数据表 students :

    DROP TABLE IF EXISTS `students`;
    CREATE TABLE `students` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(255) CHARACTER SET latin1 DEFAULT NULL,
      `info` varchar(255) CHARACTER SET latin1 DEFAULT NULL,
      `updated_at` datetime DEFAULT NULL,
      `created_at` datetime DEFAULT NULL,
      `status` varchar(255) CHARACTER SET latin1 DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;
    
    -- ----------------------------
    -- Records of students
    -- ----------------------------
    INSERT INTO `students` VALUES ('1', 'tom', 'a top student', '2022-06-02 15:28:55', '2022-06-02 15:27:01', '1');
    INSERT INTO `students` VALUES ('3', 'jimmy', 'a good student', null, null, '0');
    INSERT INTO `students` VALUES ('4', 'you', 'fea tea', null, null, '1');
    INSERT INTO `students` VALUES ('6', 'ju', '', null, null, '1');
    

    六、运行项目

    在 cmd/student 目录, 运行命令 kratos run

    $ kratos run
    INFO msg=config loaded: config.yaml format: yaml
    INFO msg=[gRPC] server listening on: [::]:9000
    INFO msg=[HTTP] server listening on: 127.0.0.1:8080
    

    使用 curlie - https://github.com/rs/curlie 测试:

    $ curlie  http://127.0.0.1:8080/student/1
    HTTP/1.1 200 OK
    {
        "name": "tom",
        "status": 1,
        "id": 1
    }
    Content-Type: application/json
    Date: Thu, 02 Jun 2022 08:04:49 GMT
    Content-Length: 32
    

    测试返回成功。

    好了,获取学生信息的代码就编写完了。

    其余部分,比如增加、修改等,自己可以试着写一写,熟能生巧嘛。

    七、项目代码地址

    go-kratos student 项目源代码地址:

    go-kratos demo:student

    上面所有代码以 github 上的代码为准。

    八、参考

  • 相关阅读:
    假期每日小结_2.2
    假期每日小结_2.1
    《新浪微博用户兴趣建模系统架构》阅读笔记
    《微博深度学习平台架构和实践》阅读笔记
    《亿级用户下的新浪微博平台架构》阅读笔记
    JavaScript中JSON的序列化和解析
    Servlet中@WebServlet("XXXX")注解无效,访问servlet报404错误
    数据卷(Data Volumes)
    Docker安装及基本命令
    springcloud服务配置中心
  • 原文地址:https://www.cnblogs.com/jiujuan/p/16338305.html
Copyright © 2020-2023  润新知