上一节我们介绍了gorm的连接和配置,并且我们也在数据库中创建了 admins、article_data、articles、attachments、categories 五个表。但是我们还没有将它和golang结合在一起,我们的博客网站还不能从数据库读写数据。需要使用gorm来读写数据,我们还需要先给每个表定义一个模型(struct)。

前面建立目录的时候,我们已经定义了数据库模型文件存放的目录 为model 文件夹。因此我们在model目录分别按表名称的单数形式创建对应的go文件,并定义对应的模型结构体。

admin.go 文件的 admins 表

type Admin struct {
	Id          uint `json:"id" gorm:"column:id;type:int(10) unsigned not null AUTO_INCREMENT;primaryKey;"`
	UserName    string `json:"user_name" gorm:"column:user_name;type:varchar(16) not null;default:'';index:idx_user_name"`
	Password    string `json:"-" gorm:"column:password;type:varchar(128) not null;default:''"`
	Status      uint   `json:"status" gorm:"column:status;type:tinyint(1) unsigned not null;default:0;index:idx_status"`
	CreatedTime int64   `json:"created_time" gorm:"column:created_time;type:int(11) not null;default:0;index:idx_created_time"`
	UpdatedTime int64   `json:"updated_time" gorm:"column:updated_time;type:int(11) not null;default:0;index:idx_updated_time"`
	DeletedTime int64   `json:"-" gorm:"column:deleted_time;type:int(11) not null;default:0"`
}

表名称的结构体是表名的单数形式,因为默认gorm在读取表的时候,会使用复数结构体名称的复数形式作为表名。当然,我们也可以通过自定义表名的形式,来重新定义表名,不使用gorm的默认表名:

func (Admin) TableName() string {
  return "admins"
}

自定义表名使用 TableName() 函数来重写。

同时要注意,结构体名称一定要使用驼峰命名法则来命名,单词首字母大写。结构体内部字段也是如此,必须要使用驼峰命名法则来命名,首字母大写,外部才能访问到这个字段,表字段和json字段则使用蛇形小写的命名方式,即它们的两个单词之间使用下划线_来连接。后面的结构体字段标记则是golang在解析这个字段的时候的变量,比如这些字段都应以了作为json输出的时候的字段名称。如果这个字段希望在转换为json的时候,忽略它,则将它的json标记为-即可,如:

Password    string `json:"-"`

后面的gorm标记,则是gorm对这个字段的数据库定义信息,它包含了这个字段在表中的字段名、字段类型、字段长度、无符号值、字段自增、关键字段、默认值、索引信息等内容,如果有必要,gorm会根据这些内容来创建和更新mysql表信息。

同样地,如果希望这个结构体中,某个字段不出现在数据库中,同样可以使用-来忽略它,如:

	Content int64   `json:"content" gorm:"-"`

这样这个字段可以通过外部赋值,并且在转换成json输出的时候,它是有值的,而它不会被写入到数据库中,gorm使用orm查查询数据库的时候,它也不会被查询出来。

category.go 文件的 categories 表

type Category struct {
	Id          uint    `json:"id" gorm:"column:id;type:int(10) unsigned not null AUTO_INCREMENT;primary_key"`
	Title       string `json:"title" gorm:"column:title;type:varchar(250) not null;default:''"`
	Description string `json:"description" gorm:"column:description;type:varchar(250) not null;default:''"`
	Content     string `json:"content" gorm:"column:content;type:longtext default null"`
	ParentId    uint    `json:"parent_id" gorm:"column:parent_id;type:int(10) unsigned not null;default:0;index:idx_parent_id"`
	Status      uint   `json:"status" gorm:"column:status;type:tinyint(1) unsigned not null;default:0;index:idx_status"`
	CreatedTime int64   `json:"created_time" gorm:"column:created_time;type:int(11) not null;default:0;index:idx_created_time"`
	UpdatedTime int64   `json:"updated_time" gorm:"column:updated_time;type:int(11) not null;default:0;index:idx_updated_time"`
	DeletedTime int64   `json:"-" gorm:"column:deleted_time;type:int(11) not null;default:0"`
}

模型字段基本上就是根据mysql表的字段一一对应的,并且将字段的信息都标记在结构体字段的标记中。

gorm的标记也可以省略,省略的话,它默认就是模型结构自字段的蛇形小写形式,如:

type Category struct {
  Id          uint    //默认字段名是id
  Title       string  //默认字段名是title
  Description string  //默认字段名是description
  Content     string  //默认字段名是content
  ParentId    uint    //默认字段名是parent_id
  Status      uint    //默认字段名是status
  CreatedTime int64   //默认字段名是created_time
  UpdatedTime int64   //默认字段名是updated_time
  DeletedTime int64   //默认字段名是deleted_time
}

为了方便gorm可以同步迁移数据库,建议我们采用完整的写法。这样我们就可以在有更新字段的时候,使用 AutoMigrate 函数来完成数据迁移工作,对部署很有帮助。

注意:自动迁移仅仅会创建表,缺少列和索引,并且不会改变现有列的类型或删除未使用的列以保护数据。

article.go 文件的 articles 表 和 article_data 表


type Article struct {
	Id          uint        `json:"id" gorm:"column:id;type:int(10) unsigned not null AUTO_INCREMENT;primary_key"`
	Title       string      `json:"title" gorm:"column:title;type:varchar(250) not null;default:''"`
	Keywords    string      `json:"keywords" gorm:"column:keywords;type:varchar(250) not null;default:''"`
	Description string      `json:"description" gorm:"column:description;type:varchar(250) not null;default:''"`
	CategoryId  uint        `json:"category_id" gorm:"column:category_id;type:int(10) unsigned not null;default:0;index:idx_category_id"`
	Views       uint        `json:"views" gorm:"column:views;type:int(10) unsigned not null;default:0;index:idx_views"`
	Status      uint        `json:"status" gorm:"column:status;type:tinyint(1) unsigned not null;default:0;index:idx_status"`
	CreatedTime int64       `json:"created_time" gorm:"column:created_time;type:int(11) not null;default:0;index:idx_created_time"`
	UpdatedTime int64       `json:"updated_time" gorm:"column:updated_time;type:int(11) not null;default:0;index:idx_updated_time"`
	DeletedTime int64       `json:"-" gorm:"column:deleted_time;type:int(11) not null;default:0"`
	Category    *Category    `json:"category" gorm:"-"`
	ArticleData *ArticleData `json:"data" gorm:"-"`
}

type ArticleData struct {
	Id uint   `json:"id" gorm:"column:id;type:int(10) unsigned not null;primary_key"`
	Content   string `json:"content" gorm:"column:content;type:longtext default null"`
}

因为articles 表和 article_data 表它们是紧密联系在一起的,因此我们将这两个模型结构体创建在了同一个文件中,方便我们查看和管理。

这里说明一下,Id 字段为自增字段,CategoryId 字段、Views 字段、Status 字段 它们的值都是只有大于零的值,因此我们定义它们的类型为uint;而CreatedTime、UpdatedTime、DeletedTime字段为时间戳字段,golang默认的时间戳类型是int64,为了减少不必要的类型转换,因此我们定义模型结构体的时候,也将它们定义为int64类型,这样不需要再做类型转换就可以直接调用这个值。

这两个模型结构体里,我们定义了Category 为 ArticleData 。 这里面为了不使用gorm的默认外键,所以这里我们在gorm中就做忽略处理。

attachment.go 文件的 attachments 表

type Attachment struct {
	Id           uint   `json:"id" gorm:"column:id;type:int(10) unsigned not null AUTO_INCREMENT;primaryKey;"`
	FileName     string `json:"file_name" gorm:"column:file_name;type:varchar(100) not null;default:''"`
	FileLocation string `json:"file_location" gorm:"column:file_location;type:varchar(250) not null;default:''"`
	FileSize     int64  `json:"file_size" gorm:"column:file_size;type:bigint(20) unsigned not null;default:0"`
	FileMd5      string `json:"file_md5" gorm:"column:file_md5;type:varchar(32) unique not null;default:''"`
	Width        uint   `json:"width" gorm:"column:width;type:int(10) unsigned not null;default:0"`
	Height       uint   `json:"height" gorm:"column:height;type:int(10) unsigned not null;default:0"`
	Status       uint   `json:"status" gorm:"column:status;type:tinyint(1) unsigned not null;default:0;index:idx_status"`
	CreatedTime  int64    `json:"created_time" gorm:"column:created_time;type:int(11) not null;default:0;index:idx_created_time"`
	UpdatedTime  int64    `json:"updated_time" gorm:"column:updated_time;type:int(11) not null;default:0;index:idx_updated_time"`
	DeletedTime  int64    `json:"-" gorm:"column:deleted_time;type:int(11) not null;default:0"`
	Logo         string `json:"logo" gorm:"-"`
	Thumb        string `json:"thumb" gorm:"-"`
}

Attachment模型结构体,我们定义了Logo和Thumb两个不在mysql表中存在的字段,因为 FileLocation 字段存放的是上传的图片的服务器存储路径,并不一定是浏览器可以直接浏览的文件路径,因为我们在图片展示的时候,还需要将它转换成可浏览器浏览的路径,图片还有原图和缩略图的区分,所以我们定义了Logo字段为图片原图可访问路径,Thumb 为图片缩略图可访问路径。这两个自动会在gorm读出数据后,再通过其他方法来给它赋值。

上面就是我们博客需要用到的5个表的gorm模型的定义。模型定义完了之后,我们就可以对数据进行操作了。下一节我们将介绍如何操作数据库。

完整的项目示例代码托管在GitHub上,需要查看完整的项目代码可以到github.com/fesiong/goblog 上查看,也可以直接fork一份来在上面做修改。