使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API     DATE: 2024-05-09 03:21:58

使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API

本文利用到的使用 Go 语言相关技术:

运行环境:

本文将使用功能强大的 Gorilla Mux 、GORM 和 CockroachDB 编写可维护 RESTful API  。编写

1 CockroachDB 介绍

使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API

CockrocreDB 是一个云原生分布式 SQL 数据库 ,旨在构建,和C护扩展和管理现代数据密集型应用程序。编写

官方介绍翻译如下 :

CockroachDB 是使用一个分布式关系型数据库,建立在事务性和强一致性键值存储之上,和C护具有水平扩展 、编写高可用、使用支持强一致性 ACID 事务隔离级别的和C护特点,并提供统一的编写 SQL API 用于结构化 、操作和查询数据。使用

简单来说,和C护CockroachDB 充分利用了上一代数据库系统的编写优势、SQL 的强大能力以及关系数据模型和现代云原生法则,成为了一个与其他基于 SQL 的事务型数据库广泛兼容的数据库系统。

CockroachDB 对 Go 也有很好的支持,比如 pgx  、pg 、GORM 和 upper/db 。

1.1 下载安装

针对最新版的 CockroachDB Linux 下载,点此处 可以看官方的教程写得很详细,这里本文将跟着照做一次。(选择其他系统 ,同理)

  1. 下载适用于 Linux 的 CockroachDB 包和其支持库,并将二进制文件复制到您的 PATH 中 ,以便您可以从任何方面 shell 执行 cockroach 命令:

curl https://binaries.cockroachdb.com/cockroach-v22.1.3.linux-amd64.tgz | tar -xz && sudo cp -i cockroach-v22.1.3.linux-amd64/cockroach /usr/local/bin/n复制代码

如图所示 :

使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API
  1. CockroachDB 使用 GEOS 库的定制版本 。默认情况下  ,CockroachDB 在 /usr/local/lib/cockroach 或 CockroachDB 二进制文件当前目录的 lib 子目录中查找外部链接库 。

所以,您可以将二进制文件复制到您的路径中 ,以便您可以从任何目录执行 Cockroach 命令 :

sudo cp -R cockroach-v22.1.3.linux-amd64/* /usr/local/binn复制代码

  1. 确保刚刚安装的 cockroach 已经成功安装:

which cockroachn复制代码使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API

  1. 启动使用启动临时本地内存集群 ,使用 cockroach demo 命令 :
使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API

如果能看到上图 ,说明 CockroachDB 安装成功,恭喜~

1.2 使用本地集群

官方提供两种方式利用 GORM 使用 CockroachDB  :一种是 CockroachDB Serverless(测试) ,另一种是使用本地集群 。

使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API

第一种方式需要注册一个 CockroachDB 云账号 ,然后按照官方提示创建链接 。

本文将使用第二种方式:

  1. 运行 cockroach start-single-node 命令:

cockroach start-single-node --advertise-addr 'localhost' --insecuren复制代码

通过增加 --insecure 参数启动了一个不安全的单节点群集。

  1. 记录下 SQL Shell 中欢迎文本中以下链接信息:

CockroachDB node starting at 2022-07-14 13:21:35.179273246 +0000 UTC (took 1.6s)nbuild: CCL v22.1.3 @ 2022/07/11 14:04:38 (go1.17.11)nwebui: http://localhost:8080nsql: postgresql://root@localhost:26257/defaultdb?sslmode=disablensql (JDBC): jdbc:postgresql://localhost:26257/defaultdb?sslmode=disable&user=rootnRPC client flags: cockroach <client cmd> --host=localhost:26257 --insecurenlogs: /home/yuzhou/cockroach-data/logsntemp dir: /home/yuzhou/cockroach-data/cockroach-temp2801178939nexternal I/O path: /home/yuzhou/cockroach-data/externnstore[0]: path=/home/yuzhou/cockroach-datanstorage engine: pebblenclusterID: 43919fea-32cd-48f9-b585-713731d43ee9nstatus: restarted pre-existing nodennodeID: 1n复制代码

其中 ,postgresql://root@localhost:26257/defaultdb?sslmode=disable,通过 SQL 连接字符串连接我们的 CockroachDB 集群中 。

PS:这条信息需要加入到后续的数据库连接 datebase.go 文件中,请注意 。

2 项目介绍

2.1 项目功能

本文创建一个应用是一个简单的 RESTful API 服务器:线上书店 ,它将公开书籍的访问和操作 ,主要包括如下功能:

2.2 API 接口规范

为了实现上述的功能,我们的应用需要提供给外界如下的信息 API 接口规范:

2.3 项目结构

本文将创建一个非常简单的应用程序结构 ,这是本文使用的 mybook应用的项目结构 :

.n├── go.modn├── go.sumn├── handlers.gon├── main.gon└── modeln ├── database.gon └── model.gon复制代码

2.4 安装项目依赖

安装 go get -u github.com/gorilla/mux,如下;

使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API

安装 go get -u gorm.io/gorm

安装 go get -u gorm.io/driver/postgres

使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API

3 应用功能设计

首先创建一个 mybook 的目录(应用文件夹) ,然后使用 go mod init mybook ,

接着在其中创建我们的 model 文件夹 ,接下来 ,在 model 文件夹下新建一个 model.go 文件。

3.1 model.go

我们需要设计出 book 模型,这里即将使用 GORM 进行创建 ,编写我们的 model.go 函数 :

package modelnnimport (nt"gorm.io/gorm"n)nntype Book struct { ntgorm.ModelnntID string `json:"id"`ntName string `json:"name"`ntAuthor string `json:"author"`ntDescription string `json:"description"`ntPrice float64 `json:"price"`ntCategory string `json:"category"`n}nn复制代码

如上所示,书籍的结构体中进行简单设计包含 id(ID) 、书名(Name)、作者(Author) 、书籍描述(Description)、价格(Price)和类别(Category)  。

3.2 database.go

该程序将创建数据库连接,需要使用到 postgresql://root@localhost:26257/defaultdb?sslmode=disable 传入到 gorm.Open() 的连接参数中  ,代码如下:

package modelnnimport (nt"log"nt"time"nnt"gorm.io/driver/postgres"nt"gorm.io/gorm"n)nnfunc SetupDB() (*gorm.DB, error) { nntdsn := "postgresql://root@localhost:26257/defaultdb?sslmode=disable"ntdb, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ })ntif err != nil { nttlog.Fatal("can't connect to database", err)nt}nntvar now time.Timentdb.Raw("SELECT NOW()").Scan(&now)nntlog.Println(now)nntif err = db.AutoMigrate(&Book{ }); err != nil { nttlog.Println(err)nt}ntreturn db, errn}nn复制代码

3.3 handler.go

回到 mybook 文件夹下  ,创建 handler.go :

package mainnnimport (nt"encoding/json"nt"net/http"nnt"mybook/model"nnt"github.com/gorilla/mux"nt"gorm.io/gorm"n)nntype Server struct { ntdb *gorm.DBn}nntype UpdateBook struct { ntPrice float64 `json:"price"`ntDescription string `json:"decription"`ntCategory string `json:"category"`n}nnfunc NewServer(db *gorm.DB) *Server { ntreturn &Server{ db: db}n}nnfunc (s *Server) RegisterRouter(router *mux.Router) { ntrouter.HandleFunc("/books", s.getBooks)ntrouter.HandleFunc("/book/{ id}", s.getBook).Methods("GET")ntrouter.HandleFunc("/book", s.createBook).Methods("POST")ntrouter.HandleFunc("/book/{ id}", s.updateBook).Methods("PUT")ntrouter.HandleFunc("/book/{ id}", s.deleteBook).Methods("DELETE")n}nnfunc (s *Server) getBooks(w http.ResponseWriter, r *http.Request) { ntw.Header().Set("Content-Type", "application/json;charset=UTF-8")ntvar books []model.Booknntif err := s.db.Find(&books).Error; err != nil { ntthttp.Error(w, err.Error(), http.StatusInternalServerError)nttreturnnt}ntw.WriteHeader(http.StatusOK)ntjson.NewEncoder(w).Encode(books)nn}nnfunc (s *Server) createBook(w http.ResponseWriter, r *http.Request) { ntw.Header().Set("Content-Type", "application/json; charset=UTF-8")ntvar book model.Booknntif err := json.NewDecoder(r.Body).Decode(&book); err != nil { ntthttp.Error(w, err.Error(), http.StatusInternalServerError)nttreturnnt}nntnewBook := model.Book{ nttID: book.ID,nttName: book.Name,nttAuthor: book.Author,nttPrice: book.Price,nttDescription: book.Description,nttCategory: book.Category,nt}ntif err := s.db.Create(&newBook).Error; err != nil { ntthttp.Error(w, err.Error(), http.StatusInternalServerError)nttreturnnt}ntw.WriteHeader(http.StatusOK)ntjson.NewEncoder(w).Encode(newBook)n}nnfunc (s *Server) getBook(w http.ResponseWriter, r *http.Request) { ntw.Header().Set("Content-Type", "application/json; charset=UTF-8")ntvar book model.Bookntvars := mux.Vars(r)ntid := vars["id"]nntif err := s.db.Where("id = ?", id).First(&book).Error; err != nil { ntthttp.Error(w, err.Error(), http.StatusNotFound)nttreturnnt}ntw.WriteHeader(http.StatusOK)ntjson.NewEncoder(w).Encode(book)n}nnfunc (s *Server) updateBook(w http.ResponseWriter, r *http.Request) { ntw.Header().Set("Content-Type", "application/json; charset=UTF-8")ntvar updateBook UpdateBookntvar book model.Booknntvars := mux.Vars(r)ntid := vars["id"]nntif err := json.NewDecoder(r.Body).Decode(&updateBook); err != nil { ntthttp.Error(w, err.Error(), http.StatusInternalServerError)nttreturnnt}nntif err := s.db.Where("id = ?", id).First(&book).Error; err != nil { ntthttp.Error(w, err.Error(), http.StatusNotFound)nttreturnnt}nntif err := s.db.Model(&book).Updates(&model.Book{ nttPrice: updateBook.Price,nttDescription: updateBook.Description,nttCategory: updateBook.Category}).Error; err != nil { ntthttp.Error(w, err.Error(), http.StatusInternalServerError)nttreturnnt}ntw.WriteHeader(http.StatusOK)ntjson.NewEncoder(w).Encode(book)n}nnfunc (s *Server) deleteBook(w http.ResponseWriter, r *http.Request) { ntw.Header().Set("Content-Type", "application/json; charset=UTF-8")ntvar book model.Bookntvars := mux.Vars(r)ntid := vars["id"]nntif err := s.db.Where("id = ?", id).First(&book).Error; err != nil { ntthttp.Error(w, err.Error(), http.StatusNotFound)nttreturnnt}ntif err := s.db.Delete(&book).Error; err != nil { ntthttp.Error(w, err.Error(), http.StatusInternalServerError)nttreturnnt}ntw.WriteHeader(http.StatusOK)ntjson.NewEncoder(w).Encode("Book Deleted Successfully!")n}nnnn复制代码

3.4 main.go

在 main.go 中 ,我们初始化数据库 ,创建一个路由器实例,将变量 db 作为参数传递给我们的服务器初始化,然后将路由器实例作为参数传递给方法 registryRouter() 。然后 ,我们使用侦听器来运行服务器 。

package mainnnimport (nt"log"nt"net/http"nnt"github.com/gorilla/mux"nnt"mybook/model"n)nnfunc main() { ntdb, err := model.SetupDB()nntif err != nil { nttlog.Println("Failed setting up database")nt}nntrouter := mux.NewRouter()nntserver := NewServer(db)nntserver.RegisterRouter(router)ntlog.Fatal(http.ListenAndServe(":8000", router))nn}nn复制代码

当写完所有的程序后,运行 go run . 命令 ,启动成功如下:

使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API

此时,运行 cockcoach sql 进入数据库命令行 ,然后运行 show tables; 命令 ,可以看到在默认数据库 defaultdb 中已经生成一个 books 数据库表 ,如下所示:

使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API

我们执行 select * from books 查看我们生成的表信息 :

使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API

可以看到已经生成了我们定义的字段 id 、name、author 、description、price、category 以外,GORM 还默认帮忙增加了 created_at (创建时间) 、updated_at(更新时间)和 deleted_at(删除时间)三个字段 。

4 API 测试

为了方便我们测试这个 book 应用的 API 功能正常,我们将利用 APIfox 工具来进行测试。先在数据库中新增一条记录 ,如下:

INSERT INTO books (id, name, author, description, price, category)nVALUES(1, 'Go程序设计语言(英文版)', '艾伦A.A.多诺万 (Alan A.A.Donovan) / 布莱恩W.柯尼汉 (Brian W.Kemighan)', '被誉为 Go 语言圣经的书  ,非常值得一读', 79.00, 'Golang');n复制代码使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API

4.1 获取书籍清单:/books测试

插入成功 ,我们访问后台 http://127.0.0.1:8000/books 路径 ,可以看到如下成功 ,说明获取书籍清单的 API 是成功的,恭喜。

使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API

接下来为了测试 ,下载 Linux 版本的 Apifox 帮助我们快速测试其他接口。

4.2 创建一本书籍 ,POST 接口测试 /book :

这里我们创建一本《On Java8》 ,Body 中传入如下的 JSON 数据 :

{ n "id": "2",n "name": "On Java8",n "author": "Bruce",n "description": "Thinking in Java8",n "price": 99.99,n "category": "Java"n}n复制代码使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API

4.3 获取一本书籍  : GET 接口测试 /book/1  :

使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API

GET 接口测试 /book/2

使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API

4.4 更新一本书籍: PUT 接口测试 book/2:

使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API

4.5 删除一本书籍 : DELETE 接口测试 book/2:

使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API

总结

本文利用 Go 语言中非常实用的 Gorilla Mux 和 GORM 库  、结合分布式 CockroachDB 数据库编写了一个简易的图书的 Restful API ,最后通过 Apifox 测试工具验证了服务器 API 的正确 。

显然本文还是有很多不足,比如并没有充分利用到 CockroachDB 数据库的分布式特性,而且没有为 API 编写测试代码,测试并不一定完善 ,这些都可以给到读者一些优化思路  。


原文https://juejin.cn/post/7120256019225116679