go语言书籍管理系统(基于gin)

go语言书籍管理系统(基于gin)

项目要求

写一个web服务器,完成对书籍的管理。

包括

  • 书籍列表展示
  • 书籍的增删改查

展示的书籍的信息有

  • 书籍名称
  • 价格

思路分析

这是一个典型的web开发。

总体分为两部分:前端页面后端服务器。在后端还涉及到数据库的操作

大概的逻辑为:

  1. 用户在浏览器输入url请求访问页面内容
  2. 后端根据设置好的路由决定其调用哪个处理器(handler,就是个函数)
  3. 在处理函数中,会完成处理的逻辑,其中可能还与数据库发送交互,渲染HTML页面并发给浏览器。

源码

目录结构

mark

源码

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;

重点总结

  1. 学习项目代码的分离,例如本例中与数据库的操作相关的都放在一起,项目用到的模板都放在了template目录下,专门设置一个文件存放公用的数据结构定义。

  2. 学习goweb开发的流程,以及前后端的交互方式。

  3. html模板在各个handler中渲染前要先进行模板的解析。

  4. 经常出现一种情况是对一个页面有访问,但是请求类型不同,如有时是GET(请求页面时)有时是POST(提交表单时),这是便要对不同的请求分别设置相应的handler,本例中时通过设置了两次路由实现,也可以用r.Any()函数,然后再函数里面再具体区分不同的请求类型。

  5. html模板中的第一行的define是定义模板的名字,后面再再HTML模板渲染的时候要与之一致。

  6. 本例中涉及到了两种网页的重定向:
    一种是在html中,例如<div> <a href="/book/new"> 添加新书 </a></div>,这一个是在浏览器点击添加新书按钮后跳转到/book/new界面;
    另一种实在handler函数中,逻辑处理完成后,调用c.Redirect(http.StatusMovedPermanently,"/book/list")实现页面的重定向(跳转)。

  7. 在删除书籍时涉及到一个问题就是”在点击删除按钮后,后端如何知道删除的是哪一本书籍?",这就需要在点击删除时,不同的书籍带一个唯一的标识信息,后端根据表示信息进行删除操作。问题又来了,如何将这个信息交给后端呢?我们知道前后端交互的方式大概有三种:1. query-string方式 2. form表单形式 3. 路径参数
    这里常用的是query-string方式,这就需要在HTML模板中做一些小操作,如下图

    mark

    点击删除后会跳转到 /book/delete页面,后面还跟了一个query-string,这是后端就可以解析query-string参数,得到这个唯一标识。

  8. 本题目最难的是书籍信息的修改部分。他要求实现一个小功能:在修改书籍时,输入框内要预先展示书籍的默认信息。这部分也可以分为两块,GET请求时给浏览器一个界面,让用户进行修改;POST请求时接受用户提交的表单,并在数据库中更新数据。
    在第一部分采用和删除时相同的方法,用query-string方式告诉后端要修改书籍,然后后端服务器五数据库取出这本书的原始信息,渲染给模板,发给用户作为默认信息。

    在第二部分form表单提交时同样需要告诉告诉后端是更新拿一本书。这时标识的传输可以是query-string方式,也可以用form表单,本文采用的是form表单方式。具体如下:

    mark

    这里强行在form表单里加入了该书籍的唯一标识(ID)。

    如果用query-string方式就需要修改form表单里的“action”内容,这个action是指form表单提交到什么地方。因此可以将其设置为/book/update?id={{.ID}},这样在后端就可以进行参数解析。

调试

书籍展示

mark

删除操作

mark

在删除完后会重定向到书籍展示界面。

添加书籍

mark

mark

编辑书籍

mark

mark

posted @ 2020-06-04 16:31  wind-zhou  Views(1607)  Comments(0Edit  收藏  举报