初学 go 语法,希望了解 go 项目的构建过程和方式。
go-gin-example 项目是使用 gin 框架构建一个简易的 blog 服务,包括对 blog 的增删改查操作以及 blog tag 的增删改查等。
项目代码结构如下
首先看一下整体项目中比较重要的模块,包括 主函数、路由模块、授权模块、数据库模块。
func main() {
router := routers.InitRouter()
s := &http.Server{
Addr: fmt.Sprintf(":%d", setting.HTTPPort),
Handler: router,
ReadTimeout: setting.ReadTimeout,
WriteTimeout: setting.WriteTimeout,
MaxHeaderBytes: 1 << 20,
}
s.ListenAndServe()
}
主函数工作
func InitRouter() *gin.Engine {
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
gin.SetMode(setting.RunMode)
r.GET("/auth", api.GetAuth)
apiv1 := r.Group("/api/v1")
apiv1.Use(jwt.JWT())
{
//获取标签列表
apiv1.GET("/tags", v1.GetTags)
//新建标签
apiv1.POST("/tags", v1.AddTag)
//更新指定标签
apiv1.PUT("/tags/:id", v1.EditTag)
//删除指定标签
apiv1.DELETE("/tags/:id", v1.DeleteTag)
//获取文章列表
apiv1.GET("/articles", v1.GetArticles)
//获取指定文章
apiv1.GET("/articles/:id", v1.GetArticle)
//新建文章
apiv1.POST("/articles", v1.AddArticle)
//更新指定文章
apiv1.PUT("/articles/:id", v1.EditArticle)
//删除指定文章
apiv1.DELETE("/articles/:id", v1.DeleteArticle)
}
return r
}
主要工作
type auth struct {
Username string `valid:"Required; MaxSize(50)"`
Password string `valid:"Required; MaxSize(50)"`
}
func GetAuth(c *gin.Context) {
username := c.Query("username")
password := c.Query("password")
valid := validation.Validation{}
a := auth{Username: username, Password: password}
ok, _ := valid.Valid(&a)
data := make(map[string]interface{})
code := e.INVALID_PARAMS
if ok {
isExist := models.CheckAuth(username, password)
if isExist {
token, err := util.GenerateToken(username, password)
if err != nil {
code = e.ERROR_AUTH_TOKEN
} else {
data["token"] = token
code = e.SUCCESS
}
} else {
code = e.ERROR_AUTH
}
} else {
for _, err := range valid.Errors {
logging.Info(err.Key, err.Message)
}
}
c.JSON(http.StatusOK, gin.H{
"code" : code,
"msg" : e.GetMsg(code),
"data" : data,
})
}
工作:
数据库信息的设置在 models 文件夹下,主要包括 model 文件以及其他具体表文件。
重点看一下 model 文件。
var db *gorm.DB
type Model struct {
ID int `gorm:"primary_key" json:"id"`
CreatedOn int `json:"created_on"`
ModifiedOn int `json:"modified_on"`
}
func init() {
var (
err error
dbType, dbName, user, password, host, tablePrefix string
)
sec, err := setting.Cfg.GetSection("database")
if err != nil {
log.Fatal(2, "Fail to get section 'database': %v", err)
}
dbType = sec.Key("TYPE").String()
dbName = sec.Key("NAME").String()
user = sec.Key("USER").String()
password = sec.Key("PASSWORD").String()
host = sec.Key("HOST").String()
tablePrefix = sec.Key("TABLE_PREFIX").String()
db, err = gorm.Open(dbType, fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local",
user,
password,
host,
dbName))
if err != nil {
log.Println(err)
}
gorm.DefaultTableNameHandler = func (db *gorm.DB, defaultTableName string) string {
return tablePrefix + defaultTableName;
}
db.SingularTable(true)
db.DB().SetMaxIdleConns(10)
db.DB().SetMaxOpenConns(100)
}
func CloseDB() {
defer db.Close()
}
组成部分
从中可知,db 主要是通过使用 gorm 框架进行操作,操作中需要设置数据库相关参数以及最大连接数。
models 下其他具体的数据库表信息在此不做赘述,基本就是数据库表的 struct 定义以及相应操作,下面贴出该项目涉及的三个数据库表及其字段。
type Article struct {
Model
TagID int `json:"tag_id" gorm:"index"`
Tag Tag `json:"tag"`
Title string `json:"title"`
Desc string `json:"title"`
Content string `json:"content"`
CreatedBy string `json:"created_by"`
ModifiedBy string `json:"modified_by"`
State int `json:"state"`
}
type Tag struct {
Model
Name string `json:"name"`
CreatedBy string `json:"created_by"`
ModifiedBy string `json:"modified_by"`
State int `json:"state"`
}
type Auth struct {
ID int `gorm:"primary_key" json:"id"`
Username string `json:"username"`
Password string `json:"password"`
}
主要模块已经了解后,我们查看修改文章 api 的请求过程和具体实现。
上述主要代码模块,已经了解到 main 函数启动后,会初始化路由,路由中包含了「修改文章」请求的具体处理函数,这里看看具体函数操作。
func EditArticle(c *gin.Context) {
valid := validation.Validation{}
id, _ := com.StrTo(c.Param("id")).Int()
tagId, _ := com.StrTo(c.Query("tag_id")).Int()
title := c.Query("title")
desc := c.Query("desc")
content := c.Query("content")
modifiedBy := c.Query("modified_by")
var state int = -1
if arg := c.Query("state"); arg != "" {
state, _ = com.StrTo(arg).Int()
valid.Range(state, 0, 1, "state").Message("状态只允许0或1")
}
valid.Min(id, 1, "id").Message("ID必须大于0")
valid.MaxSize(title, 100, "title").Message("标题最长为100字符")
valid.MaxSize(desc, 255, "desc").Message("简述最长为255字符")
valid.MaxSize(content, 65535, "content").Message("内容最长为65535字符")
valid.Required(modifiedBy, "modified_by").Message("修改人不能为空")
valid.MaxSize(modifiedBy, 100, "modified_by").Message("修改人最长为100字符")
code := e.INVALID_PARAMS
if ! valid.HasErrors() {
if models.ExistArticleByID(id) {
if models.ExistTagByID(tagId) {
data := make(map[string]interface {})
if tagId > 0 {
data["tag_id"] = tagId
}
if title != "" {
data["title"] = title
}
if desc != "" {
data["desc"] = desc
}
if content != "" {
data["content"] = content
}
data["modified_by"] = modifiedBy
models.EditArticle(id, data)
code = e.SUCCESS
} else {
code = e.ERROR_NOT_EXIST_TAG
}
} else {
code = e.ERROR_NOT_EXIST_ARTICLE
}
} else {
for _, err := range valid.Errors {
logging.Info(err.Key, err.Message)
}
}
c.JSON(http.StatusOK, gin.H{
"code" : code,
"msg" : e.GetMsg(code),
"data" : make(map[string]string),
})
}
// models article.go
func EditArticle(id int, data interface {}) bool {
db.Model(&Article{}).Where("id = ?", id).Updates(data)
return true
}
工作:
该项目第一版提交,大体上完成了 blog 项目所需的基本功能,在目录结构上也相对清晰。
不足: