2

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

 2 years ago
source link: https://blog.51cto.com/yuzhou1su/5473859
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

推荐 原创

宇宙之一粟 2022-07-14 23:54:10 博主文章分类:Go语言入门很简单 ©著作权

文章标签 json sql linux 文章分类 Go语言 编程语言 私藏项目实操分享 阅读数194


theme: devui-blue highlight: a11y-dark

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

运行环境:

  • Ubuntu 18.04.6 LTS
  • 开发工具:Visual Studio Code
  • 测试工具:​ ​APIfox​​:Apifox = Postman + Swagger + Mock + JMeter

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

1 CockroachDB 介绍

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

官方介绍翻译如下:

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

简单来说,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/

如图所示:

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

  1. CockroachDB 使用​ ​GEOS​​ 库的定制版本。默认情况下,CockroachDB 在​​/usr/local/lib/cockroach​​ 或 CockroachDB 二进制文件当前目录的​​lib​​ 子目录中查找外部链接库。

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

sudo cp -R cockroach-v22.1.3.linux-amd64/* /usr/local/bin
  1. 确保刚刚安装的 cockroach 已经成功安装:
which cockroach

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

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

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

1.2 使用本地集群

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

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

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

本文将使用第二种方式:

  1. 运行​​cockroach start-single-node​​ 命令:
cockroach start-single-node --advertise-addr 'localhost' --insecure

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

  1. 记录下 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 服务器:线上书店,它将公开书籍的访问和操作,主要包括如下功能:

  • 创建一本书籍
  • 获取书籍清单
  • 获取一本书籍信息
  • 更新已有书籍信息
  • 删除一本书籍

2.2 API 接口规范

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

  • 创建一本书籍:通过​​/book​​ 响应有效的 POST 请求
  • 获取书籍清单:通过​​/books​​ 响应有效的 GET 请求
  • 获取一本书籍:通过​​/book/{id}​​ 响应对应的 GET 请求
  • 更新一本书籍:通过​​/book/{id}​​ 响应对应的 PUT 请求
  • 删除一本书籍:通过​​/book/{id}​​ 响应对应的 DELETE 请求

通过 ​​{id}​​ 能够有效确定某本书籍。

豆瓣图书链接中 ​​https://book.douban.com/subject/26859123/​​ 的 26859123 就能对应到 《Go程序设计语言(英文版)》这本书。

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

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​​,如下;

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

安装 ​​go get -u gorm.io/gorm​

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

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

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

ID string `json:"id"`
Name string `json:"name"`
Author string `json:"author"`
Description string `json:"description"`
Price float64 `json:"price"`
Category string `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("can't 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 {
Price float64 `json:"price"`
Description string `json:"decription"`
Category string `json:"category"`
}

func NewServer(db *gorm.DB) *Server {
return &Server{db: 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.Book{Price: 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 .​​ 命令,启动成功如下:

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

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

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

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

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

可以看到已经生成了我们定义的字段 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');

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

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

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

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

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

获取一本书籍:​​/book/1​​ 测试

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

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

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

希望能对你有帮助,如果觉得文章不错,帮忙点个赞,谢谢,下一篇文章再见!

参考链接:

  • 1
  • 1收藏
  • 评论
  • 分享
  • 举报

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK