这是我参与「第三届青训营 -后端场」笔记创作活动的的第5篇笔记。
项目设计
三层架构
Repository层
采用单例模式+工厂模式封装数据库操作对象,特定场景下使用事务确保数据一致性。
Service层
业务逻辑层,执行业务逻辑
Controller层
接口层,使用request结构体接受客户端的请求字段,调用serivce层的相应业务接口,返回response消息给客户端。
技术点
中间件JWT鉴权
Jwt的额外字段为用户名及用户id
type JwtAuth struct {
Username string `json:"username"`
Uid int64 `json:"uid"`
jwt.StandardClaims
}
可以使用中间件方便地将jwt中的用户名及id存入gin上下文的全局变量中
func AuthMiddleware(auth Auth, optional bool) func(c *gin.Context) {
return func(c *gin.Context) {
var token string
if token = c.Query("token"); len(token) == 0 {
token = c.PostForm("token")
}
err := auth.ParseToken(token)
if err != nil && !optional {
c.AbortWithStatusJSON(
http.StatusUnauthorized,
controller.Response{
StatusCode: -1,
StatusMsg: "Session Expired, Please Relogin.",
},
)
return
}
c.Set("username", auth.GetUsername())
c.Set("uid", auth.GetUid())
c.Next()
}
}
如果jwt验证失败,则使用AbortWithStatusJSON方法,不再执行函数链后续的函数,并且返回401 Unauthorized。如果jwt验证成功,则在gin.Context中设置相应的全局变量。同时,该中间件工厂函数接受一个参数optional。如果optional为True,则产生一个非强制性鉴权,此时的鉴权中间件起到如果用户登录,则正常验证,并设置全局变量。如果用户未登录,也允许其访问资源,但是不设置全局变量。
视频封面截取
使用ffmpeg截取视频的第一帧作为封面图片
// ExtractFrame 提取videoPath视频的第n帧保存至framePath中
func ExtractFrame(videoPath string, framePath string) error {
err := ffmpeg_go.Input(videoPath).
Output(framePath, ffmpeg_go.KwArgs{
"vframes": "1",
}).
OverWriteOutput().
Run()
return err
}
密码加密
首先为了避免明文保存密码,所以使用哈希算法,仅保存密码的哈希值。保存哈希值后的密码仍有验证功能,但却无法逆向回密码原文。
但是保存哈希值还需要应对彩虹表碰撞问题,有以下对策:
- 每个密码哈希时使用一随机盐值,尽量使没有两个密码的盐值相同
- 使用破解成本更高的算法,如在 2015 年获得 Password Hashing Competition 冠军的Argon2算法
本项目使用argon2算法+随机盐值的组合,用户注册时随机生成一个盐值,在存储时与用户名,密码哈希值一起存储至数据库中。生成盐值时,使用随机的UUID v4作为盐值。UUID v4使用密码学安全的随机数生成器CSPRNG进行生成,可以保证随机性和安全性。