极简抖音项目——数据模型设计 | 青训营笔记

123 阅读6分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第3篇笔记

思路

  • 数据库数据模型存放用于存放到数据库中的信息,响应数据模型用于构建返回响应。
    • 分成两部分的原因是两者有所区别,有些时候返回的响应是需要用户信息和视频信息等多个维度的信息共同构造的,而这些数据库模型可以选择将这些信息分割开来,分别存放。例如在这里抽象出了点赞模型,专门用于存放点赞的用户、被点赞的用户、被点赞的视频,而HTTP响应报文中用户信息就包含了被点赞数和喜欢数,以及喜欢的视频信息,这样我们可以根据点赞模型中的被点赞视频从视频模型中提取出视频信息,综合起来构造HTTP响应报文。
  • transformation.go文件中专门构造了如何从数据库模型到响应报文模型的转换的函数。
    • 这是由于一条HTTP响应报文需要的可能不是一个数据库表的信息,而是好几个,并且这些工作是可以抽象出来的,重复性很高的工作,所以专门写了一个文件。

数据库数据模型设计

用户信息

type User struct {
	Id             int64  `gorm:"primaryKey;autoIncrement"`         //用户唯一标志符号
	Name           string `gorm:"type:varchar(128);not null;index"` //用户名
	Password       string `gorm:"type:varchar(128);not null"`       //用户密码
	Follow_count   int64  `gorm:"not null;default:0"`               //关注数
	Follower_count int64  `gorm:"not null;default:0"`               //粉丝数
	Favorite_count int64  `gorm:"not null;default:0"`               //喜欢数
	Total_favorite int64  `gorm:"not null;default:0"`               //被赞数
	Avatar         string //用户头像链接Url
	Signature      string //用户个性签名
}

演变

一开始的用户信息是没包括关注数,粉丝数,喜欢数和被赞数这些统计数量信息,专门设计了函数统计数据库中的数量信息,进行这样操作的理由是发现一旦直接对数据库操作,删除某条关注信息或者某条点赞信息,如果是现在的设计模型,并不能直接感知到关注者数量的变化和点赞总数的变化。

之后进行小组讨论后觉得,如果每一次都要对数据库遍历统计,那势必是一个非常费时费力的操作,而且不能直接对数据库操作这应该是属于基本的数据库安全问题,所以改成了现在的模式。

视频信息

type Video struct {
	Id             int64          `gorm:"primaryKey;autoIncrement"` //视频唯一标志符
	UserId         int64          `gorm:"not null"`                 //视频发布者ID
	PlayUrl        string         `gorm:"not null"`                 //视频URL
	CoverUrl       string         //视频封面URL
	Title          string         //视频标题
	Favorite_count int64          `gorm:"not null;default:0"`   //视频点赞数
	Comment_count  int64          `gorm:"not null;default:0"`   //视频评论数
	CreatedAt      int64          `gorm:"autoCreateTime:milli"` //视频创建时间。选择milli是因为发现本机的mysql操作是以millisecond的,即使使用nano,后边的值也只会补上0,例如使用nano:1653548764819000000
	UpdatedAt      int64          `gorm:"autoCreateTime:milli"` //更新时间。
	DeletedAt      gorm.DeletedAt `gorm:"index"`                //删除时间
}

视频信息用了软删除,是因为觉得已经上传的用户视频可以作为描绘用户画像,可以保留下来。

点赞信息

type Favorite struct {
	Id       int64 `gorm:"primaryKey;autoIncrement"` //点赞数据的唯一标识符
	UserId   int64 `gorm:"not null"`                 //发起点赞操作的用户ID
	ToUserId int64 `gorm:"not null"`                 //受到点赞的用户ID
	VideoId  int64 `gorm:"not null"`                 //受到点赞的视频ID
}

评论信息

type Comment struct {
	Id        int64  `gorm:"primaryKey;autoIncrement"` //评论唯一标识符
	UserId    int64  `gorm:"not null"`                 //发起评论的用户ID
	VideoId   int64  `gorm:"not null"`                 //评论所在的视频ID
	ToUserId  int64  `gorm:"not null"`                 //视频发布者的ID
	Content   string `gorm:"not null"`                 //评论内容
	CreatedAt int64  `gorm:"autoCreateTime:milli"`     //评论创建时间
}

关注信息

type Relation struct {
	Id       int64 `gorm:"primaryKey;autoIncrement"` //关注唯一标识符
	UserId   int64 `gorm:"not null"`                 //发起关注者ID
	ToUserId int64 `gorm:"not null"`                 //被关注者的ID
}

使用事务操作

在进行关注操作时,数据库会创建出一条关注信息,并更新关注用户的关注数,和被关注用户的粉丝数。

使用事务操作是因为事务拥有原子性和一致性,如果点赞操作已经添加到数据库,但是用户喜欢数添加失败,那就会导致数据的不一致性。

使用事务的操作有:关注操作、点赞操作、评论操作。因为都涉及到数据表的添加和更新,采用事务可以保证数据的一致性。

使用gorm

//添加赞信息
func FavoriteCreate(fav database.Favorite) error {
	return database.MySqlDb.Transaction(func(db *gorm.DB) error {
		if err := db.Create(&fav).Error; err != nil {
			return err
		}
		if err := FavoriteUpdataNumbers(db, fav.VideoId, fav.ToUserId, fav.UserId, true); err != nil {
			return err
		}
		return nil
	})
}

其中FavoriteUpdataNumbers函数代表更新数字操作,最后的true表示添加赞,false表示取消赞,以此来区分增加和减少操作。

//添加/取消赞操作,统一更新视频点赞数、用户获赞数、用户喜欢数
func FavoriteUpdataNumbers(db *gorm.DB, videoId, videoUserId, userId int64, add bool) error {
	var n int64
	if add {
		n = 1
	} else {
		n = -1
	}

	//更新视频点赞数
	if err := db.Model(&database.Video{}).Where("id = ?", videoId).Update("favorite_count", gorm.Expr("favorite_count + ?", n)).Error; err != nil {
		return err
	}
	//更新video用户获赞数
	if err := db.Model(&database.User{}).Where("id = ?", videoUserId).Update("total_favorite", gorm.Expr("total_favorite + ?", n)).Error; err != nil {
		return err
	}
	//更新token用户喜欢数
	if err := db.Model(&database.User{}).Where("id = ?", userId).Update("favorite_count", gorm.Expr("favorite_count + ?", n)).Error; err != nil {
		return err
	}

	return nil

}

模型转换

本项目设计了两个用于转换的函数,

  • 一个用于把数据库的Video模型转换成用于HTTP响应的MessageVideo,原因是MessageVideo需要视频用户信息,和关注信息(也就是登录用户是否关注了当前页面该视频发布者)。
  • 第二个函数用于把用户信息转化成HTTP响应需要的MessageUser,差别在于关注信息的判断。

使用了转换函数可以把工作的关注点放在数据库操作上,解耦数据库和报文的关系。

func FromDBVideosToMesVideos(videoList_db []database.Video, userId int64) []Video {
	videos := make([]Video, 0, len(videoList_db))
	for _, v := range videoList_db {
		user_db, _ := repository.UserQueryByID(v.UserId)
		videos = append(videos, Video{
			Id:            v.Id,
			Author:        FromDBUsersTOMesUsers(user_db, userId)[0],
			PlayUrl:       v.PlayUrl,
			CoverUrl:      v.CoverUrl,
			FavoriteCount: v.Favorite_count,
			CommentCount:  v.Comment_count,
			IsFavorite:    repository.FavoriteQueryByUserAndVideo(userId, v.Id),
			Title:         v.Title,
		})
	}
	return videos
}

//根据提供的[]dtb.User转化为可以用于message的user类型;用户id,用来判断用户userId是不是已经关注了列表中的用户
func FromDBUsersTOMesUsers(userList_db []database.User, userId int64) []User {
	users := make([]User, 0, len(userList_db))
	for _, u := range userList_db {
		users = append(users, User{
			Id:            u.Id,
			Name:          u.Name,
			FollowCount:   u.Follow_count,
			FollowerCount: u.Follower_count,
			IsFollow:      repository.RelationQueryByUserAndUser(userId, u.Id),
			Avatar:        u.Avatar,
			Signature:     u.Signature,
			TotalFavorite: u.Total_favorite,
			FavoriteCount: u.Favorite_count,
		})

	}
	return users
}