30天学会Go--第6天 GO语言 RESTful API 学习与实践
30天学会Go–第6天 GO语言 RESTful API 学习与实践
文章目录
- 30天学会Go--第6天 GO语言 RESTful API 学习与实践
- 一、 RESTful API 的设计原则
- 1.1 RESTful API 的核心概念
- 1.2 RESTful API 的 URL 设计
- 1.3 RESTful API 的数据格式
- 二、 实现 RESTful API
- 2.1 定义数据模型
- 2.2 实现 CRUD 操作(增,删,改,查)
- 2.3 注册路由并启动服务器
- 3. 测试 RESTful API
- 3.1 使用 `curl` 测试(也可以使用 Postman , 这里只做简单测试,故使用 curl)
- 三、 总结
- 1. `net/http` 包
- 函数
- 接口和方法
- 2. `encoding/json` 包
- 函数
- 方法
- 3. `strconv` 包
- 函数
- 4. `fmt` 包
- 函数
- 5. `io` 包
- 接口
学习 RESTful API 是后端开发的重要环节,它是现代 Web 服务中最普遍的接口设计风格之一。以下将分为 学习内容 和 实践代码 两部分,帮助你快速上手 RESTful API 的设计与实现。
go语言官方编程指南:https://pkg.go.dev/stdopen in new window
go语言的官方文档学习笔记很全,推荐去官网学习
30天学会GO–第5天 GO语言 网络编程:30天学会Go–第5天 GO语言 网络编程-CSDN博客
一、 RESTful API 的设计原则
1.1 RESTful API 的核心概念
RESTful API 是一种基于 HTTP 协议的设计风格,强调资源的表现形式和操作。以下是 RESTful 的核心概念:
- 资源(Resource):
- 资源是 API 的核心,例如用户(
/users
)、文章(/articles
)。 - 每个资源通过一个唯一的 URL 表示,例如
/users/1
表示用户 ID 为 1 的资源。
- 资源是 API 的核心,例如用户(
- HTTP 方法:
- 不同的 HTTP 方法对应不同的操作:
GET
:获取资源。POST
:创建资源。PUT
:更新资源(整体替换)。PATCH
:更新资源(部分更新)。DELETE
:删除资源。
- 不同的 HTTP 方法对应不同的操作:
- 状态码(Status Code):
- 使用 HTTP 状态码表示操作结果:
200 OK
:请求成功。201 Created
:资源创建成功。204 No Content
:删除成功,无返回内容。400 Bad Request
:请求参数错误。404 Not Found
:资源不存在。500 Internal Server Error
:服务器内部错误。
- 使用 HTTP 状态码表示操作结果:
1.2 RESTful API 的 URL 设计
URL 是 RESTful API 的入口,设计时需要遵循以下原则:
- 使用名词表示资源:
- 正确:
/users
、/articles
。 - 错误:
/getUsers
、/createArticle
。
- 正确:
- 层次结构清晰:
- 资源的父子关系通过路径表示,例如:
/users/1
:表示用户 ID 为 1 的用户。/users/1/articles
:表示用户 ID 为 1 的所有文章。
- 资源的父子关系通过路径表示,例如:
- 避免动词:
- 操作由 HTTP 方法决定,而不是 URL。
- 使用复数形式:
- 资源通常使用复数形式,例如
/users
表示用户集合。
- 资源通常使用复数形式,例如
1.3 RESTful API 的数据格式
-
请求数据:
- URL 参数:适合传递简单的资源标识符,例如
/users/1
。 - Query 参数:适合传递筛选条件或分页信息,例如
/users?age=18&page=1&size=10
。 - JSON Body:适合传递复杂的数据结构,例如创建或更新资源时的数据。
- URL 参数:适合传递简单的资源标识符,例如
-
响应数据:
- 通常使用 JSON 格式返回数据:
{ "id": 1, "name": "John Doe", "email": "john.doe@example.com" }
- 通常使用 JSON 格式返回数据:
二、 实现 RESTful API
以下是一个完整的 RESTful API 示例,使用 Go 的 net/http
包实现。
2.1 定义数据模型
package main
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
var users = []User{
{ID: 1, Name: "Alice", Email: "alice@example.com"},
{ID: 2, Name: "Bob", Email: "bob@example.com"},
}
解读:
-
定义用户结构体
-
User
是一个结构体,表示用户资源。ID
:用户的唯一标识符,类型为整数。Name
:用户的名称,类型为字符串。Email
:用户的电子邮件地址,类型为字符串。
-
JSON 标签:
- 每个字段后面的反引号部分(如
json:"id"
)是 JSON 的字段名标签。- 当将
User
转换为 JSON 时,字段名会使用标签中指定的名称。
- 当将
- 每个字段后面的反引号部分(如
-
-
初始化用户数据
-
定义了一个全局变量
users
,类型为[]User
(用户切片)。- 模拟了一个简单的“数据库”,存储了两个用户的初始数据:
- 用户 ID 为 1,名字为 “Alice”,邮箱为 “alice@example.com”。
- 用户 ID 为 2,名字为 “Bob”,邮箱为 “bob@example.com”。
- 模拟了一个简单的“数据库”,存储了两个用户的初始数据:
-
在实际开发中,这些数据通常存储在数据库中,而不是直接写在代码里。
-
2.2 实现 CRUD 操作(增,删,改,查)
GET 请求:获取所有用户
func getUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
功能:
- 提供一个接口,返回所有用户的数据。
- 数据以 JSON 格式返回。
解读:
- 设置响应头部:
w.Header().Set("Content-Type", "application/json")
:- 设置响应的
Content-Type
为application/json
,告诉客户端返回的数据是 JSON 格式。
- 设置响应的
- 编码 JSON 并写入响应:
json.NewEncoder(w).Encode(users)
:- 使用
json.NewEncoder
将全局变量users
(用户列表)编码为 JSON 格式,并写入http.ResponseWriter
,发送给客户端。
- 使用
GET 请求:获取单个用户
func getUser(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(r.URL.Query().Get("id"))
if err != nil {
http.Error(w, "Invalid user ID", http.StatusBadRequest)
return
}
for _, user := range users {
if user.ID == id {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
return
}
}
http.Error(w, "User not found", http.StatusNotFound)
}
功能:
- 根据用户 ID 查询单个用户。
- 如果用户存在,返回用户数据;否则返回
404 Not Found
。
解读:
- 获取用户 ID:
r.URL.Query().Get("id")
:- 从 URL 查询参数中获取
id
的值。 - 例如,
/users?id=1
中的id=1
。
- 从 URL 查询参数中获取
strconv.Atoi
:- 将字符串形式的 ID 转换为整数,方便后续比较。
- 错误处理:
- 如果
id
无法转换为整数,返回400 Bad Request
。
- 如果
- 查找用户:
- 遍历
users
列表,查找与id
匹配的用户。 - 如果找到用户,编码为 JSON 并返回。
- 遍历
- 用户未找到:
- 如果遍历结束后仍未找到用户,返回
404 Not Found
。
- 如果遍历结束后仍未找到用户,返回
POST 请求:创建用户
func createUser(w http.ResponseWriter, r *http.Request) {
var newUser User
err := json.NewDecoder(r.Body).Decode(&newUser)
if err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
newUser.ID = len(users) + 1
users = append(users, newUser)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(newUser)
}
功能:
- 创建一个新用户。
- 从请求的 JSON 数据中解析用户信息,并将其添加到用户列表中。
解读:
- 解析请求体:
json.NewDecoder(r.Body).Decode(&newUser)
:- 从请求体中读取 JSON 数据,并解析为
User
结构体。
- 从请求体中读取 JSON 数据,并解析为
- 错误处理:
- 如果解析失败(例如请求体不是有效的 JSON 格式),返回
400 Bad Request
。
- 如果解析失败(例如请求体不是有效的 JSON 格式),返回
- 分配新用户 ID:
newUser.ID = len(users) + 1
:- 新用户的 ID 是当前用户列表长度加 1。
- 这是一个简单的 ID 分配方式,实际项目中通常由数据库生成唯一 ID。
- 添加到用户列表:
users = append(users, newUser)
:- 将新用户追加到全局变量
users
列表中。
- 将新用户追加到全局变量
- 返回响应:
- 设置
Content-Type
为application/json
。 - 返回状态码
201 Created
,表示资源创建成功。 - 返回新创建的用户数据。
- 设置
PUT 请求:更新用户
func updateUser(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(r.URL.Query().Get("id"))
if err != nil {
http.Error(w, "Invalid user ID", http.StatusBadRequest)
return
}
var updatedUser User
err = json.NewDecoder(r.Body).Decode(&updatedUser)
if err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
for i, user := range users {
if user.ID == id {
users[i].Name = updatedUser.Name
users[i].Email = updatedUser.Email
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users[i])
return
}
}
http.Error(w, "User not found", http.StatusNotFound)
}
功能:
- 根据用户 ID 更新用户信息。
- 如果用户存在,更新其数据;否则返回
404 Not Found
。
解读:
- 获取用户 ID:
- 同
getUser
,从查询参数中获取用户 ID,并转换为整数。
- 同
- 解析请求体:
- 使用
json.NewDecoder
解析请求体中的 JSON 数据。 - 错误处理:
- 如果请求体不是有效的 JSON 格式,返回
400 Bad Request
。
- 如果请求体不是有效的 JSON 格式,返回
- 使用
- 查找并更新用户:
- 遍历
users
列表,查找与id
匹配的用户。 - 如果找到用户,更新其
Name
和Email
。
- 遍历
- 返回响应:
- 如果更新成功,返回状态码
200 OK
和更新后的用户数据。 - 如果用户未找到,返回
404 Not Found
。
- 如果更新成功,返回状态码
DELETE 请求:删除用户
func deleteUser(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(r.URL.Query().Get("id"))
if err != nil {
http.Error(w, "Invalid user ID", http.StatusBadRequest)
return
}
for i, user := range users {
if user.ID == id {
users = append(users[:i], users[i+1:]...)
w.WriteHeader(http.StatusNoContent)
return
}
}
http.Error(w, "User not found", http.StatusNotFound)
}
功能:
- 根据用户 ID 删除用户。
- 如果用户存在,删除用户并返回
204 No Content
;否则返回404 Not Found
。
解读:
- 获取用户 ID:
- 同
getUser
,从查询参数中获取用户 ID,并转换为整数。
- 同
- 查找并删除用户:
- 遍历
users
列表,查找与id
匹配的用户。 - 如果找到用户,使用切片操作删除用户:
users = append(users[:i], users[i+1:]...)
:- 创建一个新的切片,跳过索引为
i
的用户。
- 创建一个新的切片,跳过索引为
- 遍历
- 返回响应:
- 如果删除成功,返回状态码
204 No Content
,表示删除完成且无返回内容。 - 如果用户未找到,返回
404 Not Found
。
- 如果删除成功,返回状态码
2.3 注册路由并启动服务器
func main() {
http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
if r.URL.Query().Get("id") != "" {
getUser(w, r)
} else {
getUsers(w, r)
}
case http.MethodPost:
createUser(w, r)
case http.MethodPut:
updateUser(w, r)
case http.MethodDelete:
deleteUser(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})
fmt.Println("Server is running on http://localhost:8080")
http.ListenAndServe(":8080", nil)
}
功能:
- 注册路由
/users
,根据 HTTP 方法调用不同的处理函数。 - 启动 HTTP 服务器,监听
8080
端口。
解读:
-
路由注册:
-
使用
http.HandleFunc
注册路由/users
。 -
根据
r.Method
(HTTP 方法)分发到不同的处理函数:
GET
:调用getUser
或getUsers
。POST
:调用createUser
。PUT
:调用updateUser
。DELETE
:调用deleteUser
。
-
如果方法不被支持,返回
405 Method Not Allowed
。
-
-
启动服务器:
http.ListenAndServe(":8080", nil)
:- 启动 HTTP 服务器,监听
8080
端口。 - 第二个参数为
nil
,表示使用默认的多路复用器。
- 启动 HTTP 服务器,监听
3. 测试 RESTful API
3.1 使用 curl
测试(也可以使用 Postman , 这里只做简单测试,故使用 curl)
-
获取所有用户:
curl -X GET http://localhost:8080/users
-
获取单个用户:
curl -X GET "http://localhost:8080/users?id=1"
-
创建用户:
curl -X POST -H "Content-Type: application/json" -d '{"name":"Charlie", "email":"charlie@example.com"}' http://localhost:8080/users
-
更新用户:
curl -X PUT -H "Content-Type: application/json" -d '{"name":"Updated Name", "email":"updated@example.com"}' "http://localhost:8080/users?id=1"
-
删除用户:
curl -X DELETE "http://localhost:8080/users?id=1"
三、 总结
在上述 RESTful API 实现中,我们使用了 Go 的标准库 net/http
和 encoding/json
,还有一些辅助包如 strconv
。
这些函数和方法共同构成了一个完整的 RESTful API 服务的基础工具集。通过这些工具,你可以高效地处理 HTTP 请求和响应,以及 JSON 数据的序列化和反序列化。
以下是每个包中用到的函数和方法的列表(想要深入了解请移步官网):
包名 | 函数/方法 |
---|---|
net/http | http.HandleFunc ,http.ListenAndServe ,http.Error ,w.Header().Set ,w.WriteHeader ,r.Method ,r.URL.Query().Get |
encoding/json | json.NewEncoder ,json.NewDecoder ,Encode ,Decode |
strconv | strconv.Atoi |
fmt | fmt.Println |
io | io.Reader ,io.Writer |
1. net/http
包
net/http
是 Go 标准库中用于构建 HTTP 服务的核心包。以下是我们使用的函数和方法:
函数
-
http.HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
- 功能:注册路由和对应的处理函数。
- 示例:
http.HandleFunc("/users", handler)
-
http.ListenAndServe(addr string, handler http.Handler) error
- 功能:启动 HTTP 服务器,监听指定的地址和端口。
- 示例:
http.ListenAndServe(":8080", nil)
-
http.Error(w http.ResponseWriter, error string, code int)
- 功能:快速返回带有状态码的错误响应。
- 示例:
http.Error(w, "Invalid user ID", http.StatusBadRequest)
接口和方法
-
http.ResponseWriter
- 用于构建 HTTP 响应。
- 方法:
Header() http.Header
- 功能:设置响应头。
- 示例:
w.Header().Set("Content-Type", "application/json")
Write([]byte)
- 功能:将字节数据写入响应体。
WriteHeader(statusCode int)
- 功能:设置 HTTP 状态码。
- 示例:
w.WriteHeader(http.StatusCreated)
-
http.Request
- 表示 HTTP 请求。
- 属性和方法:
Method string
- 功能:获取请求的 HTTP 方法(如
GET
、POST
)。 - 示例:
r.Method
- 功能:获取请求的 HTTP 方法(如
URL.Query() url.Values
- 功能:获取 URL 中的查询参数。
- 示例:
r.URL.Query().Get("id")
Body io.ReadCloser
- 功能:读取请求体。
- 示例:
json.NewDecoder(r.Body).Decode(&newUser)
2. encoding/json
包
encoding/json
是 Go 标准库中用于处理 JSON 数据的包。以下是我们使用的函数和方法:
函数
-
json.NewEncoder(w io.Writer)
- 功能:创建一个 JSON 编码器,将数据编码为 JSON 格式并写入
io.Writer
。 - 示例:
json.NewEncoder(w).Encode(users)
- 功能:创建一个 JSON 编码器,将数据编码为 JSON 格式并写入
-
json.NewDecoder(r io.Reader)
- 功能:创建一个 JSON 解码器,从
io.Reader
中读取并解析 JSON 数据。 - 示例:
json.NewDecoder(r.Body).Decode(&newUser)
- 功能:创建一个 JSON 解码器,从
方法
-
Encode(v interface{}) error
- 功能:将 Go 数据结构编码为 JSON 格式并写入
io.Writer
。 - 示例:
json.NewEncoder(w).Encode(users)
- 功能:将 Go 数据结构编码为 JSON 格式并写入
-
Decode(v interface{}) error
- 功能:将 JSON 数据解码到指定的 Go 数据结构。
- 示例:
json.NewDecoder(r.Body).Decode(&newUser)
3. strconv
包
strconv
是 Go 标准库中用于字符串和基本数据类型之间转换的包。以下是我们使用的函数:
函数
strconv.Atoi(s string) (int, error)
- 功能:将字符串转换为整数。
- 示例:
id, err := strconv.Atoi(r.URL.Query().Get("id"))
4. fmt
包
fmt
是 Go 标准库中用于格式化 I/O 的包。以下是我们使用的函数:
函数
fmt.Println(a ...interface{}) (n int, err error)
- 功能:打印一行内容到标准输出。
- 示例:
fmt.Println("Server is running on http://localhost:8080")
5. io
包
io
是 Go 标准库中处理流式数据的包。以下是我们间接使用的接口:
接口
-
io.Reader
- 功能:表示可以读取数据的对象。
- 示例:
r.Body
实现了io.Reader
接口,用于读取 HTTP 请求体。
-
io.Writer
- 功能:表示可以写入数据的对象。
- 示例:
w
(http.ResponseWriter
)实现了io.Writer
接口,用于写入 HTTP 响应。