go語言書籍管理系統(基於gin)
項目要求
寫一個web服務器,完成對書籍的管理。
包括
- 書籍列表展示
- 書籍的增刪改查
展示的書籍的信息有
- 書籍名稱
- 價格
思路分析
這是一個典型的web開發。
總體分為兩部分:前端頁面和后端服務器。在后端還涉及到數據庫的操作。
大概的邏輯為:
- 用戶在瀏覽器輸入url請求訪問頁面內容
- 后端根據設置好的路由決定其調用哪個處理器(handler,就是個函數)
- 在處理函數中,會完成處理的邏輯,其中可能還與數據庫發送交互,渲染HTML頁面並發給瀏覽器。
源碼
目錄結構
源碼
main.go
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"strconv"
)
//BookManagementSystem
func main(){
//程序啟動連接數據庫
err:= initDB()
if err != nil {
panic(err)
}
r:=gin.Default()
//解析模板
r.LoadHTMLGlob("template/**/*")//模板解析
//各個路由
r.GET("/book/list",booklListHandle)//查詢書籍
r.GET("/book/new",newBookhandle)//增加書籍,第一次get返回html模板,給用戶填寫
r.POST("/book/new",createBookHandle)
r.GET("/book/delete",deleteHandle)//刪除書籍
r.GET("/book/update",newHandle)//更新書籍信息,價格或書名
r.POST("/book/update",updateHandle)
r.Run()
}
//查詢書籍信息
func booklListHandle(c *gin.Context){
//選數據庫
//查數據
//返回瀏覽器
bookList,err:=queryAlllBook()
if err != nil {
c.JSON(http.StatusBadRequest,gin.H{
"err":err.Error(),
"code":1,
})
return
}
//以json格式返回
//c.JSON(http.StatusOK,gin.H{//前后端分離的一個標准返回
// "code":0,
// "data":bookList,
//})
//以模板形式返回,因此需要再建一個book_list.html模板
c.HTML(http.StatusOK,"book/book_list.html",gin.H{
"code":0,
"data":bookList,
})
}
//查插入書籍數據
func newBookhandle(c *gin.Context){
c.HTML(http.StatusOK,"book/new_book.html",nil)
}
//增加書籍
func createBookHandle(c *gin.Context){
//增加新書,從form表單中提取數據
titleVal:=c.PostForm("title")
priceVal:=c.PostForm("price")
//上面接受到的時string類型,存儲前還需類型轉換一下
price,err:=strconv.ParseFloat(priceVal,64)
if err != nil {
fmt.Println("轉換失敗")
return
}
//將提取的數據寫入數據庫,調用寫好的insertAlllBook()
err=insertAlllBook(titleVal,price)
if err != nil {
c.String(http.StatusOK,"插入數據失敗")
return
}
//到此,數據插入成功
//為了友好的交互,跳轉到,書籍顯示界面
//使用重定向進行跳轉
c.Redirect(http.StatusMovedPermanently,"/book/list")
}
//刪除書籍
func deleteHandle(c *gin.Context){
//拿去query-string數據,然后根據不同數據刪除指定編號的書籍
idVal:=c.Query("id")
//將 ID轉換為整型
id,err:=strconv.ParseInt(idVal,10,64)
if err != nil {
c.JSON(http.StatusOK,gin.H{
"err":err.Error(),
"code":1,
})
}
//刪除數據
err=deleteBook(id)
if err != nil {
c.JSON(http.StatusOK,gin.H{
"err":err.Error(),
"code":1,
})
}
//重定向到書籍展示界面
c.Redirect(http.StatusMovedPermanently,"/book/list")
}
//顯示書籍更新頁面
func newHandle(c *gin.Context) {
//拿去query-string數據,然后根據不同數據刪除指定編號的書籍
idVal := c.Query("id")
//將 ID轉換為整型
id, err := strconv.ParseInt(idVal, 10, 64)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"err": err.Error(),
"code": 1,
})
return
}
book,err:= querySingalBook(id)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"err": err.Error(),
"code": 1,
})
return
}
//向指定路徑發送html模板
c.HTML(http.StatusOK, "book/updatebook.html", book)
}
func updateHandle(c *gin.Context){
//拿到更改信息(form表單內容)和query-string
//
////拿去query-string數據,然后根據不同數據刪除指定編號的書籍
//idVal := c.Query("id")
//
//fmt.Printf("id=%v\n",idVal)
////將 ID轉換為整型
//id, err := strconv.ParseInt(idVal, 10, 64)
//if err != nil {
// c.JSON(http.StatusOK, gin.H{
// "err": err.Error(),
// "code": 1,
// })
// return
//}
//拿到form表單里的update信息
//增加新書,從form表單中提取數據
titleVal:=c.PostForm("title")
priceVal:=c.PostForm("price")
idVal:=c.PostForm("id")
//上面接受到的時string類型,存儲前還需類型轉換一下
price,err:=strconv.ParseFloat(priceVal,64)
if err != nil {
fmt.Println("轉換失敗")
return
}
id,err:=strconv.ParseInt(idVal, 10, 64)
if err != nil {
fmt.Println("轉換失敗")
return
}
//在數據庫中更新
err=updateBook(titleVal,price,id)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"err": err.Error(),
"code": 1,
})
return
}
//重定向到書籍展示界面
c.Redirect(http.StatusMovedPermanently,"/book/list")
}
db.go
package main
import (
"fmt"
"github.com/jmoiron/sqlx"
_ "github.com/go-sql-driver/mysql"
)
//跟數據庫相關
var db *sqlx.DB
//初始化連接數據庫
func initDB()(err error){
dsn := "root:5210@tcp(127.0.0.1:3306)/go_test"
// 也可以使用MustConnect連接不成功就panic
db, err = sqlx.Connect("mysql", dsn)
if err != nil {
fmt.Printf("connect DB failed, err:%v\n", err)
return
}
db.SetMaxOpenConns(20)//設置最大連接數
db.SetMaxIdleConns(10)
return
}
//查詢所有數據
func queryAlllBook()( bookList []*Book,err error){
sqlStr:="select id,title ,price from book"
err=db.Select(&bookList,sqlStr)
if err != nil {
fmt.Printf("查詢信息失敗err=%v\n",err)
return
}
return
}
//查詢單條書籍
func querySingalBook(id int64)(book Book,err error){
sqlstr:="select id,title ,price from book where id=? "
err=db.Get(&book,sqlstr,id)
if err != nil {
fmt.Printf("查詢信息失敗1111err=%v\n",err)
return
}
return
}
//插入數據
func insertAlllBook(title string,price float64)( err error){
sqlStr:="insert into book(title,price) values (?,?)"
_,err=db.Exec(sqlStr,title,price)
if err != nil {
fmt.Printf("插入信息失敗err=%v\n",err)
return
}
return
}
//插刪除據
func deleteBook(id int64)( err error){
sqlStr:="delete from book where id=?"
_,err=db.Exec(sqlStr,id)
if err != nil {
fmt.Printf("刪除信息失敗err=%v\n",err)
return
}
return
}
//更新除據
func updateBook(title string,price float64,id int64)( err error){
sqlStr:="update book set title=?,price=? where id=?"
_,err=db.Exec(sqlStr,title,price,id)
if err != nil {
fmt.Printf("更新信息失敗err=%v\n",err)
return
}
return
}
model.go
package main
//專門定義與數據對應的結構體
//結構體對應數據庫的一張表
type Book struct {
ID int64`db:"id"` //和數據庫聯系加一個db的tag
Title string`db:"title"`
Price float64`db:"price"`
}
book下的三張html表
{{ define "book/book_list.html"}} //用於展示書籍歷表信息
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>書籍列表展示</title>
</head>
<body>
<div> <a href="/book/new"> 添加新書 </a></div>
<table border="1">
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Price</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{{range .data}}
<tr>
<td> {{.ID}}</td>
<td> {{.Title}}</td>
<td> {{.Price}}</td>
<td><a href="/book/delete?id={{.ID}}"> 刪除</a></td>
<td><a href="/book/update?id={{.ID}}"> 編輯</a></td>
</tr>
{{end}}
</tbody>
</table>
</body>
</html>
{{end}}
------------------------------------------------------------------------------------
{{ define "book/new_book.html"}} //用於給用於提交from表單數據
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>添加書籍信息</title>
</head>
<body>
<form action="/book/new" method="POST" >
<div>
<label>書名
<input type="text" name="title">
</label>
</div>
<div>
<label>價格
<input type="number" name="price">
</label>
</div>
<div>
<input type="submit" name="提交">
</div>
</form>
</body>
</html>
{{end}}
------------------------------------------------------------------------------------
{{ define "book/updatebook.html"}} //用於給用戶在更新書籍信息是提交form表單數據
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>更新書籍信息</title>
</head>
<body>
<form action="/book/update" method="POST" >
<div>
<label>新的書名
<input type="text" name="title" value="{{.Title}}" >
</label>
</div>
<div>
<label>新的價格
<input type="number" name="price" value="{{.Price}}">
</label>
</div>
<div>
<label>id
<input type="number" name="id" value="{{.ID}}" readonly/>
</label>
</div>
<div>
<input type="submit" name="提交">
</div>
</form>
</body>
</html>
{{end}}
table.sql
-- ------------------------------
-- 注釋 用於設置此項目鎖需要的數據庫表
-- BMS
-- --------------------------------
CREATE TABLE `book`(
`id` bigint(20) AUTO_INCREMENT PRIMARY KEY ,
`title` varchar (20) NOT NULL ,
`price` double (10,2 ) NOT NULL
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
重點總結
-
學習項目代碼的分離,例如本例中與數據庫的操作相關的都放在一起,項目用到的模板都放在了template目錄下,專門設置一個文件存放公用的數據結構定義。
-
學習goweb開發的流程,以及前后端的交互方式。
-
html模板在各個handler中渲染前要先進行模板的解析。
-
經常出現一種情況是對一個頁面有訪問,但是請求類型不同,如有時是GET(請求頁面時)有時是POST(提交表單時),這是便要對不同的請求分別設置相應的handler,本例中時通過設置了兩次路由實現,也可以用
r.Any()
函數,然后再函數里面再具體區分不同的請求類型。 -
html模板中的第一行的define是定義模板的名字,后面再再HTML模板渲染的時候要與之一致。
-
本例中涉及到了兩種網頁的重定向:
一種是在html中,例如<div> <a href="/book/new"> 添加新書 </a></div>
,這一個是在瀏覽器點擊添加新書
按鈕后跳轉到/book/new
界面;
另一種實在handler函數中,邏輯處理完成后,調用c.Redirect(http.StatusMovedPermanently,"/book/list")
實現頁面的重定向(跳轉)。 -
在刪除書籍時涉及到一個問題就是”在點擊刪除按鈕后,后端如何知道刪除的是哪一本書籍?",這就需要在點擊刪除時,不同的書籍帶一個唯一的標識信息,后端根據表示信息進行刪除操作。問題又來了,如何將這個信息交給后端呢?我們知道前后端交互的方式大概有三種:1. query-string方式 2. form表單形式 3. 路徑參數
這里常用的是query-string方式,這就需要在HTML模板中做一些小操作,如下圖點擊刪除后會跳轉到 /book/delete頁面,后面還跟了一個query-string,這是后端就可以解析query-string參數,得到這個唯一標識。
-
本題目最難的是書籍信息的修改部分。他要求實現一個小功能:在修改書籍時,輸入框內要預先展示書籍的默認信息。這部分也可以分為兩塊,GET請求時給瀏覽器一個界面,讓用戶進行修改;POST請求時接受用戶提交的表單,並在數據庫中更新數據。
在第一部分采用和刪除時相同的方法,用query-string方式告訴后端要修改書籍,然后后端服務器五數據庫取出這本書的原始信息,渲染給模板,發給用戶作為默認信息。在第二部分form表單提交時同樣需要告訴告訴后端是更新拿一本書。這時標識的傳輸可以是query-string方式,也可以用form表單,本文采用的是form表單方式。具體如下:
這里強行在form表單里加入了該書籍的唯一標識(ID)。
如果用query-string方式就需要修改form表單里的“action”內容,這個action是指form表單提交到什么地方。因此可以將其設置為
/book/update?id={{.ID}}
,這樣在后端就可以進行參數解析。
調試
書籍展示
刪除操作
在刪除完后會重定向到書籍展示界面。
添加書籍
編輯書籍