青训营抖音项目实现-1 | 青训营笔记
这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天
一、项目简介
该项目主要是实现一个极简版抖音,根据青训营官方提供的接口,使用golang技术栈实现其后端服务。
二、主要技术
go、gin框架、gorm、mysql、oss对象存储、jwt。
三、主要过程
1、数据表设计
初步实现,一切以简单为主。因此所有的表都没有任何的冗余字段。根据接口的定义我共建立了六张表。
用户表
type User struct {
gorm.Model
Name string
FollowCount int
FollowerCount int
Password string
Salt string
}
gorm.Model是gorm提供的,主要有以下四个字段,其中deleteAt字段可以让删除操作变成更新操作,只需要设置该字段,gorm查询时就会过滤的这条数据。id则是gorm给我们设置的主键。
type Model struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt DeletedAt `gorm:"index"`
}
用户表的设计除了前端需要的六个字段外,我只额外增加了一个salt字段。以及gorm增加的三个字段。salt字段主要是为了不让用户的密码明文存储在数据库加的盐值。
点赞表
type Favorite struct {
gorm.Model
UserID uint //用户id
VideoID uint //视频id
Exist bool //是否存在,避免重复的点赞取消点赞使得该表内容变得很大
}
gorm.Model和之前一样,与前端接口需要的相比,也只增加了一个Exist字段,该字段是为了避免用户重复点赞取消点赞造成数据表大小膨胀。不过增加了这个字段提高的编程的复杂性,需要考虑的事情更多了。
评论表
type Comment struct {
gorm.Model
Content string
CreateTime time.Time
VideoID uint
UserID uint
}
评论表没有任何多余内容,所有字段都是接口需要的。
消息表
type Message struct {
gorm.Model
Content string
UserID uint //发出消息的用户
TargetID uint //目标用户
CreateTime time.Time
}
同样没有任何多余字段。很明显消息字段过于简略,内容也只支持文本,之后可以添加图片,语音,表情包等等。
关系表
type Relation struct {
gorm.Model
UserID uint
TargetID uint
Type int // 1:UserID关注TargetID 2:互相关注
Exist bool // 判断是否存在,避免一个用户频繁关注取消关注造成数据膨胀,并且这样可以使用唯一索引
}
关系表的设计比较复杂。使用Type来确定是否是好友关系,关系有三种:
- A关注B,关系表中存在:一条UserID=A,TargetID=B,Type=1,Exist=1的数据,或者存在两条数据:一条UserID=A,TargetID=B,Type=1,Exist=1以及一条UserID=B,TargetID=A,Type=1,Exist=0的数据
- B关注A,与A关注B类似
- A,B互相关注,两人互为好友。关系表中存在一条UserID=A,TargetID=B,Type=2,Exist=1,以及一条UserID=B,TargetID=A,Type=2,Exist=1的数据 由于关注和取消关注属于很频繁的操作,因此同样有个exist字段。
视频表
type Video struct {
gorm.Model
AuthorID uint //作者id
Title string //视频标题
CommentCount int //评论数
FavoriteCount int //点赞数
PlayURL string //播放地址
CoverURL string //封面地址
}
没有任何多余字段。
2、项目目录
config
读取和存储配置文件
controllers
主要业务逻辑处理
docs
整合swagger产生的目录,swagger后面之后的文章进行介绍
middleware
中间件层,之后的链路追踪可以在这一层实现。gin的中间件也是一个handler,使用中间件的handler最后一条语句一般是c.Next(),这属于设计模式中的责任链模式,中间件只做一部分工作。目前该层使用处理登录用户的验证工作。
models
数据表的gorm映射
routes
gin的url设置
service
该目录没有内容,考虑之后通过将controllers目录下的代码提炼出重复的部分放在这
sql
形成数据库的sql文件
storage
对象存储客户端,目前只实现了aliyun oss
utils
一些常用的工具函数
3、gin框架下访问路径
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
home := r.Group("/douyin")
{
// 首页
home.GET("/feed", controllers.Feed)
home.GET("/user/", controllers.UserInfo)
user := home.Group("/user")
{
user.POST("/register/", controllers.UserRegister)
user.POST("/login/", controllers.UserLogin)
}
publish := home.Group("/publish")
{
publish.POST("/action/", middleware.PostAuthorization, controllers.PublishAction)
publish.GET("/list/", middleware.GetAuthorization, controllers.PublishList)
}
favorite := home.Group("/favorite", middleware.GetAuthorization)
{
favorite.POST("/action/", controllers.FavoriteAction)
favorite.GET("/list/", controllers.FavoriteList)
}
comment := home.Group("/comment", middleware.GetAuthorization)
{
comment.POST("/action/", controllers.CommentAction)
comment.GET("/list/", controllers.CommentList)
}
relation := home.Group("/relation", middleware.GetAuthorization)
{
relation.POST("/action/", controllers.RelationAction)
relation.GET("/follow/list/", controllers.RelationFollowList)
relation.GET("/follower/list/", controllers.RelationFollowerList)
relation.GET("/friend/list/", controllers.RelationFriendList)
}
message := home.Group("/message", middleware.GetAuthorization)
{
message.GET("/chat/", controllers.MessageChat)
message.POST("/action/", controllers.MessageAction)
}
}
gin框架通过group可以给url分组,需要登录才能访问的加上middleware.Authorization。Get和Post之分主要是为了对应不同token的存储位置。