0 为什么是留言板?
需要给easy-blog开一个头,想来想去,留言板好像比较合适。涉及到了需要用到的大部分内容:
- html模板
- 数据库
- gin
1 设计思路
1.1 设计数据库
数据库字段如下:
- 留言ID
- 留言人昵称
- 留言人邮箱
- 留言内容
- 留言时间
1.2 设计页面
- 页面顶部为留言人昵称和邮箱的输入框
- 下面是留言框
- 再下面是历史留言的展示
2 实现细节
2.1 创建数据库表
create TABLE message_board(
messageId int primary key auto_increment,
authorName varchar(20),
authorEmail varchar(30),
message text,
messageTime datetime)
2.2 创建html模板
我们这里先使用一个简易的页面,我承认确实很丑。
2.2.1 留言提交区
使用一个表单来实现留言提交区,使用POST方式提交到/msgboard路径。稍后会在路由中定义页面展示和后台处理的路由。
效果如下:
2.2.2 历史留言展示区
使用表格来实现留言展示。表格使用html模板来填充,使用传入的结构中的各个字段对td标签中的内容进行替换。
{{if len .}}
留言ID
留言人
邮箱
留言时间
留言内容
{{range .}}
{{.MsgID}}
{{.AuthorName}}
{{.AuthorEmail}}
{{.MsgTime}}
{{.Msg}}
{{end}}
{{end}}
效果如下:
2.3 创建路由
创建以下两个路由,分别用来响应/msgboard路径的GET和POST请求。其中GET路由用来响应界面,POST请求用来处理新增留言。
engine := gin.Default()
engine.GET("/msgboard", msgboard.GetHandler)
engine.POST("/msgboard", msgboard.PostMsgHandler)
msgboard是一个本地的包,其中有GetHandler和PostHandler两个对外的函数。
另外,因为我们的需要使用到html模板,并且模板中会引用css或js的静态文件,需要添加如下逻辑:
engine.LoadHTMLGlob("./template/*") //加载html模板,加载后才能使用
engine.Static("/static", "./static") //配置静态文件服务,配置后才可以引用本地的静态文件
2.4 创建路由处理函数
在msgboard包中创建路由函数。
2.4.1 GetHandler
func GetHandler(c *gin.Context) {
//Get msgs from sql
msgs, err := dao.SelectAllMessage(SqlxDB)
if err != nil {
fmt.Println("get msgs from db err", err)
c.String(http.StatusInternalServerError, err)
}
c.HTML(http.StatusOK, "msgboard.html", msgs)
}
其中dao是我们封装的数据库处理包,这里先从数据库中读取所有的留言信息,然后将其传入msgboard.html模板,并返回给用户。
msgs的结构如下,字段和模板中的数据对应:
type Message struct {
MsgID int `db:"messageId"`
AuthorName string `db:"authorName"`
AuthorEmail string `db:"authorEmail"`
Msg string `db:"message"`
MsgTime string `db:"messageTime"`
}
2.4.2 PostHandler
unc PostHandler(c *gin.Context) {
//Write new msg to sql
var msg model.Message
msg.AuthorName = c.DefaultPostForm("name", "nobody")
msg.AuthorEmail = c.DefaultPostForm("mail", "无邮箱信息")
msg.Msg = c.DefaultPostForm("msg", "")
dao.InsertOneMessage(SqlxDB, &msg)
GetHandler(c)
}
PostHandler
先获取POST请求中携带的信息,DefaultPostForm
在请求中未包含该字段未,使用第二个参数的默认值代替。
msg.AuthorName = c.DefaultPostForm("name", "nobody")
msg.AuthorEmail = c.DefaultPostForm("mail", "无邮箱信息")
msg.Msg = c.DefaultPostForm("msg", "")
使用dao包中封装的InsertOneMessage
函数将一条留言插入到数据库中,并调用GetHandler
将所有留言的页面返回给用户。这里后面可以优化为使用ajax的方式更新页面,而不是整个返回。
2.5 数据库处理
2.5.1 数据库连接
在dbutil
包的init
函数中初始化数据库连接,保证后台运行起来时,数据库已就绪。
数据库处理使用了sqlx
这个数据库包,其扩展了官方的sql
包。注意在这里要匿名导入mysql的驱动包github.com/go-sql-driver/mysql
,使用其中的init
函数初始化mysql驱动,否则无法连接到mysql数据库。其他数据库也是同理。
所有服务使用SQLXDB作为服务器连接的handle,当数据库连接失败时,触发panic,服务无法启动。
package dbutil
import (
"fmt"
"github.com/finalone/easy-blog/app/config"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
var (
// SQLXDB is sqlx instance
SQLXDB *sqlx.DB
)
func init() {
if SQLXDB != nil {
return
}
SQLXDB = NewSQLXDB()
}
// NewSQLXDB get a *sqlx.DB
func NewSQLXDB() *sqlx.DB {
cfg := &config.Cfg
if cfg == nil {
return nil
}
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/easyblog?charset=utf8&parseTime=True", cfg.User, cfg.Passwd, cfg.Addr, cfg.Port)
db, err := sqlx.Connect("mysql", dsn)
if err != nil {
fmt.Println("sql connect error", err)
panic("sql connect error")
}
db.SetMaxOpenConns(20)
db.SetMaxIdleConns(10)
return db
}
2.5.2 数据库使用
前面提到,我们封装了dao包来实现对数据库的操作。目前dao包中只有两个函数,获取所有留言和插入一条留言,这个逻辑比较简单,就不再详述了。
// SelectAllMessage get all message from table message_board
func SelectAllMessage(sqlxDB *sqlx.DB) (*[]model.Message, error) {
var msgs []model.Message
sql := `SELECT * FROM message_board`
err := sqlxDB.Select(&msgs, sql)
if err != nil {
return nil, err
}
return &msgs, nil
}
// InsertOneMessage insert one message to table message_board
func InsertOneMessage(sqlxDB *sqlx.DB, msg *model.Message) error {
sql := `INSERT INTO message_board (authorName, authorEmail, message, messageTime) values (?, ?, ?, NOW())`
_, err := sqlxDB.Exec(sql, msg.AuthorName, msg.AuthorEmail, msg.Msg)
if err != nil {
fmt.Println("insert msg err", err)
return err
}
return nil
}
3 小结
本节介绍了留言板页面的几个关键点:
- 数据库
- 路由
- GET和POST请求的不同处理
- HTML模板
因为我目前还在思考如何布置整个系统的框架,这部分说的比较粗糙,待框架确定后,将各个模块放的正确的位置,再进行分享。