一、简介

在上一篇文章 go-kratos实战02 中,详细介绍了用 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 文件:

三、整理 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

第三步,通过 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 上的代码为准。

八、参考

Go微服务框架go-kratos实战03:使用 gorm 实现增删改查操作的更多相关文章

  1. AD 域服务简介(三)- Java 对 AD 域用户的增删改查操作

    博客地址:http://www.moonxy.com 关于AD 域服务器搭建及其使用,请参阅:AD 域服务简介(一) - 基于 LDAP 的 AD 域服务器搭建及其使用 Java 获取 AD 域用户, ...

  2. 【OF框架】新建库表及对应实体,并实现简单的增删改查操作,封装操作标准WebApi

    准备 搭建好项目框架及数据库,了解框架规范. 1.数据库表和实体一一对应,表名实体名名字相同,用小写,下划线连接.字段名用驼峰命名法,首字母大写. 2.实体放在Entities目录下,继承Entity ...

  3. 初识Hibernate框架,进行简单的增删改查操作

    Hibernate的优势 优秀的Java 持久化层解决方案  (DAO) 主流的对象—关系映射工具产品 简化了JDBC 繁琐的编码 将数据库的连接信息都存放在配置文件 自己的ORM框架 一定要手动实现 ...

  4. 初识hibernate框架之一:进行简单的增删改查操作

    Hibernate的优势 l 优秀的Java 持久化层解决方案  (DAO) l 主流的对象—关系映射工具产品 l 简化了JDBC 繁琐的编码 l 将数据库的连接信息都存放在配置文件 l 自己的ORM ...

  5. Node+Express+node-mysql 实战于演习 全套mysql(增删改查)

    最近这段时间研究Node感觉不错,自己做了一个增删改查,虽然有些简陋,但是思想是想通的,其实所有项目都是增删改查,有助于初学者快速掌握Node 首先 本实例展示的是基于Node+Express+nod ...

  6. MongoDB学习笔记—03 增删改查操作

    MongoDB的CURD操作分别通过函数insert().update().find().remove()进行 MongoDB文档新增与删除 MongoDB中关于文档的新增与删除比较简单.主要通过in ...

  7. 控制台程序实现利用CRM组织服务和SqlConnection对数据库中数据的增删改查操作

    一.首先新建一个控制台程序.命名为TestCol. 二.打开App.config在里面加入,数据库和CRM连接字符串 <connectionStrings> <add name=&q ...

  8. 编码实战Web端联系人的增删改查

    首先画出分析图 实现效果如图 项目下的包如图: 实体包 package com.contactSystem.entiey; public class Contact { private String ...

  9. jquery-easyui实现页面布局和增删改查操作(SSH2框架支持)转载

    http://blessht.iteye.com/blog/1069749/ 已注册:ooip 关联的csdn 前几天心血来潮用jquery-easyui+spring.struts2.hiberna ...

随机推荐

  1. VISIO下载+安装+第一个数据流图

    一. 下载地址 Visio2021 (64bit).zip_免费高速下载|百度网盘-分享无限制 (baidu.com) 码3333 二. 安装步骤 Visio2021安装教程 (qq.com) 三. ...

  2. springMVC中获取request和response对象的几种方式(RequestContextHolder)

    springMVC中获取request和response对象的几种方式 1.最简单方式:参数 2.加入监听器,然后在代码里面获取 原文链接:https://blog.csdn.net/weixin_4 ...

  3. 彻底理解synchronized

    1. synchronized简介 在学习知识前,我们先来看一个现象: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public ...

  4. javascript回调地狱真的只能Promise来解决吗?js回调地狱,Promise。

    javascript的灵活在于函数可以当作函数的参数来传递,以及它的异步回调思想.但是这就带了一个很严重的问题,那就是回调次数过多,会影响代码结构,多层嵌套影响代码的可阅读性,也不便于书写. 举个例子 ...

  5. vue换算单位px自动转换rem

    cnpm i postcss-px2rem --save cnpm install px2rem-loader --save 2.配置px2rem build目录下vue-loader.conf.js ...

  6. OllyDbg---循环、串操作和寻址方式

    循环 字符串指令和寻址方式 循环 XOR ECX,ECX MOV ECX,15H LABEL: DEC ECX CMP ECX,0 JNE LABEL LOOP 重复循环,直到计数器的值为0,每次循环 ...

  7. CentOS7.x 离线安装和开机启动 supervisor 4.2.4

    CentOS7.x 服务器 离线安装 开机启动 supervisor 4.2.4

  8. 函数.python

    今日内容概要 名称空间 名字的查找顺序 作用域 global与nonlocal关键字 函数名对象 函数的嵌套 今日内容详细 1.名称空间 #名称空间其实就是存放变量名与变量名绑定关系的地方#分类1.内 ...

  9. Codeforces Round #767 (Div. 2) c d, 巧妙标记

    贪心: Problem - C - Codeforces 思维: Problem - D - Codeforces 这两个题不错, 第一个需要考虑后面,就先标记完, 从前遍历挨个除去标记 第二个需要考 ...

  10. 使用pip管理库

    2.5 使用pip管理库 安装Python后会默认安装pip工具,该工具可以用来安装.升级和移除库.默认情况下 pip 将从[Python Package Index]https://pypi.org ...