为网站网站做代理被判缓刑建筑行业征信查询平台
web/
2025/10/1 15:16:05/
文章来源:
为网站网站做代理被判缓刑,建筑行业征信查询平台,中国最大的软件开发公司,wordpress的标签页第一部分#xff1a;构建基础命令行博客系统
代码仓库
章节 1#xff1a;Go语言快速入门
1.1 Go语言简介
Go语言#xff0c;也称作Golang#xff0c;是由Google开发的一种静态强类型、编译型语言#xff0c;具有垃圾回收功能。它在2009年公开发布#xff0c;由Robert…第一部分构建基础命令行博客系统
代码仓库
章节 1Go语言快速入门
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 GOPATHc:\go1.3 Hello World程序
编写Hello World程序是学习新编程语言的传统。在Go中这个程序看起来是这样的
package mainimport fmtfunc 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程序
现在是时候动手写代码了。作为练习请尝试以下操作
编写一个Go程序打印出你最喜欢的引语。修改程序接收用户输入并打印出一个个性化的问候语。
这是一个接收用户输入的示例程序
package mainimport (bufiofmtos
)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语言的核心概念和功能。
章节 2Go语言基础
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 练习创建基本的输入输出函数
作为练习尝试以下操作
创建一个函数接受两个字符串参数并返回它们的拼接结果。编写一个函数接受一个整数数组并返回它们的和。实现一个函数接受一个整数并返回它的阶乘。
字符串拼接函数
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/ioutil和os包
文件操作是大多数程序中的常见任务。在Go中io/ioutil和os包提供了这方面的功能。
读取文件
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 处理JSONencoding/json包
JSON是一种轻量级的数据交换格式经常被用于网络通信。在Go中encoding/json包提供了JSON数据的编码和解码功能。
JSON编码
type BlogPost struct {
Title string
Content string
Author string
Views int
}post : BlogPost{
Title: Exploring Gos Standard Library,
Content: Gos 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 练习创建并操作自己的博客文章
作为练习尝试以下操作
创建一个结构体表示博客文章包括标题、内容、作者和发布日期。编写一个函数将博客文章结构体编码为JSON。编写另一个函数将JSON解码回博客文章结构体。将博客文章保存到文件并从文件中读取。
博客文章结构体
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的网络编程功能来让我们的博客系统可以通过网络进行数据交换。
章节 4Go的网络编程
在这一章节中我们将介绍Go语言在网络编程方面的能力。Go的net包提供了丰富的网络编程功能包括TCP/UDP协议、HTTP客户端和服务端的实现等。
4.1 创建TCP服务器
TCP传输控制协议是一种可靠的、面向连接的协议。下面的例子演示了如何创建一个简单的TCP服务器它监听本地端口并回显接收到的消息。
TCP Echo服务器
package mainimport (bufiofmtnetos
)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 (fmtnet/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 (fmtio/ioutilnet/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 练习构建一个简单的博客服务器
作为练习尝试以下操作
创建一个HTTP服务器它可以处理不同的路由和HTTP方法。服务器应该能够响应至少两种内容静态页面和JSON响应。实现简单的文章存储功能可以通过HTTP请求添加和检索文章。
HTTP服务器处理不同路由
package mainimport (encoding/jsonfmtnet/httpsync
)// BlogPost 定义了博客文章的结构
type BlogPost struct {Title string json:titleContent string json:contentAuthor 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/jsonfmtio/ioutilnet/httpstrconvstringssync
)// BlogPost 定义了博客文章的结构
type BlogPost struct {ID int json:idTitle string json:titleContent string json:contentAuthor 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()idCounternewPost.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[i1:]...)mutex.Unlock()w.WriteHeader(http.StatusOK)found truebreak}}if !found {http.NotFound(w, r)}default:w.WriteHeader(http.StatusMethodNotAllowed)}
}5.3 命令行客户端功能
我们将扩展命令行客户端以便它可以与API服务器交互获取和发布文章。
客户端代码
package mainimport (bytesencoding/jsonfmtio/ioutilnet/httpos
)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 练习扩展博客功能
作为练习尝试以下操作
添加命令行客户端的删除功能以便它可以通过HTTP请求删除文章。实现文章更新功能允许通过PUT请求更新现有文章的内容。添加用户认证确保只有认证用户才能创建、更新和删除文章。
添加删除功能
// 添加到 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请求。
添加用户认证
用户认证通常涉及到更复杂的逻辑和安全性考虑例如使用JWTJSON 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-sqlite3logmyblog/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 练习添加更新和认证功能
作为练习尝试以下操作
实现更新文章的功能允许通过PUT请求更新现有文章的内容。添加基本的用户认证确保只有认证用户才能创建、更新和删除文章。
实现更新文章功能
// 添加到数据库操作代码中
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)添加用户认证功能
用户认证通常涉及到更复杂的逻辑和安全性考虑例如使用JWTJSON Web Tokens或OAuth。这将超出本章的范围但是作为一个练习你可以探索如何在Go中实现这些认证机制。 通过完成第六章读者应该能够理解如何为Go语言编写的博客系统添加持久化存储功能。我们介绍了如何使用SQLite数据库来存储数据并展示了如何将数据库操作集成到我们的API服务器中。这些知识是构建能够长期存储数据的网络应用程序的基础。在后续的章节中我们可以进一步探讨如何扩展系统的功能例如添加更复杂的数据库操作、用户认证和安全性措施。
章节 7增加用户认证和授权
在本章中我们将为我们的命令行博客系统添加用户认证和授权功能。这将确保只有经过验证的用户才能创建、更新或删除文章。我们将使用JSON Web TokensJWT来实现这一功能。
7.1 设计用户模型
首先我们需要设计一个用户模型来存储用户信息。
用户模型
CREATE TABLE IF NOT EXISTS users
(idINTEGERPRIMARYKEYAUTOINCREMENT,usernameTEXTNOTNULLUNIQUE,password_hashTEXTNOTNULL
);7.2 实现用户注册和登录
我们将允许用户注册和登录以便我们可以发行JWT给认证用户。
用户注册和登录代码
package mainimport (database/sqlfmtloggolang.org/x/crypto/bcryptgithub.com/dgrijalva/jwt-go
)var jwtKey []byte(my_secret_key)type Claims struct {Username string json:usernamejwt.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 练习扩展认证功能
作为练习尝试以下操作
实现用户注销功能使得JWT在用户注销时失效。添加密码重置功能允许用户通过某种机制重置他们的密码。实现更复杂的权限系统允许不同的用户有不同的操作权限。
实现用户注销功能
用户注销功能通常涉及到使当前的JWT失效。这可以通过在服务器端维护一个失效的token列表来实现或者通过设置JWT的exp 过期时间字段为当前时间来使其立即失效。
添加密码重置功能
密码重置功能通常涉及到发送一次性链接到用户注册的电子邮件地址用户可以通过该链接来重置他们的密码。
实现更复杂的权限系统
更复杂的权限系统可能需要在用户模型中添加角色字段并在认证时检查用户角色以确定他们是否有权执行特定操作。 通过完成第七章读者应该能够理解如何在Go语言编写的博客系统中添加用户认证和授权功能。我们介绍了如何使用JWT来验证用户并保护API路由以确保只有认证用户才能执行某些操作。这些知识是构建安全网络应用程序的基础。在后续的章节中我们可以进一步探讨如何扩展系统的安全性例如通过HTTPS提供服务、实现密码策略和添加更多的安全性措施。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/85121.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!