diff --git a/.gitignore b/.gitignore index 82c4ca1c..fc9c90a0 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,8 @@ # Go workspace file go.work -.idea \ No newline at end of file +.idea + +.vscode + +log \ No newline at end of file diff --git a/README.md b/README.md index 73b96f53..5be6976b 100644 --- a/README.md +++ b/README.md @@ -19,4 +19,9 @@ go build && ./simple-demo ### 测试数据 -测试数据写在 demo_data.go 中,用于列表接口的 mock 测试 \ No newline at end of file +测试数据写在 demo_data.go 中,用于列表接口的 mock 测试 + +### 目录结构说明 + +- document: 项目相关的一些文档记录 +- model: gorm模型 diff --git a/config/config.yml b/config/config.yml new file mode 100644 index 00000000..d1c6e328 --- /dev/null +++ b/config/config.yml @@ -0,0 +1,34 @@ +env: + debug: true +server: + port: "8080" +mysql: + local: + host: "127.0.0.1" + port: "3306" + database: "douyin" + username: "root" + password: "root" + default: + host: "xinzf0520.top" + port: "20607" + database: "douyin" + username: "dyweadmin" + password: "JXU2MTNGJXU5NzUyJXU5Rjk5JXU2MzA3JXU1RjE1JXU0RjYw" +redis: + local: + host: "127.0.0.1" + port: "6379" + password: "" + default: + host: "xinzf0520.top" + port: "20606" + password: "JXU2MTNGJXU5NzUyJXU5Rjk5JXU2MzA3JXU1RjE1JXU0RjYw" + databases: + default: 0 +log: + level: "debug" + filename: "./log/log.log" + max_size: 200 + max_age: 7 + max_backups: 10 \ No newline at end of file diff --git a/controller/comment.go b/controller/comment.go index 6875a1e8..0d810d24 100644 --- a/controller/comment.go +++ b/controller/comment.go @@ -1,38 +1,56 @@ package controller import ( - "github.com/gin-gonic/gin" + "context" + "fmt" "net/http" -) + "strconv" + "sync" -type CommentListResponse struct { - Response - CommentList []Comment `json:"comment_list,omitempty"` -} - -type CommentActionResponse struct { - Response - Comment Comment `json:"comment,omitempty"` -} + "github.com/BaiZe1998/douyin-simple-demo/db/model" + "github.com/BaiZe1998/douyin-simple-demo/dto" + "github.com/BaiZe1998/douyin-simple-demo/pkg/util" + "github.com/BaiZe1998/douyin-simple-demo/service" + "github.com/gin-gonic/gin" +) // CommentAction no practical effect, just check if token is valid func CommentAction(c *gin.Context) { token := c.Query("token") actionType := c.Query("action_type") - - if user, exist := usersLoginInfo[token]; exist { + videoId, _ := strconv.ParseInt(c.Query("video_id"), 10, 64) + userClaims, _ := util.ParseToken(token) + userModel, _ := model.QueryUserById(context.Background(), userClaims.ID) + users := dto.User{ + Id: userModel.ID, + Name: userModel.Name, + FollowCount: userModel.FollowCount, + FollowerCount: userModel.FollowerCount, + IsFollow: false, + } + if userModel.ID > 0 { if actionType == "1" { text := c.Query("comment_text") - c.JSON(http.StatusOK, CommentActionResponse{Response: Response{StatusCode: 0}, - Comment: Comment{ - Id: 1, - User: user, - Content: text, - CreateDate: "05-01", - }}) - return + //comment addcomment + responseComment := service.AddComment(text, users, videoId) + c.JSON(http.StatusOK, + dto.CommentActionResponse{ + Response: dto.Response{StatusCode: 0}, + Comment: responseComment, + }) + } else { + commentId, _ := strconv.ParseInt(c.Query("comment_id"), 10, 64) + //comment delete + model.DeleteCommnet(context.Background(), videoId, commentId) + + c.JSON(http.StatusOK, Response{StatusCode: 0}) } - c.JSON(http.StatusOK, Response{StatusCode: 0}) + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + service.UpdatCacheCommentList(context.Background(), videoId, 10, 0) + }() } else { c.JSON(http.StatusOK, Response{StatusCode: 1, StatusMsg: "User doesn't exist"}) } @@ -40,8 +58,11 @@ func CommentAction(c *gin.Context) { // CommentList all videos have same demo comment list func CommentList(c *gin.Context) { - c.JSON(http.StatusOK, CommentListResponse{ - Response: Response{StatusCode: 0}, - CommentList: DemoComments, + videoId, _ := strconv.ParseInt(c.Query("video_id"), 10, 64) + res, total, _ := service.GetCacheCommentList(context.Background(), videoId, 10, 0) + c.JSON(http.StatusOK, dto.CommentListResponse{ + Response: dto.Response{StatusCode: 0, StatusMsg: ""}, + CommentList: res, }) + fmt.Println(total) } diff --git a/controller/common.go b/controller/common.go index a8213a16..e6b75569 100644 --- a/controller/common.go +++ b/controller/common.go @@ -1,29 +1,32 @@ package controller +import "github.com/BaiZe1998/douyin-simple-demo/dto" + type Response struct { StatusCode int32 `json:"status_code"` StatusMsg string `json:"status_msg,omitempty"` + NextTime int64 `json:"next_time,omitempty"` } type Video struct { - Id int64 `json:"id,omitempty"` - Author User `json:"author"` - PlayUrl string `json:"play_url" 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"` + Id int64 `json:"id,omitempty"` + Author dto.User `json:"author"` + PlayUrl string `json:"play_url" 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"` } type Comment struct { - Id int64 `json:"id,omitempty"` - User User `json:"user"` - Content string `json:"content,omitempty"` - CreateDate string `json:"create_date,omitempty"` + Id int64 `json:"id,omitempty"` + User dto.User `json:"dto.user"` + Content string `json:"content,omitempty"` + CreateDate string `json:"create_date,omitempty"` } type User struct { - Id int64 `json:"id,omitempty"` + Id string `json:"id,omitempty"` Name string `json:"name,omitempty"` FollowCount int64 `json:"follow_count,omitempty"` FollowerCount int64 `json:"follower_count,omitempty"` diff --git a/controller/demo_data.go b/controller/demo_data.go index cc10ff36..ea5c5c3b 100644 --- a/controller/demo_data.go +++ b/controller/demo_data.go @@ -1,30 +1,45 @@ package controller -var DemoVideos = []Video{ +import "github.com/BaiZe1998/douyin-simple-demo/dto" + +var DemoVideos = []dto.Video{ { Id: 1, - Author: DemoUser, - PlayUrl: "https://www.w3schools.com/html/movie.mp4", + Author: DemoUser1, + PlayUrl: "http://niuyefan.oss-cn-beijing.aliyuncs.com/douyin/bear.mp4", CoverUrl: "https://cdn.pixabay.com/photo/2016/03/27/18/10/bear-1283347_1280.jpg", - FavoriteCount: 0, - CommentCount: 0, + FavoriteCount: 2, + CommentCount: 2, IsFavorite: false, }, } -var DemoComments = []Comment{ - { - Id: 1, - User: DemoUser, - Content: "Test Comment", - CreateDate: "05-01", - }, -} +// var DemoComments = []Comment{ +// { +// Id: 1, +// User: DemoUser1, +// Content: "Test Comment", +// CreateDate: "05-01", +// }, +// { +// Id: 2, +// User: DemoUser1, +// Content: "Test Commen22", +// CreateDate: "05-012", +// }, +// } -var DemoUser = User{ +var DemoUser1 = dto.User{ Id: 1, Name: "TestUser", FollowCount: 0, FollowerCount: 0, IsFollow: false, } +var DemoUser = User{ + Id: "1", + Name: "TestUser", + FollowCount: 0, + FollowerCount: 0, + IsFollow: false, +} diff --git a/controller/favorite.go b/controller/favorite.go index 5f19213f..2106c0ea 100644 --- a/controller/favorite.go +++ b/controller/favorite.go @@ -1,27 +1,42 @@ package controller import ( + "github.com/BaiZe1998/douyin-simple-demo/dto" + "github.com/BaiZe1998/douyin-simple-demo/service" "github.com/gin-gonic/gin" "net/http" + "strconv" ) // FavoriteAction no practical effect, just check if token is valid func FavoriteAction(c *gin.Context) { - token := c.Query("token") - - if _, exist := usersLoginInfo[token]; exist { - c.JSON(http.StatusOK, Response{StatusCode: 0}) - } else { - c.JSON(http.StatusOK, Response{StatusCode: 1, StatusMsg: "User doesn't exist"}) + UserIdFromC, _ := c.Get("user_id") + UserID, _ := UserIdFromC.(int64) + VideoID, _ := strconv.ParseInt(c.Query("video_id"), 10, 64) + actionType, _ := strconv.ParseInt(c.Query("action_type"), 10, 64) + if err := service.FavoriteAction(c, UserID, VideoID, int(actionType)); err != nil { + c.JSON(http.StatusBadRequest, dto.Response{ + StatusCode: 1, + StatusMsg: err.Error(), + }) } + c.JSON(http.StatusOK, VideoListResponse{ + Response: Response{ + StatusCode: 0, + StatusMsg: "操作成功", + }, + }) } // FavoriteList all users have same favorite video list func FavoriteList(c *gin.Context) { + UserIdFromC, _ := c.Get("user_id") + UserID, _ := UserIdFromC.(int64) + videoList, _ := service.GetFavoriteList(c, UserID) c.JSON(http.StatusOK, VideoListResponse{ Response: Response{ StatusCode: 0, }, - VideoList: DemoVideos, + VideoList: videoList, }) } diff --git a/controller/feed.go b/controller/feed.go index b4ac439a..8d26609a 100644 --- a/controller/feed.go +++ b/controller/feed.go @@ -1,22 +1,33 @@ package controller import ( - "github.com/gin-gonic/gin" + "log" "net/http" - "time" -) -type FeedResponse struct { - Response - VideoList []Video `json:"video_list,omitempty"` - NextTime int64 `json:"next_time,omitempty"` -} + "github.com/BaiZe1998/douyin-simple-demo/dto" + "github.com/BaiZe1998/douyin-simple-demo/pkg/util" + "github.com/BaiZe1998/douyin-simple-demo/service" + "github.com/gin-gonic/gin" +) // Feed same demo video list for every request func Feed(c *gin.Context) { - c.JSON(http.StatusOK, FeedResponse{ - Response: Response{StatusCode: 0}, - VideoList: DemoVideos, - NextTime: time.Now().Unix(), + lastTime := c.Query("latest_time") + token := c.Query("token") + userInfo, parseTokenErr := util.ParseToken(token) + + if parseTokenErr != nil { + dto.WriteLog( + "error", "解析token错误", + ) + } + + videoList, reTime := service.QueryFeedResponse1(userInfo.ID, lastTime) + log.Println(reTime.Unix()) + c.JSON(http.StatusOK, dto.FeedResponse{ + Response: dto.Response{StatusCode: 0, + StatusMsg: "", + NextTime: reTime.Unix()}, + VideoList: videoList, }) } diff --git a/controller/publish.go b/controller/publish.go index 6990f85a..3b31e0e7 100644 --- a/controller/publish.go +++ b/controller/publish.go @@ -2,58 +2,48 @@ package controller import ( "fmt" + "strconv" + + "github.com/BaiZe1998/douyin-simple-demo/dto" + "github.com/BaiZe1998/douyin-simple-demo/service" "github.com/gin-gonic/gin" + "net/http" - "path/filepath" ) type VideoListResponse struct { Response - VideoList []Video `json:"video_list"` + VideoList []dto.Video `json:"video_list"` } // Publish check token then save upload file to public directory func Publish(c *gin.Context) { - token := c.PostForm("token") - - if _, exist := usersLoginInfo[token]; !exist { - c.JSON(http.StatusOK, Response{StatusCode: 1, StatusMsg: "User doesn't exist"}) - return - } - - data, err := c.FormFile("data") + //需要指定最大上传尺寸,此处无法指定 + fmt.Println("进入publish") + file, err := c.FormFile("data") if err != nil { - c.JSON(http.StatusOK, Response{ - StatusCode: 1, - StatusMsg: err.Error(), - }) - return - } - - filename := filepath.Base(data.Filename) - user := usersLoginInfo[token] - finalName := fmt.Sprintf("%d_%s", user.Id, filename) - saveFile := filepath.Join("./public/", finalName) - if err := c.SaveUploadedFile(data, saveFile); err != nil { - c.JSON(http.StatusOK, Response{ - StatusCode: 1, - StatusMsg: err.Error(), - }) return } - + token := c.PostForm("token") + title := c.PostForm("title") + //上传视频,并添加一个video到数据库 + userIdFromC, _ := c.Get("user_id") + userId, _ := userIdFromC.(int64) + service.UploadVideoAliyun(file, token, title, userId) c.JSON(http.StatusOK, Response{ StatusCode: 0, - StatusMsg: finalName + " uploaded successfully", + StatusMsg: "test" + " uploaded successfully", }) } // PublishList all users have same publish video list func PublishList(c *gin.Context) { + userId, _ := strconv.ParseInt(c.Query("user_id"), 10, 64) + videoList := service.QueryPublishList1(userId) c.JSON(http.StatusOK, VideoListResponse{ Response: Response{ StatusCode: 0, }, - VideoList: DemoVideos, + VideoList: videoList, }) } diff --git a/controller/relation.go b/controller/relation.go index 8dc8e30b..940b5e9e 100644 --- a/controller/relation.go +++ b/controller/relation.go @@ -1,42 +1,84 @@ package controller import ( - "github.com/gin-gonic/gin" "net/http" -) + "strconv" -type UserListResponse struct { - Response - UserList []User `json:"user_list"` -} + "github.com/BaiZe1998/douyin-simple-demo/dto" + + "github.com/BaiZe1998/douyin-simple-demo/service" + "github.com/gin-gonic/gin" +) // RelationAction no practical effect, just check if token is valid func RelationAction(c *gin.Context) { - token := c.Query("token") + // token := c.Query("token") + + user_id_from_c, _ := c.Get("user_id") + user_id, _ := user_id_from_c.(int64) + to_user_id, _ := strconv.ParseInt(c.Query("to_user_id"), 10, 64) + action_type, _ := strconv.ParseInt(c.Query("action_type"), 10, 64) + + err := service.FollowAction(c, user_id, to_user_id, int(action_type)) - if _, exist := usersLoginInfo[token]; exist { - c.JSON(http.StatusOK, Response{StatusCode: 0}) + if err != nil { + c.JSON(http.StatusBadRequest, dto.Response{ + StatusCode: 1, + StatusMsg: err.Error(), + }) } else { - c.JSON(http.StatusOK, Response{StatusCode: 1, StatusMsg: "User doesn't exist"}) + c.JSON(http.StatusAccepted, dto.Response{ + StatusCode: 1, + StatusMsg: "操作成功", + }) } } // FollowList all users have same follow list func FollowList(c *gin.Context) { - c.JSON(http.StatusOK, UserListResponse{ - Response: Response{ - StatusCode: 0, - }, - UserList: []User{DemoUser}, - }) + userId, _ := strconv.ParseInt(c.Query("user_id"), 10, 64) + followList, err := service.GetFollowList(c, userId, 1) + + if err == nil { + c.JSON(http.StatusAccepted, dto.UserListResponse{ + Response: dto.Response{ + StatusCode: 0, + StatusMsg: "查找成功", + }, + UserList: followList, + }) + } else { + c.JSON(http.StatusBadRequest, dto.UserListResponse{ + Response: dto.Response{ + StatusCode: 1, + StatusMsg: "查找失败", + }, + UserList: nil, + }) + } } // FollowerList all users have same follower list func FollowerList(c *gin.Context) { - c.JSON(http.StatusOK, UserListResponse{ - Response: Response{ - StatusCode: 0, - }, - UserList: []User{DemoUser}, - }) + toUserId, _ := strconv.ParseInt(c.Query("user_id"), 10, 64) + + followerList, err := service.GetFollowList(c, toUserId, 2) + + if err == nil { + c.JSON(http.StatusAccepted, dto.UserListResponse{ + Response: dto.Response{ + StatusCode: 0, + StatusMsg: "查找成功", + }, + UserList: followerList, + }) + } else { + c.JSON(http.StatusBadRequest, dto.UserListResponse{ + Response: dto.Response{ + StatusCode: 1, + StatusMsg: "查找失败", + }, + UserList: nil, + }) + } } diff --git a/controller/user.go b/controller/user.go index 72fc57ec..ee57d2a0 100644 --- a/controller/user.go +++ b/controller/user.go @@ -1,18 +1,23 @@ package controller import ( + "context" + "github.com/BaiZe1998/douyin-simple-demo/db/model" + "github.com/BaiZe1998/douyin-simple-demo/dto" + "github.com/BaiZe1998/douyin-simple-demo/pkg/util" + "github.com/BaiZe1998/douyin-simple-demo/service" "github.com/gin-gonic/gin" + "log" "net/http" - "sync/atomic" ) // usersLoginInfo use map to store user info, and key is username+password for demo // user data will be cleared every time the server starts // test data: username=zhanglei, password=douyin var usersLoginInfo = map[string]User{ - "zhangleidouyin": { - Id: 1, - Name: "zhanglei", + "tasdgfasdg123456": { + Id: "08dc2b99ef974d47a2554ed3dea73ea0", + Name: "tasdgfasdg", FollowCount: 10, FollowerCount: 5, IsFollow: true, @@ -21,38 +26,38 @@ var usersLoginInfo = map[string]User{ var userIdSequence = int64(1) -type UserLoginResponse struct { - Response - UserId int64 `json:"user_id,omitempty"` - Token string `json:"token"` -} - -type UserResponse struct { - Response - User User `json:"user"` -} - func Register(c *gin.Context) { + username := c.Query("username") password := c.Query("password") - token := username + password + //Password encrypted with salt + password, _ = service.Encryption(password) + + //QueryUser QueryUser By Name for judged user is exit or not + user, _ := model.QueryUserByName(context.Background(), username) - if _, exist := usersLoginInfo[token]; exist { - c.JSON(http.StatusOK, UserLoginResponse{ - Response: Response{StatusCode: 1, StatusMsg: "User already exist"}, + //judege user exit or not + if user.Name != "" { + log.Printf(user.Name, user.ID) + c.JSON(http.StatusOK, dto.UserLoginResponse{ + Response: dto.Response{StatusCode: 1, StatusMsg: "User already exist"}, }) } else { - atomic.AddInt64(&userIdSequence, 1) - newUser := User{ - Id: userIdSequence, - Name: username, + newUser := &model.User{ + Name: username, + Password: password, } - usersLoginInfo[token] = newUser - c.JSON(http.StatusOK, UserLoginResponse{ - Response: Response{StatusCode: 0}, - UserId: userIdSequence, - Token: username + password, + //userinfo register + model.CreateUser(context.Background(), newUser) + //Query Userinfo for get id + userInfo, _ := model.QueryUserByName(context.Background(), username) + //token + token, _ := util.GenerateToken(&util.UserClaims{ID: userInfo.ID, Name: username, PassWord: password}) + c.JSON(http.StatusOK, dto.UserLoginResponse{ + Response: dto.Response{StatusCode: 0}, + UserId: userInfo.ID, + Token: token, }) } } @@ -60,18 +65,28 @@ func Register(c *gin.Context) { func Login(c *gin.Context) { username := c.Query("username") password := c.Query("password") + //Password encrypted with salt + encryptionPassWord, _ := service.Encryption(password) - token := username + password + user, _ := model.QueryUserByName(context.Background(), username) + token, _ := util.GenerateToken(&util.UserClaims{ID: user.ID, Name: username, PassWord: encryptionPassWord}) - if user, exist := usersLoginInfo[token]; exist { - c.JSON(http.StatusOK, UserLoginResponse{ - Response: Response{StatusCode: 0}, - UserId: user.Id, - Token: token, - }) + if user != nil { + //judge password + if service.ComparePasswords(user.Password, password) { + c.JSON(http.StatusOK, dto.UserLoginResponse{ + Response: dto.Response{StatusCode: 0}, + UserId: user.ID, + Token: token, + }) + } else { + c.JSON(http.StatusOK, dto.UserLoginResponse{ + Response: dto.Response{StatusCode: 1, StatusMsg: "password wrong"}, + }) + } } else { - c.JSON(http.StatusOK, UserLoginResponse{ - Response: Response{StatusCode: 1, StatusMsg: "User doesn't exist"}, + c.JSON(http.StatusOK, dto.UserLoginResponse{ + Response: dto.Response{StatusCode: 1, StatusMsg: "User doesn't exist"}, }) } } @@ -79,14 +94,21 @@ func Login(c *gin.Context) { func UserInfo(c *gin.Context) { token := c.Query("token") - if user, exist := usersLoginInfo[token]; exist { - c.JSON(http.StatusOK, UserResponse{ - Response: Response{StatusCode: 0}, - User: user, - }) - } else { - c.JSON(http.StatusOK, UserResponse{ - Response: Response{StatusCode: 1, StatusMsg: "User doesn't exist"}, - }) + userClaims, _ := util.ParseToken(token) + userModel, _ := model.QueryUserById(context.Background(), userClaims.ID) + + user := dto.User{ + Id: userModel.ID, + Name: userModel.Name, + FollowCount: userModel.FollowCount, + FollowerCount: userModel.FollowerCount, + IsFollow: false, } + c.JSON(http.StatusOK, dto.UserResponse{ + Response: dto.Response{StatusCode: 0}, + User: user, + }) + + //log.Printf(token) + //log.Printf(userinfo.ID) } diff --git a/db/cache.go b/db/cache.go new file mode 100644 index 00000000..8cc19006 --- /dev/null +++ b/db/cache.go @@ -0,0 +1,72 @@ +package db + +import ( + "context" + "encoding/json" + "time" + + "github.com/BaiZe1998/douyin-simple-demo/dto" + "github.com/go-redis/redis/v8" +) + +var RedisCaches map[string]*redis.Client + +func InitRedisPools() error { + var RedisDSN string + var RedisPWD string + if dto.Conf.Env.IsDebug { + // 开发环境 + RedisDSN = dto.Conf.Redis.Local.Host + ":" + dto.Conf.Redis.Local.Port + RedisPWD = dto.Conf.Redis.Local.Password + } else { + // 生产环境 + RedisDSN = dto.Conf.Redis.Default.Host + ":" + dto.Conf.Redis.Default.Port + RedisPWD = dto.Conf.Redis.Default.Password + } + + RedisCaches = make(map[string]*redis.Client) + for k, v := range dto.Conf.Redis.Databases { + RedisCaches[k] = redis.NewClient(&redis.Options{ + Addr: RedisDSN, + Password: RedisPWD, + DB: v, + PoolSize: 20, + }) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + _, err := RedisCaches[k].Ping(ctx).Result() + + if err != nil { + return err + } + } + + return nil +} + +func CacheSet(ctx context.Context, dbName string, key string, value string, expire time.Duration) error { + err := RedisCaches[dbName].Set(ctx, key, value, expire).Err() + return err +} + +func CacheGet(ctx context.Context, dbName string, key string) (string, error) { + result, err := RedisCaches[dbName].Get(ctx, key).Result() + return result, err +} + +func CacheSetList(ctx context.Context, dbName string, key string, value interface{}, expire time.Duration) error { + var valueJson []byte + valueJson, _ = json.Marshal(value) + if err := CacheSet(ctx, dbName, key, string(valueJson), expire); err != nil { + return err + } + return nil +} + +func CacheGetList[T any](ctx context.Context, dbName string, key string, fix []T) ([]T, error) { + result, err := CacheGet(ctx, dbName, key) + var value []T + json.Unmarshal([]byte(result), &value) + return value, err +} diff --git a/db/douyin.sql b/db/douyin.sql new file mode 100644 index 00000000..fa7fa016 --- /dev/null +++ b/db/douyin.sql @@ -0,0 +1,99 @@ +/* + Navicat Premium Data Transfer + + Source Server : douyin + Source Server Type : MySQL + Source Server Version : 80029 + Source Host : xinzf0520.top:20607 + Source Schema : douyin + + Target Server Type : MySQL + Target Server Version : 80029 + File Encoding : 65001 + + Date: 11/06/2022 17:17:38 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for comment +-- ---------------------------- +DROP TABLE IF EXISTS `comment`; +CREATE TABLE `comment` ( + `id` int NOT NULL AUTO_INCREMENT, + `user_id` int NULL DEFAULT NULL COMMENT '用户id', + `video_id` int NULL DEFAULT NULL COMMENT '视频id', + `status` tinyint NULL DEFAULT NULL COMMENT '评论状态', + `content` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '内容', + `created_at` datetime NULL DEFAULT NULL COMMENT '创建时间', + `updated_at` datetime NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_video_id_status`(`video_id`, `status`) USING BTREE COMMENT '获取某个视频的所有评论' +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for favorite +-- ---------------------------- +DROP TABLE IF EXISTS `favorite`; +CREATE TABLE `favorite` ( + `id` int NOT NULL AUTO_INCREMENT, + `user_id` int NULL DEFAULT NULL COMMENT '用户id', + `video_id` int NULL DEFAULT NULL COMMENT '视频id', + `status` tinyint NULL DEFAULT NULL COMMENT '点赞状态', + `created_at` datetime NULL DEFAULT NULL COMMENT '创建时间', + `updated_at` datetime NULL DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_user_id_status_video_id`(`user_id`, `status`, `video_id`) USING BTREE COMMENT '获取某个用户所有点赞视频id' +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for follow +-- ---------------------------- +DROP TABLE IF EXISTS `follow`; +CREATE TABLE `follow` ( + `id` int NOT NULL AUTO_INCREMENT, + `user_id` int NULL DEFAULT NULL COMMENT '用户id', + `followed_user` int NULL DEFAULT NULL COMMENT '被关注者id', + `status` tinyint NULL DEFAULT 1 COMMENT '关注状态', + `created_at` datetime NULL DEFAULT NULL COMMENT '创建时间', + `updated_at` datetime NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_user_id_status_followed_user`(`user_id`, `status`, `followed_user`) USING BTREE COMMENT '查询关注列表', + INDEX `idx_followed_user_status_user_id`(`followed_user`, `status`, `user_id`) USING BTREE COMMENT '查询粉丝列表' +) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for user +-- ---------------------------- +DROP TABLE IF EXISTS `user`; +CREATE TABLE `user` ( + `id` int NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名', + `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码', + `follow_count` int NULL DEFAULT NULL COMMENT '关注数', + `follower_count` int NULL DEFAULT NULL COMMENT '粉丝数', + `created_at` datetime NULL DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_name`(`name`) USING BTREE COMMENT '根据用户名查询信息(如注册)' +) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for video +-- ---------------------------- +DROP TABLE IF EXISTS `video`; +CREATE TABLE `video` ( + `id` int NOT NULL AUTO_INCREMENT, + `author_id` int NULL DEFAULT NULL COMMENT '作者id', + `play_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '播放路径', + `cover_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '封面路径', + `favorite_count` int NULL DEFAULT NULL COMMENT '喜欢数', + `comment_count` int NULL DEFAULT NULL COMMENT '评论数', + `created_at` datetime NULL DEFAULT NULL COMMENT '创建时间', + `title` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_author_id`(`author_id`) USING BTREE COMMENT '根据用户id查询所有发布的视频' +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/db/init.go b/db/init.go new file mode 100644 index 00000000..f5c1aadb --- /dev/null +++ b/db/init.go @@ -0,0 +1,9 @@ +package db + +import "github.com/BaiZe1998/douyin-simple-demo/db/model" + +// Init init db +func Init() { + model.Init() // mysql + InitRedisPools() +} diff --git a/db/model/comment.go b/db/model/comment.go new file mode 100644 index 00000000..8c5ded3f --- /dev/null +++ b/db/model/comment.go @@ -0,0 +1,97 @@ +package model + +import ( + "context" + "time" + + "gorm.io/gorm" +) + +type Comment struct { + ID int64 `gorm:"primarykey"` + UserId int64 + VideoId int64 + Content string + Status int + CreatedAt time.Time + UpdatedAt time.Time +} + +// CreateComment Comment info +func CreateComment(ctx context.Context, videoId int64, comment *Comment) error { + tx := DB.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + if err := tx.Error; err != nil { + return err + } + + if err := tx.Table("comment").WithContext(ctx).Create(comment).Error; err != nil { + tx.Rollback() + return err + } + + //addVidelist commentcount + if err := tx.Table("video").WithContext(ctx).Where("id = ?", videoId).Update("comment_count", gorm.Expr("comment_count+?", 1)).Error; err != nil { + tx.Rollback() + return err + } + return tx.Commit().Error +} + +// QueryComment query list of Comment info +func QueryComment(ctx context.Context, videoId int64, limit, offset int) ([]Comment, int64, error) { + var total int64 + var res []Comment + var conn *gorm.DB + tx := DB.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + if err := tx.Error; err != nil { + return res, total, err + } + + conn = tx.Table("comment").WithContext(ctx).Model(&Comment{}).Where("video_id = ? and status = 1 ", videoId) + + if err := conn.Count(&total).Error; err != nil { + tx.Rollback() + return res, total, err + } + if err := conn.Limit(limit).Offset(offset).Find(&res).Error; err != nil { + tx.Rollback() + return res, total, err + } + + return res, total, nil +} + +// DeleteComment delete comment info +func DeleteCommnet(ctx context.Context, videoId int64, commentId int64) error { + tx := DB.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + if err := tx.Error; err != nil { + return err + } + if err := tx.Table("comment").WithContext(ctx).Where("id = ? ", commentId).Update("status", 2).Error; err != nil { + tx.Rollback() + return err + } + + //addVidelist commentcount + if err := tx.Table("video").WithContext(ctx).Where("id = ?", videoId).Update("comment_count", gorm.Expr("comment_count-?", 1)).Error; err != nil { + tx.Rollback() + return err + } + return tx.Commit().Error + +} diff --git a/db/model/favorite.go b/db/model/favorite.go new file mode 100644 index 00000000..62d6e658 --- /dev/null +++ b/db/model/favorite.go @@ -0,0 +1,83 @@ +package model + +import ( + "context" + "gorm.io/gorm" + "time" +) + +type Favorite struct { + ID int64 `gorm:"primarykey"` + UserId int64 + VideoId int64 + Status int + CreatedAt time.Time + UpdatedAt time.Time +} + +func CreateFavorite(ctx context.Context, favorite *Favorite) error { + if err := DB.Table("favorite").WithContext(ctx).Create(favorite).Error; err != nil { + return err + } + return nil +} + +func UpdateFavorite(ctx context.Context, userID, videoID int64, status *int) error { + params := map[string]interface{}{} + if status != nil { + params["status"] = *status + } + return DB.Table("favorite").WithContext(ctx).Model(&Favorite{}).Where("user_id = ? and video_id = ?", userID, videoID). + Updates(params).Error +} + +func QueryFavorite(ctx context.Context, userID int64, videoID int64) (Favorite, error) { + var favorite Favorite + + if err := DB.Table("favorite").WithContext(ctx).Where("user_id=? and video_id=? ", userID, videoID).First(&favorite).Error; err != nil { + return Favorite{}, nil + } + return favorite, nil +} + +func QueryFavorites(ctx context.Context, userID int64, limit, offset int) ([]Video, int64, error) { + var videoList []Video + var total int64 + var conn *gorm.DB + conn = DB.Table("favorite").WithContext(ctx).Joins("inner join video on favorite.video_id = video.id"). + Select("video.id", "video.author_id", "video.play_url", "video.cover_url", "video.favorite_count", "video.comment_count"). + Where("favorite.user_id = ?", userID) + + if err := conn.Count(&total).Error; err != nil { + return nil, 0, err + } + if limit == 0 { + if err := conn.Scan(&videoList).Error; err != nil { + return nil, 0, err + } + } else { + if err := conn.Limit(limit).Offset(offset).Scan(&videoList).Error; err != nil { + return nil, 0, err + } + } + return videoList, total, nil +} + +//QueryIsFavoritequerytheuserisornotfavoritethevideo +func QueryIsFavorite(ctx context.Context, userId int64, videoId int64) (Favorite, error) { + var res Favorite + tx := DB.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + if err := tx.Error; err != nil { + return Favorite{}, err + } + if err := DB.Table("favorite").WithContext(ctx).Where("user_id=? AND video_id=?", userId, videoId).Find(&res).Error; err != nil { + tx.Rollback() + return res, err + } + return res, nil +} diff --git a/db/model/follow.go b/db/model/follow.go new file mode 100644 index 00000000..dfb245e8 --- /dev/null +++ b/db/model/follow.go @@ -0,0 +1,79 @@ +package model + +import ( + "context" + "time" + + "gorm.io/gorm" +) + +type Follow struct { + ID int64 `gorm:"primarykey"` + UserId int64 + FollowedUser int64 + Status int + CreatedAt time.Time + UpdatedAt time.Time +} + +// CreateFollow create follow info +func CreateFollow(ctx context.Context, follow *Follow) error { + if err := DB.Table("follow").WithContext(ctx).Create(follow).Error; err != nil { + return err + } + return nil +} + +// UpdateFollow update follow info +func UpdateFollow(ctx context.Context, userID, followedUser int64, status *int) error { + params := map[string]interface{}{} + if status != nil { + params["status"] = *status + } + return DB.Table("follow").WithContext(ctx).Model(&Follow{}).Where("user_id = ? and followed_user = ?", userID, followedUser). + Updates(params).Error +} + +// DeleteFollow delete follow info +func DeleteFollow(ctx context.Context, userID int64, followedUser int64) error { + return DB.Table("follow").WithContext(ctx).Where("user_id = ? and followed_user = ? ", userID, followedUser).Delete(&Follow{}).Error +} + +//First +// QueryFollow query list of note info +func QueryFollow(ctx context.Context, userID int64, status, limit, offset int) ([]*Follow, int64, error) { + var total int64 + var res []*Follow + var conn *gorm.DB + if status == 1 { + conn = DB.Table("follow").WithContext(ctx).Model(&Follow{}).Where("user_id = ? and status = 1", userID) + } else { // query for followers + conn = DB.Table("follow").WithContext(ctx).Model(&Follow{}).Where("followed_user = ? and status = 1", userID) + } + if err := conn.Count(&total).Error; err != nil { + return res, total, err + } + if err := conn.Limit(limit).Offset(offset).Find(&res).Error; err != nil { + return res, total, err + } + return res, total, nil +} + +//QueryIsFollow query is follow or not +func QueryIsFollow(ctx context.Context, userID int64, followedUser int64) (Follow, error) { + var res Follow + tx := DB.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + if err := tx.Error; err != nil { + return Follow{}, err + } + if err := tx.Table("follow").WithContext(ctx).Where("user_id=?andfollowed_user=?", userID, followedUser).Find(&res).Error; err != nil { + tx.Rollback() + return res, err + } + return res, nil +} diff --git a/db/model/init.go b/db/model/init.go new file mode 100644 index 00000000..761d4ec9 --- /dev/null +++ b/db/model/init.go @@ -0,0 +1,47 @@ +package model + +import ( + "github.com/BaiZe1998/douyin-simple-demo/dto" + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +var DB *gorm.DB + +// Init init DB +func Init() { + var err error + + if dto.Conf.Env.IsDebug { + // 开发环境 + DB, err = gorm.Open( + mysql.Open( + dto.Conf.MySQL.Local.Username+":"+ + dto.Conf.MySQL.Local.Password+"@tcp("+ + dto.Conf.MySQL.Local.Host+":"+ + dto.Conf.MySQL.Local.Port+")/"+ + dto.Conf.MySQL.Local.Database+"?charset=utf8&parseTime=True&loc=Local"), + &gorm.Config{ + PrepareStmt: true, + SkipDefaultTransaction: true, + }, + ) + } else { + // 生产环境 + DB, err = gorm.Open( + mysql.Open( + dto.Conf.MySQL.Default.Username+":"+ + dto.Conf.MySQL.Default.Password+"@tcp("+ + dto.Conf.MySQL.Default.Host+":"+ + dto.Conf.MySQL.Default.Port+")/"+ + dto.Conf.MySQL.Default.Database+"?charset=utf8&parseTime=True&loc=Local"), + &gorm.Config{ + PrepareStmt: true, + SkipDefaultTransaction: true, + }, + ) + } + if err != nil { + panic(err) + } +} diff --git a/db/model/user.go b/db/model/user.go new file mode 100644 index 00000000..87bd533c --- /dev/null +++ b/db/model/user.go @@ -0,0 +1,70 @@ +package model + +import ( + "context" + "time" +) + +type User struct { + ID int64 `gorm:"primarykey"` + Name string + Password string + FollowCount int64 + FollowerCount int64 + CreatedAt time.Time +} + +//CteateUser create user info +func CreateUser(ctx context.Context, user *User) error { + tx := DB.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + if err := tx.Error; err != nil { + return err + } + if err := tx.Table("user").WithContext(ctx).Create(user).Error; err != nil { + tx.Rollback() + return err + } + return nil +} + +//QueryUser Quert User By Name +func QueryUserByName(ctx context.Context, username string) (*User, error) { + var userInfo *User + tx := DB.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + if err := tx.Error; err != nil { + return nil, err + } + if err := tx.Table("user").WithContext(ctx).Where("name=?", username).Find(&userInfo).Error; err != nil { + tx.Rollback() + return userInfo, err + } + return userInfo, nil +} + +func QueryUserById(ctx context.Context, id int64) (*User, error) { + var userInfo *User + tx := DB.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + if err := tx.Error; err != nil { + return nil, err + } + if err := tx.Table("user").WithContext(ctx).Where("id=?", id).Find(&userInfo).Error; err != nil { + tx.Rollback() + return userInfo, err + } + return userInfo, nil +} diff --git a/db/model/video.go b/db/model/video.go new file mode 100644 index 00000000..ee74053f --- /dev/null +++ b/db/model/video.go @@ -0,0 +1,62 @@ +package model + +import ( + "context" + "gorm.io/gorm" + "time" +) + +type Video struct { + ID int64 `gorm:"primarykey"` + AuthorID int64 + PlayUrl string + CoverUrl string + FavoriteCount int64 + CommentCount int64 + CreatedAt time.Time + Title string +} + +// CreateVideo create video info +func CreateVideo(ctx context.Context, video *Video) error { + if err := DB.Table("video").WithContext(ctx).Create(video).Error; err != nil { + return err + } + return nil +} + +//QueryVideoListqueryvideolist +func QueryVideoList(ctx context.Context, lastTime string) (error, []Video) { + var videoList []Video + tx := DB.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + if err := tx.Error; err != nil { + return err, nil + } + if err := tx.Table("video").WithContext(ctx).Where("created_at BETWEEN(\"2022-06-01 21:42:52\") and (\"" + lastTime + "\")").Order("video.created_at desc").Limit(10).Find(&videoList).Error; err != nil { + tx.Rollback() + return err, videoList + } + return nil, videoList +} + +func UpdateVideoFavorite(ctx context.Context, videoID int64, action int) error { + tx := DB.Begin() + if err := tx.Table("video").WithContext(ctx).Where("id = ?", videoID).Update("favorite_count", gorm.Expr("favorite_count+?", action)).Error; err != nil { + tx.Rollback() + return err + } + return tx.Commit().Error +} + +func QueryPublishList(ctx context.Context, userId string) (error, []Video) { + var videoList []Video + if err := DB.Table("video").WithContext(ctx).Where("author_id = " + userId).Order("video.created_at desc").Limit(10).Find(&videoList).Error; err != nil { + return err, videoList + } + return nil, videoList +} diff --git a/documents/database.md b/documents/database.md new file mode 100644 index 00000000..a2ba8030 --- /dev/null +++ b/documents/database.md @@ -0,0 +1,165 @@ +# 数据库设计与技术选型 + +[TOC] + +## MySQL数据库字段 + +> MySQL包含5个表,分别是User、Video、Comment、Favorite、Follow,分别记录用户信息、视频信息、评论信息、点赞信息、关注信息 + +## 数据库表 + +### User + +| 字段 | 类型 | 备注 | +| -------------- | -------- | ---- | +| id | int | | +| name | varchar | 姓名 | +| follow_count | Int | 关注数 | +| follower_count | Int | 粉丝数 | +| create_time | datetime | 创建时间 | + +### Video + +| 字段 | 类型 | 备注 | +| -------------- | -------- | ---- | +| id | int | | +| author_id | Int | 姓名 | +| play_url | varcher | 播放地址 | +| cover_url | varcher | 封面地址 | +| favorite_count | Int | 点赞数 | +| comment_count | int | 评论数 | +| created_at | datetime | 创建时间 | + +### Comment + +| 字段 | 类型 | 备注 | +| ---------- | -------- |-----------| +| id | int | | +| user_id | Int | 用户id | +| video_id | int | 视频id | +| content | text | 评论内容 | +| status | tinyint | 评论状态(1/0) | +| created_at | datetime | 创建时间 | +| updated_at | datetime | 修改时间 | + +### Favorite + +| 字段 | 类型 | 备注 | +| ---------- | -------- | --------- | +| id | int | | +| user_id | Int | 用户id | +| video_id | int | 视频id | +| status | tinyint | 是否点赞(1/0) | +| created_at | datetime | 创建时间 | +| updated_at | datetime | 修改时间 | + +### Follow + +| 字段 | 类型 | 备注 | +| ------------- | -------- | ------ | +| id | int | | +| user_id | Int | 用户id | +| followed_user | int | 被关注者id | +| status | tinyint | 关注状态 | +| created_at | datetime | 创建时间 | +| updated_at | datetime | 修改时间| + +## MongoDB数据库字段 + +> MongoDB数据库主要是用于记录用户之间的关系,即关注和被关注,考虑到在大量用户的场景下,使用MySQL记录和查询用户关系的开销比较大且不够方便,所以考虑使用MongoDB来存放用户之间的关系 +> +> 查询时,直接取出一个用户的关注和被关注列表 + +### 关注列表 + +```json +{ + "user_id": "1", + "follow_list": [ + { + "user_id": "2", + "name": "2号用户" + }, + { + "user_id": "3", + "name": "3号用户" + } + ] +} +``` + +### 被关注列表 + +```json +{ + "user_id": "1", + "follower_list": [ + { + "user_id": "2", + "name": "2号用户" + }, + { + "user_id": "3", + "name": "3号用户" + } + ] +} +``` + +## Redis数据库字段(MongoDB数据库的另一个备选方案) + +> 上述使用MongoDB所完成的用户关系记录也可以考虑使用Redis来完成;Redis和方案相比Mongo方案,查询速度应该是更快的,但是相比之下,查看关注列表和被关注列表可能不是特别常用的操作,这些数据长期占用Redis的内存可能有些浪费。 +> +> 由此也可能有一个更扩展的做法,先从MongoDB读取列表,然后存放在Redis中,分批次加载给用户(一般场景可能是这样的:用户通常不会查看关注列表,若要查看关注列表,则可能会查看多个关注列表) + +### follow库 + +```json +{ + "[user_id]": [ + { + "user_id": "2", + "name": "2号用户" + }, + { + "user_id": "3", + "name": "3号用户" + } + ] +} +``` + +### follower库 + +```json +{ + "[user_id]": [ + { + "user_id": "2", + "name": "2号用户" + }, + { + "user_id": "3", + "name": "3号用户" + } + ] +} +``` + +## 接口字段 + +|接口|依赖的表| +|:---:|:---:| +|douyin/feed|user, video| +|douyin/user/register|user| +|douyin/user/login|user| +|douyin/user|user| +|douyin/publish/action|user, video| +|douyin/publish/list|user, video| +|douyin/favorite/action|video| +|douyin/favorite/list|video, user| +|douyin/comment/action|comment, user| +|douyin/comment/list|comment, user| +|douyin/relation/action|| +|douyin/relation/follow/list|| +|douyin/relation/follower/list|| diff --git a/documents/logger_manual.md b/documents/logger_manual.md new file mode 100644 index 00000000..f32194f4 --- /dev/null +++ b/documents/logger_manual.md @@ -0,0 +1,46 @@ +# 关于如何使用本项目的日志组件 + +## 日志组件概述 + +日志组件基于zap库进行进一步封装,日志组件代码位于`dto/logger.go`由于涉及循环引用,所以暂时将文件放置于`dto`目录下,日志记录的格式为每次记录一行,每行都是一个json格式的字符串 + +日志组件将记录3种情况的日志 + +1. 接口访问日志,请求接口时,默认将会记录下相关的访问信息,如所访问的接口、返回值、请求时间等 +2. panic所触发的报错,日志组件将会掩盖掉panic错误,将不再因为panic而导致程序终止 +3. 人为增加日志,在代码重使用类似于`logger.Info()`的函数来记录日志 + +## 使用方法 + +在整个项目的绝大多数地方,使用`dto.WriteLog()`函数即可记录日志,此函数主要有如下参数 + +- 第一个参数:日志类型,目前支持info、warn、error共3个类型的日志记录,此参数必须为三者之一,填写三者之外的内容,将默认记录为info类型的日志 +- 第二个参数:日志主要信息,此字段为一个字符串,为当前日志的概括信息 +- 第3到n个参数:对日志进行特殊化的记录,例如记录一些关键参数的值;第3到n个参数必须是偶数个参数,且第`3+k`个参数和第`3+k+1`个参数为一组,分别为键和值;目前此参数只支持`string`类型 + +### 具体使用 + +```golang +dto.WriteLog( + "info", "Log test", + "key1", "value1", + "key2", "value2", +) +``` + +在上述实列中,`"info"`和`"Log test"`分别对应上述描述的第1和第2个参数,从`"key1"`开始是第`3+k`个参数,分别与`3+k+k`个参数一一对应 + +上述代码记录的日志如下(经过格式化处理的日志) + +```json +{ + "level": "info", + "msg": "Log test", + "key1": "value1", + "key2": "value2" +} +``` + +## 推荐使用方式 + +在编写代码的过程中,对错误的处理和记录多使用`dto.WriteLog()`函数记录而不是`panic()`,使用`dto.WriteLog()`可以更准确的记录调用日志记录函数的文件和行号 diff --git a/douyin-simple-demo b/douyin-simple-demo new file mode 100755 index 00000000..8e060c11 Binary files /dev/null and b/douyin-simple-demo differ diff --git a/dto/comment.go b/dto/comment.go new file mode 100644 index 00000000..7b91f4ff --- /dev/null +++ b/dto/comment.go @@ -0,0 +1,25 @@ +package dto + +type Comment struct { + Id int64 `json:"id,omitempty"` + User User `json:"user"` + Content string `json:"content,omitempty"` + CreateDate string `json:"create_date,omitempty"` +} + +type CommentListResponse struct { + Response + CommentList []ResponseComment `json:"comment_list,omitempty"` +} + +type CommentActionResponse struct { + Response + Comment ResponseComment `json:"comment,omitempty"` +} + +type ResponseComment struct { + ID int64 `json:"id,omitempty"` + User User `json:"user,omitempty"` + Content string `json:"content,omitempty"` + CreatedAt string `json:"create_date,omitempty"` +} diff --git a/dto/common.go b/dto/common.go new file mode 100644 index 00000000..62e89997 --- /dev/null +++ b/dto/common.go @@ -0,0 +1,7 @@ +package dto + +type Response struct { + StatusCode int32 `json:"status_code"` + StatusMsg string `json:"status_msg,omitempty"` + NextTime int64 `json:"next_time,omitempty"` +} diff --git a/dto/config.go b/dto/config.go new file mode 100644 index 00000000..02eacf11 --- /dev/null +++ b/dto/config.go @@ -0,0 +1,77 @@ +package dto + +import ( + "io/ioutil" + + "github.com/BaiZe1998/douyin-simple-demo/pkg/util" + "gopkg.in/yaml.v2" +) + +type Config struct { + Env struct { + IsDebug bool `yaml:"debug"` + } `yaml:"env"` + Server struct { + Port string `yaml:"port"` + } `yaml:"server"` + MySQL struct { + Local struct { + Host string `yaml:"host"` + Port string `yaml:"port"` + Database string `yaml:"database"` + Username string `yaml:"username"` + Password string `yaml:"password"` + } `yaml:"local"` + Default struct { + Host string `yaml:"host"` + Port string `yaml:"port"` + Database string `yaml:"database"` + Username string `yaml:"username"` + Password string `yaml:"password"` + } `yaml:"default"` + } `yaml:"mysql"` + Redis struct { + Local struct { + Host string `yaml:"host"` + Port string `yaml:"port"` + Password string `yaml:"password"` + } `yaml:"local"` + Default struct { + Host string `yaml:"host"` + Port string `yaml:"port"` + Password string `yaml:"password"` + } `yaml:"default"` + Databases map[string]int `yaml:"databases"` + } `yaml:"redis"` + Log struct { + Level string `yaml:"level"` + Filename string `yaml:"filename"` + MaxSize int `yaml:"max_size"` + MaxAge int `yaml:"max_age"` + MaxBackups int `yaml:"max_backups"` + } `yaml:"log"` +} + +var Conf *Config + +func GetConfig() *Config { + return Conf +} + +func InitConfig() error { + var config Config + + configFilePath := util.GetConfigPath() + file, err := ioutil.ReadFile(configFilePath) + + if err != nil { + return err + } + + if err = yaml.Unmarshal(file, &config); err != nil { + return err + } + + Conf = &config + return nil +} diff --git a/dto/logger.go b/dto/logger.go new file mode 100644 index 00000000..0500be8f --- /dev/null +++ b/dto/logger.go @@ -0,0 +1,154 @@ +package dto + +import ( + "fmt" + "net" + "net/http" + "net/http/httputil" + "os" + "runtime/debug" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/natefinch/lumberjack" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var Lg *zap.Logger + +// InitLogger 初始化Logger +func InitLogger() (err error) { + writeSyncer := getLogWriter( + Conf.Log.Filename, + Conf.Log.MaxSize, + Conf.Log.MaxBackups, + Conf.Log.MaxAge, + ) + encoder := getEncoder() + var l = new(zapcore.Level) + err = l.UnmarshalText([]byte(Conf.Log.Level)) + if err != nil { + return + } + core := zapcore.NewCore(encoder, writeSyncer, l) + + Lg = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1)) + zap.ReplaceGlobals(Lg) // 替换zap包中全局的logger实例,后续在其他包中只需使用zap.L()调用即可 + return +} + +func getEncoder() zapcore.Encoder { + encoderConfig := zap.NewProductionEncoderConfig() + encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder + encoderConfig.TimeKey = "time" + encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder + encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder + encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder + return zapcore.NewJSONEncoder(encoderConfig) +} + +func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer { + lumberJackLogger := &lumberjack.Logger{ + Filename: filename, + MaxSize: maxSize, + MaxBackups: maxBackup, + MaxAge: maxAge, + } + return zapcore.AddSync(lumberJackLogger) +} + +// GinLogger 接收gin框架默认的日志 +func GinLogger() gin.HandlerFunc { + return func(c *gin.Context) { + start := time.Now() + path := c.Request.URL.Path + query := c.Request.URL.RawQuery + c.Next() + + cost := time.Since(start) + Lg.Info(path, + zap.Int("status", c.Writer.Status()), + zap.String("method", c.Request.Method), + zap.String("path", path), + zap.String("query", query), + zap.String("ip", c.ClientIP()), + zap.String("user-agent", c.Request.UserAgent()), + zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()), + zap.Duration("cost", cost), + ) + } +} + +// GinRecovery recover掉项目可能出现的panic,并使用zap记录相关日志 +func GinRecovery(stack bool) gin.HandlerFunc { + return func(c *gin.Context) { + defer func() { + if err := recover(); err != nil { + // Check for a broken connection, as it is not really a + // condition that warrants a panic stack trace. + var brokenPipe bool + if ne, ok := err.(*net.OpError); ok { + if se, ok := ne.Err.(*os.SyscallError); ok { + if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { + brokenPipe = true + } + } + } + + httpRequest, _ := httputil.DumpRequest(c.Request, false) + if brokenPipe { + Lg.Error(c.Request.URL.Path, + zap.Any("error", err), + zap.String("request", string(httpRequest)), + ) + // If the connection is dead, we can't write a status to it. + c.Error(err.(error)) // nolint: errcheck + c.Abort() + return + } + + if stack { + Lg.Error("[Recovery from panic]", + zap.Any("error", err), + zap.String("request", string(httpRequest)), + zap.String("stack", string(debug.Stack())), + ) + } else { + Lg.Error("[Recovery from panic]", + zap.Any("error", err), + zap.String("request", string(httpRequest)), + ) + } + c.AbortWithStatus(http.StatusInternalServerError) + } + }() + c.Next() + } +} + +func WriteLog(logType string, msg string, values ...string) error { + if len(values)%2 != 0 { + panic("日志信息的键与值数量不匹配") + } + + logInfo := make([]zapcore.Field, len(values)/2) + for i := 0; i < len(values); i += 2 { + logInfo[i/2] = zap.String(values[i], values[i+1]) + } + + fmt.Println(logInfo) + + switch logType { + case "info": + Lg.Info(msg, logInfo...) + case "error": + Lg.Error(msg, logInfo...) + case "warn": + Lg.Warn(msg, logInfo...) + default: + Lg.Info(msg, logInfo...) + } + return nil +} diff --git a/dto/user.go b/dto/user.go new file mode 100644 index 00000000..cbd56e6f --- /dev/null +++ b/dto/user.go @@ -0,0 +1,25 @@ +package dto + +type User struct { + Id int64 `json:"id,omitempty"` + Name string `json:"name,omitempty"` + FollowCount int64 `json:"follow_count"` + FollowerCount int64 `json:"follower_count"` + IsFollow bool `json:"is_follow,omitempty"` +} + +type UserListResponse struct { + Response + UserList []User `json:"user_list"` +} + +type UserLoginResponse struct { + Response + UserId int64 `json:"user_id,omitempty"` + Token string `json:"token"` +} + +type UserResponse struct { + Response + User User `json:"user"` +} diff --git a/dto/video.go b/dto/video.go new file mode 100644 index 00000000..17e18759 --- /dev/null +++ b/dto/video.go @@ -0,0 +1,19 @@ +package dto + +type Video struct { + Id int64 `json:"id,omitempty"` + Author User `json:"author"` + PlayUrl string `json:"play_url" 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"` +} + +type FeedResponse struct { + Response + VideoList []Video `json:"video_list,omitempty"` + NextTime int64 `json:"next_time,omitempty"` + CommentCount int64 `json:"comment_count,omitempty"` +} diff --git a/go.mod b/go.mod index 2a0d336e..0132845c 100644 --- a/go.mod +++ b/go.mod @@ -1,24 +1,48 @@ -module github.com/RaymondCode/simple-demo +module github.com/BaiZe1998/douyin-simple-demo -go 1.17 +go 1.18 -require github.com/gin-gonic/gin v1.7.7 +require ( + github.com/aliyun/alibaba-cloud-sdk-go v1.61.1633 + github.com/aliyun/aliyun-oss-go-sdk v2.2.4+incompatible + 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 + github.com/natefinch/lumberjack v2.0.0+incompatible + go.uber.org/zap v1.21.0 + golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f + gopkg.in/yaml.v2 v2.4.0 + gorm.io/driver/mysql v1.3.4 + gorm.io/gorm v1.23.5 +) require ( + github.com/BurntSushi/toml v1.1.0 // indirect + github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/validator/v10 v10.11.0 // indirect + github.com/go-sql-driver/mysql v1.6.0 // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/satori/go.uuid v1.2.0 // indirect github.com/ugorji/go/codec v1.2.7 // indirect - golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect golang.org/x/text v0.3.7 // indirect + golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect google.golang.org/protobuf v1.28.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/ini.v1 v1.66.6 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect ) diff --git a/go.sum b/go.sum index e398fec5..abb3a528 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,24 @@ +github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.1633 h1:qIiqeB6j5Rec6mFXbZGQt87BIDGKHowi8Ymj+Vf1jSg= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.1633/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU= +github.com/aliyun/aliyun-oss-go-sdk v2.2.4+incompatible h1:cD1bK/FmYTpL+r5i9lQ9EU6ScAjA173EVsii7gAc6SQ= +github.com/aliyun/aliyun-oss-go-sdk v2.2.4+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA= +github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 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= @@ -17,6 +34,11 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +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/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= @@ -24,6 +46,17 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 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/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/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= @@ -45,14 +78,24 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= +github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 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/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -60,23 +103,42 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= 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/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -85,12 +147,21 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc 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/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= +golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= @@ -100,6 +171,12 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI= +gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -107,3 +184,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.3.4 h1:/KoBMgsUHC3bExsekDcmNYaBnfH2WNeFuXqqrqMc98Q= +gorm.io/driver/mysql v1.3.4/go.mod h1:s4Tq0KmD0yhPGHbZEwg1VPlH0vT/GBHJZorPzhcxBUE= +gorm.io/gorm v1.23.4/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= diff --git a/main.go b/main.go index 1ad0aa33..da74282b 100644 --- a/main.go +++ b/main.go @@ -1,13 +1,29 @@ package main import ( + "github.com/BaiZe1998/douyin-simple-demo/db" + "github.com/BaiZe1998/douyin-simple-demo/dto" "github.com/gin-gonic/gin" ) +func Init() { + dto.InitConfig() + db.Init() +} + func main() { + Init() + + cfg := dto.GetConfig() + + if err := dto.InitLogger(); err != nil { + return + } + r := gin.Default() + r.Use(dto.GinLogger(), dto.GinRecovery(true)) initRouter(r) - r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") + r.Run(":" + cfg.Server.Port) } diff --git a/pkg/constants/constant.go b/pkg/constants/constant.go new file mode 100644 index 00000000..dc95fd18 --- /dev/null +++ b/pkg/constants/constant.go @@ -0,0 +1,15 @@ +package constants + +const ( + MySQLDefaultDSN = "dyweadmin:JXU2MTNGJXU5NzUyJXU5Rjk5JXU2MzA3JXU1RjE1JXU0RjYw@tcp(xinzf0520.top:20607)/douyin?charset=utf8&parseTime=True&loc=Local" + MySQLLocalDSN = "root:root@tcp(localhost:3306)/douyin?charset=utf8&parseTime=True&loc=Local" + + RedisLocalDSN = "localhost:6379" + RedisLocalPWD = "" + RedisDefaultDSN = "localhost:6379" + RedisDefaultPWD = "" +) + +var RedisDBList = map[string]int{ + "Default": 0, +} diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go new file mode 100644 index 00000000..86d4582e --- /dev/null +++ b/pkg/middleware/middleware.go @@ -0,0 +1,64 @@ +package middleware + +import ( + "fmt" + "net/url" + "strings" + + "github.com/BaiZe1998/douyin-simple-demo/pkg/util" + "github.com/gin-gonic/gin" +) + +func whiteList() map[string]string { + return map[string]string{ + "/douyin/feed/": "GET", + "/douyin/user/register/": "POST", + "/douyin/user/login/": "POST", + "/douyin/publish/action/": "POST", + } +} + +func withinWhiteList(url *url.URL, method string) bool { + target := whiteList() + queryUrl := strings.Split(fmt.Sprint(url), "?")[0] + if _, ok := target[queryUrl]; ok { + if target[queryUrl] == method { + return true + } + return false + } + return false +} + +func Authorize() gin.HandlerFunc { + return func(c *gin.Context) { + + type QueryToken struct { + Token string `binding:"required" form:"token"` + } + + // 当路由不在白名单内时进行token检测 + if !withinWhiteList(c.Request.URL, c.Request.Method) { + var queryToken QueryToken + if c.ShouldBindQuery(&queryToken) != nil { + c.AbortWithStatusJSON(200, gin.H{ + "status_code": 40001, + "status_msg": "请先登录", + }) + return + } + // 验证token有效性 + userClaims, err := util.ParseToken(queryToken.Token) + if err != nil { + c.AbortWithStatusJSON(200, gin.H{ + "status_code": 40001, + "status_msg": "登陆过期", + }) + return + } + c.Set("token", userClaims) + c.Set("user_id", userClaims.ID) + } + c.Next() + } +} diff --git a/pkg/middleware/publish_middleware.go b/pkg/middleware/publish_middleware.go new file mode 100644 index 00000000..1552d5d5 --- /dev/null +++ b/pkg/middleware/publish_middleware.go @@ -0,0 +1,27 @@ +package middleware + +import ( + "github.com/BaiZe1998/douyin-simple-demo/pkg/util" + "github.com/gin-gonic/gin" + "log" +) + +func PbulishMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + token := c.PostForm("token") + log.Printf(token) + // 验证token有效性 + userClaims, err := util.ParseToken(token) + if err != nil { + c.AbortWithStatusJSON(200, gin.H{ + "status_code": 40001, + "status_msg": "请登录", + }) + return + } + c.Set("token", userClaims) + c.Set("user_id", userClaims.ID) + c.Next() + } + +} diff --git a/pkg/util/OOSInit.go b/pkg/util/OOSInit.go new file mode 100644 index 00000000..27345d54 --- /dev/null +++ b/pkg/util/OOSInit.go @@ -0,0 +1,13 @@ +package util + +import "github.com/aliyun/alibaba-cloud-sdk-go/services/vod" + +func OOSInit() *vod.Client { + var accessKeyId string = "LTAI5tGcbULwMvTKqaSzgLQY" + var accessKeySecret string = "zuLS8V0o65bSZBWMuOyeZtnQs4OTIG" + client, err := InitVodClient(accessKeyId, accessKeySecret) + if err != nil { + return nil + } + return client +} diff --git a/pkg/util/base_path.go b/pkg/util/base_path.go new file mode 100644 index 00000000..514bf1cc --- /dev/null +++ b/pkg/util/base_path.go @@ -0,0 +1,16 @@ +package util + +import ( + "os" + "strings" +) + +func GetConfigPath() string { + execFilePath := os.Args[0] + + if strings.Contains(execFilePath, ".test") { + return "../config/config.yml" + } else { + return "config/config.yml" + } +} diff --git a/pkg/util/oss_connect.go b/pkg/util/oss_connect.go new file mode 100644 index 00000000..6a7f7838 --- /dev/null +++ b/pkg/util/oss_connect.go @@ -0,0 +1,109 @@ +package util + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "github.com/aliyun/alibaba-cloud-sdk-go/sdk" + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials" + "github.com/aliyun/alibaba-cloud-sdk-go/services/vod" + "github.com/aliyun/aliyun-oss-go-sdk/oss" + "os" +) + +func InitVodClient(accessKeyId string, accessKeySecret string) (client *vod.Client, err error) { + // 点播服务接入区域 + regionId := "cn-shanghai" + // 创建授权对象 + credential := &credentials.AccessKeyCredential{ + accessKeyId, + accessKeySecret, + } + // 自定义config + config := sdk.NewConfig() + config.AutoRetry = true // 失败是否自动重试 + config.MaxRetryTime = 3 // 最大重试次数 + config.Timeout = 3000000000 // 连接超时,单位:纳秒;默认为3秒 + // 创建vodClient实例 + return vod.NewClientWithOptions(regionId, config, credential) +} + +func MyCreateUploadVideo(client *vod.Client) (response *vod.CreateUploadVideoResponse, err error) { + request := vod.CreateCreateUploadVideoRequest() + request.Title = "Sample Video Title" + request.Description = "Sample Description" + request.FileName = "/opt/video/sample/video_file.mp4" + //request.CateId = "-1" + //Cover URL示例:http://example.alicdn.com/tps/TB1qnJ1PVXXXXXCXXXXXXXXXXXX-700-****.png + request.CoverURL = "" + request.Tags = "tag1,tag2" + request.AcceptFormat = "JSON" + return client.CreateUploadVideo(request) +} + +func InitOssClient(uploadAuthDTO UploadAuthDTO, uploadAddressDTO UploadAddressDTO) (*oss.Client, error) { + client, err := oss.New(uploadAddressDTO.Endpoint, + uploadAuthDTO.AccessKeyId, + uploadAuthDTO.AccessKeySecret, + oss.SecurityToken(uploadAuthDTO.SecurityToken), + oss.Timeout(86400*7, 86400*7)) + return client, err +} + +func UploadLocalFile(client *oss.Client, uploadAddressDTO UploadAddressDTO, localFile string) { + // 获取存储空间。 + bucket, err := client.Bucket(uploadAddressDTO.Bucket) + if err != nil { + fmt.Println("Error:", err) + os.Exit(-1) + } + // 上传本地文件。 + err = bucket.PutObjectFromFile(uploadAddressDTO.FileName, localFile) + if err != nil { + fmt.Println("Error:", err) + os.Exit(-1) + } +} + +type UploadAuthDTO struct { + AccessKeyId string + AccessKeySecret string + SecurityToken string +} +type UploadAddressDTO struct { + Endpoint string + Bucket string + FileName string +} + +func main() { + var accessKeyId string = "" // 您的AccessKeyId + var accessKeySecret string = "" // 您的AccessKeySecret + var localFile string = "/opt/video/sample/video_file.mp4" // 需要上传到VOD的本地视频文件的完整路径 + // 初始化VOD客户端并获取上传地址和凭证 + var vodClient, initVodClientErr = InitVodClient(accessKeyId, accessKeySecret) + if initVodClientErr != nil { + fmt.Println("Error:", initVodClientErr) + return + } + // 获取上传地址和凭证 + var response, createUploadVideoErr = MyCreateUploadVideo(vodClient) + if createUploadVideoErr != nil { + fmt.Println("Error:", createUploadVideoErr) + return + } + // 执行成功会返回VideoId、UploadAddress和UploadAuth + var videoId = response.VideoId + var uploadAuthDTO UploadAuthDTO + var uploadAddressDTO UploadAddressDTO + var uploadAuthDecode, _ = base64.StdEncoding.DecodeString(response.UploadAuth) + var uploadAddressDecode, _ = base64.StdEncoding.DecodeString(response.UploadAddress) + json.Unmarshal(uploadAuthDecode, &uploadAuthDTO) + json.Unmarshal(uploadAddressDecode, &uploadAddressDTO) + // 使用UploadAuth和UploadAddress初始化OSS客户端 + var ossClient, _ = InitOssClient(uploadAuthDTO, uploadAddressDTO) + // 上传文件,注意是同步上传会阻塞等待,耗时与文件大小和网络上行带宽有关 + UploadLocalFile(ossClient, uploadAddressDTO, localFile) + //MultipartUploadFile(ossClient, uploadAddressDTO, localFile) + fmt.Println("Succeed, VideoId:", videoId) +} diff --git a/pkg/util/token.go b/pkg/util/token.go new file mode 100644 index 00000000..6cafffcb --- /dev/null +++ b/pkg/util/token.go @@ -0,0 +1,49 @@ +package util + +import ( + "errors" + + "github.com/dgrijalva/jwt-go" +) + +//用户信息类,作为生成token的参数 +type UserClaims struct { + ID int64 `json:"user_id"` + Name string `json:"name"` + PassWord string `json:"password"` + //jwt-go提供的标准claim + jwt.StandardClaims +} + +var ( + //自定义的token秘钥 + secret = []byte("16849841325189456f487") +) + +// 生成token +func GenerateToken(claims *UserClaims) (string, error) { + //生成token + sign, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(secret) + if err != nil { + //这里因为项目接入了统一异常处理,所以使用panic并不会使程序终止,如不接入,可使用原始方式处理错误 + //接入统一异常可参考 https://blog.csdn.net/u014155085/article/details/106733391 + panic(err) + } + return sign, nil +} + +// 解析Token +func ParseToken(tokenString string) (*UserClaims, error) { + //解析token + token, err := jwt.ParseWithClaims(tokenString, &UserClaims{}, func(token *jwt.Token) (interface{}, error) { + return secret, nil + }) + if err != nil { + return nil, err + } + claims, ok := token.Claims.(*UserClaims) + if !ok { + return nil, errors.New("token is invalid") + } + return claims, nil +} diff --git a/router.go b/router.go index 087b1dde..e2335b56 100644 --- a/router.go +++ b/router.go @@ -1,22 +1,22 @@ package main import ( - "github.com/RaymondCode/simple-demo/controller" + "github.com/BaiZe1998/douyin-simple-demo/controller" + "github.com/BaiZe1998/douyin-simple-demo/pkg/middleware" "github.com/gin-gonic/gin" ) func initRouter(r *gin.Engine) { // public directory is used to serve static resources r.Static("/static", "./public") - apiRouter := r.Group("/douyin") - + apiRouter.Use(middleware.Authorize()) // basic apis apiRouter.GET("/feed/", controller.Feed) apiRouter.GET("/user/", controller.UserInfo) apiRouter.POST("/user/register/", controller.Register) apiRouter.POST("/user/login/", controller.Login) - apiRouter.POST("/publish/action/", controller.Publish) + apiRouter.POST("/publish/action/", middleware.PbulishMiddleware(), controller.Publish) apiRouter.GET("/publish/list/", controller.PublishList) // extra apis - I diff --git a/service/comment.go b/service/comment.go new file mode 100644 index 00000000..12c96ee7 --- /dev/null +++ b/service/comment.go @@ -0,0 +1,79 @@ +package service + +import ( + "context" + "fmt" + "strconv" + "time" + + "github.com/BaiZe1998/douyin-simple-demo/db" + "github.com/BaiZe1998/douyin-simple-demo/db/model" + "github.com/BaiZe1998/douyin-simple-demo/dto" +) + +func AddComment(text string, users dto.User, videoId int64) dto.ResponseComment { + newComment := &model.Comment{ + VideoId: videoId, + UserId: users.Id, + Content: text, + Status: 1, + } + //comment commit + model.CreateComment(context.Background(), videoId, newComment) + responseComment := dto.ResponseComment{ + ID: newComment.ID, + User: users, + Content: text, + CreatedAt: newComment.CreatedAt.Format("2006-01-02"), + } + + return responseComment +} + +func UpdatCacheCommentList(ctx context.Context, videoId int64, limit, offset int) { + res, total := GetCommentList(ctx, videoId, limit, offset) + fmt.Println("更新数据", total) + videoList_id := strconv.FormatInt(videoId, 10) + "_comment_list" + db.CacheSetList(context.Background(), "default", videoList_id, res, time.Minute) +} + +func GetCacheCommentList(ctx context.Context, videoId int64, limit, offset int) ([]dto.ResponseComment, int64, error) { + + videoList_id := strconv.FormatInt(videoId, 10) + "_comment_list" + + if value, err := db.CacheGetList(context.Background(), "default", videoList_id, []dto.ResponseComment{}); len(value) > 0 { + fmt.Println("获取缓存数据", value) + return value, int64(len(value)), err + } else { + fmt.Println(videoList_id) + fmt.Println(len(value)) + } + responseComment, total := GetCommentList(ctx, videoId, 10, 0) + err := db.CacheSetList(context.Background(), "default", videoList_id, responseComment, time.Minute) + return responseComment, total, err +} + +func GetCommentList(ctx context.Context, videoId int64, limit, offset int) ([]dto.ResponseComment, int64) { + var responseComment []dto.ResponseComment + res, total, _ := model.QueryComment(context.Background(), videoId, limit, offset) + responseComment = make([]dto.ResponseComment, len(res)) + fmt.Println("评论总数", total) + for index, v := range res { + userInfo, _ := model.QueryUserById(context.Background(), v.UserId) + fmt.Println("user:", userInfo.Name) + users := dto.User{ + Id: userInfo.ID, + Name: userInfo.Name, + FollowCount: userInfo.FollowCount, + FollowerCount: userInfo.FollowerCount, + IsFollow: false, + } + responseComment[index] = dto.ResponseComment{ + ID: v.ID, + User: users, + Content: v.Content, + CreatedAt: v.CreatedAt.Format("2006-01-02"), + } + } + return responseComment, total +} diff --git a/service/comment_test.go b/service/comment_test.go new file mode 100644 index 00000000..fb4e68bf --- /dev/null +++ b/service/comment_test.go @@ -0,0 +1,9 @@ +package service + +import ( + "testing" +) + +func TestComment(t *testing.T) { + +} diff --git a/service/favorite.go b/service/favorite.go new file mode 100644 index 00000000..1c352700 --- /dev/null +++ b/service/favorite.go @@ -0,0 +1,91 @@ +package service + +import ( + "context" + "errors" + "fmt" + "github.com/BaiZe1998/douyin-simple-demo/db/model" + "github.com/BaiZe1998/douyin-simple-demo/dto" +) + +func IsFavorite(ctx context.Context, userID int64, videoID int64) (isExist bool, isFavorite bool) { + favoriteRelation, _ := model.QueryFavorite(ctx, userID, videoID) + if favoriteRelation.ID == 0 { + return false, false + } + if favoriteRelation.Status == 1 { + return true, true + } + return true, false +} + +func FavoriteCountAction(ctx context.Context, videoID int64, actionType int) error { + if actionType == 1 { + // 视频点赞数+1 + model.UpdateVideoFavorite(ctx, videoID, 1) + } else { + // 视频点赞数-1 + model.UpdateVideoFavorite(ctx, videoID, -1) + } + return nil +} + +func FavoriteAction(ctx context.Context, userID int64, videoID int64, actionType int) error { + isExist, isFavorite := IsFavorite(ctx, userID, videoID) + + // TODO 修改video的点赞数据 + if !isExist { + // 不存在的关系直接创建 + favorite := model.Favorite{ + UserId: userID, + VideoId: videoID, + Status: actionType, + } + if err := model.CreateFavorite(ctx, &favorite); err != nil { + return err + } + FavoriteCountAction(ctx, videoID, actionType) + } else { + // 存在的关系进行更新 + if (actionType == 1 && !isFavorite) || (actionType == 2 && isFavorite) { + if err := model.UpdateFavorite(ctx, userID, videoID, &actionType); err != nil { + return err + } + FavoriteCountAction(ctx, videoID, actionType) + } + } + return nil +} + +func GetFavoriteList(ctx context.Context, userId int64) ([]dto.Video, error) { + + videoList, _, _ := model.QueryFavorites(ctx, userId, 0, 0) + + res := make([]dto.Video, 0) + for i := range videoList { + video := dto.Video{} + video.Id = videoList[i].ID + video.PlayUrl = videoList[i].PlayUrl + video.CoverUrl = videoList[i].CoverUrl + video.FavoriteCount = videoList[i].FavoriteCount + video.CommentCount = videoList[i].CommentCount + author, _ := model.QueryUserById(ctx, videoList[i].AuthorID) + user := dto.User{} + user.Id = author.ID + user.FollowerCount = author.FollowCount + user.Name = author.Name + user.FollowerCount = author.FollowerCount + isExist, isFollow := IsFollow(ctx, userId, author.ID) + if isExist && isFollow { + user.IsFollow = true + } else { + return nil, errors.New("get favirote list error") + } + video.Author = user + video.IsFavorite = true + res = append(res, video) + fmt.Println(video) + } + + return res, nil +} diff --git a/service/favorite_test.go b/service/favorite_test.go new file mode 100644 index 00000000..0feed6a4 --- /dev/null +++ b/service/favorite_test.go @@ -0,0 +1,28 @@ +package service + +import ( + "context" + "fmt" + "github.com/BaiZe1998/douyin-simple-demo/db" + "github.com/BaiZe1998/douyin-simple-demo/dto" + "testing" + "time" +) + +func TestFavorite(t *testing.T) { + dto.InitConfig() + db.Init() + //userList := make([]dto.User, 0) + //userList = append(userList, dto.User{Id: 1, Name: "xiaoming"}) + //userList = append(userList, dto.User{Id: 2, Name: "xiaohong"}) + //db.CacheSetList(context.Background(), "default", "user_list", userList, 0) + //value, _ := db.CacheGetList(context.Background(), "default", "user_list", []dto.User{}) + //op, ok := value.([]dto.User) + //fmt.Println("------------", op, ok) + videoList := make([]dto.Video, 0) + videoList = append(videoList, dto.Video{Id: 1}) + videoList = append(videoList, dto.Video{Id: 2}) + db.CacheSetList(context.Background(), "default", "video_list", videoList, time.Minute*20) + value, _ := db.CacheGetList(context.Background(), "default", "video_list", []dto.Video{}) + fmt.Println("------------", value) +} diff --git a/service/feed.go b/service/feed.go new file mode 100644 index 00000000..54c0b366 --- /dev/null +++ b/service/feed.go @@ -0,0 +1,157 @@ +package service + +import ( + "context" + "fmt" + "github.com/BaiZe1998/douyin-simple-demo/db/model" + "github.com/BaiZe1998/douyin-simple-demo/dto" + "log" + "strconv" + "time" + + "github.com/BaiZe1998/douyin-simple-demo/db/model" + "github.com/BaiZe1998/douyin-simple-demo/dto" +) + +type videoListType struct { + ID int64 + AuthorId int + PlayUrl string + CoverUrl string + FavoriteCount int64 + CommentCount int64 + CreatedAt time.Time + Title string + IsFavorite int + IsFollow int + + AuthorName string + AuthorFollowCount int64 + AuthorFollowerCount int64 +} + +func QueryFeedResponse(useId int64, lastTime string) ([]dto.Video, time.Time) { + var videoList []dto.Video = make([]dto.Video, 10) + var isFavorite bool + var isFollow bool + //query video list for feed + if lastTime == "0" { + now := time.Now() + lastTime = now.Format("2006-01-02 15:04:05") + } else { + parseInt, err := strconv.ParseInt(lastTime, 10, 64) + parseInt = parseInt / 1000 + log.Println(parseInt) + if err != nil { + return nil, time.Time{} + } + lastTime = time.Unix(parseInt, 0).Format("2006-01-02 15:04:05") + log.Println(lastTime) + } + videoListErr, res := model.QueryVideoList(context.Background(), lastTime) + + if videoListErr != nil { + dto.WriteLog( + "error", "获取视频列表失败", + "errInfo", videoListErr.Error(), + ) + } + + at := res[len(res)-1].CreatedAt + //constitute FeedResponse struct ([]dto.video) + for index, value := range res { + //user is favorite + if favoriteInfo, _ := model.QueryIsFavorite(context.Background(), useId, value.ID); favoriteInfo.Status == 1 { + isFavorite = true + } else { + isFavorite = false + } + + authorInfo, _ := model.QueryUserById(context.Background(), value.AuthorID) + //user is follow + if followInfo, _ := model.QueryIsFavorite(context.Background(), useId, value.AuthorID); followInfo.Status == 1 { + isFollow = true + } else { + isFollow = false + } + videoList[index] = dto.Video{ + Id: value.ID, + Author: dto.User{ + Id: authorInfo.ID, + Name: authorInfo.Name, + FollowCount: authorInfo.FollowCount, + FollowerCount: authorInfo.FollowerCount, + IsFollow: isFollow, + }, + PlayUrl: value.PlayUrl, + CoverUrl: value.CoverUrl, + FavoriteCount: value.FavoriteCount, + CommentCount: value.CommentCount, + IsFavorite: isFavorite, + Title: value.Title, + } + } + return videoList[0:len(res)], at +} + +func QueryFeedResponse1(useId int64, lastTime string) ([]dto.Video, time.Time) { + var videoList []dto.Video = make([]dto.Video, 10) + //query video list for feed + if lastTime == "0" { + now := time.Now() + lastTime = now.Format("2006-01-02 15:04:05") + } else { + parseInt, err := strconv.ParseInt(lastTime, 10, 64) + if err != nil { + return nil, time.Time{} + } + lastTime = time.Unix(parseInt, 0).Format("2006-01-02 15:04:05") + } + // _, res := model.QueryVideoList(context.Background(), lastTime) + + var res []videoListType + sqlQuery := "SELECT video.*, IFNULL(favoriteList.status, 2) AS IsFavorite, IFNULL(followList.status, 2) as IsFollow, user.name AS AuthorName, user.follow_count AS AuthorFollowCount, user.follower_count AS AuthorFollowerCount FROM video LEFT JOIN (SELECT video_id, user_id, status FROM favorite WHERE user_id = 11) AS favoriteList ON video.id = favoriteList.video_id LEFT JOIN ( SELECT followed_user, status FROM follow WHERE user_id = 11) AS followList ON video.author_id = followList.followed_user LEFT JOIN user ON video.author_id=user.id LIMIT 10;" + queryErr := model.DB.Raw(sqlQuery).Scan(&res).Error + + if queryErr != nil { + fmt.Println(queryErr) + } + + at := res[len(res)-1].CreatedAt + + for index, value := range res { + + var is_follow bool + var is_fav bool + + if value.IsFollow == 1 { + is_follow = true + } else { + is_follow = false + } + + if value.IsFavorite == 1 { + is_fav = true + } else { + is_fav = false + } + + videoList[index] = dto.Video{ + Id: value.ID, + Author: dto.User{ + Id: int64(value.AuthorId), + Name: value.AuthorName, + FollowCount: value.AuthorFollowCount, + FollowerCount: value.AuthorFollowerCount, + IsFollow: is_follow, + }, + PlayUrl: value.PlayUrl, + CoverUrl: value.CoverUrl, + FavoriteCount: value.FavoriteCount, + CommentCount: value.CommentCount, + IsFavorite: is_fav, + Title: value.Title, + } + } + return videoList[0:len(res)], at +} diff --git a/service/follow.go b/service/follow.go new file mode 100644 index 00000000..d6a361be --- /dev/null +++ b/service/follow.go @@ -0,0 +1,109 @@ +package service + +import ( + "context" + "errors" + "fmt" + "github.com/BaiZe1998/douyin-simple-demo/dto" + + "github.com/BaiZe1998/douyin-simple-demo/db/model" + "gorm.io/gorm" +) + +func IsFollow(ctx context.Context, userID int64, followedUser int64) (bool, bool) { + var relation model.Follow + + if err := model.DB.Table("follow").WithContext(ctx).Where("user_id=? and followed_user=? ", userID, followedUser).First(&relation).Error; err == nil { + if relation.Status == 1 { + return true, true + } else { + return true, false + } + } else { + return false, false + } +} + +func FollowCountAction(ctx context.Context, userID int64, followedUser int64, action_type int) error { + if action_type == 1 { + // 关注操作 + // user_id 关注数+1, to_user_id 被关注数+1 + model.DB.Table("user").WithContext(ctx).Where("id = ?", userID).Update("follow_count", gorm.Expr("follow_count+?", 1)) + model.DB.Table("user").WithContext(ctx).Where("id = ?", followedUser).Update("follower_count", gorm.Expr("follower_count+?", 1)) + } else { + // 取关操作 + model.DB.Table("user").WithContext(ctx).Where("id = ?", userID).Update("follow_count", gorm.Expr("follow_count-?", 1)) + model.DB.Table("user").WithContext(ctx).Where("id = ?", followedUser).Update("follower_count", gorm.Expr("follower_count-?", 1)) + } + + return nil +} + +func FollowAction(ctx context.Context, user_id int64, to_user_id int64, action_type int) error { + is_exist, is_follow := IsFollow(ctx, user_id, to_user_id) + + if !is_exist { + // 不存在的关系直接创建 + new_follow_relation := model.Follow{ + UserId: int64(user_id), + FollowedUser: int64(to_user_id), + Status: int(action_type), + } + + err := model.CreateFollow(ctx, &new_follow_relation) + + if err == nil { + FollowCountAction(ctx, user_id, to_user_id, action_type) + } else { + fmt.Println(err) + } + } else { + // 存在的关系进行更新 + if (action_type == 1 && !is_follow) || (action_type == 2 && is_follow) { + err := model.UpdateFollow(ctx, user_id, to_user_id, &action_type) + + if err == nil { + FollowCountAction(ctx, user_id, to_user_id, action_type) + } else { + fmt.Println(err) + } + } + } + + return nil +} + +func GetFollowList(ctx context.Context, userId int64, actionType uint) ([]dto.User, error) { + var followList []dto.User + + if actionType == 1 { + // 操作类型为获取关注列表 + if err := model.DB.Table("user").WithContext(ctx).Joins("left join follow on user.id = follow.followed_user"). + Select("user.id", "user.name", "user.follow_count", "user.follower_count"). + Where("follow.user_id = ?", userId).Scan(&followList).Error; err != nil { + return followList, nil + } else { + fmt.Println(err) + } + } else if actionType == 2 { + // 操作类型为获取粉丝列表 + if err := model.DB.Table("user").WithContext(ctx).Joins("left join follow on user.id = follow.user_id"). + Select("user.id", "user.name", "user.follow_count", "user.follower_count"). + Where("follow.followed_user = ?", userId).Scan(&followList).Error; err != nil { + return followList, nil + } + } else { + return followList, errors.New("ambiguous actionType") + } + + for i, n := range followList { + isExist, isFollow := IsFollow(ctx, userId, n.Id) + if isExist && isFollow { + followList[i].IsFollow = true + } else { + followList[i].IsFollow = false + } + } + + return followList, nil +} diff --git a/service/follow_test.go b/service/follow_test.go new file mode 100644 index 00000000..07aeac77 --- /dev/null +++ b/service/follow_test.go @@ -0,0 +1,23 @@ +package service + +import ( + "context" + "fmt" + "testing" + + "github.com/BaiZe1998/douyin-simple-demo/db" + "github.com/BaiZe1998/douyin-simple-demo/db/model" +) + +func TestFollow(t *testing.T) { + db.Init() + + followModel := &model.Follow{ + UserId: 1, + FollowedUser: 7, + Status: 1, + } + model.CreateFollow(context.Background(), followModel) + res, total, _ := model.QueryFollow(context.Background(), 1, 1, 10, 0) + fmt.Println(len(res), total) +} diff --git a/service/publish.go b/service/publish.go new file mode 100644 index 00000000..4fdeab1f --- /dev/null +++ b/service/publish.go @@ -0,0 +1,107 @@ +package service + +import ( + "context" + "fmt" + "strconv" + + "github.com/BaiZe1998/douyin-simple-demo/db/model" + "github.com/BaiZe1998/douyin-simple-demo/dto" +) + +func QueryPublishList(userId string) []dto.Video { + var videoList []dto.Video = make([]dto.Video, 10) + var isFavorite bool + var isFollow bool + //query video list for feed + + _, res := model.QueryPublishList(context.Background(), userId) + if len(res) == 0 { + return videoList[0:0] + } + //constitute FeedResponse struct ([]dto.video) + useId, _ := strconv.ParseInt(userId, 10, 64) + for index, value := range res { + //user is favorite + if favoriteInfo, _ := model.QueryIsFavorite(context.Background(), useId, value.ID); favoriteInfo.Status == 1 { + isFavorite = true + } else { + isFavorite = false + } + + authorInfo, _ := model.QueryUserById(context.Background(), value.AuthorID) + //user is follow + if followInfo, _ := model.QueryIsFavorite(context.Background(), useId, value.AuthorID); followInfo.Status == 1 { + isFollow = true + } else { + isFollow = false + } + fmt.Println(isFollow, isFavorite) + videoList[index] = dto.Video{ + Id: value.ID, + Author: dto.User{ + Id: authorInfo.ID, + Name: authorInfo.Name, + FollowCount: authorInfo.FollowCount, + FollowerCount: authorInfo.FollowerCount, + IsFollow: isFollow, + }, + PlayUrl: value.PlayUrl, + CoverUrl: value.CoverUrl, + FavoriteCount: value.FavoriteCount, + CommentCount: value.CommentCount, + IsFavorite: isFavorite, + } + } + return videoList[0:len(res)] +} + +func QueryPublishList1(useId int64) []dto.Video { + var videoList []dto.Video = make([]dto.Video, 10) + //query video list for feed + // _, res := model.QueryVideoList(context.Background(), lastTime) + + var res []videoListType + sqlQuery := "SELECT video.*, IFNULL(favoriteList.status, 2) AS IsFavorite, IFNULL(followList.status, 2) as IsFollow, user.name AS AuthorName, user.follow_count AS AuthorFollowCount, user.follower_count AS AuthorFollowerCount FROM video LEFT JOIN (SELECT video_id, user_id, status FROM favorite WHERE user_id = 11) AS favoriteList ON video.id = favoriteList.video_id LEFT JOIN ( SELECT followed_user, status FROM follow WHERE user_id = 11) AS followList ON video.author_id = followList.followed_user LEFT JOIN user ON video.author_id=user.id LIMIT 10;" + queryErr := model.DB.Raw(sqlQuery).Scan(&res).Error + + if queryErr != nil { + fmt.Println(queryErr) + } + + for index, value := range res { + + var is_follow bool + var is_fav bool + + if value.IsFollow == 1 { + is_follow = true + } else { + is_follow = false + } + + if value.IsFavorite == 1 { + is_fav = true + } else { + is_fav = false + } + + videoList[index] = dto.Video{ + Id: value.ID, + Author: dto.User{ + Id: int64(value.AuthorId), + Name: value.AuthorName, + FollowCount: value.AuthorFollowCount, + FollowerCount: value.AuthorFollowerCount, + IsFollow: is_follow, + }, + PlayUrl: value.PlayUrl, + CoverUrl: value.CoverUrl, + FavoriteCount: value.FavoriteCount, + CommentCount: value.CommentCount, + IsFavorite: is_fav, + Title: value.Title, + } + } + return videoList[0:len(res)] +} diff --git a/service/user.go b/service/user.go new file mode 100644 index 00000000..71690529 --- /dev/null +++ b/service/user.go @@ -0,0 +1,24 @@ +package service + +import "golang.org/x/crypto/bcrypt" + +func Encryption(password string) (pwdHash string, err error) { + pwd := []byte(password) + hash, err := bcrypt.GenerateFromPassword(pwd, bcrypt.MinCost) + if err != nil { + return "", err + } + pwdHash = string(hash) + return pwdHash, nil +} + +// 验证密码 +func ComparePasswords(hashedPwd string, plainPwd string) bool { + byteHash := []byte(hashedPwd) + bytePwd := []byte(plainPwd) + err := bcrypt.CompareHashAndPassword(byteHash, bytePwd) + if err != nil { + return false + } + return true +} diff --git a/service/user_test.go b/service/user_test.go new file mode 100644 index 00000000..8034d10c --- /dev/null +++ b/service/user_test.go @@ -0,0 +1,37 @@ +package service + +import ( + "context" + "fmt" + "testing" + + "github.com/BaiZe1998/douyin-simple-demo/db" + "github.com/BaiZe1998/douyin-simple-demo/db/model" + "github.com/BaiZe1998/douyin-simple-demo/dto" +) + +func TestUser(t *testing.T) { + dto.InitConfig() + db.Init() + //followModel := &model.User{ + // ID: "223", + // Name: "nyf123456", + // PassWord: "12345678", + //} + //model.CreateUser(context.Background(), followModel) + //res, total, _ := model.QueryFollow(context.Background(), "223", 1, 1, 10) + //fmt.Println(res, total) + //re, _ := model.QueryUserById(context.Background(), "08dc2b99ef974d47a2554ed3dea73ea0") + //for index, value := range re { + // fmt.Println("index=", index, "value=", value) + //} + //fmt.Println(*re[0]) + //userModel := &model.User{ + // Name: "nyf123456", + // PassWord: "12345678", + //} + //model.CreateUser(context.Background(), userModel) + res, m := model.QueryUserByName(context.Background(), "nyf12") + fmt.Println(res) + fmt.Println(m) +} diff --git a/service/vod.go b/service/vod.go new file mode 100644 index 00000000..0f0f9c97 --- /dev/null +++ b/service/vod.go @@ -0,0 +1,98 @@ +package service + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "github.com/BaiZe1998/douyin-simple-demo/db/model" + "github.com/BaiZe1998/douyin-simple-demo/pkg/util" + "github.com/aliyun/alibaba-cloud-sdk-go/services/vod" + "github.com/aliyun/aliyun-oss-go-sdk/oss" + "mime/multipart" + "os" + "time" +) + +func UploadVideoAliyun(file *multipart.FileHeader, token string, title string, userid int64) { + vodClient := util.OOSInit() + request := vod.CreateCreateUploadVideoRequest() + request.Title = title + request.FileName = file.Filename + response, err := vodClient.CreateUploadVideo(request) + if err != nil { + fmt.Println("Error:", err) + return + } + var videoId = response.VideoId + var uploadAuthDTO UploadAuthDTO + var uploadAddressDTO UploadAddressDTO + var uploadAuthDecode, _ = base64.StdEncoding.DecodeString(response.UploadAuth) + var uploadAddressDecode, _ = base64.StdEncoding.DecodeString(response.UploadAddress) + json.Unmarshal(uploadAuthDecode, &uploadAuthDTO) + json.Unmarshal(uploadAddressDecode, &uploadAddressDTO) + // 使用UploadAuth和UploadAddress初始化OSS客户端 + var ossClient, _ = InitOssClient(uploadAuthDTO, uploadAddressDTO) + // 上传文件,注意是同步上传会阻塞等待,耗时与文件大小和网络上行带宽有关 + open, err := file.Open() + if err != nil { + return + } + UploadLocalFile(ossClient, uploadAddressDTO, open) + //MultipartUploadFile(ossClient, uploadAddressDTO, localFile) + fmt.Println("Succeed, VideoId:", videoId) + //https://video.liufei.fun/sv/2eaa675f-1815638c4ce/2eaa675f-1815638c4ce.mp4 + //https://video.liufei.fun/553d29da2b3844c3bc930e5888f4c5d4/snapshots/216c32eac6064241ada9704637c88101-00002.jpg + var playurl = "https://video.liufei.fun" + "/" + uploadAddressDTO.FileName + cover := playurl + "?x-oss-process=video/snapshot,t_30000,f_jpg,w_0,h_0,m_fast,ar_auto" + video := &model.Video{ + AuthorID: userid, + PlayUrl: playurl, + CoverUrl: cover, + FavoriteCount: 0, + CommentCount: 0, + CreatedAt: time.Time{}, + Title: title, + } + err = model.CreateVideo(context.Background(), video) + if err != nil { + return + } +} + +func InitOssClient(uploadAuthDTO UploadAuthDTO, uploadAddressDTO UploadAddressDTO) (*oss.Client, error) { + client, err := oss.New(uploadAddressDTO.Endpoint, + uploadAuthDTO.AccessKeyId, + uploadAuthDTO.AccessKeySecret, + oss.SecurityToken(uploadAuthDTO.SecurityToken), + oss.Timeout(86400*7, 86400*7)) + return client, err +} +func UploadLocalFile(client *oss.Client, uploadAddressDTO UploadAddressDTO, open multipart.File) { + // 获取存储空间。 + bucket, err := client.Bucket(uploadAddressDTO.Bucket) + if err != nil { + fmt.Println("Error:", err) + os.Exit(-1) + } + + err = bucket.PutObject(uploadAddressDTO.FileName, open) + if err != nil { + return + } + if err != nil { + fmt.Println("Error:", err) + os.Exit(-1) + } +} + +type UploadAuthDTO struct { + AccessKeyId string + AccessKeySecret string + SecurityToken string +} +type UploadAddressDTO struct { + Endpoint string + Bucket string + FileName string +}