Repository: ACking-you/byte_douyin_project Branch: master Commit: 09a12ca6caf1 Files: 52 Total size: 92.4 KB Directory structure: gitextract_cvanrqe9/ ├── .gitignore ├── LICENSE ├── README.md └── deprecated/ ├── cache/ │ ├── index_map.go │ └── indexmap_test.go ├── config/ │ ├── conf.go │ └── config.toml ├── go.mod ├── go.sum ├── handlers/ │ ├── comment/ │ │ ├── post_comment_handler.go │ │ └── query_commentlist_handler.go │ ├── user_info/ │ │ ├── post_follow_action_handler.go │ │ ├── query_follow_list_handler.go │ │ ├── query_follower_handler.go │ │ └── userinfo_handler.go │ ├── user_login/ │ │ ├── user_login_handler.go │ │ └── user_register_handler.go │ └── video/ │ ├── feed_videolist_handler.go │ ├── post_favor_handler.go │ ├── publish_video_handler.go │ ├── query_favor_videolist_handler.go │ └── query_videolist_handler.go ├── main.go ├── middleware/ │ ├── jwt.go │ ├── jwt_test.go │ ├── normal.go │ ├── password.go │ └── password_test.go ├── models/ │ ├── comment.go │ ├── common.go │ ├── init_db.go │ ├── user_info.go │ ├── user_info_test.go │ ├── user_login.go │ ├── video.go │ └── video_test.go ├── router/ │ └── router_douyin.go ├── service/ │ ├── comment/ │ │ ├── post_comment.go │ │ └── query_comment_list.go │ ├── user_info/ │ │ ├── post_follow_action.go │ │ ├── query_follow_list.go │ │ └── query_follower_list.go │ ├── user_login/ │ │ ├── post_user_login.go │ │ └── query_user_login.go │ └── video/ │ ├── feed_videolist.go │ ├── post_favor_state.go │ ├── post_video.go │ ├── query_favor_videolist.go │ └── query_videolist.go └── util/ ├── comment.go ├── ffmpeg.go └── video.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .idea !deprecated/static/1-1.jpg !deprecated/static/1-1.mp4 deprecated/static/* ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2022 L_B__ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ [写给后续参加字节跳动青训营的同学](https://github.com/ACking-you/byte_douyin_project/issues/10) # 抖音极简版 [![Contributors][contributors-shield]][contributors-url] [![Forks][forks-shield]][forks-url] [![Stargazers][stars-shield]][stars-url] [![Issues][issues-shield]][issues-url] [![MIT License][license-shield]][license-url] [your-project-path]:ACking-you/byte_douyin_project [contributors-shield]: https://img.shields.io/github/contributors/ACking-you/byte_douyin_project.svg?style=flat-square [contributors-url]: https://github.com/ACking-you/byte_douyin_project/graphs/contributors [forks-shield]: https://img.shields.io/github/forks/ACking-you/byte_douyin_project.svg?style=flat-square [forks-url]: https://github.com/ACking-you/byte_douyin_project/network/members [stars-shield]: https://img.shields.io/github/stars/ACking-you/byte_douyin_project.svg?style=flat-square [stars-url]: https://github.com/ACking-you/byte_douyin_project/stargazers [issues-shield]: https://img.shields.io/github/issues/ACking-you/byte_douyin_project.svg?style=flat-square [issues-url]: https://img.shields.io/github/issues/ACking-you/byte_douyin_project.svg [license-shield]: https://img.shields.io/github/license/ACking-you/byte_douyin_project?style=flat-square [license-url]: https://github.com/ACking-you/byte_douyin_project/blob/master/LICENSE * [数据库说明](#数据库说明) * [数据库关系说明](#数据库关系说明) * [数据库建立说明](#数据库建立说明) * [架构说明](#架构说明) * [各模块代码详细说明](#各模块代码详细说明) * [Handlers](#handlers) * [Service](#service) * [Models](#models) * [遇到的问题及对应解决方案](#遇到的问题及对应解决方案) * [返回json数据的完整性和前端要求的一致性](#返回json数据的完整性和前端要求的一致性) * [is\_favorite和is\_follow字段的更新](#is_favorite和is_follow字段的更新) * [视频的保存和封面的切片](#视频的保存和封面的切片) * [视频的保存](#视频的保存) * [封面的截取](#封面的截取) * [可改进的地方](#可改进的地方) * [项目运行](#项目运行) ## 数据库说明 ![database.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/892fbbe46695467ebe4fb4a12ebd412e~tplv-k3u1fbpfcp-watermark.image?) > 单纯看上面的图会感觉很混乱,现在我们来将关系拆解。 ### 数据库关系说明 **关系图如下:** ![database_relation.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f08918db1ea84126bc21d23fe9401a75~tplv-k3u1fbpfcp-watermark.image?) > 所有的表都有自己的id主键为唯一的标识。 user_logins:存下用户的用户名和密码 user_infos:存下用户的基本信息 videos:存下视频的基本信息 comment:存下每个评论的基本信息 **具体的关系索引:** 所有的一对一和一对多关系,只需要在一个表中加入对方的id索引。 * 比如user_infos和user_logins的一对一关系,在user_logins中加入user_id字段设为外键存储user_infos中对应的行的id信息。 * 比如user_infos和和videos的一对多关系,在videos中加入user_id字段设为外键存储user_infos中对应的行的id信息。 所有的多对多关系,需要多建立一张表,用该表作为媒介存储两个对象的id作为关系的产生,而它们各自表中不需要再存下额外的字段。 * 比如user_infos和videos的多对多关系,创建一张user_favor_videos中间表,然后将该表的字段均设为外键,分别存下user_infos和videos对应行的id。如id为1的用户对id为2的视频点了个赞,那么就把这个1和2存入中间表user_favor_videos即可。 ### 数据库建立说明 数据库各表的建立无需自己实现额外的建表操作,一切都由gorm框架自动建表,具体逻辑在models层的代码中。 > gorm官方文档链接:[链接](https://gorm.io/zh_CN/docs/index.html) 建表和初始化操作由init_db.go来执行: ```go func InitDB() { var err error DB, err = gorm.Open(mysql.Open(config.DBConnectString()), &gorm.Config{ PrepareStmt: true, //缓存预编译命令 SkipDefaultTransaction: true, //禁用默认事务操作 //Logger: logger.Default.LogMode(logger.Info), //打印sql语句 }) if err != nil { panic(err) } err = DB.AutoMigrate(&UserInfo{}, &Video{}, &Comment{}, &UserLogin{}) if err != nil { panic(err) } } ``` ## 架构说明 ![architecture.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ae11d82b8de74787a258ef36f4cf2557~tplv-k3u1fbpfcp-watermark.image?) > 以用户登录为例共需要经过以下过程: 1. 进入中间件SHAMiddleWare内的函数逻辑,得到password明文加密后再设置password。具体需要调用gin.Context的Set方法设置password。随后调用next()方法继续下层路由。 2. 进入UserLoginHandler函数逻辑,获取username,并调用gin.Context的Get方法得到中间件设置的password。再调用service层的QueryUserLogin函数。 3. 进入QueryUserLogin函数逻辑,执行三个过程:checkNum,prepareData,packData。也就是检查参数、准备数据、打包数据,准备数据的过程中会调用models层的UserLoginDAO。 4. 进入UserLoginDAO的逻辑,执行最终的数据库请求过程,返回给上层。 ### 各模块代码详细说明 我开发的过程中是以单个函数为单个文件进行开发,所以代码会比较长,故我根据数据库内的模型对函数文件进行了如下分包: ![handlers.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6dc222793d6f4038b1bf2435053bfee4~tplv-k3u1fbpfcp-watermark.image?) service层的分包也是一样的。 #### Handlers 对于handlers层级的所有函数实现有如下规范: 所有的逻辑由代理对象进行,完成以下两个逻辑 1. 解析得到参数。 2. 开始调用下层逻辑。 例如一个关注动作触发的逻辑: ```go NewProxyPostFollowAction().Do() //其中Do主要包含以下两个逻辑,对应两个方法 p.parseNum() //解析参数 p.startAction() //开始调用下层逻辑 ``` #### Service 对于service层级的函数实现由如下规范: 同样由一个代理对象进行,完成以下三个或两个逻辑 当上层需要返回数据信息,则进行三个逻辑: 1. 检查参数。 2. 准备数据。 3. 打包数据。 当上层不需要返回数据信息,则进行两个逻辑: 1. 检查参数。 2. 执行上层指定的动作。 例如关注动作在service层的逻辑属于第二类: ```go NewPostFollowActionFlow(...).Do() //Do中包含以下两个逻辑 p.checkNum() //检查参数 p.publish() //执行动作 ``` #### Models 对于models层的各个操作,没有像service和handler层针对前端发来的请求就行对应的处理,models层是面向于数据库的增删改查,不需要考虑和上层的交互。 而service层根据上层的需要来调用models层的不同代码请求数据库内的内容。 ## 遇到的问题及对应解决方案 ### 返回json数据的完整性和前端要求的一致性 由于数据库内的一对一、一对多、多对多关系是根据id进行映射,所以models层请求得到的字段并不包含前端所需要的直接数据,比如前端要求Comment结构中需要包含UserInfo,而我的Comment结构如下: ```go type Comment struct { Id int64 `json:"id"` UserInfoId int64 `json:"-"` //用于一对多关系的id VideoId int64 `json:"-"` //一对多,视频对评论 User UserInfo `json:"user" gorm:"-"` Content string `json:"content"` CreatedAt time.Time `json:"-"` CreateDate string `json:"create_date" gorm:"-"` } ``` 很明显,为了与数据库中设计的表一一对应,在原数据的基础上加了几个字段,且在gorm屏蔽了User字段,所以service调用models层得到是Comment数据中User字段还未被填充,还需再填充这部分内容,好在由对应的UserId,故可以正确填充该字段。 为了重用以及不破坏代码的一致性,将填充逻辑写入util包内,比如以上的字段填充函数,同时前端要求的日期格式也能够按要求设置: ```go func FillCommentListFields(comments *[]*models.Comment) error { size := len(*comments) if comments == nil || size == 0 { return errors.New("util.FillCommentListFields comments为空") } dao := models.NewUserInfoDAO() for _, v := range *comments { _ = dao.QueryUserInfoById(v.UserInfoId, &v.User) //填充这条评论的作者信息 v.CreateDate = v.CreatedAt.Format("1-2") //转为前端要求的日期格式 } return nil } ``` 这里举了Comment这一个例子,其他的Video也是同理。 ### is_favorite和is_follow字段的更新 每次为视频点赞都会在数据库的user_favor_videos表中加入用户的id和视频的id,很明显is_favorite字段是针对每个用户来判断的,而我所设计的数据库中的videos表也是包含这个字段的,但这个字段很明显不能直接进行复用,而是需要每次判断用户和此视频的关系来重新更新。 这个更新过程放入util包的填充函数中即可,为了点赞过程的迅速响应,我采取了nosql的方式存储了这个点赞的映射,也就是userId和videoId的映射,也就是用nosql代替了这个中间表的功效。 具体代码逻辑在cache包内。 ### 视频的保存和封面的切片 #### 视频的保存 在本地建立static文件夹存储视频和封面图片。 具体逻辑如下: 1. 检查视频格式 2. 根据userId和该作者发布的视频数量产生唯一的名称,如id为1的用户发布了0个视频,那么本次上传的名称为1-0.mp4 3. 截取第一帧画面作为封面 4. 保存视频基本信息到数据库(包括视频链接和封面链接 #### 封面的截取 使用ffmpeg调用命令行对视频进行截取。 设计ffmpeg请求类Video2Image,通过对它内部的参数设置来构造对应的命令行字符串。具体请看util包内的ffmpeg.go的实现。 由于我设计的命令请求字符串是直接的一行字符串,而go语言exec包里面的Command函数执行所需的仅仅是一个个参数。 所以此处我想到用cgo直接调用 system(args)来解决。 代码如下: ```go //#include //int startCmd(const char* cmd){ // return system(cmd); //} import "C" func (v *Video2Image) ExecCommand(cmd string) error { if v.debug { log.Println(cmd) } cCmd := C.CString(cmd) defer C.free(unsafe.Pointer(cCmd)) status := C.startCmd(cCmd) if status != 0 { return errors.New("视频切截图失败") } return nil } ``` ## 可改进的地方 1. 写到后面发现很多mysql的数据可以用redis优化。 2. 很多执行逻辑可以通过并行优化。 3. 路由分组可以更为详实。 4. ... ## 项目运行 > 本项目运行不需要手动建表,项目启动后会自动建表。 **运行所需环境**: * mysql 5.7及以上 * redis 5.0.14及以上 * ffmepg(已放入lib自带,用于对视频切片得到封面 * 需要gcc环境(主要用于cgo,windows请将mingw-w64设置到环境变量 **运行需要更改配置**: > 进入config目录更改对应的mysql、redis、server、path信息。 * mysql:mysql相关的配置信息 * redis:redis相关配置信息 * server:当前服务器(当前启动的机器)的配置信息,用于生成对应的视频和图片链接 * path:其中ffmpeg_path为lib里的文件路径,static_source_path为本项目的static目录,这里请根据本地的绝对路径进行更改 > 完成config配置文件的更改后,需要再更改conf.go里的解析文件路径为config.toml文件的绝对路径,内容如下: > > ```go > if _, err := toml.DecodeFile("你的绝对路径\\config.toml", &Info); err != nil { > panic(err) > } > ``` > > **运行所需命令**: ```shell cd .\byte_douyin_project\ go run main.go ``` ================================================ FILE: deprecated/cache/index_map.go ================================================ package cache import ( "context" "fmt" "github.com/ACking-you/byte_douyin_project/config" "github.com/go-redis/redis/v8" ) // 用户id->被点赞的视频id集合->是否含有该视频id var ctx = context.Background() var rdb *redis.Client const ( favor = "favor" relation = "relation" ) func init() { rdb = redis.NewClient( &redis.Options{ Addr: fmt.Sprintf("%s:%d", config.Global.RDB.IP, config.Global.RDB.Port), Password: "", //没有设置密码 DB: config.Global.RDB.Database, }) } var ( proxyIndexOperation ProxyIndexMap ) type ProxyIndexMap struct { } func NewProxyIndexMap() *ProxyIndexMap { return &proxyIndexOperation } // UpdateVideoFavorState 更新点赞状态,state:true为点赞,false为取消点赞 func (i *ProxyIndexMap) UpdateVideoFavorState(userId int64, videoId int64, state bool) { key := fmt.Sprintf("%s:%d", favor, userId) if state { rdb.SAdd(ctx, key, videoId) return } rdb.SRem(ctx, key, videoId) } // GetVideoFavorState 得到点赞状态 func (i *ProxyIndexMap) GetVideoFavorState(userId int64, videoId int64) bool { key := fmt.Sprintf("%s:%d", favor, userId) ret := rdb.SIsMember(ctx, key, videoId) return ret.Val() } // UpdateUserRelation 更新点赞状态,state:true为点关注,false为取消关注 func (i *ProxyIndexMap) UpdateUserRelation(userId int64, followId int64, state bool) { key := fmt.Sprintf("%s:%d", relation, userId) if state { rdb.SAdd(ctx, key, followId) return } rdb.SRem(ctx, key, followId) } // GetUserRelation 得到关注状态 func (i *ProxyIndexMap) GetUserRelation(userId int64, followId int64) bool { key := fmt.Sprintf("%s:%d", relation, userId) ret := rdb.SIsMember(ctx, key, followId) return ret.Val() } ================================================ FILE: deprecated/cache/indexmap_test.go ================================================ package cache import ( "fmt" "testing" ) func TestProxyIndexMap_UpdateUserRelation(t *testing.T) { NewProxyIndexMap().UpdateUserRelation(1, 2, true) fmt.Println(NewProxyIndexMap().GetUserRelation(1, 3)) } func TestProxyIndexMap_GetVideoFavorState(t *testing.T) { fmt.Println(NewProxyIndexMap().GetVideoFavorState(1, 2)) NewProxyIndexMap().UpdateVideoFavorState(1, 2, true) fmt.Println(NewProxyIndexMap().GetVideoFavorState(1, 2)) } ================================================ FILE: deprecated/config/conf.go ================================================ package config import ( "fmt" "github.com/BurntSushi/toml" "log" "os" "os/exec" "path/filepath" "strings" ) type Mysql struct { Host string Port int Database string Username string Password string Charset string ParseTime bool `toml:"parse_time"` Loc string } type Redis struct { IP string Port int Database int } type Server struct { IP string Port int } type Path struct { FfmpegPath string `toml:"ffmpeg_path"` StaticSourcePath string `toml:"static_source_path"` } type Config struct { DB Mysql `toml:"mysql"` RDB Redis `toml:"redis"` Server `toml:"server"` Path `toml:"path"` } var Global Config func ensurePathValid() { var err error if _, err = os.Stat(Global.StaticSourcePath); os.IsNotExist(err) { if err = os.Mkdir(Global.StaticSourcePath, 0755); err != nil { log.Fatalf("mkdir error:path %s", Global.StaticSourcePath) } } if _, err = os.Stat(Global.FfmpegPath); os.IsNotExist(err) { if _, err = exec.Command("ffmpeg", "-version").Output(); err != nil { log.Fatalf("ffmpeg not valid %s", Global.FfmpegPath) } else { Global.FfmpegPath = "ffmpeg" } } else { Global.FfmpegPath, err = filepath.Abs(Global.FfmpegPath) if err != nil { log.Fatalln("get abs path failed:", Global.FfmpegPath) } } //把资源路径转化为绝对路径,防止调用ffmpeg命令失效 Global.StaticSourcePath, err = filepath.Abs(Global.StaticSourcePath) if err != nil { log.Fatalln("get abs path failed:", Global.StaticSourcePath) } } //包初始化加载时候会调用的函数 func init() { if _, err := toml.DecodeFile("./config/config.toml", &Global); err != nil { panic(err) } //去除左右的空格 strings.Trim(Global.Server.IP, " ") strings.Trim(Global.RDB.IP, " ") strings.Trim(Global.DB.Host, " ") //保证路径正常 ensurePathValid() } // DBConnectString 填充得到数据库连接字符串 func DBConnectString() string { arg := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=%v&loc=%s", Global.DB.Username, Global.DB.Password, Global.DB.Host, Global.DB.Port, Global.DB.Database, Global.DB.Charset, Global.DB.ParseTime, Global.DB.Loc) log.Println(arg) return arg } ================================================ FILE: deprecated/config/config.toml ================================================ #关系型数据库配置 [mysql] host = "127.0.0.1" port = 3306 database = "douyin" username = "root" password = "123" charset = "utf8mb4" parse_time = true loc = "Local" #nosql配置,用于存储每个用户是否对某个视频点赞,以及关注了某个人(主要用于反馈前端情况,比如点了赞后会变成红心 [redis] host = "127.0.0.1" port = 49153 database = 0 #记录当前服务器的ip和启动端口号,当前服务器的ip用于生成对应的视频链接地址 [server] ip = "127.0.0.1" port = 8080 #用于保存资源的路径,以及用于截图工具的路径(截图工具放在lib目录 [path] ffmpeg_path = "./lib/ffmpeg.exe" static_source_path = "./static" ================================================ FILE: deprecated/go.mod ================================================ module github.com/ACking-you/byte_douyin_project go 1.16 require ( github.com/BurntSushi/toml v1.1.0 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/gin-gonic/gin v1.7.7 github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/golang/protobuf v1.5.2 // indirect gorm.io/driver/mysql v1.3.3 gorm.io/gorm v1.23.5 ) ================================================ FILE: deprecated/go.sum ================================================ github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas= github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gorm.io/driver/mysql v1.3.3 h1:jXG9ANrwBc4+bMvBcSl8zCfPBaVoPyBEBshA8dA93X8= gorm.io/driver/mysql v1.3.3/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U= gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= gorm.io/gorm v1.23.5 h1:TnlF26wScKSvknUC/Rn8t0NLLM22fypYBlvj1+aH6dM= gorm.io/gorm v1.23.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= ================================================ FILE: deprecated/handlers/comment/post_comment_handler.go ================================================ package comment import ( "errors" "fmt" "github.com/ACking-you/byte_douyin_project/models" "github.com/ACking-you/byte_douyin_project/service/comment" "github.com/gin-gonic/gin" "net/http" "strconv" ) type PostCommentResponse struct { models.CommonResponse *comment.Response } func PostCommentHandler(c *gin.Context) { NewProxyPostCommentHandler(c).Do() } type ProxyPostCommentHandler struct { *gin.Context videoId int64 userId int64 commentId int64 actionType int64 commentText string } func NewProxyPostCommentHandler(context *gin.Context) *ProxyPostCommentHandler { return &ProxyPostCommentHandler{Context: context} } func (p *ProxyPostCommentHandler) Do() { //解析参数 if err := p.parseNum(); err != nil { p.SendError(err.Error()) return } //正式调用Service层 commentRes, err := comment.PostComment(p.userId, p.videoId, p.commentId, p.actionType, p.commentText) if err != nil { p.SendError(err.Error()) return } //成功返回 p.SendOk(commentRes) } func (p *ProxyPostCommentHandler) parseNum() error { rawUserId, _ := p.Get("user_id") userId, ok := rawUserId.(int64) if !ok { return errors.New("userId解析出错") } p.userId = userId rawVideoId := p.Query("video_id") videoId, err := strconv.ParseInt(rawVideoId, 10, 64) if err != nil { return err } p.videoId = videoId //根据actionType解析对应的可选参数 rawActionType := p.Query("action_type") actionType, err := strconv.ParseInt(rawActionType, 10, 64) switch actionType { case comment.CREATE: p.commentText = p.Query("comment_text") case comment.DELETE: p.commentId, err = strconv.ParseInt(p.Query("comment_id"), 10, 64) if err != nil { return err } default: return fmt.Errorf("未定义的行为%d", actionType) } p.actionType = actionType return nil } func (p *ProxyPostCommentHandler) SendError(msg string) { p.JSON(http.StatusOK, PostCommentResponse{ CommonResponse: models.CommonResponse{StatusCode: 1, StatusMsg: msg}, Response: &comment.Response{}}) } func (p *ProxyPostCommentHandler) SendOk(comment *comment.Response) { p.JSON(http.StatusOK, PostCommentResponse{ CommonResponse: models.CommonResponse{StatusCode: 0}, Response: comment, }) } ================================================ FILE: deprecated/handlers/comment/query_commentlist_handler.go ================================================ package comment import ( "errors" "github.com/ACking-you/byte_douyin_project/handlers/video" "github.com/ACking-you/byte_douyin_project/models" "github.com/ACking-you/byte_douyin_project/service/comment" "github.com/gin-gonic/gin" "net/http" "strconv" ) type ListResponse struct { models.CommonResponse *comment.List } func QueryCommentListHandler(c *gin.Context) { NewProxyCommentListHandler(c).Do() } type ProxyCommentListHandler struct { *gin.Context videoId int64 userId int64 } func NewProxyCommentListHandler(context *gin.Context) *ProxyCommentListHandler { return &ProxyCommentListHandler{Context: context} } func (p *ProxyCommentListHandler) Do() { //解析参数 if err := p.parseNum(); err != nil { p.SendError(err.Error()) return } //正式调用 commentList, err := comment.QueryCommentList(p.userId, p.videoId) if err != nil { p.SendError(err.Error()) return } //成功返回 p.SendOk(commentList) } func (p *ProxyCommentListHandler) parseNum() error { rawUserId, _ := p.Get("user_id") userId, ok := rawUserId.(int64) if !ok { return errors.New("userId解析出错") } p.userId = userId rawVideoId := p.Query("video_id") videoId, err := strconv.ParseInt(rawVideoId, 10, 64) if err != nil { return err } p.videoId = videoId return nil } func (p *ProxyCommentListHandler) SendError(msg string) { p.JSON(http.StatusOK, video.FavorVideoListResponse{ CommonResponse: models.CommonResponse{StatusCode: 1, StatusMsg: msg}}) } func (p *ProxyCommentListHandler) SendOk(commentList *comment.List) { p.JSON(http.StatusOK, ListResponse{CommonResponse: models.CommonResponse{StatusCode: 0}, List: commentList, }) } ================================================ FILE: deprecated/handlers/user_info/post_follow_action_handler.go ================================================ package user_info import ( "errors" "github.com/ACking-you/byte_douyin_project/models" "github.com/ACking-you/byte_douyin_project/service/user_info" "github.com/gin-gonic/gin" "net/http" "strconv" ) func PostFollowActionHandler(c *gin.Context) { NewProxyPostFollowAction(c).Do() } type ProxyPostFollowAction struct { *gin.Context userId int64 followId int64 actionType int } func NewProxyPostFollowAction(context *gin.Context) *ProxyPostFollowAction { return &ProxyPostFollowAction{Context: context} } func (p *ProxyPostFollowAction) Do() { var err error if err = p.prepareNum(); err != nil { p.SendError(err.Error()) return } if err = p.startAction(); err != nil { //当错误为model层发生的,那么就是重复键值的插入了 if errors.Is(err, user_info.ErrIvdAct) || errors.Is(err, user_info.ErrIvdFolUsr) { p.SendError(err.Error()) } else { p.SendError("请勿重复关注") } return } p.SendOk("操作成功") } func (p *ProxyPostFollowAction) prepareNum() error { rawUserId, _ := p.Get("user_id") userId, ok := rawUserId.(int64) if !ok { return errors.New("userId解析出错") } p.userId = userId //解析需要关注的id followId := p.Query("to_user_id") parseInt, err := strconv.ParseInt(followId, 10, 64) if err != nil { return err } p.followId = parseInt //解析action_type actionType := p.Query("action_type") parseInt, err = strconv.ParseInt(actionType, 10, 32) if err != nil { return err } p.actionType = int(parseInt) return nil } func (p *ProxyPostFollowAction) startAction() error { err := user_info.PostFollowAction(p.userId, p.followId, p.actionType) if err != nil { return err } return nil } func (p *ProxyPostFollowAction) SendError(msg string) { p.JSON(http.StatusOK, models.CommonResponse{StatusCode: 1, StatusMsg: msg}) } func (p *ProxyPostFollowAction) SendOk(msg string) { p.JSON(http.StatusOK, models.CommonResponse{StatusCode: 1, StatusMsg: msg}) } ================================================ FILE: deprecated/handlers/user_info/query_follow_list_handler.go ================================================ package user_info import ( "errors" "github.com/ACking-you/byte_douyin_project/models" "github.com/ACking-you/byte_douyin_project/service/user_info" "github.com/gin-gonic/gin" "net/http" ) type FollowListResponse struct { models.CommonResponse *user_info.FollowList } func QueryFollowListHandler(c *gin.Context) { NewProxyQueryFollowList(c).Do() } type ProxyQueryFollowList struct { *gin.Context userId int64 *user_info.FollowList } func NewProxyQueryFollowList(context *gin.Context) *ProxyQueryFollowList { return &ProxyQueryFollowList{Context: context} } func (p *ProxyQueryFollowList) Do() { var err error if err = p.parseNum(); err != nil { p.SendError(err.Error()) return } if err = p.prepareData(); err != nil { p.SendError(err.Error()) return } p.SendOk("请求成功") } func (p *ProxyQueryFollowList) parseNum() error { rawUserId, _ := p.Get("user_id") userId, ok := rawUserId.(int64) if !ok { return errors.New("userId解析出错") } p.userId = userId return nil } func (p *ProxyQueryFollowList) prepareData() error { list, err := user_info.QueryFollowList(p.userId) if err != nil { return err } p.FollowList = list return nil } func (p *ProxyQueryFollowList) SendError(msg string) { p.JSON(http.StatusOK, FollowListResponse{ CommonResponse: models.CommonResponse{StatusCode: 1, StatusMsg: msg}, }) } func (p *ProxyQueryFollowList) SendOk(msg string) { p.JSON(http.StatusOK, FollowListResponse{ CommonResponse: models.CommonResponse{StatusCode: 0, StatusMsg: msg}, FollowList: p.FollowList, }) } ================================================ FILE: deprecated/handlers/user_info/query_follower_handler.go ================================================ package user_info import ( "errors" "github.com/ACking-you/byte_douyin_project/models" user_info2 "github.com/ACking-you/byte_douyin_project/service/user_info" "github.com/gin-gonic/gin" "net/http" ) type FollowerListResponse struct { models.CommonResponse *user_info2.FollowerList } func QueryFollowerHandler(c *gin.Context) { NewProxyQueryFollowerHandler(c).Do() } type ProxyQueryFollowerHandler struct { *gin.Context userId int64 *user_info2.FollowerList } func NewProxyQueryFollowerHandler(context *gin.Context) *ProxyQueryFollowerHandler { return &ProxyQueryFollowerHandler{Context: context} } func (p *ProxyQueryFollowerHandler) Do() { var err error if err = p.parseNum(); err != nil { p.SendError(err.Error()) return } if err = p.prepareData(); err != nil { if errors.Is(err, user_info2.ErrUserNotExist) { p.SendError(err.Error()) } else { p.SendError("准备数据出错") } return } p.SendOk("成功") } func (p *ProxyQueryFollowerHandler) parseNum() error { rawUserId, _ := p.Get("user_id") userId, ok := rawUserId.(int64) if !ok { return errors.New("userId解析出错") } p.userId = userId return nil } func (p *ProxyQueryFollowerHandler) prepareData() error { list, err := user_info2.QueryFollowerList(p.userId) if err != nil { return err } p.FollowerList = list return nil } func (p *ProxyQueryFollowerHandler) SendError(msg string) { p.JSON(http.StatusOK, FollowerListResponse{ CommonResponse: models.CommonResponse{ StatusCode: 1, StatusMsg: msg, }, }) } func (p *ProxyQueryFollowerHandler) SendOk(msg string) { p.JSON(http.StatusOK, FollowerListResponse{ CommonResponse: models.CommonResponse{ StatusCode: 1, StatusMsg: msg, }, FollowerList: p.FollowerList, }) } ================================================ FILE: deprecated/handlers/user_info/userinfo_handler.go ================================================ package user_info import ( "errors" models2 "github.com/ACking-you/byte_douyin_project/models" "github.com/gin-gonic/gin" "net/http" ) type UserResponse struct { models2.CommonResponse User *models2.UserInfo `json:"user"` } func UserInfoHandler(c *gin.Context) { p := NewProxyUserInfo(c) //得到上层中间件根据token解析的userId rawId, ok := c.Get("user_id") if !ok { p.UserInfoError("解析userId出错") return } err := p.DoQueryUserInfoByUserId(rawId) if err != nil { p.UserInfoError(err.Error()) } } type ProxyUserInfo struct { c *gin.Context } func NewProxyUserInfo(c *gin.Context) *ProxyUserInfo { return &ProxyUserInfo{c: c} } func (p *ProxyUserInfo) DoQueryUserInfoByUserId(rawId interface{}) error { userId, ok := rawId.(int64) if !ok { return errors.New("解析userId失败") } //由于得到userinfo不需要组装model层的数据,所以直接调用model层的接口 userinfoDAO := models2.NewUserInfoDAO() var userInfo models2.UserInfo err := userinfoDAO.QueryUserInfoById(userId, &userInfo) if err != nil { return err } p.UserInfoOk(&userInfo) return nil } func (p *ProxyUserInfo) UserInfoError(msg string) { p.c.JSON(http.StatusOK, UserResponse{ CommonResponse: models2.CommonResponse{StatusCode: 1, StatusMsg: msg}, }) } func (p *ProxyUserInfo) UserInfoOk(user *models2.UserInfo) { p.c.JSON(http.StatusOK, UserResponse{ CommonResponse: models2.CommonResponse{StatusCode: 0}, User: user, }) } ================================================ FILE: deprecated/handlers/user_login/user_login_handler.go ================================================ package user_login import ( "github.com/ACking-you/byte_douyin_project/models" "github.com/ACking-you/byte_douyin_project/service/user_login" "github.com/gin-gonic/gin" "net/http" ) type UserLoginResponse struct { models.CommonResponse *user_login.LoginResponse } func UserLoginHandler(c *gin.Context) { username := c.Query("username") raw, _ := c.Get("password") password, ok := raw.(string) if !ok { c.JSON(http.StatusOK, UserLoginResponse{ CommonResponse: models.CommonResponse{ StatusCode: 1, StatusMsg: "密码解析错误", }, }) } userLoginResponse, err := user_login.QueryUserLogin(username, password) //用户不存在返回对应的错误 if err != nil { c.JSON(http.StatusOK, UserLoginResponse{ CommonResponse: models.CommonResponse{StatusCode: 1, StatusMsg: err.Error()}, }) return } //用户存在,返回相应的id和token c.JSON(http.StatusOK, UserLoginResponse{ CommonResponse: models.CommonResponse{StatusCode: 0}, LoginResponse: userLoginResponse, }) } ================================================ FILE: deprecated/handlers/user_login/user_register_handler.go ================================================ package user_login import ( "github.com/ACking-you/byte_douyin_project/models" user_login2 "github.com/ACking-you/byte_douyin_project/service/user_login" "github.com/gin-gonic/gin" "net/http" ) type UserRegisterResponse struct { models.CommonResponse *user_login2.LoginResponse } func UserRegisterHandler(c *gin.Context) { username := c.Query("username") rawVal, _ := c.Get("password") password, ok := rawVal.(string) if !ok { c.JSON(http.StatusOK, UserRegisterResponse{ CommonResponse: models.CommonResponse{ StatusCode: 1, StatusMsg: "密码解析出错", }, }) return } registerResponse, err := user_login2.PostUserLogin(username, password) if err != nil { c.JSON(http.StatusOK, UserRegisterResponse{ CommonResponse: models.CommonResponse{ StatusCode: 1, StatusMsg: err.Error(), }, }) return } c.JSON(http.StatusOK, UserRegisterResponse{ CommonResponse: models.CommonResponse{StatusCode: 0}, LoginResponse: registerResponse, }) } ================================================ FILE: deprecated/handlers/video/feed_videolist_handler.go ================================================ package video import ( "errors" "github.com/ACking-you/byte_douyin_project/middleware" "github.com/ACking-you/byte_douyin_project/models" "github.com/ACking-you/byte_douyin_project/service/video" "github.com/gin-gonic/gin" "net/http" "strconv" "time" ) type FeedResponse struct { models.CommonResponse *video.FeedVideoList } func FeedVideoListHandler(c *gin.Context) { p := NewProxyFeedVideoList(c) token, ok := c.GetQuery("token") //无登录状态 if !ok { err := p.DoNoToken() if err != nil { p.FeedVideoListError(err.Error()) } return } //有登录状态 err := p.DoHasToken(token) if err != nil { p.FeedVideoListError(err.Error()) } } type ProxyFeedVideoList struct { *gin.Context } func NewProxyFeedVideoList(c *gin.Context) *ProxyFeedVideoList { return &ProxyFeedVideoList{Context: c} } // DoNoToken 未登录的视频流推送处理 func (p *ProxyFeedVideoList) DoNoToken() error { rawTimestamp := p.Query("latest_time") var latestTime time.Time intTime, err := strconv.ParseInt(rawTimestamp, 10, 64) if err == nil { latestTime = time.Unix(0, intTime*1e6) //注意:前端传来的时间戳是以ms为单位的 } videoList, err := video.QueryFeedVideoList(0, latestTime) if err != nil { return err } p.FeedVideoListOk(videoList) return nil } // DoHasToken 如果是登录状态,则生成UserId字段 func (p *ProxyFeedVideoList) DoHasToken(token string) error { //解析成功 if claim, ok := middleware.ParseToken(token); ok { //token超时 if time.Now().Unix() > claim.ExpiresAt { return errors.New("token超时") } rawTimestamp := p.Query("latest_time") var latestTime time.Time intTime, err := strconv.ParseInt(rawTimestamp, 10, 64) if err != nil { latestTime = time.Unix(0, intTime*1e6) //注意:前端传来的时间戳是以ms为单位的 } //调用service层接口 videoList, err := video.QueryFeedVideoList(claim.UserId, latestTime) if err != nil { return err } p.FeedVideoListOk(videoList) return nil } //解析失败 return errors.New("token不正确") } func (p *ProxyFeedVideoList) FeedVideoListError(msg string) { p.JSON(http.StatusOK, FeedResponse{CommonResponse: models.CommonResponse{ StatusCode: 1, StatusMsg: msg, }}) } func (p *ProxyFeedVideoList) FeedVideoListOk(videoList *video.FeedVideoList) { p.JSON(http.StatusOK, FeedResponse{ CommonResponse: models.CommonResponse{ StatusCode: 0, }, FeedVideoList: videoList, }, ) } ================================================ FILE: deprecated/handlers/video/post_favor_handler.go ================================================ package video import ( "errors" "github.com/ACking-you/byte_douyin_project/models" "github.com/ACking-you/byte_douyin_project/service/video" "github.com/gin-gonic/gin" "net/http" "strconv" ) func PostFavorHandler(c *gin.Context) { NewProxyPostFavorHandler(c).Do() } type ProxyPostFavorHandler struct { *gin.Context userId int64 videoId int64 actionType int64 } func NewProxyPostFavorHandler(c *gin.Context) *ProxyPostFavorHandler { return &ProxyPostFavorHandler{Context: c} } func (p *ProxyPostFavorHandler) Do() { //解析参数 if err := p.parseNum(); err != nil { p.SendError(err.Error()) return } //正式调用 err := video.PostFavorState(p.userId, p.videoId, p.actionType) if err != nil { p.SendError(err.Error()) return } //成功返回 p.SendOk() } func (p *ProxyPostFavorHandler) parseNum() error { //解析userId rawUserId, _ := p.Get("user_id") userId, ok := rawUserId.(int64) if !ok { return errors.New("userId解析出错") } rawVideoId := p.Query("video_id") videoId, err := strconv.ParseInt(rawVideoId, 10, 64) if err != nil { return err } rawActionType := p.Query("action_type") actionType, err := strconv.ParseInt(rawActionType, 10, 64) if err != nil { return err } p.videoId = videoId p.actionType = actionType p.userId = userId return nil } func (p *ProxyPostFavorHandler) SendError(msg string) { p.JSON(http.StatusOK, models.CommonResponse{StatusCode: 1, StatusMsg: msg}) } func (p *ProxyPostFavorHandler) SendOk() { p.JSON(http.StatusOK, models.CommonResponse{StatusCode: 0}) } ================================================ FILE: deprecated/handlers/video/publish_video_handler.go ================================================ package video import ( "github.com/ACking-you/byte_douyin_project/config" "github.com/ACking-you/byte_douyin_project/models" "github.com/ACking-you/byte_douyin_project/service/video" util2 "github.com/ACking-you/byte_douyin_project/util" "github.com/gin-gonic/gin" "net/http" "path/filepath" ) var ( videoIndexMap = map[string]struct{}{ ".mp4": {}, ".avi": {}, ".wmv": {}, ".flv": {}, ".mpeg": {}, ".mov": {}, } pictureIndexMap = map[string]struct{}{ ".jpg": {}, ".bmp": {}, ".png": {}, ".svg": {}, } ) // PublishVideoHandler 发布视频,并截取一帧画面作为封面 func PublishVideoHandler(c *gin.Context) { //准备参数 rawId, _ := c.Get("user_id") userId, ok := rawId.(int64) if !ok { PublishVideoError(c, "解析UserId出错") return } title := c.PostForm("title") form, err := c.MultipartForm() if err != nil { PublishVideoError(c, err.Error()) return } //支持多文件上传 files := form.File["data"] for _, file := range files { suffix := filepath.Ext(file.Filename) //得到后缀 if _, ok := videoIndexMap[suffix]; !ok { //判断是否为视频格式 PublishVideoError(c, "不支持的视频格式") continue } name := util2.NewFileName(userId) //根据userId得到唯一的文件名 filename := name + suffix savePath := filepath.Join(config.Global.StaticSourcePath, filename) err = c.SaveUploadedFile(file, savePath) if err != nil { PublishVideoError(c, err.Error()) continue } //截取一帧画面作为封面 err = util2.SaveImageFromVideo(name, false) if err != nil { PublishVideoError(c, err.Error()) continue } //数据库持久化 err := video.PostVideo(userId, filename, name+util2.GetDefaultImageSuffix(), title) if err != nil { PublishVideoError(c, err.Error()) continue } PublishVideoOk(c, file.Filename+"上传成功") } } func PublishVideoError(c *gin.Context, msg string) { c.JSON(http.StatusOK, models.CommonResponse{StatusCode: 1, StatusMsg: msg}) } func PublishVideoOk(c *gin.Context, msg string) { c.JSON(http.StatusOK, models.CommonResponse{StatusCode: 0, StatusMsg: msg}) } ================================================ FILE: deprecated/handlers/video/query_favor_videolist_handler.go ================================================ package video import ( "errors" "github.com/ACking-you/byte_douyin_project/models" "github.com/ACking-you/byte_douyin_project/service/video" "github.com/gin-gonic/gin" "net/http" ) type FavorVideoListResponse struct { models.CommonResponse *video.FavorList } func QueryFavorVideoListHandler(c *gin.Context) { NewProxyFavorVideoListHandler(c).Do() } type ProxyFavorVideoListHandler struct { *gin.Context userId int64 } func NewProxyFavorVideoListHandler(c *gin.Context) *ProxyFavorVideoListHandler { return &ProxyFavorVideoListHandler{Context: c} } func (p *ProxyFavorVideoListHandler) Do() { //解析参数 if err := p.parseNum(); err != nil { p.SendError(err.Error()) return } //正式调用 favorVideoList, err := video.QueryFavorVideoList(p.userId) if err != nil { p.SendError(err.Error()) return } //成功返回 p.SendOk(favorVideoList) } func (p *ProxyFavorVideoListHandler) parseNum() error { rawUserId, _ := p.Get("user_id") userId, ok := rawUserId.(int64) if !ok { return errors.New("userId解析出错") } p.userId = userId return nil } func (p *ProxyFavorVideoListHandler) SendError(msg string) { p.JSON(http.StatusOK, FavorVideoListResponse{ CommonResponse: models.CommonResponse{StatusCode: 1, StatusMsg: msg}}) } func (p *ProxyFavorVideoListHandler) SendOk(favorList *video.FavorList) { p.JSON(http.StatusOK, FavorVideoListResponse{CommonResponse: models.CommonResponse{StatusCode: 0}, FavorList: favorList, }) } ================================================ FILE: deprecated/handlers/video/query_videolist_handler.go ================================================ package video import ( "errors" "github.com/ACking-you/byte_douyin_project/models" "github.com/ACking-you/byte_douyin_project/service/video" "github.com/gin-gonic/gin" "net/http" ) type ListResponse struct { models.CommonResponse *video.List } func QueryVideoListHandler(c *gin.Context) { p := NewProxyQueryVideoList(c) rawId, _ := c.Get("user_id") err := p.DoQueryVideoListByUserId(rawId) if err != nil { p.QueryVideoListError(err.Error()) } } // ProxyQueryVideoList 代理类 type ProxyQueryVideoList struct { c *gin.Context } func NewProxyQueryVideoList(c *gin.Context) *ProxyQueryVideoList { return &ProxyQueryVideoList{c: c} } // DoQueryVideoListByUserId 根据userId字段进行查询 func (p *ProxyQueryVideoList) DoQueryVideoListByUserId(rawId interface{}) error { userId, ok := rawId.(int64) if !ok { return errors.New("userId解析出错") } videoList, err := video.QueryVideoListByUserId(userId) if err != nil { return err } p.QueryVideoListOk(videoList) return nil } func (p *ProxyQueryVideoList) QueryVideoListError(msg string) { p.c.JSON(http.StatusOK, ListResponse{CommonResponse: models.CommonResponse{ StatusCode: 1, StatusMsg: msg, }}) } func (p *ProxyQueryVideoList) QueryVideoListOk(videoList *video.List) { p.c.JSON(http.StatusOK, ListResponse{ CommonResponse: models.CommonResponse{ StatusCode: 0, }, List: videoList, }) } ================================================ FILE: deprecated/main.go ================================================ package main import ( "fmt" "github.com/ACking-you/byte_douyin_project/config" "github.com/ACking-you/byte_douyin_project/router" ) func main() { r := router.Init() err := r.Run(fmt.Sprintf(":%d", config.Global.Port)) // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") if err != nil { return } } ================================================ FILE: deprecated/middleware/jwt.go ================================================ package middleware import ( models2 "github.com/ACking-you/byte_douyin_project/models" "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/gin" "net/http" "time" ) var jwtKey = []byte("acking-you.xyz") type Claims struct { UserId int64 jwt.StandardClaims } // ReleaseToken 颁发token func ReleaseToken(user models2.UserLogin) (string, error) { expirationTime := time.Now().Add(7 * 24 * time.Hour) claims := &Claims{ UserId: user.UserInfoId, StandardClaims: jwt.StandardClaims{ ExpiresAt: expirationTime.Unix(), IssuedAt: time.Now().Unix(), Issuer: "douyin_pro_131", Subject: "L_B__", }} token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString(jwtKey) if err != nil { return "", err } return tokenString, nil } // ParseToken 解析token func ParseToken(tokenString string) (*Claims, bool) { token, _ := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { return jwtKey, nil }) if token != nil { if key, ok := token.Claims.(*Claims); ok { if token.Valid { return key, true } else { return key, false } } } return nil, false } // JWTMiddleWare 鉴权中间件,鉴权并设置user_id func JWTMiddleWare() gin.HandlerFunc { return func(c *gin.Context) { tokenStr := c.Query("token") if tokenStr == "" { tokenStr = c.PostForm("token") } //用户不存在 if tokenStr == "" { c.JSON(http.StatusOK, models2.CommonResponse{StatusCode: 401, StatusMsg: "用户不存在"}) c.Abort() //阻止执行 return } //验证token tokenStruck, ok := ParseToken(tokenStr) if !ok { c.JSON(http.StatusOK, models2.CommonResponse{ StatusCode: 403, StatusMsg: "token不正确", }) c.Abort() //阻止执行 return } //token超时 if time.Now().Unix() > tokenStruck.ExpiresAt { c.JSON(http.StatusOK, models2.CommonResponse{ StatusCode: 402, StatusMsg: "token过期", }) c.Abort() //阻止执行 return } c.Set("user_id", tokenStruck.UserId) c.Next() } } ================================================ FILE: deprecated/middleware/jwt_test.go ================================================ package middleware import ( "testing" ) func TestJwt(t *testing.T) { } ================================================ FILE: deprecated/middleware/normal.go ================================================ package middleware import ( "github.com/ACking-you/byte_douyin_project/models" "github.com/gin-gonic/gin" "net/http" "strconv" ) func NoAuthToGetUserId() gin.HandlerFunc { return func(c *gin.Context) { rawId := c.Query("user_id") if rawId == "" { rawId = c.PostForm("user_id") } //用户不存在 if rawId == "" { c.JSON(http.StatusOK, models.CommonResponse{StatusCode: 401, StatusMsg: "用户不存在"}) c.Abort() //阻止执行 return } userId, err := strconv.ParseInt(rawId, 10, 64) if err != nil { c.JSON(http.StatusOK, models.CommonResponse{StatusCode: 401, StatusMsg: "用户不存在"}) c.Abort() //阻止执行 } c.Set("user_id", userId) c.Next() } } ================================================ FILE: deprecated/middleware/password.go ================================================ package middleware import ( "crypto/sha1" "encoding/hex" "github.com/gin-gonic/gin" ) func SHA1(s string) string { o := sha1.New() o.Write([]byte(s)) return hex.EncodeToString(o.Sum(nil)) } func SHAMiddleWare() gin.HandlerFunc { return func(context *gin.Context) { password := context.Query("password") if password == "" { password = context.PostForm("password") } context.Set("password", SHA1(password)) context.Next() } } ================================================ FILE: deprecated/middleware/password_test.go ================================================ package middleware import "testing" //40bd001563085fc35165329ea1ff5c5ecbdbbeef func TestSHA1(t *testing.T) { print(SHA1("123")) } ================================================ FILE: deprecated/models/comment.go ================================================ package models import ( "errors" "gorm.io/gorm" "time" ) type Comment struct { Id int64 `json:"id"` UserInfoId int64 `json:"-"` //用于一对多关系的id VideoId int64 `json:"-"` //一对多,视频对评论 User UserInfo `json:"user" gorm:"-"` Content string `json:"content"` CreatedAt time.Time `json:"-"` CreateDate string `json:"create_date" gorm:"-"` } type CommentDAO struct { } var ( commentDao CommentDAO ) func NewCommentDAO() *CommentDAO { return &commentDao } func (c *CommentDAO) AddCommentAndUpdateCount(comment *Comment) error { if comment == nil { return errors.New("AddCommentAndUpdateCount comment空指针") } //执行事务 return DB.Transaction(func(tx *gorm.DB) error { //添加评论数据 if err := tx.Create(comment).Error; err != nil { // 返回任何错误都会回滚事务 return err } //增加count if err := tx.Exec("UPDATE videos v SET v.comment_count = v.comment_count+1 WHERE v.id=?", comment.VideoId).Error; err != nil { return err } // 返回 nil 提交事务 return nil }) } func (c *CommentDAO) DeleteCommentAndUpdateCountById(commentId, videoId int64) error { //执行事务 return DB.Transaction(func(tx *gorm.DB) error { //删除评论 if err := tx.Exec("DELETE FROM comments WHERE id = ?", commentId).Error; err != nil { // 返回任何错误都会回滚事务 return err } //减少count if err := tx.Exec("UPDATE videos v SET v.comment_count = v.comment_count-1 WHERE v.id=? AND v.comment_count>0", videoId).Error; err != nil { return err } // 返回 nil 提交事务 return nil }) } func (c *CommentDAO) QueryCommentById(id int64, comment *Comment) error { if comment == nil { return errors.New("QueryCommentById comment 空指针") } return DB.Where("id=?", id).First(comment).Error } func (c *CommentDAO) QueryCommentListByVideoId(videoId int64, comments *[]*Comment) error { if comments == nil { return errors.New("QueryCommentListByVideoId comments空指针") } if err := DB.Model(&Comment{}).Where("video_id=?", videoId).Find(comments).Error; err != nil { return err } return nil } ================================================ FILE: deprecated/models/common.go ================================================ package models type CommonResponse struct { StatusCode int32 `json:"status_code"` StatusMsg string `json:"status_msg,omitempty"` } ================================================ FILE: deprecated/models/init_db.go ================================================ package models import ( "github.com/ACking-you/byte_douyin_project/config" "gorm.io/driver/mysql" "gorm.io/gorm" ) var DB *gorm.DB func InitDB() { var err error DB, err = gorm.Open(mysql.Open(config.DBConnectString()), &gorm.Config{ PrepareStmt: true, //缓存预编译命令 SkipDefaultTransaction: true, //禁用默认事务操作 //Logger: logger.Default.LogMode(logger.Global), //打印sql语句 }) if err != nil { panic(err) } err = DB.AutoMigrate(&UserInfo{}, &Video{}, &Comment{}, &UserLogin{}) if err != nil { panic(err) } } ================================================ FILE: deprecated/models/user_info.go ================================================ package models import ( "errors" "gorm.io/gorm" "log" "sync" ) var ( ErrIvdPtr = errors.New("空指针错误") ErrEmptyUserList = errors.New("用户列表为空") ) type UserInfo struct { Id int64 `json:"id" gorm:"id,omitempty"` Name string `json:"name" gorm:"name,omitempty"` FollowCount int64 `json:"follow_count" gorm:"follow_count,omitempty"` FollowerCount int64 `json:"follower_count" gorm:"follower_count,omitempty"` IsFollow bool `json:"is_follow" gorm:"is_follow,omitempty"` User *UserLogin `json:"-"` //用户与账号密码之间的一对一 Videos []*Video `json:"-"` //用户与投稿视频的一对多 Follows []*UserInfo `json:"-" gorm:"many2many:user_relations;"` //用户之间的多对多 FavorVideos []*Video `json:"-" gorm:"many2many:user_favor_videos;"` //用户与点赞视频之间的多对多 Comments []*Comment `json:"-"` //用户与评论的一对多 } type UserInfoDAO struct { } var ( userInfoDAO *UserInfoDAO userInfoOnce sync.Once ) func NewUserInfoDAO() *UserInfoDAO { userInfoOnce.Do(func() { userInfoDAO = new(UserInfoDAO) }) return userInfoDAO } func (u *UserInfoDAO) QueryUserInfoById(userId int64, userinfo *UserInfo) error { if userinfo == nil { return ErrIvdPtr } //DB.Where("id=?",userId).First(userinfo) DB.Where("id=?", userId).Select([]string{"id", "name", "follow_count", "follower_count", "is_follow"}).First(userinfo) //id为零值,说明sql执行失败 if userinfo.Id == 0 { return errors.New("该用户不存在") } return nil } func (u *UserInfoDAO) AddUserInfo(userinfo *UserInfo) error { if userinfo == nil { return ErrIvdPtr } return DB.Create(userinfo).Error } func (u *UserInfoDAO) IsUserExistById(id int64) bool { var userinfo UserInfo if err := DB.Where("id=?", id).Select("id").First(&userinfo).Error; err != nil { log.Println(err) } if userinfo.Id == 0 { return false } return true } func (u *UserInfoDAO) AddUserFollow(userId, userToId int64) error { return DB.Transaction(func(tx *gorm.DB) error { if err := tx.Exec("UPDATE user_infos SET follow_count=follow_count+1 WHERE id = ?", userId).Error; err != nil { return err } if err := tx.Exec("UPDATE user_infos SET follower_count=follower_count+1 WHERE id = ?", userToId).Error; err != nil { return err } if err := tx.Exec("INSERT INTO `user_relations` (`user_info_id`,`follow_id`) VALUES (?,?)", userId, userToId).Error; err != nil { return err } return nil }) } func (u *UserInfoDAO) CancelUserFollow(userId, userToId int64) error { return DB.Transaction(func(tx *gorm.DB) error { if err := tx.Exec("UPDATE user_infos SET follow_count=follow_count-1 WHERE id = ? AND follow_count>0", userId).Error; err != nil { return err } if err := tx.Exec("UPDATE user_infos SET follower_count=follower_count-1 WHERE id = ? AND follower_count>0", userToId).Error; err != nil { return err } if err := tx.Exec("DELETE FROM `user_relations` WHERE user_info_id=? AND follow_id=?", userId, userToId).Error; err != nil { return err } return nil }) } func (u *UserInfoDAO) GetFollowListByUserId(userId int64, userList *[]*UserInfo) error { if userList == nil { return ErrIvdPtr } var err error if err = DB.Raw("SELECT u.* FROM user_relations r, user_infos u WHERE r.user_info_id = ? AND r.follow_id = u.id", userId).Scan(userList).Error; err != nil { return err } if len(*userList) == 0 || (*userList)[0].Id == 0 { return ErrEmptyUserList } return nil } func (u *UserInfoDAO) GetFollowerListByUserId(userId int64, userList *[]*UserInfo) error { if userList == nil { return ErrIvdPtr } var err error if err = DB.Raw("SELECT u.* FROM user_relations r, user_infos u WHERE r.follow_id = ? AND r.user_info_id = u.id", userId).Scan(userList).Error; err != nil { return err } //if len(*userList) == 0 || (*userList)[0].Id == 0 { // return ErrEmptyUserList //} return nil } ================================================ FILE: deprecated/models/user_info_test.go ================================================ package models import ( "fmt" "os" "testing" ) func TestMain(m *testing.M) { InitDB() code := m.Run() os.Exit(code) } func TestUserInfoDAO_GetFollowListByUserId(t *testing.T) { var userList []*UserInfo err := NewUserInfoDAO().GetFollowListByUserId(1, &userList) if err != nil { panic(err) } for _, user := range userList { fmt.Printf("%#v\n", *user) } } func TestUserInfoDAO_GetFollowerListByUserId(t *testing.T) { var userList []*UserInfo err := NewUserInfoDAO().GetFollowerListByUserId(2, &userList) if err != nil { panic(err) } for _, user := range userList { fmt.Printf("%#v\n", *user) } } ================================================ FILE: deprecated/models/user_login.go ================================================ package models import ( "errors" "sync" ) // UserLogin 用户登录表,和UserInfo属于一对一关系 type UserLogin struct { Id int64 `gorm:"primary_key"` UserInfoId int64 Username string `gorm:"primary_key"` Password string `gorm:"size:200;notnull"` } type UserLoginDAO struct { } var ( userLoginDao *UserLoginDAO userLoginOnce sync.Once ) func NewUserLoginDao() *UserLoginDAO { userLoginOnce.Do(func() { userLoginDao = new(UserLoginDAO) }) return userLoginDao } func (u *UserLoginDAO) QueryUserLogin(username, password string, login *UserLogin) error { if login == nil { return errors.New("结构体指针为空") } DB.Where("username=? and password=?", username, password).First(login) if login.Id == 0 { return errors.New("用户不存在,账号或密码出错") } return nil } func (u *UserLoginDAO) IsUserExistByUsername(username string) bool { var userLogin UserLogin DB.Where("username=?", username).First(&userLogin) if userLogin.Id == 0 { return false } return true } ================================================ FILE: deprecated/models/video.go ================================================ package models import ( "errors" "gorm.io/gorm" "log" "sync" "time" ) type Video struct { Id int64 `json:"id,omitempty"` UserInfoId int64 `json:"-"` Author UserInfo `json:"author,omitempty" gorm:"-"` //这里应该是作者对视频的一对多的关系,而不是视频对作者,故gorm不能存他,但json需要返回它 PlayUrl string `json:"play_url,omitempty"` CoverUrl string `json:"cover_url,omitempty"` FavoriteCount int64 `json:"favorite_count,omitempty"` CommentCount int64 `json:"comment_count,omitempty"` IsFavorite bool `json:"is_favorite,omitempty"` Title string `json:"title,omitempty"` Users []*UserInfo `json:"-" gorm:"many2many:user_favor_videos;"` Comments []*Comment `json:"-"` CreatedAt time.Time `json:"-"` UpdatedAt time.Time `json:"-"` } type VideoDAO struct { } var ( videoDAO *VideoDAO videoOnce sync.Once ) func NewVideoDAO() *VideoDAO { videoOnce.Do(func() { videoDAO = new(VideoDAO) }) return videoDAO } // AddVideo 添加视频 // 注意:由于视频和userinfo有多对一的关系,所以传入的Video参数一定要进行id的映射处理! func (v *VideoDAO) AddVideo(video *Video) error { if video == nil { return errors.New("AddVideo video 空指针") } return DB.Create(video).Error } func (v *VideoDAO) QueryVideoByVideoId(videoId int64, video *Video) error { if video == nil { return errors.New("QueryVideoByVideoId 空指针") } return DB.Where("id=?", videoId). Select([]string{"id", "user_info_id", "play_url", "cover_url", "favorite_count", "comment_count", "is_favorite", "title"}). First(video).Error } func (v *VideoDAO) QueryVideoCountByUserId(userId int64, count *int64) error { if count == nil { return errors.New("QueryVideoCountByUserId count 空指针") } return DB.Model(&Video{}).Where("user_info_id=?", userId).Count(count).Error } func (v *VideoDAO) QueryVideoListByUserId(userId int64, videoList *[]*Video) error { if videoList == nil { return errors.New("QueryVideoListByUserId videoList 空指针") } return DB.Where("user_info_id=?", userId). Select([]string{"id", "user_info_id", "play_url", "cover_url", "favorite_count", "comment_count", "is_favorite", "title"}). Find(videoList).Error } // QueryVideoListByLimitAndTime 返回按投稿时间倒序的视频列表,并限制为最多limit个 func (v *VideoDAO) QueryVideoListByLimitAndTime(limit int, latestTime time.Time, videoList *[]*Video) error { if videoList == nil { return errors.New("QueryVideoListByLimit videoList 空指针") } return DB.Model(&Video{}).Where("created_at0", videoId).Error; err != nil { return err } if err := tx.Exec("DELETE FROM `user_favor_videos` WHERE `user_info_id` = ? AND `video_id` = ?", userId, videoId).Error; err != nil { return err } return nil }) } func (v *VideoDAO) QueryFavorVideoListByUserId(userId int64, videoList *[]*Video) error { if videoList == nil { return errors.New("QueryFavorVideoListByUserId videoList 空指针") } //多表查询,左连接得到结果,再映射到数据 if err := DB.Raw("SELECT v.* FROM user_favor_videos u , videos v WHERE u.user_info_id = ? AND u.video_id = v.id", userId).Scan(videoList).Error; err != nil { return err } //如果id为0,则说明没有查到数据 if len(*videoList) == 0 || (*videoList)[0].Id == 0 { return errors.New("点赞列表为空") } return nil } func (v *VideoDAO) IsVideoExistById(id int64) bool { var video Video if err := DB.Where("id=?", id).Select("id").First(&video).Error; err != nil { log.Println(err) } if video.Id == 0 { return false } return true } ================================================ FILE: deprecated/models/video_test.go ================================================ package models import ( "fmt" "testing" "time" ) func TestVideoDAO_QueryVideoListByUserId(t *testing.T) { InitDB() s := make([]*Video, 8) err := NewVideoDAO().QueryVideoListByUserId(1, &s) if err != nil { panic(err) } for _, v := range s { fmt.Printf("%#v\n", *v) } } func TestVideoDAO_QueryVideoListByLimit(t *testing.T) { InitDB() s := make([]*Video, 8) err := NewVideoDAO().QueryVideoListByLimitAndTime(2, time.Unix(1652895580, 0), &s) if err != nil { panic(err) } for _, v := range s { fmt.Printf("%#v\n", *v) } } func TestTime(t *testing.T) { println(time.Now().UnixNano()) } ================================================ FILE: deprecated/router/router_douyin.go ================================================ package router import ( "github.com/ACking-you/byte_douyin_project/config" comment2 "github.com/ACking-you/byte_douyin_project/handlers/comment" user_info2 "github.com/ACking-you/byte_douyin_project/handlers/user_info" user_login2 "github.com/ACking-you/byte_douyin_project/handlers/user_login" video2 "github.com/ACking-you/byte_douyin_project/handlers/video" middleware2 "github.com/ACking-you/byte_douyin_project/middleware" "github.com/ACking-you/byte_douyin_project/models" "github.com/gin-gonic/gin" ) func Init() *gin.Engine { models.InitDB() r := gin.Default() r.Static("static", config.Global.StaticSourcePath) baseGroup := r.Group("/douyin") //根据灵活性考虑是否加入JWT中间件来进行鉴权,还是在之后再做鉴权 // basic apis baseGroup.GET("/feed/", video2.FeedVideoListHandler) baseGroup.GET("/user/", middleware2.JWTMiddleWare(), user_info2.UserInfoHandler) baseGroup.POST("/user/login/", middleware2.SHAMiddleWare(), user_login2.UserLoginHandler) baseGroup.POST("/user/register/", middleware2.SHAMiddleWare(), user_login2.UserRegisterHandler) baseGroup.POST("/publish/action/", middleware2.JWTMiddleWare(), video2.PublishVideoHandler) baseGroup.GET("/publish/list/", middleware2.NoAuthToGetUserId(), video2.QueryVideoListHandler) //extend 1 baseGroup.POST("/favorite/action/", middleware2.JWTMiddleWare(), video2.PostFavorHandler) baseGroup.GET("/favorite/list/", middleware2.NoAuthToGetUserId(), video2.QueryFavorVideoListHandler) baseGroup.POST("/comment/action/", middleware2.JWTMiddleWare(), comment2.PostCommentHandler) baseGroup.GET("/comment/list/", middleware2.JWTMiddleWare(), comment2.QueryCommentListHandler) //extend 2 baseGroup.POST("/relation/action/", middleware2.JWTMiddleWare(), user_info2.PostFollowActionHandler) baseGroup.GET("/relation/follow/list/", middleware2.NoAuthToGetUserId(), user_info2.QueryFollowListHandler) baseGroup.GET("/relation/follower/list/", middleware2.NoAuthToGetUserId(), user_info2.QueryFollowerHandler) return r } ================================================ FILE: deprecated/service/comment/post_comment.go ================================================ package comment import ( "errors" "fmt" models2 "github.com/ACking-you/byte_douyin_project/models" "github.com/ACking-you/byte_douyin_project/util" ) const ( CREATE = 1 DELETE = 2 ) type Response struct { MyComment *models2.Comment `json:"comment"` } func PostComment(userId int64, videoId int64, commentId int64, actionType int64, commentText string) (*Response, error) { return NewPostCommentFlow(userId, videoId, commentId, actionType, commentText).Do() } type PostCommentFlow struct { userId int64 videoId int64 commentId int64 actionType int64 commentText string comment *models2.Comment *Response } func NewPostCommentFlow(userId int64, videoId int64, commentId int64, actionType int64, commentText string) *PostCommentFlow { return &PostCommentFlow{userId: userId, videoId: videoId, commentId: commentId, actionType: actionType, commentText: commentText} } func (p *PostCommentFlow) Do() (*Response, error) { var err error if err = p.checkNum(); err != nil { return nil, err } if err = p.prepareData(); err != nil { return nil, err } if err = p.packData(); err != nil { return nil, err } return p.Response, err } // CreateComment 增加评论 func (p *PostCommentFlow) CreateComment() (*models2.Comment, error) { comment := models2.Comment{UserInfoId: p.userId, VideoId: p.videoId, Content: p.commentText} err := models2.NewCommentDAO().AddCommentAndUpdateCount(&comment) if err != nil { return nil, err } return &comment, nil } // DeleteComment 删除评论 func (p *PostCommentFlow) DeleteComment() (*models2.Comment, error) { //获取comment var comment models2.Comment err := models2.NewCommentDAO().QueryCommentById(p.commentId, &comment) if err != nil { return nil, err } //删除comment err = models2.NewCommentDAO().DeleteCommentAndUpdateCountById(p.commentId, p.videoId) if err != nil { return nil, err } return &comment, nil } func (p *PostCommentFlow) checkNum() error { if !models2.NewUserInfoDAO().IsUserExistById(p.userId) { return fmt.Errorf("用户%d不存在", p.userId) } if !models2.NewVideoDAO().IsVideoExistById(p.videoId) { return fmt.Errorf("视频%d不存在", p.videoId) } if p.actionType != CREATE && p.actionType != DELETE { return errors.New("未定义的行为") } return nil } func (p *PostCommentFlow) prepareData() error { var err error switch p.actionType { case CREATE: p.comment, err = p.CreateComment() case DELETE: p.comment, err = p.DeleteComment() default: return errors.New("未定义的操作") } return err } func (p *PostCommentFlow) packData() error { //填充字段 userInfo := models2.UserInfo{} _ = models2.NewUserInfoDAO().QueryUserInfoById(p.comment.UserInfoId, &userInfo) p.comment.User = userInfo _ = util.FillCommentFields(p.comment) p.Response = &Response{MyComment: p.comment} return nil } ================================================ FILE: deprecated/service/comment/query_comment_list.go ================================================ package comment import ( "errors" "fmt" models2 "github.com/ACking-you/byte_douyin_project/models" "github.com/ACking-you/byte_douyin_project/util" ) type List struct { Comments []*models2.Comment `json:"comment_list"` } func QueryCommentList(userId, videoId int64) (*List, error) { return NewQueryCommentListFlow(userId, videoId).Do() } type QueryCommentListFlow struct { userId int64 videoId int64 comments []*models2.Comment commentList *List } func NewQueryCommentListFlow(userId, videoId int64) *QueryCommentListFlow { return &QueryCommentListFlow{userId: userId, videoId: videoId} } func (q *QueryCommentListFlow) Do() (*List, error) { if err := q.checkNum(); err != nil { return nil, err } if err := q.prepareData(); err != nil { return nil, err } if err := q.packData(); err != nil { return nil, err } return q.commentList, nil } func (q *QueryCommentListFlow) checkNum() error { if !models2.NewUserInfoDAO().IsUserExistById(q.userId) { return fmt.Errorf("用户%d处于登出状态", q.userId) } if !models2.NewVideoDAO().IsVideoExistById(q.videoId) { return fmt.Errorf("视频%d不存在或已经被删除", q.videoId) } return nil } func (q *QueryCommentListFlow) prepareData() error { err := models2.NewCommentDAO().QueryCommentListByVideoId(q.videoId, &q.comments) if err != nil { return err } //根据前端的要求填充正确的时间格式 err = util.FillCommentListFields(&q.comments) if err != nil { return errors.New("暂时还没有人评论") } return nil } func (q *QueryCommentListFlow) packData() error { q.commentList = &List{Comments: q.comments} return nil } ================================================ FILE: deprecated/service/user_info/post_follow_action.go ================================================ package user_info import ( "errors" "github.com/ACking-you/byte_douyin_project/cache" "github.com/ACking-you/byte_douyin_project/models" ) const ( FOLLOW = 1 CANCEL = 2 ) var ( ErrIvdAct = errors.New("未定义操作") ErrIvdFolUsr = errors.New("关注用户不存在") ) func PostFollowAction(userId, userToId int64, actionType int) error { return NewPostFollowActionFlow(userId, userToId, actionType).Do() } type PostFollowActionFlow struct { userId int64 userToId int64 actionType int } func NewPostFollowActionFlow(userId int64, userToId int64, actionType int) *PostFollowActionFlow { return &PostFollowActionFlow{userId: userId, userToId: userToId, actionType: actionType} } func (p *PostFollowActionFlow) Do() error { var err error if err = p.checkNum(); err != nil { return err } if err = p.publish(); err != nil { return err } return nil } func (p *PostFollowActionFlow) checkNum() error { //由于userId是经过乐token鉴权故不需要check,只需要检查userToId if !models.NewUserInfoDAO().IsUserExistById(p.userToId) { return ErrIvdFolUsr } if p.actionType != FOLLOW && p.actionType != CANCEL { return ErrIvdAct } //自己不能关注自己 if p.userId == p.userToId { return ErrIvdAct } return nil } func (p *PostFollowActionFlow) publish() error { userDAO := models.NewUserInfoDAO() var err error switch p.actionType { case FOLLOW: err = userDAO.AddUserFollow(p.userId, p.userToId) //更新redis的关注信息 cache.NewProxyIndexMap().UpdateUserRelation(p.userId, p.userToId, true) case CANCEL: err = userDAO.CancelUserFollow(p.userId, p.userToId) cache.NewProxyIndexMap().UpdateUserRelation(p.userId, p.userToId, false) default: return ErrIvdAct } return err } ================================================ FILE: deprecated/service/user_info/query_follow_list.go ================================================ package user_info import ( "errors" "github.com/ACking-you/byte_douyin_project/models" ) var ( ErrUserNotExist = errors.New("用户不存在或已注销") ) type FollowList struct { UserList []*models.UserInfo `json:"user_list"` } func QueryFollowList(userId int64) (*FollowList, error) { return NewQueryFollowListFlow(userId).Do() } type QueryFollowListFlow struct { userId int64 userList []*models.UserInfo *FollowList } func NewQueryFollowListFlow(userId int64) *QueryFollowListFlow { return &QueryFollowListFlow{userId: userId} } func (q *QueryFollowListFlow) Do() (*FollowList, error) { var err error if err = q.checkNum(); err != nil { return nil, err } if err = q.prepareData(); err != nil { return nil, err } if err = q.packData(); err != nil { return nil, err } return q.FollowList, nil } func (q *QueryFollowListFlow) checkNum() error { if !models.NewUserInfoDAO().IsUserExistById(q.userId) { return ErrUserNotExist } return nil } func (q *QueryFollowListFlow) prepareData() error { var userList []*models.UserInfo err := models.NewUserInfoDAO().GetFollowListByUserId(q.userId, &userList) if err != nil { return err } for i, _ := range userList { userList[i].IsFollow = true //当前用户的关注列表,故isFollow定为true } q.userList = userList return nil } func (q *QueryFollowListFlow) packData() error { q.FollowList = &FollowList{UserList: q.userList} return nil } ================================================ FILE: deprecated/service/user_info/query_follower_list.go ================================================ package user_info import ( "github.com/ACking-you/byte_douyin_project/cache" "github.com/ACking-you/byte_douyin_project/models" ) type FollowerList struct { UserList []*models.UserInfo `json:"user_list"` } func QueryFollowerList(userId int64) (*FollowerList, error) { return NewQueryFollowerListFlow(userId).Do() } type QueryFollowerListFlow struct { userId int64 userList []*models.UserInfo *FollowerList } func NewQueryFollowerListFlow(userId int64) *QueryFollowerListFlow { return &QueryFollowerListFlow{userId: userId} } func (q *QueryFollowerListFlow) Do() (*FollowerList, error) { var err error if err = q.checkNum(); err != nil { return nil, err } if err = q.prepareData(); err != nil { return nil, err } if err = q.packData(); err != nil { return nil, err } return q.FollowerList, nil } func (q *QueryFollowerListFlow) checkNum() error { if !models.NewUserInfoDAO().IsUserExistById(q.userId) { return ErrUserNotExist } return nil } func (q *QueryFollowerListFlow) prepareData() error { err := models.NewUserInfoDAO().GetFollowerListByUserId(q.userId, &q.userList) if err != nil { return err } //填充is_follow字段 for _, v := range q.userList { v.IsFollow = cache.NewProxyIndexMap().GetUserRelation(q.userId, v.Id) } return nil } func (q *QueryFollowerListFlow) packData() error { q.FollowerList = &FollowerList{UserList: q.userList} return nil } ================================================ FILE: deprecated/service/user_login/post_user_login.go ================================================ package user_login import ( "errors" "github.com/ACking-you/byte_douyin_project/middleware" models2 "github.com/ACking-you/byte_douyin_project/models" ) // PostUserLogin 注册用户并得到token和id func PostUserLogin(username, password string) (*LoginResponse, error) { return NewPostUserLoginFlow(username, password).Do() } func NewPostUserLoginFlow(username, password string) *PostUserLoginFlow { return &PostUserLoginFlow{username: username, password: password} } type PostUserLoginFlow struct { username string password string data *LoginResponse userid int64 token string } func (q *PostUserLoginFlow) Do() (*LoginResponse, error) { //对参数进行合法性验证 if err := q.checkNum(); err != nil { return nil, err } //更新数据到数据库 if err := q.updateData(); err != nil { return nil, err } //打包response if err := q.packResponse(); err != nil { return nil, err } return q.data, nil } func (q *PostUserLoginFlow) checkNum() error { if q.username == "" { return errors.New("用户名为空") } if len(q.username) > MaxUsernameLength { return errors.New("用户名长度超出限制") } if q.password == "" { return errors.New("密码为空") } return nil } func (q *PostUserLoginFlow) updateData() error { //准备好userInfo,默认name为username userLogin := models2.UserLogin{Username: q.username, Password: q.password} userinfo := models2.UserInfo{User: &userLogin, Name: q.username} //判断用户名是否已经存在 userLoginDAO := models2.NewUserLoginDao() if userLoginDAO.IsUserExistByUsername(q.username) { return errors.New("用户名已存在") } //更新操作,由于userLogin属于userInfo,故更新userInfo即可,且由于传入的是指针,所以插入的数据内容也是清楚的 userInfoDAO := models2.NewUserInfoDAO() err := userInfoDAO.AddUserInfo(&userinfo) if err != nil { return err } //颁发token token, err := middleware.ReleaseToken(userLogin) if err != nil { return err } q.token = token q.userid = userinfo.Id return nil } func (q *PostUserLoginFlow) packResponse() error { q.data = &LoginResponse{ UserId: q.userid, Token: q.token, } return nil } ================================================ FILE: deprecated/service/user_login/query_user_login.go ================================================ package user_login import ( "errors" "github.com/ACking-you/byte_douyin_project/middleware" "github.com/ACking-you/byte_douyin_project/models" ) const ( MaxUsernameLength = 100 MaxPasswordLength = 20 MinPasswordLength = 8 ) type LoginResponse struct { UserId int64 `json:"user_id"` Token string `json:"token"` } // QueryUserLogin 查询用户是否存在,并返回token和id func QueryUserLogin(username, password string) (*LoginResponse, error) { return NewQueryUserLoginFlow(username, password).Do() } func NewQueryUserLoginFlow(username, password string) *QueryUserLoginFlow { return &QueryUserLoginFlow{username: username, password: password} } type QueryUserLoginFlow struct { username string password string data *LoginResponse userid int64 token string } func (q *QueryUserLoginFlow) Do() (*LoginResponse, error) { //对参数进行合法性验证 if err := q.checkNum(); err != nil { return nil, err } //准备好数据 if err := q.prepareData(); err != nil { return nil, err } //打包最终数据 if err := q.packData(); err != nil { return nil, err } return q.data, nil } func (q *QueryUserLoginFlow) checkNum() error { if q.username == "" { return errors.New("用户名为空") } if len(q.username) > MaxUsernameLength { return errors.New("用户名长度超出限制") } if q.password == "" { return errors.New("密码为空") } return nil } func (q *QueryUserLoginFlow) prepareData() error { userLoginDAO := models.NewUserLoginDao() var login models.UserLogin //准备好userid err := userLoginDAO.QueryUserLogin(q.username, q.password, &login) if err != nil { return err } q.userid = login.UserInfoId //准备颁发token token, err := middleware.ReleaseToken(login) if err != nil { return err } q.token = token return nil } func (q *QueryUserLoginFlow) packData() error { q.data = &LoginResponse{ UserId: q.userid, Token: q.token, } return nil } ================================================ FILE: deprecated/service/video/feed_videolist.go ================================================ package video import ( "github.com/ACking-you/byte_douyin_project/models" "github.com/ACking-you/byte_douyin_project/util" "time" ) // MaxVideoNum 每次最多返回的视频流数量 const ( MaxVideoNum = 30 ) type FeedVideoList struct { Videos []*models.Video `json:"video_list,omitempty"` NextTime int64 `json:"next_time,omitempty"` } func QueryFeedVideoList(userId int64, latestTime time.Time) (*FeedVideoList, error) { return NewQueryFeedVideoListFlow(userId, latestTime).Do() } type QueryFeedVideoListFlow struct { userId int64 latestTime time.Time videos []*models.Video nextTime int64 feedVideo *FeedVideoList } func NewQueryFeedVideoListFlow(userId int64, latestTime time.Time) *QueryFeedVideoListFlow { return &QueryFeedVideoListFlow{userId: userId, latestTime: latestTime} } func (q *QueryFeedVideoListFlow) Do() (*FeedVideoList, error) { //所有传入的参数不填也应该给他正常处理 q.checkNum() if err := q.prepareData(); err != nil { return nil, err } if err := q.packData(); err != nil { return nil, err } return q.feedVideo, nil } func (q *QueryFeedVideoListFlow) checkNum() { //上层通过把userId置零,表示userId不存在或不需要 if q.userId > 0 { //这里说明userId是有效的,可以定制性的做一些登录用户的专属视频推荐 } if q.latestTime.IsZero() { q.latestTime = time.Now() } } func (q *QueryFeedVideoListFlow) prepareData() error { err := models.NewVideoDAO().QueryVideoListByLimitAndTime(MaxVideoNum, q.latestTime, &q.videos) if err != nil { return err } //如果用户为登录状态,则更新该视频是否被该用户点赞的状态 latestTime, _ := util.FillVideoListFields(q.userId, &q.videos) //不是致命错误,不返回 //准备好时间戳 if latestTime != nil { q.nextTime = (*latestTime).UnixNano() / 1e6 return nil } q.nextTime = time.Now().Unix() / 1e6 return nil } func (q *QueryFeedVideoListFlow) packData() error { q.feedVideo = &FeedVideoList{ Videos: q.videos, NextTime: q.nextTime, } return nil } ================================================ FILE: deprecated/service/video/post_favor_state.go ================================================ package video import ( "errors" "github.com/ACking-you/byte_douyin_project/cache" models2 "github.com/ACking-you/byte_douyin_project/models" ) const ( PLUS = 1 MINUS = 2 ) func PostFavorState(userId, videoId, actionType int64) error { return NewPostFavorStateFlow(userId, videoId, actionType).Do() } type PostFavorStateFlow struct { userId int64 videoId int64 actionType int64 } func NewPostFavorStateFlow(userId, videoId, action int64) *PostFavorStateFlow { return &PostFavorStateFlow{ userId: userId, videoId: videoId, actionType: action, } } func (p *PostFavorStateFlow) Do() error { var err error if err = p.checkNum(); err != nil { return err } switch p.actionType { case PLUS: err = p.PlusOperation() case MINUS: err = p.MinusOperation() default: return errors.New("未定义的操作") } return err } // PlusOperation 点赞操作 func (p *PostFavorStateFlow) PlusOperation() error { //视频点赞数目+1 err := models2.NewVideoDAO().PlusOneFavorByUserIdAndVideoId(p.userId, p.videoId) if err != nil { return errors.New("不要重复点赞") } //对应的用户是否点赞的映射状态更新 cache.NewProxyIndexMap().UpdateVideoFavorState(p.userId, p.videoId, true) return nil } // MinusOperation 取消点赞 func (p *PostFavorStateFlow) MinusOperation() error { //视频点赞数目-1 err := models2.NewVideoDAO().MinusOneFavorByUserIdAndVideoId(p.userId, p.videoId) if err != nil { return errors.New("点赞数目已经为0") } //对应的用户是否点赞的映射状态更新 cache.NewProxyIndexMap().UpdateVideoFavorState(p.userId, p.videoId, false) return nil } func (p *PostFavorStateFlow) checkNum() error { if !models2.NewUserInfoDAO().IsUserExistById(p.userId) { return errors.New("用户不存在") } if p.actionType != PLUS && p.actionType != MINUS { return errors.New("未定义的行为") } return nil } ================================================ FILE: deprecated/service/video/post_video.go ================================================ package video import ( "github.com/ACking-you/byte_douyin_project/models" "github.com/ACking-you/byte_douyin_project/util" ) // PostVideo 投稿视频 func PostVideo(userId int64, videoName, coverName, title string) error { return NewPostVideoFlow(userId, videoName, coverName, title).Do() } func NewPostVideoFlow(userId int64, videoName, coverName, title string) *PostVideoFlow { return &PostVideoFlow{ videoName: videoName, coverName: coverName, userId: userId, title: title, } } type PostVideoFlow struct { videoName string coverName string title string userId int64 video *models.Video } func (f *PostVideoFlow) Do() error { f.prepareParam() if err := f.publish(); err != nil { return err } return nil } //准备好参数 func (f *PostVideoFlow) prepareParam() { f.videoName = util.GetFileUrl(f.videoName) f.coverName = util.GetFileUrl(f.coverName) } //组合并添加到数据库 func (f *PostVideoFlow) publish() error { video := &models.Video{ UserInfoId: f.userId, PlayUrl: f.videoName, CoverUrl: f.coverName, Title: f.title, } return models.NewVideoDAO().AddVideo(video) } ================================================ FILE: deprecated/service/video/query_favor_videolist.go ================================================ package video import ( "errors" models2 "github.com/ACking-you/byte_douyin_project/models" ) type FavorList struct { Videos []*models2.Video `json:"video_list"` } func QueryFavorVideoList(userId int64) (*FavorList, error) { return NewQueryFavorVideoListFlow(userId).Do() } type QueryFavorVideoListFlow struct { userId int64 videos []*models2.Video videoList *FavorList } func NewQueryFavorVideoListFlow(userId int64) *QueryFavorVideoListFlow { return &QueryFavorVideoListFlow{userId: userId} } func (q *QueryFavorVideoListFlow) Do() (*FavorList, error) { if err := q.checkNum(); err != nil { return nil, err } if err := q.prepareData(); err != nil { return nil, err } if err := q.packData(); err != nil { return nil, err } return q.videoList, nil } func (q *QueryFavorVideoListFlow) checkNum() error { if !models2.NewUserInfoDAO().IsUserExistById(q.userId) { return errors.New("用户状态异常") } return nil } func (q *QueryFavorVideoListFlow) prepareData() error { err := models2.NewVideoDAO().QueryFavorVideoListByUserId(q.userId, &q.videos) if err != nil { return err } //填充信息(Author和IsFavorite字段,由于是点赞列表,故所有的都是点赞状态 for i := range q.videos { //作者信息查询 var userInfo models2.UserInfo err = models2.NewUserInfoDAO().QueryUserInfoById(q.videos[i].UserInfoId, &userInfo) if err == nil { //若查询未出错则更新,否则不更新作者信息 q.videos[i].Author = userInfo } q.videos[i].IsFavorite = true } return nil } func (q *QueryFavorVideoListFlow) packData() error { q.videoList = &FavorList{Videos: q.videos} return nil } ================================================ FILE: deprecated/service/video/query_videolist.go ================================================ package video import ( "errors" "github.com/ACking-you/byte_douyin_project/cache" models2 "github.com/ACking-you/byte_douyin_project/models" ) type List struct { Videos []*models2.Video `json:"video_list,omitempty"` } func QueryVideoListByUserId(userId int64) (*List, error) { return NewQueryVideoListByUserIdFlow(userId).Do() } func NewQueryVideoListByUserIdFlow(userId int64) *QueryVideoListByUserIdFlow { return &QueryVideoListByUserIdFlow{userId: userId} } type QueryVideoListByUserIdFlow struct { userId int64 videos []*models2.Video videoList *List } func (q *QueryVideoListByUserIdFlow) Do() (*List, error) { if err := q.checkNum(); err != nil { return nil, err } if err := q.packData(); err != nil { return nil, err } return q.videoList, nil } func (q *QueryVideoListByUserIdFlow) checkNum() error { //检查userId是否存在 if !models2.NewUserInfoDAO().IsUserExistById(q.userId) { return errors.New("用户不存在") } return nil } //注意:Video由于在数据库中没有存储作者信息,所以需要手动填充 func (q *QueryVideoListByUserIdFlow) packData() error { err := models2.NewVideoDAO().QueryVideoListByUserId(q.userId, &q.videos) if err != nil { return err } //作者信息查询 var userInfo models2.UserInfo err = models2.NewUserInfoDAO().QueryUserInfoById(q.userId, &userInfo) p := cache.NewProxyIndexMap() if err != nil { return err } //填充信息(Author和IsFavorite字段 for i := range q.videos { q.videos[i].Author = userInfo q.videos[i].IsFavorite = p.GetVideoFavorState(q.userId, q.videos[i].Id) } q.videoList = &List{Videos: q.videos} return nil } ================================================ FILE: deprecated/util/comment.go ================================================ package util import ( "errors" models2 "github.com/ACking-you/byte_douyin_project/models" ) func FillCommentListFields(comments *[]*models2.Comment) error { size := len(*comments) if comments == nil || size == 0 { return errors.New("util.FillCommentListFields comments为空") } dao := models2.NewUserInfoDAO() for _, v := range *comments { _ = dao.QueryUserInfoById(v.UserInfoId, &v.User) //填充这条评论的作者信息 v.CreateDate = v.CreatedAt.Format("1-2") //转为前端要求的日期格式 } return nil } func FillCommentFields(comment *models2.Comment) error { if comment == nil { return errors.New("FillCommentFields comments为空") } comment.CreateDate = comment.CreatedAt.Format("1-2") //转为前端要求的日期格式 return nil } ================================================ FILE: deprecated/util/ffmpeg.go ================================================ package util import ( "errors" "fmt" "os/exec" "path/filepath" "strconv" "sync" "github.com/ACking-you/byte_douyin_project/config" "log" ) // 可以更改 var ( globalMutex sync.RWMutex defaultVideoSuffix = ".mp4" defaultImageSuffix = ".jpg" ) type Video2Image struct { inputPath string outputPath string startTime string keepTime string filter string frameCount int debug bool } func NewVideo2Image() *Video2Image { return &Video2Image{} // 每次返回新实例,避免共享状态 } func GetDefaultVideoSuffix() string { globalMutex.RLock() defer globalMutex.RUnlock() return defaultVideoSuffix } func GetDefaultImageSuffix() string { globalMutex.RLock() defer globalMutex.RUnlock() return defaultImageSuffix } // ChangeVideoDefaultSuffix 全局配置修改方法(线程安全) func ChangeVideoDefaultSuffix(suffix string) { globalMutex.Lock() defer globalMutex.Unlock() defaultVideoSuffix = normalizeExtension(suffix) } func ChangeImageDefaultSuffix(suffix string) { globalMutex.Lock() defer globalMutex.Unlock() defaultImageSuffix = normalizeExtension(suffix) } func normalizeExtension(ext string) string { if ext == "" { return ext } if ext[0] != '.' { return "." + ext } return ext } // SetInputPath 方法链式调用(非共享实例,无需加锁) func (v *Video2Image) SetInputPath(path string) *Video2Image { v.inputPath = path return v } func (v *Video2Image) SetOutputPath(path string) *Video2Image { v.outputPath = path return v } func (v *Video2Image) SetTimeOptions(start, duration string) *Video2Image { v.startTime = start v.keepTime = duration return v } func (v *Video2Image) SetFilter(filter string) *Video2Image { v.filter = filter return v } func (v *Video2Image) SetFrameCount(count int) *Video2Image { v.frameCount = count return v } func (v *Video2Image) SetDebug(debug bool) *Video2Image { v.debug = debug return v } func (v *Video2Image) buildArgs() ([]string, error) { if v.inputPath == "" || v.outputPath == "" { return nil, errors.New("input and output path must be specified") } args := []string{ "-i", filepath.ToSlash(v.inputPath), "-f", "image2", } if v.filter != "" { args = append(args, "-vf", v.filter) } if v.startTime != "" { args = append(args, "-ss", v.startTime) } if v.keepTime != "" { args = append(args, "-t", v.keepTime) } if v.frameCount > 0 { args = append(args, "-frames:v", strconv.Itoa(v.frameCount)) } args = append(args, "-y", filepath.ToSlash(v.outputPath)) return args, nil } func (v *Video2Image) Execute() error { args, err := v.buildArgs() if err != nil { return fmt.Errorf("参数构建失败: %w", err) } ffmpegPath := filepath.FromSlash(config.Global.FfmpegPath) cmd := exec.Command(ffmpegPath, args...) if v.debug { log.Printf("执行命令: %q", cmd.String()) cmd.Stdout = log.Writer() cmd.Stderr = log.Writer() } if err := cmd.Run(); err != nil { return fmt.Errorf("ffmpeg执行失败: %w (命令: %q)", err, cmd.String()) } return nil } ================================================ FILE: deprecated/util/video.go ================================================ package util import ( "errors" "fmt" "github.com/ACking-you/byte_douyin_project/cache" "github.com/ACking-you/byte_douyin_project/config" models2 "github.com/ACking-you/byte_douyin_project/models" "log" "path/filepath" "time" ) func GetFileUrl(fileName string) string { base := fmt.Sprintf("http://%s:%d/static/%s", config.Global.IP, config.Global.Port, fileName) return base } // NewFileName 根据userId+用户发布的视频数量连接成独一无二的文件名 func NewFileName(userId int64) string { var count int64 err := models2.NewVideoDAO().QueryVideoCountByUserId(userId, &count) if err != nil { log.Println(err) } return fmt.Sprintf("%d-%d", userId, count) } // FillVideoListFields 填充每个视频的作者信息(因为作者与视频的一对多关系,数据库中存下的是作者的id // 当userId>0时,我们判断当前为登录状态,其余情况为未登录状态,则不需要填充IsFavorite字段 func FillVideoListFields(userId int64, videos *[]*models2.Video) (*time.Time, error) { if videos == nil || (len(*videos) == 0) { return nil, errors.New("util.FillVideoListFields videos为空") } size := len(*videos) dao := models2.NewUserInfoDAO() p := cache.NewProxyIndexMap() latestTime := (*videos)[size-1].CreatedAt //获取最近的投稿时间 //添加作者信息,以及is_follow状态 for i := 0; i < size; i++ { var userInfo models2.UserInfo err := dao.QueryUserInfoById((*videos)[i].UserInfoId, &userInfo) if err != nil { continue } userInfo.IsFollow = p.GetUserRelation(userId, userInfo.Id) //根据cache更新是否被点赞 (*videos)[i].Author = userInfo //填充有登录信息的点赞状态 if userId > 0 { (*videos)[i].IsFavorite = p.GetVideoFavorState(userId, (*videos)[i].Id) } } return &latestTime, nil } // SaveImageFromVideo 将视频切一帧保存到本地 // isDebug用于控制是否打印出执行的ffmepg命令 func SaveImageFromVideo(name string, isDebug bool) error { return NewVideo2Image(). SetInputPath(filepath.Join(config.Global.StaticSourcePath, name+GetDefaultVideoSuffix())). SetOutputPath(filepath.Join(config.Global.StaticSourcePath, name+GetDefaultImageSuffix())). SetFrameCount(1). SetDebug(isDebug).Execute() }