gpt写的go语言入门——通过博客系统 part1

第一部分:构建基础命令行博客系统

代码仓库

章节 1:Go语言快速入门

1.1 Go语言简介

Go语言,也称作Golang,是由Google开发的一种静态强类型、编译型语言,具有垃圾回收功能。它在2009年公开发布,由Robert Griesemer、Rob Pike和Ken Thompson设计。Go语言的设计目标是为了解决大型软件系统的构建问题,特别是在Google内部,这些系统需要高效的编译、高效的执行以及高效的代码维护。

Go的主要特点包括:

  • 简洁、快速和安全
  • 支持并发,通过goroutines和channels轻松实现
  • 丰富的标准库,尤其在网络服务和并发处理方面
  • 简单的依赖管理
  • 跨平台,支持多种操作系统

Go语言适用于各种类型的项目,从小型个人项目到大型分布式系统。在本书中,我们将使用Go语言构建一个博客系统,这将帮助我们理解Go语言在实际应用中的强大功能。

1.2 安装和设置Go开发环境

让我们开始安装Go语言。请访问Go语言官方网站(https://golang.org/dl/)下载适合您操作系统的安装包。下载完成后,请按照官方指南完成安装。

安装Go后,您可以打开终端或命令提示符并运行以下命令来验证安装:

go version

这应该会显示安装的Go版本。例如:

go version go1.15.6 linux/amd64

接下来,设置您的工作空间。Go语言的工作空间是存放Go代码的地方。它有一个特定的目录结构:

  • src 目录包含Go的源文件,
  • pkg 目录包含包对象,
  • bin 目录包含可执行文件。

您可以通过设置环境变量GOPATH来指定您的工作空间目录。例如,在Unix系统上:

export GOPATH=$HOME/go

在Windows系统上:

set GOPATH=c:\go

1.3 Hello World程序

编写Hello World程序是学习新编程语言的传统。在Go中,这个程序看起来是这样的:

package mainimport "fmt"func main() {fmt.Println("Hello, World!")
}

将上面的代码保存为hello.go。然后在命令行中运行以下命令来编译并运行程序:

go run hello.go

如果一切顺利,您将看到终端打印出“Hello, World!”。

1.4 Go程序基本结构

Go程序由包(packages)组成。每个Go文件都属于一个包,且文件的第一行声明了它所属的包。main包是特殊的,它告诉Go编译器这个程序是可执行的,而不是一个库。

main包中,main函数也是特殊的——它是程序执行的入口点。在上面的Hello World程序中,我们导入了fmt
包,这是一个包含I/O函数的标准库包。我们使用fmt.Println来输出字符串到标准输出。

1.5 练习:编写第一个Go程序

现在是时候动手写代码了。作为练习,请尝试以下操作:

  1. 编写一个Go程序,打印出你最喜欢的引语。
  2. 修改程序,接收用户输入,并打印出一个个性化的问候语。

这是一个接收用户输入的示例程序:

package mainimport ("bufio""fmt""os"
)func main() {reader := bufio.NewReader(os.Stdin)fmt.Print("Enter your name: ")name, _ := reader.ReadString('\n')fmt.Printf("Hello, %s", name)
}

这个程序使用bufio包创建一个新的缓冲读取器,用于读取来自标准输入的数据。它提示用户输入名字,然后读取输入并存储在变量name
中,最后使用fmt.Printf打印个性化的问候语。


通过完成第一章,读者应该能够理解Go语言的基础,安装并设置好Go开发环境,并编写、运行简单的Go程序。下一章将深入探讨Go语言的核心概念和功能。

章节 2:Go语言基础

2.1 变量和数据类型

在Go语言中,变量是存储程序执行过程中数据的容器。Go是静态类型语言,这意味着变量是有明确定义的类型,类型在编译时就已确定,并且类型在整个程序运行期间不会改变。

声明变量
var message string
message = "Hello, Go!"// 或者一步到位
var greeting = "Hello, Go!"// 短变量声明,最常用
name := "World"
基本数据类型

Go语言中的基本数据类型包括:

  • 整型(int、uint、int8、int16、int32、int64等)
  • 浮点型(float32、float64)
  • 布尔型(bool)
  • 字符串(string)

2.2 控制结构

控制结构在Go语言中用于控制程序的执行流程。Go提供了多种控制结构,例如if语句、for循环和switch语句。

If语句
if number := 10; number%2 == 0 {
fmt.Println(number, "is even")
} else {
fmt.Println(number, "is odd")
}
For循环
// 标准的for循环
for i := 0; i < 5; i++ {
fmt.Println("Value of i is:", i)
}// 类似while的for循环
sum := 1
for sum < 1000 {
sum += sum
}
fmt.Println("Sum is:", sum)
Switch语句
dayOfWeek := 3
switch dayOfWeek {
case 1:
fmt.Println("Monday")
case 2:
fmt.Println("Tuesday")
case 3:
fmt.Println("Wednesday")
// ...
default:
fmt.Println("Invalid day")
}

2.3 函数定义和返回值

函数是执行特定任务的代码块。在Go中,您可以定义带有参数和返回值的函数。

func add(x int, y int) int {
return x + y
}// 当连续两个或多个参数的类型相同时,我们可以仅声明最后一个参数的类型
func subtract(x, y int) int {
return x - y
}result1 := add(6, 7)
result2 := subtract(10, 3)
fmt.Println("Addition result:", result1)
fmt.Println("Subtraction result:", result2)

2.4 错误处理基础

在Go中,错误处理是通过返回一个错误类型的值来完成的。如果一个函数可能产生错误,它通常是函数返回值列表中的最后一个。

func divide(x, y float64) (float64, error) {
if y == 0.0 {
return 0.0, errors.New("cannot divide by zero")
}
return x / y, nil
}result, err := divide(10.0, 0.0)
if err != nil {
log.Fatal(err)
}
fmt.Println("Result:", result)

2.5 练习:创建基本的输入输出函数

作为练习,尝试以下操作:

  1. 创建一个函数,接受两个字符串参数并返回它们的拼接结果。
  2. 编写一个函数,接受一个整数数组并返回它们的和。
  3. 实现一个函数,接受一个整数并返回它的阶乘。
字符串拼接函数
func concatenate(str1, str2 string) string {
return str1 + str2
}fmt.Println(concatenate("Hello, ", "Go!"))
整数数组求和函数
func sum(numbers []int) int {
total := 0
for _, number := range numbers {
total += number
}
return total
}fmt.Println(sum([]int{1, 2, 3, 4, 5}))
阶乘函数
func factorial(n int) int {
if n == 0 {
return 1
}
return n * factorial(n-1)
}fmt.Println(factorial(5))

通过完成第二章,读者应该能够理解Go语言的变量、数据类型、控制结构、函数定义以及错误处理的基础知识。这些是构建更复杂程序的基石。在下一章中,我们将探索Go的标准库,它提供了大量方便的工具和函数,可以帮助我们更快地开发程序。

章节 3:探索Go的标准库

Go的标准库是一组广泛的包,提供了从输入/输出处理到网络编程的功能。在这一章节中,我们将探索一些对于构建命令行博客系统非常有用的标准库包。

3.1 使用fmt

fmt包实现了格式化的I/O函数,类似于C语言的printf和scanf。我们已经在前面的Hello World程序中使用了fmt.Println来输出文本。

格式化输出
name := "Go Programmer"
age := 30fmt.Printf("My name is %s and I am %d years old.\n", name, age)
从标准输入读取
var input string
fmt.Print("Enter your input: ")
fmt.Scanln(&input)
fmt.Println("You entered:", input)

3.2 文件操作:io/ioutilos

文件操作是大多数程序中的常见任务。在Go中,io/ioutilos包提供了这方面的功能。

读取文件
content, err := ioutil.ReadFile("blogpost.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println("File content:", string(content))
写入文件
message := []byte("Hello, Go Blog!")
err := ioutil.WriteFile("blogpost.txt", message, 0644)
if err != nil {
log.Fatal(err)
}
检查文件是否存在
if _, err := os.Stat("blogpost.txt"); os.IsNotExist(err) {
fmt.Println("The file does not exist.")
} else {
fmt.Println("The file exists.")
}

3.3 日期和时间:time

处理日期和时间是编程中的一个常见需求。Go的time包提供了这方面的功能。

获取当前时间
now := time.Now()
fmt.Println("Current time:", now)
格式化日期和时间
fmt.Println("Formatted time:", now.Format("2006-01-02 15:04:05"))
计算时间差
past := time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC)
duration := now.Sub(past)
fmt.Println("Duration since past:", duration)

3.4 处理JSON:encoding/json

JSON是一种轻量级的数据交换格式,经常被用于网络通信。在Go中,encoding/json包提供了JSON数据的编码和解码功能。

JSON编码
type BlogPost struct {
Title   string
Content string
Author  string
Views   int
}post := BlogPost{
Title:   "Exploring Go's Standard Library",
Content: "Go's standard library is vast...",
Author:  "Jane Doe",
Views:   3490,
}jsonBytes, err := json.Marshal(post)
if err != nil {
log.Fatal(err)
}
fmt.Println("JSON encoding:", string(jsonBytes))
JSON解码
var post BlogPost
err := json.Unmarshal(jsonBytes, &post)
if err != nil {
log.Fatal(err)
}
fmt.Println("Blog post:", post)

3.5 练习:创建并操作自己的博客文章

作为练习,尝试以下操作:

  1. 创建一个结构体表示博客文章,包括标题、内容、作者和发布日期。
  2. 编写一个函数将博客文章结构体编码为JSON。
  3. 编写另一个函数将JSON解码回博客文章结构体。
  4. 将博客文章保存到文件,并从文件中读取。
博客文章结构体
type BlogPost struct {
Title   string    `json:"title"`
Content string    `json:"content"`
Author  string    `json:"author"`
Date    time.Time `json:"date"`
}
编码为JSON
func encodeToJSON(post BlogPost) ([]byte, error) {
return json.Marshal(post)
}
解码JSON
func decodeFromJSON(jsonBytes []byte) (BlogPost, error) {
var post BlogPost
err := json.Unmarshal(jsonBytes, &post)
return post, err
}
保存和读取博客文章
func savePostToFile(filename string, post BlogPost) error {
jsonBytes, err := encodeToJSON(post)
if err != nil {
return err
}
return ioutil.WriteFile(filename, jsonBytes, 0644)
}func loadPostFromFile(filename string) (BlogPost, error) {
jsonBytes, err := ioutil.ReadFile(filename)
if err != nil {
return BlogPost{}, err
}
return decodeFromJSON(jsonBytes)
}// 使用上述函数
post := BlogPost{
Title:   "My First Blog Post",
Content: "Content of my first blog post",
Author:  "John Doe",
Date:    time.Now(),
}filename := "post.json"// 保存博客文章到文件
err := savePostToFile(filename, post)
if err != nil {
log.Fatal(err)
}// 从文件中读取博客文章
loadedPost, err := loadPostFromFile(filename)
if err != nil {
log.Fatal(err)
}
fmt.Println("Loaded blog post:", loadedPost)

通过完成第三章,读者应该能够理解如何使用Go的标准库来进行文件操作、日期和时间处理、以及JSON的编码和解码。这些技能对于构建命令行博客系统至关重要。在下一章中,我们将学习如何使用Go的网络编程功能来让我们的博客系统可以通过网络进行数据交换。

章节 4:Go的网络编程

在这一章节中,我们将介绍Go语言在网络编程方面的能力。Go的net包提供了丰富的网络编程功能,包括TCP/UDP协议、HTTP客户端和服务端的实现等。

4.1 创建TCP服务器

TCP(传输控制协议)是一种可靠的、面向连接的协议。下面的例子演示了如何创建一个简单的TCP服务器,它监听本地端口,并回显接收到的消息。

TCP Echo服务器
package mainimport ("bufio""fmt""net""os"
)func main() {// 监听本地的12345端口listener, err := net.Listen("tcp", "localhost:12345")if err != nil {fmt.Println("Error listening:", err.Error())os.Exit(1)}defer listener.Close()fmt.Println("Listening on localhost:12345")for {// 等待连接conn, err := listener.Accept()if err != nil {fmt.Println("Error accepting:", err.Error())os.Exit(1)}fmt.Println("Received connection")// 处理连接go handleRequest(conn)}
}// 处理请求
func handleRequest(conn net.Conn) {defer conn.Close()// 创建一个新的reader,从TCP连接读取数据reader := bufio.NewReader(conn)for {// 读取客户端发送的数据message, err := reader.ReadString('\n')if err != nil {fmt.Println("Error reading:", err.Error())break}fmt.Print("Message received: ", string(message))// 回显消息conn.Write([]byte(message))}
}

4.2 创建HTTP服务器

Go的net/http包让创建HTTP服务器变得非常简单。下面的代码展示了如何创建一个基本的HTTP服务器,它可以响应GET请求。

HTTP服务器
package mainimport ("fmt""net/http"
)func main() {// 设置路由和处理函数http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Welcome to the Go Blog!")})// 监听并在8080端口启动服务器fmt.Println("Server is listening on port 8080...")if err := http.ListenAndServe(":8080", nil); err != nil {fmt.Println("Error starting server:", err)return}
}

4.3 创建HTTP客户端

Go的net/http包不仅可以创建服务器,还可以作为客户端发送请求。以下示例展示了如何发送GET请求并读取响应。

HTTP客户端
package mainimport ("fmt""io/ioutil""net/http"
)func main() {// 向服务器发送GET请求response, err := http.Get("http://example.com")if err != nil {fmt.Println("Error making GET request:", err)return}defer response.Body.Close()// 读取响应内容body, err := ioutil.ReadAll(response.Body)if err != nil {fmt.Println("Error reading response:", err)return}fmt.Println("Response from server:", string(body))
}

4.4 练习:构建一个简单的博客服务器

作为练习,尝试以下操作:

  1. 创建一个HTTP服务器,它可以处理不同的路由和HTTP方法。
  2. 服务器应该能够响应至少两种内容:静态页面和JSON响应。
  3. 实现简单的文章存储功能,可以通过HTTP请求添加和检索文章。
HTTP服务器处理不同路由
package mainimport ("encoding/json""fmt""net/http""sync"
)// BlogPost 定义了博客文章的结构
type BlogPost struct {Title   string `json:"title"`Content string `json:"content"`Author  string `json:"author"`
}// blogPosts 存储了所有博客文章
var blogPosts = make([]BlogPost, 0)
var mutex sync.Mutexfunc main() {// 静态页面路由http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Welcome to the Go Blog!")})// 获取所有文章http.HandleFunc("/posts", func(w http.ResponseWriter, r *http.Request) {switch r.Method {case http.MethodGet:mutex.Lock()postsJSON, _ := json.Marshal(blogPosts)mutex.Unlock()w.Header().Set("Content-Type", "application/json")w.Write(postsJSON)default:w.WriteHeader(http.StatusMethodNotAllowed)}})// 添加新文章http.HandleFunc("/posts/new", func(w http.ResponseWriter, r *http.Request) {switch r.Method {case http.MethodPost:var newPost BlogPosterr := json.NewDecoder(r.Body).Decode(&newPost)if err != nil {http.Error(w, err.Error(), http.StatusBadRequest)return}mutex.Lock()blogPosts = append(blogPosts, newPost)mutex.Unlock()w.WriteHeader(http.StatusCreated)default:w.WriteHeader(http.StatusMethodNotAllowed)}})// 监听并在8080端口启动服务器fmt.Println("Server is listening on port 8080...")if err := http.ListenAndServe(":8080", nil); err != nil {fmt.Println("Error starting server:", err)}
}

在这个例子中,我们创建了一个简单的HTTP服务器,它可以处理静态页面和JSON响应。我们还实现了一个简单的文章存储功能,可以通过HTTP
POST请求添加文章,并通过HTTP GET请求检索所有文章。这个练习为创建一个更复杂和功能丰富的博客系统奠定了基础。


通过完成第四章,读者应该能够理解Go语言在网络编程方面的基本概念,包括创建TCP和HTTP服务器、发送HTTP请求等。这些知识对于构建网络应用程序和服务是非常重要的。在下一章中,我们将学习如何将这些概念应用于我们的命令行博客系统,使其能够处理网络上的博客文章。

章节 5:整合网络功能到命令行博客系统

在本章中,我们将把网络编程的概念整合到我们的命令行博客系统中。我们的目标是使博客系统能够通过网络接收文章,并能够通过HTTP请求提供文章内容。

5.1 设计RESTful API

我们将设计一个简单的RESTful API,以便于通过HTTP方法管理博客文章,包括获取、创建和删除文章。

  • GET /posts - 获取所有文章
  • POST /posts - 创建新文章
  • GET /posts/{id} - 获取特定ID的文章
  • DELETE /posts/{id} - 删除特定ID的文章

5.2 实现API服务器

我们将使用Go的net/http包来实现上述API。

服务器代码
package mainimport ("encoding/json""fmt""io/ioutil""net/http""strconv""strings""sync"
)// BlogPost 定义了博客文章的结构
type BlogPost struct {ID      int    `json:"id"`Title   string `json:"title"`Content string `json:"content"`Author  string `json:"author"`
}// blogPosts 存储了所有博客文章
var blogPosts []BlogPost
var mutex sync.Mutex
var idCounter intfunc main() {http.HandleFunc("/posts", postsHandler)http.HandleFunc("/posts/", postHandler)// 监听并在8080端口启动服务器fmt.Println("Server is listening on port 8080...")if err := http.ListenAndServe(":8080", nil); err != nil {fmt.Println("Error starting server:", err)}
}func postsHandler(w http.ResponseWriter, r *http.Request) {switch r.Method {case http.MethodGet:mutex.Lock()postsJSON, _ := json.Marshal(blogPosts)mutex.Unlock()w.Header().Set("Content-Type", "application/json")w.Write(postsJSON)case http.MethodPost:var newPost BlogPostbodyBytes, err := ioutil.ReadAll(r.Body)if err != nil {http.Error(w, "Invalid request", http.StatusBadRequest)return}err = json.Unmarshal(bodyBytes, &newPost)if err != nil {http.Error(w, "Invalid JSON", http.StatusBadRequest)return}mutex.Lock()idCounter++newPost.ID = idCounterblogPosts = append(blogPosts, newPost)mutex.Unlock()w.WriteHeader(http.StatusCreated)default:w.WriteHeader(http.StatusMethodNotAllowed)}
}func postHandler(w http.ResponseWriter, r *http.Request) {idStr := strings.TrimPrefix(r.URL.Path, "/posts/")id, err := strconv.Atoi(idStr)if err != nil {http.Error(w, "Invalid post ID", http.StatusBadRequest)return}switch r.Method {case http.MethodGet:found := falsefor _, post := range blogPosts {if post.ID == id {postJSON, _ := json.Marshal(post)w.Header().Set("Content-Type", "application/json")w.Write(postJSON)found = truebreak}}if !found {http.NotFound(w, r)}case http.MethodDelete:found := falsefor i, post := range blogPosts {if post.ID == id {mutex.Lock()blogPosts = append(blogPosts[:i], blogPosts[i+1:]...)mutex.Unlock()w.WriteHeader(http.StatusOK)found = truebreak}}if !found {http.NotFound(w, r)}default:w.WriteHeader(http.StatusMethodNotAllowed)}
}

5.3 命令行客户端功能

我们将扩展命令行客户端,以便它可以与API服务器交互,获取和发布文章。

客户端代码
package mainimport ("bytes""encoding/json""fmt""io/ioutil""net/http""os"
)func main() {if len(os.Args) < 2 {fmt.Println("Usage: go run blogclient.go <action> [<params>]")return}switch os.Args[1] {case "list":listPosts()case "post":if len(os.Args) != 5 {fmt.Println("Usage: go run blogclient.go post <title> <content> <author>")return}createPost(os.Args[2], os.Args[3], os.Args[4])default:fmt.Println("Unknown action")}
}func listPosts() {response, err := http.Get("http://localhost:8080/posts")if err != nil {fmt.Println("Error fetching posts:", err)return}defer response.Body.Close()body, err := ioutil.ReadAll(response.Body)if err != nil {fmt.Println("Error reading response:", err)return}var posts []BlogPosterr = json.Unmarshal(body, &posts)if err != nil {fmt.Println("Error decoding posts:", err)return}for _, post := range posts {fmt.Printf("ID: %d\nTitle: %s\nContent: %s\nAuthor: %s\n\n", post.ID, post.Title, post.Content, post.Author)}
}func createPost(title, content, author string) {post := BlogPost{Title:   title,Content: content,Author:  author,}postJSON, err := json.Marshal(post)if err != nil {fmt.Println("Error encoding post:", err)return}response, err := http.Post("http://localhost:8080/posts", "application/json", bytes.NewBuffer(postJSON))if err != nil {fmt.Println("Error creating post:", err)return}defer response.Body.Close()if response.StatusCode == http.StatusCreated {fmt.Println("Post created successfully")} else {fmt.Printf("Failed to create post, status code: %d\n", response.StatusCode)}
}

5.4 练习:扩展博客功能

作为练习,尝试以下操作:

  1. 添加命令行客户端的删除功能,以便它可以通过HTTP请求删除文章。
  2. 实现文章更新功能,允许通过PUT请求更新现有文章的内容。
  3. 添加用户认证,确保只有认证用户才能创建、更新和删除文章。
添加删除功能
// 添加到 main 函数的 switch-case 中
case "delete":
if len(os.Args) != 3 {
fmt.Println("Usage: go run blogclient.go delete <id>")
return
}
id := os.Args[2]
deletePost(id)
// ...func deletePost(id string) {
client := &http.Client{}
req, err := http.NewRequest(http.MethodDelete, "http://localhost:8080/posts/"+id, nil)
if err != nil {
fmt.Println("Error creating request:", err)
return
}resp, err := client.Do(req)
if err != nil {
fmt.Println("Error sending request:", err)
return
}
defer resp.Body.Close()if resp.StatusCode == http.StatusOK {
fmt.Println("Post deleted successfully")
} else {
fmt.Printf("Failed to delete post, status code: %d\n", resp.StatusCode)
}
}
实现更新功能

这需要在API服务器和客户端中添加对应的逻辑来处理PUT请求。

添加用户认证

用户认证通常涉及到更复杂的逻辑和安全性考虑,例如使用JWT(JSON Web Tokens)或OAuth。这将超出本章的范围,但是作为一个练习,你可以探索如何在Go中实现这些认证机制。


通过完成第五章,读者应该能够理解如何将网络编程整合到命令行博客系统中,使得系统能够通过网络接收和发送数据。这些技能是构建现代网络应用程序的基础。在后续的章节中,我们可以进一步探讨如何扩展系统的功能,例如添加数据库支持、用户认证和更多的HTTP路由处理。

章节 6:为博客系统添加持久化存储

在本章中,我们将为我们的命令行博客系统添加持久化存储功能。这将允许我们的系统在服务重启后保留博客文章数据。我们将使用Go的database/sql
包来实现与SQLite数据库的交互。

6.1 设计数据库模型

我们将创建一个简单的数据库模型,用于存储博客文章。

使用go mod创建项目
go mod init myblog
go mod tidy

项目结构

- project- db- db.go- model- models.go- api-server.go- cmd-client.go
数据库模型
CREATE TABLE IF NOT EXISTS posts
(idINTEGERPRIMARYKEYAUTOINCREMENT,titleTEXTNOTNULL,contentTEXTNOTNULL,authorTEXTNOTNULL
);

6.2 实现数据库操作

我们将实现一个简单的数据库操作层,用于执行CRUD(创建、读取、更新和删除)操作。

数据库操作代码
package dbimport ("database/sql"_ "github.com/mattn/go-sqlite3""log""myblog/model"
)var db *sql.DBfunc InitDB(filePath string) {var err errordb, err = sql.Open("sqlite3", filePath)if err != nil {log.Fatal(err)}createTableSQL := `CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY AUTOINCREMENT,title TEXT NOT NULL,content TEXT NOT NULL,author TEXT NOT NULL);`_, err = db.Exec(createTableSQL)if err != nil {log.Fatal(err)}
}func GetPosts() ([]*model.BlogPost, error) {rows, err := db.Query("SELECT id, title, content, author FROM posts")if err != nil {return nil, err}defer rows.Close()var posts []*model.BlogPostfor rows.Next() {var post model.BlogPostif err := rows.Scan(&post.ID, &post.Title, &post.Content, &post.Author); err != nil {return nil, err}posts = append(posts, &post)}return posts, nil
}func CreatePost(post model.BlogPost) (int64, error) {result, err := db.Exec("INSERT INTO posts (title, content, author) VALUES (?, ?, ?)", post.Title, post.Content, post.Author)if err != nil {return 0, err}return result.LastInsertId()
}func GetPostByID(id int) (*model.BlogPost, error) {row := db.QueryRow("SELECT id, title, content, author FROM posts WHERE id = ?", id)var post model.BlogPostif err := row.Scan(&post.ID, &post.Title, &post.Content, &post.Author); err != nil {if err == sql.ErrNoRows {return nil, nil}return nil, err}return &post, nil
}func DeletePostByID(id int) error {_, err := db.Exec("DELETE FROM posts WHERE id = ?", id)return err
}

6.3 集成数据库操作到API服务器

我们需要修改API服务器的代码,以使用数据库操作层来处理数据。

修改后的API服务器代码
// ... 保留之前的代码 ...func main() {
initDB("blog.db")http.HandleFunc("/posts", postsHandler)
http.HandleFunc("/posts/", postHandler)// 监听并在8080端口启动服务器
fmt.Println("Server is listening on port 8080...")
if err := http.ListenAndServe(":8080", nil); err != nil {
fmt.Println("Error starting server:", err)
}
}func postsHandler(w http.ResponseWriter, r *http.Request) {
// ... 保留之前的代码 ...
case http.MethodPost:
var newPost BlogPost
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
err = json.Unmarshal(bodyBytes, &newPost)
if err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}id, err := createPost(newPost)
if err != nil {
http.Error(w, "Error saving post", http.StatusInternalServerError)
return
}newPost.ID = int(id)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(newPost)
w.WriteHeader(http.StatusCreated)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}// ... 修改其他处理函数以使用数据库操作 ...

6.4 练习:添加更新和认证功能

作为练习,尝试以下操作:

  1. 实现更新文章的功能,允许通过PUT请求更新现有文章的内容。
  2. 添加基本的用户认证,确保只有认证用户才能创建、更新和删除文章。
实现更新文章功能
// 添加到数据库操作代码中
func UpdatePostByID(id int, post model.BlogPost) error {
_, err := db.Exec("UPDATE posts SET title = ?, content = ?, author = ? WHERE id = ?", post.Title, post.Content, post.Author, id)
return err
}// 添加到API服务器中的 postHandler
case http.MethodPut:
var updatedPost BlogPost
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
err = json.Unmarshal(bodyBytes, &updatedPost)
if err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}err = updatePostByID(id, updatedPost)
if err != nil {
http.Error(w, "Error updating post", http.StatusInternalServerError)
return
}w.WriteHeader(http.StatusOK)
添加用户认证功能

用户认证通常涉及到更复杂的逻辑和安全性考虑,例如使用JWT(JSON Web Tokens)或OAuth。这将超出本章的范围,但是作为一个练习,你可以探索如何在Go中实现这些认证机制。


通过完成第六章,读者应该能够理解如何为Go语言编写的博客系统添加持久化存储功能。我们介绍了如何使用SQLite数据库来存储数据,并展示了如何将数据库操作集成到我们的API服务器中。这些知识是构建能够长期存储数据的网络应用程序的基础。在后续的章节中,我们可以进一步探讨如何扩展系统的功能,例如添加更复杂的数据库操作、用户认证和安全性措施。

章节 7:增加用户认证和授权

在本章中,我们将为我们的命令行博客系统添加用户认证和授权功能。这将确保只有经过验证的用户才能创建、更新或删除文章。我们将使用JSON
Web Tokens(JWT)来实现这一功能。

7.1 设计用户模型

首先,我们需要设计一个用户模型来存储用户信息。

用户模型
CREATE TABLE IF NOT EXISTS users
(idINTEGERPRIMARYKEYAUTOINCREMENT,usernameTEXTNOTNULLUNIQUE,password_hashTEXTNOTNULL
);

7.2 实现用户注册和登录

我们将允许用户注册和登录,以便我们可以发行JWT给认证用户。

用户注册和登录代码
package mainimport ("database/sql""fmt""log""golang.org/x/crypto/bcrypt""github.com/dgrijalva/jwt-go"
)var jwtKey = []byte("my_secret_key")type Claims struct {Username string `json:"username"`jwt.StandardClaims
}// Register a new user
func registerUser(username, password string) error {hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 8)if err != nil {return err}_, err = db.Exec("INSERT INTO users (username, password_hash) VALUES (?, ?)", username, hashedPassword)return err
}// Authenticate a user and return a JWT
func authenticateUser(username, password string) (string, error) {// Verify the username and passwordvar passwordHash stringrow := db.QueryRow("SELECT password_hash FROM users WHERE username = ?", username)err := row.Scan(&passwordHash)if err == sql.ErrNoRows {return "", fmt.Errorf("user not found")} else if err != nil {return "", err}err = bcrypt.CompareHashAndPassword([]byte(passwordHash), []byte(password))if err != nil {return "", fmt.Errorf("invalid password")}// Create a new token object, specifying signing method and the claimstoken := jwt.NewWithClaims(jwt.SigningMethodHS256, &Claims{Username: username,StandardClaims: jwt.StandardClaims{ExpiresAt: 15000, // Token expires in 15 seconds for demonstration purposes},})// Sign and get the complete encoded token as a string using the secrettokenString, err := token.SignedString(jwtKey)if err != nil {return "", err}return tokenString, nil
}

7.3 集成认证到API服务器

现在我们需要修改API服务器的代码,以验证JWT并根据用户权限处理请求。

修改后的API服务器代码
// ... 保留之前的代码 ...func main() {
// ... 保留之前的代码 ...http.HandleFunc("/register", registerHandler)
http.HandleFunc("/login", loginHandler)// ... 保留之前的代码 ...
}func registerHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}var credentials struct {
Username string `json:"username"`
Password string `json:"password"`
}err := json.NewDecoder(r.Body).Decode(&credentials)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}err = registerUser(credentials.Username, credentials.Password)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}w.WriteHeader(http.StatusCreated)
}func loginHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}var credentials struct {
Username string `json:"username"`
Password string `json:"password"`
}err := json.NewDecoder(r.Body).Decode(&credentials)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}tokenString, err := authenticateUser(credentials.Username, credentials.Password)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}w.Header().Set("Token", tokenString)
w.WriteHeader(http.StatusOK)
}// Middleware to protect private routes
func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
tokenString := r.Header.Get("Authorization")
claims := &Claims{}token, err := jwt.ParseWithClaims(tokenString, claims, func (token *jwt.Token) (interface{}, error) {
return jwtKey, nil
})if err != nil {
if err == jwt.ErrSignatureInvalid {
w.WriteHeader(http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusBadRequest)
return
}if !token.Valid {
w.WriteHeader(http.StatusUnauthorized)
return
}next.ServeHTTP(w, r)
})
}// ... 使用 authMiddleware 包装需要保护的路由 ...

7.4 练习:扩展认证功能

作为练习,尝试以下操作:

  1. 实现用户注销功能,使得JWT在用户注销时失效。
  2. 添加密码重置功能,允许用户通过某种机制重置他们的密码。
  3. 实现更复杂的权限系统,允许不同的用户有不同的操作权限。
实现用户注销功能

用户注销功能通常涉及到使当前的JWT失效。这可以通过在服务器端维护一个失效的token列表来实现,或者通过设置JWT的exp
(过期时间)字段为当前时间来使其立即失效。

添加密码重置功能

密码重置功能通常涉及到发送一次性链接到用户注册的电子邮件地址,用户可以通过该链接来重置他们的密码。

实现更复杂的权限系统

更复杂的权限系统可能需要在用户模型中添加角色字段,并在认证时检查用户角色以确定他们是否有权执行特定操作。


通过完成第七章,读者应该能够理解如何在Go语言编写的博客系统中添加用户认证和授权功能。我们介绍了如何使用JWT来验证用户,并保护API路由以确保只有认证用户才能执行某些操作。这些知识是构建安全网络应用程序的基础。在后续的章节中,我们可以进一步探讨如何扩展系统的安全性,例如通过HTTPS提供服务、实现密码策略和添加更多的安全性措施。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/652212.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue3使用最新的属性defineModel实现父子组件数据响应式绑定

子父之间使用v-model双向绑定数据&#xff0c;子组件每次都要写emit和props觉得麻烦&#xff1f;以前&#xff0c;为了使组件支持与v-model双向绑定&#xff0c;它需要&#xff08;1&#xff09;声明prop&#xff0c;&#xff08;2&#xff09;在打算更新prop时发出相应的updat…

架构篇21:高性能负载均衡-算法

文章目录 轮询加权轮询负载最低优先性能最优类Hash 类源地址 HashID Hash小结负载均衡算法数量较多,而且可以根据一些业务特性进行定制开发,抛开细节上的差异,根据算法期望达到的目的,大体上可以分为下面几类。 任务平分类:负载均衡系统将收到的任务平均分配给服务器进行处…

动态规划(DP)---- 买卖股票的最大利润(1)

假设把某股票的价格按照时间先后顺序存储在数组中&#xff0c;请问买卖 一次 该股票可能获得的利润是多少&#xff1f; 例如一只股票在某些时间节点的价格为 [9,11,8,5,7,12,16,14]。 如果我们能在价格为 5 的时候买入并在价格为 16 时卖出&#xff0c;则能收获最大的利润 11。…

【Vue】1-2、Webpack 中的插件

一、Webpack 插件的作用 通过安装和配置第三方的插件&#xff0c;可以拓展 webpack 的能力&#xff0c;从而让 webpack 用起来更方便。 二、两个常用插件 1&#xff09;webpack-dev-server 类似于 node.js 使用的 nodemon 工具 每当修改了源代码&#xff0c;webpack 会自动…

C++ 类的初始化列表

C 中的类必须使用初始化列表的 4 种情况&#xff1a; 一&#xff0c;继承于一个基类&#xff0c;这个基类的构造函数有参数时。 #include <stdio.h> #include <stdlib.h>#pragma pack(4) class CBase { public:CBase(int value): bBaseValue(value){}~CBase(){}p…

处理 Maven jar 包下载失败问题

目录 1、配置国内 Maven 源 配置和检测 settings.xml 配置国内源 2、删除本地 maven 创库的 jar 重新下载 3、其他问题 一般情况下 maven jar 包下载不成功可能有两种情况&#xff1a; 1&#xff09;没有配置国内源 2&#xff09;jar 包需要重新下载 下面详细讲解如何解决这两…

【jetson笔记】解决vscode远程调试qt.qpa.xcb: could not connect to display报错

配置x11转发 jetson远程安装x11转发 安装Xming Xming下载 安装完成后打开安装目录C:\Program Files (x86)\Xming 用记事本打开X0.hosts文件&#xff0c;添加jetson IP地址 后续IP改变需要重新修改配置文件 localhost 192.168.107.57打开Xlaunch Win菜单搜索Xlaundch打开 一…

x-cmd pkg | go - Google 开发的开源编程语言

目录 简介首次用户技术特点竞品分析编译型语言解释型语言JavaWebAssebmly 进一步阅读 简介 Go 语言&#xff08;或 Golang&#xff09;是 Google 开发的开源编程语言&#xff0c;诞生于 2006 年。其设计目标是“兼具 Python 等动态语言的开发速度和 C/C 等编译型语言的性能与安…

机器学习---无偏估计

1. 如何理解无偏估计 无偏估计&#xff1a;就是我认为所有样本出现的概率⼀样。 假如有N种样本我们认为所有样本出现概率都是 1/N。然后根据这个来计算数学期望。此时的数学期望就是我们平常讲 的平均值。数学期望本质就 是平均值。 2. 无偏估计为何叫做“无偏”&#xff1…

20240128-读书带来的影响

我本身不算是一个特别喜欢读书的人&#xff0c;更多的时候其实是为了读书而读书。在坚持每天读了一小时书之后&#xff0c;我发现自身开始慢慢有些变化。是什么时候突然有了这种感悟呢&#xff0c;是最近每周5小时左右的微信读书以及纸质书籍的阅读&#xff0c;让我体会到了读书…

常见の算法5

位图 一个int类型32字节&#xff0c;可以表示0-31这32个数出没出现过&#xff0c;出现过1没出现0&#xff0c;再扩大一点搞个数组&#xff0c;就可以表示0-1023出没出现过&#xff0c;一个long类型可储存64位 如何把10位组成的数&#xff0c;第四位由1改成零 package class05…

嵌入式linux面试题目总结

Linux系统中常见的面试题目&#xff0c;分享&#xff0c;欢迎大家前来交流学习。 1、嵌入式系统中的CAN通信协议是什么&#xff1f; CAN&#xff08;Controller Area Network&#xff09;通信协议是一种广泛应用于嵌入式系统中的串行通信协议。它最初由德国汽车工业联合会开发…

mysql升级5.X到mysql8关键字问题

数据库系统升级&#xff0c;要注意很多东西&#xff0c;5.x版本升级到mysql8&#xff0c;是一次大的版本升级&#xff0c;因此要注意诸多事项. charset更新 关键字 一些在5.x版本没有问题的字段&#xff0c;在mysql8中会触发关键字. 例如&#xff1a; rank&#xff0c;在mys…

VS如何打包环境

以VS2005为例子,做好的软件需要发给客户现场升级,有时候总是因为系统,环境变量不同导致软件不能正常运行打开,这也是程序员非常头疼的问题,今天我们就一起看下打包环境变量. 这样我们的环境变量就打包到setup中了,目标机台安装即可!!!

新概念英语第二册(43)上

【New words and expressions】生词和短语&#xff08;13&#xff09; pole n.&#xff08;地球的&#xff09;极 flight n. 飞行 explorer n. 探险家 lie …

[Python] numpy - 如何对数组进行降维或者升维

什么是数组升维&#xff1f; 数组升维是指将原本低维数组转换为更高维的数组的操作。在编程中&#xff0c;数组是一种存储多个相同类型数据的连续内存空间。升维操作可以通过添加额外的维度来增加数组的维数。 举个例子&#xff0c;将一个一维数组升维为二维数组可以使用resh…

Java集合-Map接口(key-value)

Map接口的特点&#xff1a;①KV键值对方式存储②Key键唯一&#xff0c;Value允许重复③无序。 Map有四个实现类&#xff1a;1.HashMap类2.LinkedHashMap类3.TreeMap类4.Hashtable类 1.HashMap类&#xff1a; 存储结构&#xff1a;哈希表 数组Node[ ] 链表&#xff08;红黑…

在windows环境下安装hadoop

Hadoop是一个分布式系统基础架构。用户可以在不了解分布式底层细节的情况下&#xff0c;开发分布式程序。但这个架构是基于java语言开发的&#xff0c;所以要先进行jdk的安装&#xff0c;如果电脑已经配置过jdk或者是曾经运行成功过java文件&#xff0c;那就可以跳过第一步。 …

想用verilog写一个npu 需要什么学习路线?

要用Verilog编写一个NPU&#xff08;神经处理单元&#xff09;&#xff0c;你需要经过以下学习路线&#xff1a; 数字电路基础&#xff1a; 学习数字电路的基本概念&#xff0c;包括逻辑门、寄存器、时钟信号、信号传输等。 Verilog编程语言&#xff1a; 学习Verilog HDL&…

Jenkins上跑自动化项目,case出现错误时,导致项目运行时间过长,该如何处理?

1、方案一&#xff1a;Jenkins上调整 进入配置&#xff1a; 构建环境&#xff1a; 自行选择超时时间即可&#xff5e; 2、方案二&#xff1a;代码调整【python】 安装插件&#xff1a;pytest-timeout 选择一&#xff1a;装饰器用法&#xff1a;将单个测试用例标记为超时&…