瑜槿 發表於 2022-6-2 16:47:00

Go微服务框架go-kratos实战学习03:使用 gorm 实现增删改查操作

<h2 id="一简介">一、简介</h2>
<p>在上一篇文章 go-kratos学习02 (https://www.cnblogs.com/jiujuan/p/16331967.html)中,详细介绍了用 kratos 编写项目代码的步骤。这篇就在上篇基础上,再结合 Go 数据库操作库 gorm 一步一步来实现一个简单的增删改查操作。</p>
<p>首先假定你已经会使用 gorm 的基本操作。</p>
<p>安装 gorm:</p>
<pre><code class="language-shell">$ go get -u gorm.io/gorm
go: downloading gorm.io/gorm v1.23.5

... ...
</code></pre>
<p>GORM 文档:https://gorm.io/zh_CN/docs/</p>
<p>Go,gorm 和 go-kratos 版本:</p>
<blockquote>
<p>go v1.17.10 windows/amd64</p>
<p>go-kratos v2.2.1</p>
<p>gorm v1.23.5</p>
</blockquote>
<h2 id="二新建-student-项目">二、新建 student 项目</h2>
<p>在前面文章中,我们知道可以使用 <code>kratos new</code> 命令,用 kratos-layout 这个模板快速新建出一个项目。</p>
<pre><code class="language-shell">$ 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..6715fbcmain       -&gt; origin/main
*          v2.3.0   -&gt; v2.3.0
*          v2.2.2   -&gt; v2.2.2

... ...
</code></pre>
<blockquote>
<p>发现 <code>kratos new</code> 命令每次创建新项目都会使用最新版 go-kratos。看上面的信息 kratos 版本到了 v2.3.0。</p>
<p>前面项目用的还是 v2.2.1,为了和前面项目版本保持一致,把 go.mod 里的 kratos 改成 v2.2.1 ,然后</p>
<p>运行 <code>go mod tidy</code> 命令,重新下载依赖包。</p>
</blockquote>
<p>因为使用 kratos-layout 模板新建的 student 项目,为了使项目看起来干净点,需要修改和删除里面的文件。</p>
<p>比如 proto 文件:</p>
<p><img src="https://img2022.cnblogs.com/blog/650581/202206/650581-20220602163117297-2028139247.png" alt="image-20220601182607229" loading="lazy"></p>
<h2 id="三整理-student-项目">三、整理 student 项目</h2>
<p>这时候项目里的很多文件,变量名等都是以 greeter 为名字的,因为这个是模板自带的。先简单整理下。</p>
<ol>
<li>
<p>删掉 helloworld/v1 文件夹,新建 student/v1 文件夹</p>
</li>
<li>
<p>在 internal 目录下的 greeter.go 文件都可以修改为 student.go ,里面的内容后面在逐一修改,或者直接删掉文件后在添加 student.go 文件。我这里直接修改好了,它是一个参考模板。</p>
</li>
</ol>
<h2 id="四编写项目代码">四、编写项目代码</h2>
<h3 id="41-用命令新建-studentproto">4.1 用命令新建 student.proto</h3>
<pre><code class="language-shell">kratos proto add api/student/v1/student.proto
</code></pre>
<h3 id="42-通过-studentproto-生成代码">4.2 通过 student.proto 生成代码</h3>
<p><strong>第一步</strong>,给 student.proto 添加如下代码:</p>
<pre><code class="language-protobuf">// 先引入 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;
        int32status = 2;
        int32id   = 3;
}
</code></pre>
<p><strong>第二步</strong>,通过 <code>kratos proto client</code> 生成 pb 相关代码:</p>
<pre><code class="language-shell">kratos proto client api/student/v1/student.proto
</code></pre>
<p><img src="https://img2022.cnblogs.com/blog/650581/202206/650581-20220602163117276-1173352802.png" alt="image-20220601204724113" loading="lazy"></p>
<p><strong>第三步</strong>,通过 student.proto 生成 Service(服务) 代码:</p>
<pre><code class="language-shell">$ kratos proto server api/student/v1/student.proto -t internal/service
internal/service/student.go
</code></pre>
<p>修改 internal/service/service.go 里依赖注入部分:</p>
<pre><code class="language-go">var ProviderSet = wire.NewSet(NewStudentService)
</code></pre>
<h3 id="43-实例化-http-和-grpc">4.3 实例化 HTTP 和 gRPC</h3>
<p>在 internal/server 目录下,修改 http.go, grpc.go, server.go。</p>
<p>http.go:</p>
<pre><code class="language-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
}
</code></pre>
<p>grpc.go:</p>
<pre><code class="language-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
}
</code></pre>
<h3 id="44-编写获取学生信息代码">4.4 编写获取学生信息代码</h3>
<p>下面编写用学生 id 来获取学生信息。</p>
<p><strong>第一步</strong>:在 internal/biz/student.go 里编写代码</p>
<p>前面第一篇文章讲过 biz 目录作用,起到业务组装作用,定义了 biz 的 repo 接口。</p>
<p>如果没有这个文件就新建一个,student.go 中代码如下:</p>
<pre><code class="language-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 &amp;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)
}
</code></pre>
<p>用 wire 注入代码,修改 internal/biz/biz.go :</p>
<pre><code class="language-go">var ProviderSet = wire.NewSet(NewStudentUsecase)
</code></pre>
<p><strong>第二步</strong>:在 internal/data/student.go 里编写代码</p>
<p>前面第一篇文章已经讲过 data 目录作用,对数据持久化的操作,业务数据访问,包含 DB、redis 等封装,实现了 biz 的 repo interface。biz 里定义了 repo interface。</p>
<p>如果没有这个文件就新建一个,student.go 代码如下:</p>
<pre><code class="language-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 &amp;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(&amp;stu) // 这里使用了 gorm
      r.log.WithContext(ctx).Info("gormDB: GetStudent, id: ", id)
        return &amp;biz.Student{
                ID:      stu.ID,
                Name:      stu.Name,
                Status:    stu.Status,
                Info:      stu.Info,
                UpdatedAt: stu.UpdatedAt,
                CreatedAt: stu.CreatedAt,
        }, nil
}
</code></pre>
<p>上面代码里有个 r.data.gormDB, gormDB 这个东东从哪里来?就是下面要编写的 data/data.go,连接数据库。</p>
<p><strong>第三步</strong>:编写 internal/data/data.go:</p>
<p>数据库的封装操作代码。</p>
<pre><code class="language-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), &amp;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 &amp;Data{gormDB: db}, cleanup, nil
}

// 第 4 步,用 wire 注入代码,修改 原来的 NewSet
var ProviderSet = wire.NewSet(NewData, NewGormDB, NewStudentRepo)
</code></pre>
<p>生成的模板代码是在 <code>NewData</code> 里初始化 db,这里把 gormDB 独立封装,然后用 wire 注入。</p>
<p><strong>第四步</strong>,编写 internal/service/student.go 代码</p>
<p>上面通过 student.proto 文件生成了一份 service/student.go 代码模板,具体代码还没有编写,下面就来编写 service 代码。</p>
<pre><code class="language-go">// 引入 biz.StudentUsecase
type StudentService struct {
        pb.UnimplementedStudentServer

        student *biz.StudentUsecase
        log   *log.Helper
}
// 初始化
func NewStudentService(stu *biz.StudentUsecase, logger log.Logger) *StudentService {
        return &amp;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 &amp;pb.GetStudentReply{
                Id:   stu.ID,
                Status: stu.Status,
                Name:   stu.Name,
        }, nil
}
</code></pre>
<h3 id="45-修改配置文件">4.5 修改配置文件</h3>
<p>配置文件 student/configs/config.yaml。</p>
<p>修改 mysql 配置项 source,这里 source 要修改成 gorm 的 dsn 数据格式,driver 不变,</p>
<pre><code>// https://gorm.io/zh_CN/docs/connecting_to_the_database.html#MySQL
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&amp;parseTime=True&amp;loc=Local
</code></pre>
<pre><code class="language-yaml">data:
database:
    driver: mysql
    source: root:root@tcp(127.0.0.1:3306)/test?charset=utf8mb4&amp;parseTime=True&amp;loc=Local
</code></pre>
<p>我使用的数据库名就是 test,所以就不用修改数据库名。</p>
<p>把 server.http.addr 端口修改为 8000 -&gt; 8080。</p>
<blockquote>
<p>如果修改了 conf.proto,请使用 <code>make config</code> 命令重新生成 conf.pb.go 文件。我这里没有修改,就不需要重新生成。</p>
</blockquote>
<h3 id="46-重新生成-wire_gengo-文件">4.6 重新生成 wire_gen.go 文件</h3>
<p>进入到 cmd/student 目录,然后用 <code>wire</code> 命令重新生成 wire_gen.go,</p>
<pre><code class="language-shell">$ cd ./cmd/student
$ wire
wire: student/cmd/student: wrote D:\mygo\go-kratos-demos\student\cmd\student\wire_gen.go
</code></pre>
<h2 id="五数据库">五、数据库</h2>
<p>在 mysql 里创建一个名为 test 的数据库,然后运行下面的 sql,创建数据表 students :</p>
<pre><code class="language-sql">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');
</code></pre>
<h2 id="六运行项目">六、运行项目</h2>
<p>在 cmd/student 目录, 运行命令 <code>kratos run</code></p>
<pre><code class="language-shell">$ kratos run
INFO msg=config loaded: config.yaml format: yaml
INFO msg= server listening on: [::]:9000
INFO msg= server listening on: 127.0.0.1:8080
</code></pre>
<p>使用 curlie - https://github.com/rs/curlie 测试:</p>
<pre><code class="language-shell">$ curliehttp://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
</code></pre>
<p>测试返回成功。</p>
<p>好了,获取学生信息的代码就编写完了。</p>
<p>其余部分,比如增加、修改等,自己可以试着写一写,熟能生巧嘛。</p>
<h2 id="七项目代码地址">七、项目代码地址</h2>
<p>go-kratos student 项目源代码地址:</p>
<p>go-kratos demo:student</p>
<p>上面所有代码以 github 上的代码为准。</p>
<hr>
<blockquote>
<p>也可以到我的公众号:九卷技术录, go-kratos实战学习03:使用 gorm 实现增删改查操作 继续讨论</p>
</blockquote>
<h2 id="八参考">八、参考</h2>
<ul>
<li>https://go-kratos.dev/docs/getting-started/start kratos 新建模板项目</li>
<li>https://go-kratos.dev/docs/getting-started/usage kratos cli 工具</li>
<li>https://gorm.io/zh_CN/docs/connecting_to_the_database.html#MySQL gorm mysql数据库连接</li>
<li>https://gorm.io/zh_CN/docs/query.html gorm 查询</li>
<li>https://www.cnblogs.com/jiujuan/p/12676195.html gorm 基本操作</li>
<li>https://github.com/rs/curlie curlie http 请求</li>
</ul>


</div>
<div id="MySignature" role="contentinfo">
    == just do it ==<br><br>
来源:https://www.cnblogs.com/jiujuan/p/16338305.html
頁: [1]
查看完整版本: Go微服务框架go-kratos实战学习03:使用 gorm 实现增删改查操作