大鹏一日同风起,扶摇直上九万里。这篇文章主要讲述使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API相关的知识,希望能为你提供帮助。
theme: devui-blue
highlight: a11y-dark
本文利用到的 Go 语言相关技术:
- ??Gorilla/Mux??:功能强大的 URL 路由器和调度组件
- ??CockroachDB??:开源,云原生分布式 SQL 数据库系统
- ??GORM??:神奇的 ORM 库
- Ubuntu 18.04.6 LTS
- 开发工具:Visual Studio Code
- 测试工具:??APIfox??:Apifox = Postman + Swagger + Mock + JMeter
1 CockroachDB 介绍
CockrocreDB 是一个云原生分布式 SQL 数据库,旨在构建,扩展和管理现代数据密集型应用程序。
官方介绍翻译如下:
CockroachDB 是一个分布式关系型数据库,建立在事务性和强一致性键值存储之上,具有水平扩展、高可用、支持强一致性 ACID 事务隔离级别的特点,并提供统一的 SQL API 用于结构化、操作和查询数据。
简单来说,CockroachDB 充分利用了上一代数据库系统的优势、SQL 的强大能力以及关系数据模型和现代云原生法则,成为了一个与其他基于 SQL 的事务型数据库广泛兼容的数据库系统。
CockroachDB 对 Go 也有很好的支持,比如 pgx、pg、GORM 和 upper/db。
1.1 下载安装针对最新版的 CockroachDB Linux 下载,点??此处?? 可以看官方的教程写的很详细,这里本文将跟着照做一次。(选择其他系统,同理)
- 下载适用于 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/
如图所示:
文章图片
- CockroachDB 使用??GEOS?? 库的定制版本。默认情况下,CockroachDB 在?
?/usr/local/lib/cockroach?
? 或 CockroachDB 二进制文件当前目录的??lib?
? 子目录中查找外部链接库。
sudo cp -R cockroach-v22.1.3.linux-amd64/* /usr/local/bin
- 确保刚刚安装的 cockroach 已经成功安装:
which cockroach
文章图片
- 启动使用启动临时本地内存集群,使用?
?cockroach demo?
? 命令:
文章图片
如果能看到上图,说明 CockroachDB 安装成功,恭喜~
1.2 使用本地集群官方提供两种方式利用 GORM 使用 CockroachDB:一种是 CockroachDB Serverless(测试),另一种是使用本地集群。
文章图片
第一种方式需要??注册??一个 CockroachDB 云账号,然后按照官方提示创建链接。
本文将使用第二种方式:
- 运行?
?cockroach start-single-node?
? 命令:
cockroach start-single-node --advertise-addr localhost --insecure
通过增加 ?
?--insecure?
? 参数启动了一个不安全的单节点群集。- 记录下 SQL Shell 中欢迎文本中以下连接信息:
CockroachDB node starting at 2022-07-14 13:21:35.179273246 +0000 UTC (took 1.6s)
build:CCL v22.1.3 @ 2022/07/11 14:04:38 (go1.17.11)
webui:http://localhost:8080
sql:postgresql://root@localhost:26257/defaultdb?sslmode=disable
sql (JDBC):jdbc:postgresql://localhost:26257/defaultdb?sslmode=disable& user=root
RPC client flags:cockroach < client cmd> --host=localhost:26257 --insecure
logs:/home/yuzhou/cockroach-data/logs
temp dir:/home/yuzhou/cockroach-data/cockroach-temp2801178939
external I/O path:/home/yuzhou/cockroach-data/extern
store[0]:path=/home/yuzhou/cockroach-data
storage engine:pebble
clusterID:43919fea-32cd-48f9-b585-713731d43ee9
status:restarted pre-existing node
nodeID:1
其中,?
?postgresql://root@localhost:26257/defaultdb?sslmode=disable?
?PS:这条信息需要加入到后续的数据库连接 ?
?datebase.go?
? 文件中,请注意。2 项目介绍
2.1 项目功能本文创建一个应用是一个简单的 RESTful API 服务器:线上书店,它将公开书籍的访问和操作,主要包括如下功能:
- 创建一本书籍
- 获取书籍清单
- 获取一本书籍信息
- 更新已有书籍信息
- 删除一本书籍
- 创建一本书籍:通过?
?/book?
? 响应有效的 POST 请求 - 获取书籍清单:通过?
?/books?
? 响应有效的 GET 请求 - 获取一本书籍:通过?
?/book/id?
? 响应对应的 GET 请求 - 更新一本书籍:通过?
?/book/id?
? 响应对应的 PUT 请求 - 删除一本书籍:通过?
?/book/id?
? 响应对应的 DELETE 请求
?id?
? 能够有效确定某本书籍。豆瓣图书链接中 ??https://book.douban.com/subject/26859123/?
? 的 26859123 就能对应到 《Go程序设计语言(英文版)》这本书。
文章图片
2.3 项目结构本文将创建一个非常简单的应用程序结构,这是本文使用的 mybook 应用的项目结构:
.
├── go.mod
├── go.sum
├── handlers.go
├── main.go
└── model
├── database.go
└── model.go
2.4 安装项目依赖安装 ?
?go get -u github.com/gorilla/mux?
?,如下;文章图片
安装 ?
?go get -u gorm.io/gorm?
?安装 ?
?go get -u gorm.io/driver/postgres?
?文章图片
3 应用功能设计
首先创建一个 mybook 的目录(应用文件夹),然后使用 ?
?go mod init mybook?
?,接着在其中创建我们的 model 文件夹,接下来,在 model 文件夹下新建一个 ?
?model.go?
? 文件。3.1 model.go我们需要设计出 book 模型,这里即将使用 GORM 进行创建,编写我们的 ?
?model.go?
? 函数:package model
import (
"gorm.io/gorm"
)
type Book struct
gorm.Model
IDstring`json:"id"`
Namestring`json:"name"`
Authorstring`json:"author"`
Description string`json:"description"`
Pricefloat64 `json:"price"`
Categorystring`json:"category"`
如上所示,书籍的结构体中进行简单设计包含 id(ID)、书名(Name)、作者(Author)、书籍描述(Description)、价格(Price)和类别(Category)。
3.2 database.go该程序将创建数据库连接,需要使用到 ?
?postgresql://root@localhost:26257/defaultdb?sslmode=disable?
? 传入到 ??gorm.Open()?
? 的连接参数中,代码如下:package model
import (
"log"
"time"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
func SetupDB() (*gorm.DB, error)
dsn := "postgresql://root@localhost:26257/defaultdb?sslmode=disable"
db, err := gorm.Open(postgres.Open(dsn), & gorm.Config)
if err != nil
log.Fatal("cant connect to database", err)
var now time.Time
db.Raw("SELECT NOW()").Scan(& now)
log.Println(now)
if err = db.AutoMigrate(& Book); err != nil
log.Println(err)
return db, err
3.3 handler.go回到 mybook 文件夹下,创建 ?
?handler.go?
? :package main
import (
"encoding/json"
"net/http"
"mybook/model"
"github.com/gorilla/mux"
"gorm.io/gorm"
)
type Server struct
db *gorm.DB
type UpdateBook struct
Pricefloat64 `json:"price"`
Description string`json:"decription"`
Categorystring`json:"category"`
func NewServer(db *gorm.DB) *Server
return & Serverdb: db
func (s *Server) RegisterRouter(router *mux.Router)
router.HandleFunc("/books", s.getBooks)
router.HandleFunc("/book/id", s.getBook).Methods("GET")
router.HandleFunc("/book", s.createBook).Methods("POST")
router.HandleFunc("/book/id", s.updateBook).Methods("PUT")
router.HandleFunc("/book/id", s.deleteBook).Methods("DELETE")
func (s *Server) getBooks(w http.ResponseWriter, r *http.Request)
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
var books []model.Book
if err := s.db.Find(& books).Error; err != nil
http.Error(w, err.Error(), http.StatusInternalServerError)
return
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(books)
func (s *Server) createBook(w http.ResponseWriter, r *http.Request)
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
var book model.Book
if err := json.NewDecoder(r.Body).Decode(& book); err != nil
http.Error(w, err.Error(), http.StatusInternalServerError)
return
newBook := model.BookPrice: book.Price, Description: book.Description, Category: book.Category
if err := s.db.Create(newBook).Error; err != nil
http.Error(w, err.Error(), http.StatusInternalServerError)
return
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(newBook)
func (s *Server) getBook(w http.ResponseWriter, r *http.Request)
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
var book model.Book
vars := mux.Vars(r)
id := vars["id"]
if err := s.db.Where("id = ?", id).First(& book).Error; err != nil
http.Error(w, err.Error(), http.StatusNotFound)
return
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(book)
func (s *Server) updateBook(w http.ResponseWriter, r *http.Request)
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
var updateBook UpdateBook
var book model.Book
vars := mux.Vars(r)
id := vars["id"]
if err := json.NewDecoder(r.Body).Decode(& updateBook); err != nil
http.Error(w, err.Error(), http.StatusInternalServerError)
return
if err := s.db.Where("id = ?", id).First(& book).Error; err != nil
http.Error(w, err.Error(), http.StatusNotFound)
return
if err := s.db.Model(& book).Updates(& model.Book
Price:updateBook.Price,
Description: updateBook.Description,
Category:updateBook.Category).Error; err != nil
http.Error(w, err.Error(), http.StatusInternalServerError)
return
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(book)
func (s *Server) deleteBook(w http.ResponseWriter, r *http.Request)
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
var book model.Book
vars := mux.Vars(r)
id := vars["id"]
if err := s.db.Where("id = ?", id).First(& book).Error; err != nil
http.Error(w, err.Error(), http.StatusNotFound)
return
if err := s.db.Delete(& book).Error; err != nil
http.Error(w, err.Error(), http.StatusInternalServerError)
return
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode("Book Deleted Successfully!")
3.4 main.go在 ?
?main.go?
? 中,我们初始化数据库,创建一个路由器实例,将变量 db 作为参数传递给我们的服务器初始化,然后将路由器实例作为参数传递给方法 ??registryRouter()?
?。然后,我们使用侦听器来运行服务器。package main
import (
"log"
"net/http"
"github.com/gorilla/mux"
"mybook/model"
)
func main()
db, err := model.SetupDB()
if err != nil
log.Println("Failed setting up database")
router := mux.NewRouter()
server := NewServer(db)
server.RegisterRouter(router)
log.Fatal(http.ListenAndServe(":8000", router))
当写完所有的程序后,运行 ?
?go run .?
? 命令,启动成功如下:文章图片
此时,运行 ?
?cockcoach sql?
? 进入数据库命令行,然后运行 ??show tables;
?
? 命令,可以看到在默认数据库 ??defaultdb?
? 中已经生成一个 books 数据库表,如下所示:文章图片
我们执行 ?
?select * from books?
? 查看我们生成的表信息:文章图片
可以看到已经生成了我们定义的字段 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)
VALUES(1, Go程序设计语言(英文版), 艾伦A.A.多诺万 (Alan A.A.Donovan) / 布莱恩W.柯尼汉 (Brian W.Kemighan), 被誉为 Go 语言圣经的书,非常值得一读, 79.00, Golang);
文章图片
获取书籍清单:?
?/books?
?测试插入成功,我们访问后台 ?
?http://127.0.0.1:8000/books?
? 路径,可以看到如下成功,说明获取书籍清单的 API 是成功的,恭喜。文章图片
接下来为了测试,??下载?? Linux 版本的 Apifox 帮助我们快速测试其他接口。
获取一本书籍:?
?/book/1?
? 测试文章图片
总结
本文利用 Go 语言中非常实用的 Gorilla Mux 和 GORM 库、结合分布式 CockroachDB 数据库编写了一个简易的图书的 Restful API,最后通过 Apifox 测试工具验证了服务器 API 的正确。
显然本文还是有很多不足,比如并没有充分利用到 CockroachDB 数据库的分布式特性,而且没有为 API 编写测试代码,测试并不一定完善,这些都可以给到读者一些优化思路。
希望能对你有帮助,如果觉得文章不错,帮忙点个赞,谢谢,下一篇文章再见!
【使用 Gorilla Mux 和 CockroachDB 编写可维护 RESTful API】参考链接:
- ??Install CockroachDB on Linux??
- ??Build a Go App with CockroachDB and GORM??
- ??CockroachDB as a database with Gorilla/Mux | Go??
- ??Gorilla web toolkit??
- ??Golang RESTful API using GORM and Gorilla Mux??
推荐阅读
- 笔记法F2键不能重命名
- 统一化
- FTXUI编译和使用(不含ROS1/2)Linux手机或平板版本
- 数据湖(十七)(Flink与Iceberg整合DataStream API操作)
- 蓝桥ROS之 cmake gcc g++ 默认版本和升级
- 云原生核心技术之(Service Mesh(服务网格))
- linux手机更新 gcc g++ 版本
- Git 配置
- git:使用分支