Go语言图书管理RESTful API开发实战
ztj100 2025-01-11 18:52 15 浏览 0 评论
Go(Golang)是最近流行起来,且相对较新的编程语言。
它小而稳定,使用和学习简单,速度快,经过编译(原生代码),并大量用于云工具和服务(Docker、Kubernetes...)。
考虑到它所带来的所有好处,没有理由不去尝试一下。
在本教程中,我们将建立一个简单的图书商店REST API。
程序员宝藏库:https://github.com/Jackpopc/CS-Books-Store
1. 准备工作
在我们开始之前,我们需要事先做一些准备工作:
- 浏览器
- gorilla/handlers
- gorilla/mux
做好这些准备,我们就可以开始Go之旅了!
2. 应用结构
你现在应该已经安装好Go,并且做好了事先准备工作。
打开你最喜欢的IDE(Visual Studio Code, GoLand, ...),创建一个新的项目。
正如我前面提到的,我们的想法是通过使用Mux建立一个简单的REST API,用于图书商店管理。
一旦你创建了你的空白项目,在其中创建以下结构:
├── main.go
└── src
├── app.go
├── data.go
├── handlers.go
├── helpers.go
└── middlewares.go
Go工具包和模块
我们先来了解一下Go模块和包,如果你熟悉Python,你可能会对这些东西有一个概念,因为它们的操作很相似。
描述 Go 包的最好方法是,它是同一目录下的源文件的集合,被编译成一个可重复使用的单元。
这意味着所有有类似用途的文件都应该放在一个包里。
按照我们上面的结构,src是我们的包之一。
Go模块是Go包及其依赖关系的集合,这意味着一个模块可以由多个包组成。
为了便于理解,你可以把我们的整个应用程序看成是一个Go模块。
让我们在项目目录下执行这个命令来创建我们的模块。
go mod init bookstore
你应该在你的根目录中看到一个新文件,名为go.mod。
3. 构建API
现在是时候开始构建我们的应用程序了。
打开你的main.go文件,在其中插入以下代码。
package main
import"bookstore/src"
func main() {
src.Start()
}
我们声明了我们的主Go包(package main),并将我们的src包与模块bookstore的前缀一起导入。
在函数main()中,我们将运行包src的Start()函数。
这是我们的入口文件(main.go)的唯一职责--启动API。
路线和处理程序
现在我们需要创建我们的API路由器(Mux),并通过创建一些端点和它们的处理程序来配置它。
在你的src包中打开app.go并在其中插入以下代码。
package src
import (
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"log"
"net/http"
"os"
)
func Start() {
router := mux.NewRouter()
router.Use(commonMiddleware)
router.HandleFunc("/book", getAllBooks).Methods(http.MethodGet)
router.HandleFunc("/book", addBook).Methods(http.MethodPost)
router.HandleFunc("/book/{book_id:[0-9]+}", getBook).Methods(http.MethodGet)
router.HandleFunc("/book/{book_id:[0-9]+}", updateBook).Methods(http.MethodPut)
router.HandleFunc("/book/{book_id:[0-9]+}", deleteBook).Methods(http.MethodDelete)
log.Fatal(http.ListenAndServe("localhost:5000", handlers.LoggingHandler(os.Stdout, router)))
}
如你所见,我们声明app.go是src包的一部分,它包含了我们在main.go文件中使用的Start()函数。
我们还导入两个外部模块,我们需要程序中需要依赖到的mux和handlers。
在你的终端中执行以下命令:
go get github.com/gorilla/handlers
go get github.com/gorilla/mux
你的go.mod文件应该也同步了,现在它应该是这样的:
module bookstore
go1.17
require (
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
)
require github.com/felixge/httpsnoop v1.0.1// indirect
让我们深入了解一下我们的Start()函数。
首先,我们声明了一个新的Mux路由器变量,它将负责路由和处理整个API的请求。
然后,我们告诉Mux,我们要用到一个中间件,它将在每个来到我们API的请求中执行下面这一行:
router.Use(commonMiddleware)
稍后会有更多关于中间件的内容。
继续分析我们的代码,我们可以看到我们在哪里创建端点以及处理程序(回调函数)和一些原始的验证,例如:
router.HandleFunc("/book/{book_id:[0-9]+}", updateBook).Methods(http.MethodPut)
一旦用户在/book/123(或任何其他数字)路径上用PUT方法点击我们的服务器,这个端点就会启动。
然后,它将把请求传递给updateBook处理函数进行进一步处理。
book_id变量必须是一个数字,因为我们在变量名声明后指定了一个简单的验证。
最后,我们将在特定的主机和端口组合上运行我们的服务器,并让它把所有的东西都记录到我们的终端。
中间件
我们都知道,REST APIs在接受请求和返回响应时大多使用JSON。
这是通过使用Content-Type头信息传达给我们的浏览器/HTTP客户端的。
由于我们的API将只使用JSON表示的数据,我们可以使用一个中间件,以确保我们的内容类型始终被设置为JSON。
如前所述,app.go的Start()方法包含这一行:
router.Use(commonMiddleware)
让我们打开我们的middlewares.go文件并创建所需的函数:
package src
import"net/http"
func commonMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("content-type", "application/json; charset=utf-8")
w.Header().Set("x-content-type-options", "nosniff")
next.ServeHTTP(w, r)
})
}
一旦用户点击我们在Start()函数中向Mux路由器注册的任何一个端点,中间件将拦截该请求并添加我们在commonMiddleware函数中指定的两个头信息。
然后,它将把修改后的请求进一步传递给被请求端点的处理函数或另一个中间件。
静态数据
由于我们不会使用任何数据存储服务(数据库、缓存......),我们需要有某种静态数据。
另外,我们将为自定义响应创建一个数据类型,我将在后面解释。
打开src包内的data.go,在其中放入以下内容。
package src
type Book struct {
Id int`json:"id"`
Title string`json:"title"`
Author string`json:"author"`
Genre string`json:"genre"`
}
var booksDB = []Book{
{Id: 123, Title: "The Hobbit", Author: "J. R. R. Tolkien", Genre: "Fantasy"},
{Id: 456, Title: "Harry Potter and the Philosopher's Stone", Author: "J. K. Rowling", Genre: "Fantasy"},
{Id: 789, Title: "The Little Prince", Author: "Antoine de Saint-Exupéry", Genre: "Novella"},
}
我们刚刚创建了一个数据结构,它将在我们的API中保存一本书所需的信息。
我还创建了json标签,如果数据类型将以JSON形式传递,它将把字段名翻译成JSON表示。此外,还创建了一个原始的图书存储系统(在内存中)和一些初始图书数据(booksDB)。
把这段代码添加到上面的表格下面:
type CustomResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Description string `json:"description,omitempty"`
}
var responseCodes = map[int]string {
400: "Bad Request",
401: "Unauthorized",
403: "Forbidden",
404: "Not Found",
409: "Conflict",
422: "Validation Error",
429: "Too Many Requests",
500: "Internal Server Error",
}
我们刚刚做了一个新的数据结构,将统一我们的API将返回的错误/响应,稍后会有更多这方面的内容。
辅助工具
我们将需要一些辅助工具来充分利用我们的API。例如,我们将需要检查具有给定ID的书是否存在(添加新书,修改现有的书)、需要删除一个具有给定ID的书(删除书)、需要为给定的HTTP状态代码返回一个自定义的JSON响应。
打开src包中的helpers.go,在里面插入以下内容:
package src
import (
"encoding/json"
"net/http"
)
func removeBook(s []Book, i int) []Book {
if i != len(s)-1 {
s[i] = s[len(s)-1]
}
return s[:len(s)-1]
}
func checkDuplicateBookId(s []Book, id int) bool {
for _, book := range s {
if book.Id == id {
return true
}
}
return false
}
func JSONResponse(w http.ResponseWriter, code int, desc string) {
w.WriteHeader(code)
message, ok := responseCodes[code]
if !ok {
message = "Undefined"
}
r := CustomResponse{
Code: code,
Message: message,
Description: desc,
}
_ = json.NewEncoder(w).Encode(r)
}
removeBook函数会遍历Book,如果它不是片段的最后一个元素,它将把它移到片段的末尾并返回一个没有它的新片段(避免最后一个元素)。
checkDuplicateBookId函数将返回一个bool值(真或假),这取决于给定的id是否存在于Book中。
JSONResponse函数负责使用我们先前创建的CustomResponse和responseCodes。它将返回一个CustomResponse的JSON表示,其中包含了responseCodes将提供的状态代码和消息。
这样,我们将避免在我们的API中对相同的HTTP状态代码有不同的消息。
处理程序
现在来到了最后一步,把端点处理程序放在一起。
打开你的handlers.go,让我们在其中输入一些代码:
package src
import (
"encoding/json"
"github.com/gorilla/mux"
"net/http"
"strconv"
)
获取单本图书
func getBook(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bookId, _ := strconv.Atoi(vars["book_id"])
for _, book := range booksDB {
if book.Id == bookId {
_ = json.NewEncoder(w).Encode(book)
return
}
}
JSONResponse(w, http.StatusNotFound, "")
}
我们从Mux路由器获得传递的变量,把它从字符串转换为int值。然后,遍历我们的booksDB,寻找匹配的书籍ID。如果它存在,返回它 - 如果不存在,我们返回404: Not Found错误。
获取所有图书
func getAllBooks(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(booksDB)
}
是不是很简单?将booksDB转换为JSON,并将其返回给用户。
添加一本新图书
func addBook(w http.ResponseWriter, r *http.Request) {
decoder := json.NewDecoder(r.Body)
var b Book
err := decoder.Decode(&b)
if err != nil {
JSONResponse(w, http.StatusBadRequest, "")
return
}
if checkDuplicateBookId(booksDB, b.Id) {
JSONResponse(w, http.StatusConflict, "")
return
}
booksDB = append(booksDB, b)
w.WriteHeader(201)
_ = json.NewEncoder(w).Encode(b)
}
由于这是在POST方法上触发的,用户必须在请求体中提供符合Book结构的JSON数据。
{
"id": 999,
"title": "SomeTitle",
"author": "SomeAuthor",
"genre": "SomeGenre"
}
一旦我们根据我们的图书结构解码并验证JSON主体(如果失败,我们将返回400: Bad Request error),我们需要检查具有相同ID的图书是否已经存在。如果是这样,我们将返回409: Conflict error back。反之,将用用户提供的书追加我们的booksDB,并将其JSON表示返回给用户。
更新现有书籍
func updateBook(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bookId, _ := strconv.Atoi(vars["book_id"])
decoder := json.NewDecoder(r.Body)
var b Book
err := decoder.Decode(&b)
if err != nil {
JSONResponse(w, http.StatusBadRequest, "")
return
}
for i, book := range booksDB {
if book.Id == bookId {
booksDB[i] = b
_ = json.NewEncoder(w).Encode(b)
return
}
}
JSONResponse(w, http.StatusNotFound, "")
}
与addBook函数处理程序几乎相同,但有一个主要区别。
要更新这本书,它必须已经存在(ID必须在booksDB中)。
如果它存在,我们将更新现有书籍的值,否则,我们将返回404: Not Found错误。
删除现有书籍
func deleteBook(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bookId, _ := strconv.Atoi(vars["book_id"])
for i, book := range booksDB {
if book.Id == bookId {
booksDB = removeBook(booksDB, i)
_ = json.NewEncoder(w).Encode(book)
return
}
}
JSONResponse(w, http.StatusNotFound, "")
}
在我们得到book_id变量的整数值后,我们遍历booksDB,找到用户想删除的书。
如果它存在,我们利用我们的辅助函数removeBook,从Book结构片中删除该书。如果它不存在,我们将返回404: Not Found错误。
4. 运行和测试API
现在我们的API已经完成,让我们来运行一下,在你的终端执行这个程序:
go run main.go
启动你最喜欢的HTTP客户端(Insomnia, Postman, ...),试试我们创建的一些接口吧
相关推荐
- Vue 技术栈(全家桶)(vue technology)
-
Vue技术栈(全家桶)尚硅谷前端研究院第1章:Vue核心Vue简介官网英文官网:https://vuejs.org/中文官网:https://cn.vuejs.org/...
- vue 基础- nextTick 的使用场景(vue的nexttick这个方法有什么用)
-
前言《vue基础》系列是再次回炉vue记的笔记,除了官网那部分知识点外,还会加入自己的一些理解。(里面会有部分和官网相同的文案,有经验的同学择感兴趣的阅读)在开发时,是不是遇到过这样的场景,响应...
- vue3 组件初始化流程(vue组件初始化顺序)
-
学习完成响应式系统后,咋们来看看vue3组件的初始化流程既然是看vue组件的初始化流程,咋们先来创建基本的代码,跑跑流程(在app.vue中写入以下内容,来跑流程)...
- vue3优雅的设置element-plus的table自动滚动到底部
-
场景我是需要在table最后添加一行数据,然后把滚动条滚动到最后。查网上的解决方案都是读取html结构,暴力的去获取,虽能解决问题,但是不喜欢这种打补丁的解决方案,我想着官方应该有相关的定义,于是就去...
- Vue3为什么推荐使用ref而不是reactive
-
为什么推荐使用ref而不是reactivereactive本身具有很大局限性导致使用过程需要额外注意,如果忽视这些问题将对开发造成不小的麻烦;ref更像是vue2时代optionapi的data的替...
- 9、echarts 在 vue 中怎么引用?(必会)
-
首先我们初始化一个vue项目,执行vueinitwebpackechart,接着我们进入初始化的项目下。安装echarts,npminstallecharts-S//或...
- 无所不能,将 Vue 渲染到嵌入式液晶屏
-
该文章转载自公众号@前端时刻,https://mp.weixin.qq.com/s/WDHW36zhfNFVFVv4jO2vrA前言...
- vue-element-admin 增删改查(五)(vue-element-admin怎么用)
-
此篇幅比较长,涉及到的小知识点也比较多,一定要耐心看完,记住学东西没有耐心可不行!!!一、添加和修改注:添加和编辑用到了同一个组件,也就是此篇文章你能学会如何封装组件及引用组件;第二能学会async和...
- 最全的 Vue 面试题+详解答案(vue面试题知识点大全)
-
前言本文整理了...
- 基于 vue3.0 桌面端朋友圈/登录验证+60s倒计时
-
今天给大家分享的是Vue3聊天实例中的朋友圈的实现及登录验证和倒计时操作。先上效果图这个是最新开发的vue3.x网页端聊天项目中的朋友圈模块。用到了ElementPlus...
- 不来看看这些 VUE 的生命周期钩子函数?| 原力计划
-
作者|huangfuyk责编|王晓曼出品|CSDN博客VUE的生命周期钩子函数:就是指在一个组件从创建到销毁的过程自动执行的函数,包含组件的变化。可以分为:创建、挂载、更新、销毁四个模块...
- Vue3.5正式上线,父传子props用法更丝滑简洁
-
前言Vue3.5在2024-09-03正式上线,目前在Vue官网显最新版本已经是Vue3.5,其中主要包含了几个小改动,我留意到日常最常用的改动就是props了,肯定是用Vue3的人必用的,所以针对性...
- Vue 3 生命周期完整指南(vue生命周期及使用)
-
Vue2和Vue3中的生命周期钩子的工作方式非常相似,我们仍然可以访问相同的钩子,也希望将它们能用于相同的场景。...
- 救命!这 10 个 Vue3 技巧藏太深了!性能翻倍 + 摸鱼神器全揭秘
-
前端打工人集合!是不是经常遇到这些崩溃瞬间:Vue3项目越写越卡,组件通信像走迷宫,复杂逻辑写得脑壳疼?别慌!作为在一线摸爬滚打多年的老前端,今天直接甩出10个超实用的Vue3实战技巧,手把...
- 怎么在 vue 中使用 form 清除校验状态?
-
在Vue中使用表单验证时,经常需要清除表单的校验状态。下面我将介绍一些方法来清除表单的校验状态。1.使用this.$refs...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- Vue 技术栈(全家桶)(vue technology)
- vue 基础- nextTick 的使用场景(vue的nexttick这个方法有什么用)
- vue3 组件初始化流程(vue组件初始化顺序)
- vue3优雅的设置element-plus的table自动滚动到底部
- Vue3为什么推荐使用ref而不是reactive
- 9、echarts 在 vue 中怎么引用?(必会)
- 无所不能,将 Vue 渲染到嵌入式液晶屏
- vue-element-admin 增删改查(五)(vue-element-admin怎么用)
- 最全的 Vue 面试题+详解答案(vue面试题知识点大全)
- 基于 vue3.0 桌面端朋友圈/登录验证+60s倒计时
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- node卸载 (33)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- exceptionininitializererror (33)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)