├── .gitattributes ├── .gitignore ├── README.md ├── controller ├── comment.go ├── common.go ├── demo_data.go ├── favorite.go ├── feed.go ├── message.go ├── publish.go ├── relation.go └── user.go ├── go.mod ├── go.sum ├── main.go ├── public ├── bear.mp4 └── data ├── router.go ├── service └── message.go └── test ├── base_api_test.go ├── common.go ├── interact_api_test.go └── social_api_test.go /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | 23 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simple-demo 2 | 3 | ## 抖音项目服务端简单示例 4 | 5 | 具体功能内容参考飞书说明文档 6 | 7 | 工程无其他依赖,直接编译运行即可 8 | 9 | ```shell 10 | go build && ./simple-demo 11 | ``` 12 | 13 | ### 功能说明 14 | 15 | 接口功能不完善,仅作为示例 16 | 17 | * 用户登录数据保存在内存中,单次运行过程中有效 18 | * 视频上传后会保存到本地 public 目录中,访问时用 127.0.0.1:8080/static/video_name 即可 19 | 20 | ### 测试 21 | 22 | test 目录下为不同场景的功能测试case,可用于验证功能实现正确性 23 | 24 | 其中 common.go 中的 _serverAddr_ 为服务部署的地址,默认为本机地址,可以根据实际情况修改 25 | 26 | 测试数据写在 demo_data.go 中,用于列表接口的 mock 测试 -------------------------------------------------------------------------------- /controller/comment.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | ) 7 | 8 | type CommentListResponse struct { 9 | Response 10 | CommentList []Comment `json:"comment_list,omitempty"` 11 | } 12 | 13 | type CommentActionResponse struct { 14 | Response 15 | Comment Comment `json:"comment,omitempty"` 16 | } 17 | 18 | // CommentAction no practical effect, just check if token is valid 19 | func CommentAction(c *gin.Context) { 20 | token := c.Query("token") 21 | actionType := c.Query("action_type") 22 | 23 | if user, exist := usersLoginInfo[token]; exist { 24 | if actionType == "1" { 25 | text := c.Query("comment_text") 26 | c.JSON(http.StatusOK, CommentActionResponse{Response: Response{StatusCode: 0}, 27 | Comment: Comment{ 28 | Id: 1, 29 | User: user, 30 | Content: text, 31 | CreateDate: "05-01", 32 | }}) 33 | return 34 | } 35 | c.JSON(http.StatusOK, Response{StatusCode: 0}) 36 | } else { 37 | c.JSON(http.StatusOK, Response{StatusCode: 1, StatusMsg: "User doesn't exist"}) 38 | } 39 | } 40 | 41 | // CommentList all videos have same demo comment list 42 | func CommentList(c *gin.Context) { 43 | c.JSON(http.StatusOK, CommentListResponse{ 44 | Response: Response{StatusCode: 0}, 45 | CommentList: DemoComments, 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /controller/common.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | type Response struct { 4 | StatusCode int32 `json:"status_code"` 5 | StatusMsg string `json:"status_msg,omitempty"` 6 | } 7 | 8 | type Video struct { 9 | Id int64 `json:"id,omitempty"` 10 | Author User `json:"author"` 11 | PlayUrl string `json:"play_url" json:"play_url,omitempty"` 12 | CoverUrl string `json:"cover_url,omitempty"` 13 | FavoriteCount int64 `json:"favorite_count,omitempty"` 14 | CommentCount int64 `json:"comment_count,omitempty"` 15 | IsFavorite bool `json:"is_favorite,omitempty"` 16 | } 17 | 18 | type Comment struct { 19 | Id int64 `json:"id,omitempty"` 20 | User User `json:"user"` 21 | Content string `json:"content,omitempty"` 22 | CreateDate string `json:"create_date,omitempty"` 23 | } 24 | 25 | type User struct { 26 | Id int64 `json:"id,omitempty"` 27 | Name string `json:"name,omitempty"` 28 | FollowCount int64 `json:"follow_count,omitempty"` 29 | FollowerCount int64 `json:"follower_count,omitempty"` 30 | IsFollow bool `json:"is_follow,omitempty"` 31 | } 32 | 33 | type Message struct { 34 | Id int64 `json:"id,omitempty"` 35 | Content string `json:"content,omitempty"` 36 | CreateTime string `json:"create_time,omitempty"` 37 | } 38 | 39 | type MessageSendEvent struct { 40 | UserId int64 `json:"user_id,omitempty"` 41 | ToUserId int64 `json:"to_user_id,omitempty"` 42 | MsgContent string `json:"msg_content,omitempty"` 43 | } 44 | 45 | type MessagePushEvent struct { 46 | FromUserId int64 `json:"user_id,omitempty"` 47 | MsgContent string `json:"msg_content,omitempty"` 48 | } 49 | -------------------------------------------------------------------------------- /controller/demo_data.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | var DemoVideos = []Video{ 4 | { 5 | Id: 1, 6 | Author: DemoUser, 7 | PlayUrl: "https://www.w3schools.com/html/movie.mp4", 8 | CoverUrl: "https://cdn.pixabay.com/photo/2016/03/27/18/10/bear-1283347_1280.jpg", 9 | FavoriteCount: 0, 10 | CommentCount: 0, 11 | IsFavorite: false, 12 | }, 13 | } 14 | 15 | var DemoComments = []Comment{ 16 | { 17 | Id: 1, 18 | User: DemoUser, 19 | Content: "Test Comment", 20 | CreateDate: "05-01", 21 | }, 22 | } 23 | 24 | var DemoUser = User{ 25 | Id: 1, 26 | Name: "TestUser", 27 | FollowCount: 0, 28 | FollowerCount: 0, 29 | IsFollow: false, 30 | } 31 | -------------------------------------------------------------------------------- /controller/favorite.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | ) 7 | 8 | // FavoriteAction no practical effect, just check if token is valid 9 | func FavoriteAction(c *gin.Context) { 10 | token := c.Query("token") 11 | 12 | if _, exist := usersLoginInfo[token]; exist { 13 | c.JSON(http.StatusOK, Response{StatusCode: 0}) 14 | } else { 15 | c.JSON(http.StatusOK, Response{StatusCode: 1, StatusMsg: "User doesn't exist"}) 16 | } 17 | } 18 | 19 | // FavoriteList all users have same favorite video list 20 | func FavoriteList(c *gin.Context) { 21 | c.JSON(http.StatusOK, VideoListResponse{ 22 | Response: Response{ 23 | StatusCode: 0, 24 | }, 25 | VideoList: DemoVideos, 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /controller/feed.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | type FeedResponse struct { 10 | Response 11 | VideoList []Video `json:"video_list,omitempty"` 12 | NextTime int64 `json:"next_time,omitempty"` 13 | } 14 | 15 | // Feed same demo video list for every request 16 | func Feed(c *gin.Context) { 17 | c.JSON(http.StatusOK, FeedResponse{ 18 | Response: Response{StatusCode: 0}, 19 | VideoList: DemoVideos, 20 | NextTime: time.Now().Unix(), 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /controller/message.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "strconv" 8 | "sync/atomic" 9 | "time" 10 | ) 11 | 12 | var tempChat = map[string][]Message{} 13 | 14 | var messageIdSequence = int64(1) 15 | 16 | type ChatResponse struct { 17 | Response 18 | MessageList []Message `json:"message_list"` 19 | } 20 | 21 | // MessageAction no practical effect, just check if token is valid 22 | func MessageAction(c *gin.Context) { 23 | token := c.Query("token") 24 | toUserId := c.Query("to_user_id") 25 | content := c.Query("content") 26 | 27 | if user, exist := usersLoginInfo[token]; exist { 28 | userIdB, _ := strconv.Atoi(toUserId) 29 | chatKey := genChatKey(user.Id, int64(userIdB)) 30 | 31 | atomic.AddInt64(&messageIdSequence, 1) 32 | curMessage := Message{ 33 | Id: messageIdSequence, 34 | Content: content, 35 | CreateTime: time.Now().Format(time.Kitchen), 36 | } 37 | 38 | if messages, exist := tempChat[chatKey]; exist { 39 | tempChat[chatKey] = append(messages, curMessage) 40 | } else { 41 | tempChat[chatKey] = []Message{curMessage} 42 | } 43 | c.JSON(http.StatusOK, Response{StatusCode: 0}) 44 | } else { 45 | c.JSON(http.StatusOK, Response{StatusCode: 1, StatusMsg: "User doesn't exist"}) 46 | } 47 | } 48 | 49 | // MessageChat all users have same follow list 50 | func MessageChat(c *gin.Context) { 51 | token := c.Query("token") 52 | toUserId := c.Query("to_user_id") 53 | 54 | if user, exist := usersLoginInfo[token]; exist { 55 | userIdB, _ := strconv.Atoi(toUserId) 56 | chatKey := genChatKey(user.Id, int64(userIdB)) 57 | 58 | c.JSON(http.StatusOK, ChatResponse{Response: Response{StatusCode: 0}, MessageList: tempChat[chatKey]}) 59 | } else { 60 | c.JSON(http.StatusOK, Response{StatusCode: 1, StatusMsg: "User doesn't exist"}) 61 | } 62 | } 63 | 64 | func genChatKey(userIdA int64, userIdB int64) string { 65 | if userIdA > userIdB { 66 | return fmt.Sprintf("%d_%d", userIdB, userIdA) 67 | } 68 | return fmt.Sprintf("%d_%d", userIdA, userIdB) 69 | } 70 | -------------------------------------------------------------------------------- /controller/publish.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "path/filepath" 8 | ) 9 | 10 | type VideoListResponse struct { 11 | Response 12 | VideoList []Video `json:"video_list"` 13 | } 14 | 15 | // Publish check token then save upload file to public directory 16 | func Publish(c *gin.Context) { 17 | token := c.PostForm("token") 18 | 19 | if _, exist := usersLoginInfo[token]; !exist { 20 | c.JSON(http.StatusOK, Response{StatusCode: 1, StatusMsg: "User doesn't exist"}) 21 | return 22 | } 23 | 24 | data, err := c.FormFile("data") 25 | if err != nil { 26 | c.JSON(http.StatusOK, Response{ 27 | StatusCode: 1, 28 | StatusMsg: err.Error(), 29 | }) 30 | return 31 | } 32 | 33 | filename := filepath.Base(data.Filename) 34 | user := usersLoginInfo[token] 35 | finalName := fmt.Sprintf("%d_%s", user.Id, filename) 36 | saveFile := filepath.Join("./public/", finalName) 37 | if err := c.SaveUploadedFile(data, saveFile); err != nil { 38 | c.JSON(http.StatusOK, Response{ 39 | StatusCode: 1, 40 | StatusMsg: err.Error(), 41 | }) 42 | return 43 | } 44 | 45 | c.JSON(http.StatusOK, Response{ 46 | StatusCode: 0, 47 | StatusMsg: finalName + " uploaded successfully", 48 | }) 49 | } 50 | 51 | // PublishList all users have same publish video list 52 | func PublishList(c *gin.Context) { 53 | c.JSON(http.StatusOK, VideoListResponse{ 54 | Response: Response{ 55 | StatusCode: 0, 56 | }, 57 | VideoList: DemoVideos, 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /controller/relation.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | ) 7 | 8 | type UserListResponse struct { 9 | Response 10 | UserList []User `json:"user_list"` 11 | } 12 | 13 | // RelationAction no practical effect, just check if token is valid 14 | func RelationAction(c *gin.Context) { 15 | token := c.Query("token") 16 | 17 | if _, exist := usersLoginInfo[token]; exist { 18 | c.JSON(http.StatusOK, Response{StatusCode: 0}) 19 | } else { 20 | c.JSON(http.StatusOK, Response{StatusCode: 1, StatusMsg: "User doesn't exist"}) 21 | } 22 | } 23 | 24 | // FollowList all users have same follow list 25 | func FollowList(c *gin.Context) { 26 | c.JSON(http.StatusOK, UserListResponse{ 27 | Response: Response{ 28 | StatusCode: 0, 29 | }, 30 | UserList: []User{DemoUser}, 31 | }) 32 | } 33 | 34 | // FollowerList all users have same follower list 35 | func FollowerList(c *gin.Context) { 36 | c.JSON(http.StatusOK, UserListResponse{ 37 | Response: Response{ 38 | StatusCode: 0, 39 | }, 40 | UserList: []User{DemoUser}, 41 | }) 42 | } 43 | 44 | // FriendList all users have same friend list 45 | func FriendList(c *gin.Context) { 46 | c.JSON(http.StatusOK, UserListResponse{ 47 | Response: Response{ 48 | StatusCode: 0, 49 | }, 50 | UserList: []User{DemoUser}, 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /controller/user.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "sync/atomic" 7 | ) 8 | 9 | // usersLoginInfo use map to store user info, and key is username+password for demo 10 | // user data will be cleared every time the server starts 11 | // test data: username=zhanglei, password=douyin 12 | var usersLoginInfo = map[string]User{ 13 | "zhangleidouyin": { 14 | Id: 1, 15 | Name: "zhanglei", 16 | FollowCount: 10, 17 | FollowerCount: 5, 18 | IsFollow: true, 19 | }, 20 | } 21 | 22 | var userIdSequence = int64(1) 23 | 24 | type UserLoginResponse struct { 25 | Response 26 | UserId int64 `json:"user_id,omitempty"` 27 | Token string `json:"token"` 28 | } 29 | 30 | type UserResponse struct { 31 | Response 32 | User User `json:"user"` 33 | } 34 | 35 | func Register(c *gin.Context) { 36 | username := c.Query("username") 37 | password := c.Query("password") 38 | 39 | token := username + password 40 | 41 | if _, exist := usersLoginInfo[token]; exist { 42 | c.JSON(http.StatusOK, UserLoginResponse{ 43 | Response: Response{StatusCode: 1, StatusMsg: "User already exist"}, 44 | }) 45 | } else { 46 | atomic.AddInt64(&userIdSequence, 1) 47 | newUser := User{ 48 | Id: userIdSequence, 49 | Name: username, 50 | } 51 | usersLoginInfo[token] = newUser 52 | c.JSON(http.StatusOK, UserLoginResponse{ 53 | Response: Response{StatusCode: 0}, 54 | UserId: userIdSequence, 55 | Token: username + password, 56 | }) 57 | } 58 | } 59 | 60 | func Login(c *gin.Context) { 61 | username := c.Query("username") 62 | password := c.Query("password") 63 | 64 | token := username + password 65 | 66 | if user, exist := usersLoginInfo[token]; exist { 67 | c.JSON(http.StatusOK, UserLoginResponse{ 68 | Response: Response{StatusCode: 0}, 69 | UserId: user.Id, 70 | Token: token, 71 | }) 72 | } else { 73 | c.JSON(http.StatusOK, UserLoginResponse{ 74 | Response: Response{StatusCode: 1, StatusMsg: "User doesn't exist"}, 75 | }) 76 | } 77 | } 78 | 79 | func UserInfo(c *gin.Context) { 80 | token := c.Query("token") 81 | 82 | if user, exist := usersLoginInfo[token]; exist { 83 | c.JSON(http.StatusOK, UserResponse{ 84 | Response: Response{StatusCode: 0}, 85 | User: user, 86 | }) 87 | } else { 88 | c.JSON(http.StatusOK, UserResponse{ 89 | Response: Response{StatusCode: 1, StatusMsg: "User doesn't exist"}, 90 | }) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/RaymondCode/simple-demo 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/gavv/httpexpect/v2 v2.8.0 7 | github.com/gin-gonic/gin v1.7.7 8 | github.com/stretchr/testify v1.7.0 9 | ) 10 | 11 | require ( 12 | github.com/ajg/form v1.5.1 // indirect 13 | github.com/andybalholm/brotli v1.0.4 // indirect 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/fatih/structs v1.1.0 // indirect 16 | github.com/gin-contrib/sse v0.1.0 // indirect 17 | github.com/go-playground/locales v0.14.0 // indirect 18 | github.com/go-playground/universal-translator v0.18.0 // indirect 19 | github.com/go-playground/validator/v10 v10.11.0 // indirect 20 | github.com/golang/protobuf v1.5.2 // indirect 21 | github.com/google/go-querystring v1.1.0 // indirect 22 | github.com/gorilla/websocket v1.4.2 // indirect 23 | github.com/imkira/go-interpol v1.1.0 // indirect 24 | github.com/json-iterator/go v1.1.12 // indirect 25 | github.com/klauspost/compress v1.15.0 // indirect 26 | github.com/leodido/go-urn v1.2.1 // indirect 27 | github.com/mattn/go-isatty v0.0.14 // indirect 28 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 29 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 30 | github.com/modern-go/reflect2 v1.0.2 // indirect 31 | github.com/pmezard/go-difflib v1.0.0 // indirect 32 | github.com/sanity-io/litter v1.5.5 // indirect 33 | github.com/sergi/go-diff v1.0.0 // indirect 34 | github.com/ugorji/go/codec v1.2.7 // indirect 35 | github.com/valyala/bytebufferpool v1.0.0 // indirect 36 | github.com/valyala/fasthttp v1.34.0 // indirect 37 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect 38 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect 39 | github.com/xeipuuv/gojsonschema v1.2.0 // indirect 40 | github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect 41 | github.com/yudai/gojsondiff v1.0.0 // indirect 42 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect 43 | golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect 44 | golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect 45 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect 46 | golang.org/x/text v0.3.7 // indirect 47 | google.golang.org/protobuf v1.28.0 // indirect 48 | gopkg.in/yaml.v2 v2.4.0 // indirect 49 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 50 | moul.io/http2curl/v2 v2.3.0 // indirect 51 | ) 52 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= 2 | github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= 3 | github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= 4 | github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= 5 | github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 6 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 7 | github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/fasthttp/websocket v1.4.3-rc.6 h1:omHqsl8j+KXpmzRjF8bmzOSYJ8GnS0E3efi1wYT+niY= 12 | github.com/fasthttp/websocket v1.4.3-rc.6/go.mod h1:43W9OM2T8FeXpCWMsBd9Cb7nE2CACNqNvCqQCoty/Lc= 13 | github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= 14 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 15 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 16 | github.com/gavv/httpexpect/v2 v2.8.0 h1:sIYO3vVjWq06X9LVncVXGvDGtVytedGLoJLp7tR+m5A= 17 | github.com/gavv/httpexpect/v2 v2.8.0/go.mod h1:jIj2f4rLediVaQK7rIH2EcU4W1ovjeSI8D0g85VJe9o= 18 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 19 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 20 | github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= 21 | github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= 22 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 23 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 24 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 25 | github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= 26 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= 27 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 28 | github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= 29 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= 30 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 31 | github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= 32 | github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= 33 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 34 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 35 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 36 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 37 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 38 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 39 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 40 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 41 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 42 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 43 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 44 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 45 | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 46 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= 47 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 48 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 49 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 50 | github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= 51 | github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= 52 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 53 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 54 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 55 | github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= 56 | github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= 57 | github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U= 58 | github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 59 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 60 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 61 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 62 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 63 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 64 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 65 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 66 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 67 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 68 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= 69 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 70 | github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= 71 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 72 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 73 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 74 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 75 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 76 | github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= 77 | github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= 78 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 79 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 80 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 81 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 82 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 83 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 84 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 85 | github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= 86 | github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 87 | github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= 88 | github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 89 | github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28= 90 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 91 | github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 92 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 93 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 94 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 95 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 96 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 97 | github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= 98 | github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= 99 | github.com/savsgio/gotils v0.0.0-20210617111740-97865ed5a873 h1:N3Af8f13ooDKcIhsmFT7Z05CStZWu4C7Md0uDEy4q6o= 100 | github.com/savsgio/gotils v0.0.0-20210617111740-97865ed5a873/go.mod h1:dmPawKuiAeG/aFYVs2i+Dyosoo7FNcm+Pi8iK6ZUrX8= 101 | github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= 102 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 103 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 104 | github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 105 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 106 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 107 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 108 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 109 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 110 | github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= 111 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 112 | github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= 113 | github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= 114 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 115 | github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= 116 | github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= 117 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 118 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 119 | github.com/valyala/fasthttp v1.27.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA= 120 | github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4= 121 | github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0= 122 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 123 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 124 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= 125 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 126 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= 127 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= 128 | github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= 129 | github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= 130 | github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= 131 | github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= 132 | github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= 133 | github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= 134 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= 135 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= 136 | github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= 137 | github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= 138 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 139 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 140 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 141 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 142 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= 143 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 144 | golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 145 | golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc= 146 | golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 147 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 148 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 149 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 150 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 151 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 152 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 153 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 154 | golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 155 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 156 | golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= 157 | golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 158 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 159 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 160 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 161 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 162 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 163 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 164 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 165 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 166 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 167 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 168 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 169 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 170 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 171 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 172 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 173 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 174 | golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 175 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60= 176 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 177 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 178 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 179 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 180 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 181 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 182 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 183 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 184 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 185 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 186 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 187 | golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 188 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 189 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 190 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 191 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 192 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 193 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 194 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 195 | google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= 196 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 197 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 198 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 199 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 200 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 201 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 202 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 203 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 204 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 205 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 206 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 207 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 208 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 209 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 210 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 211 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 212 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 213 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 214 | moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= 215 | moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= 216 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/RaymondCode/simple-demo/service" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | func main() { 9 | go service.RunMessageServer() 10 | 11 | r := gin.Default() 12 | 13 | initRouter(r) 14 | 15 | r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") 16 | } 17 | -------------------------------------------------------------------------------- /public/bear.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaymondCode/simple-demo/89b8c9434e0479a46ba75e46b8a372b04b1bdcb6/public/bear.mp4 -------------------------------------------------------------------------------- /public/data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaymondCode/simple-demo/89b8c9434e0479a46ba75e46b8a372b04b1bdcb6/public/data -------------------------------------------------------------------------------- /router.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/RaymondCode/simple-demo/controller" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | func initRouter(r *gin.Engine) { 9 | // public directory is used to serve static resources 10 | r.Static("/static", "./public") 11 | 12 | apiRouter := r.Group("/douyin") 13 | 14 | // basic apis 15 | apiRouter.GET("/feed/", controller.Feed) 16 | apiRouter.GET("/user/", controller.UserInfo) 17 | apiRouter.POST("/user/register/", controller.Register) 18 | apiRouter.POST("/user/login/", controller.Login) 19 | apiRouter.POST("/publish/action/", controller.Publish) 20 | apiRouter.GET("/publish/list/", controller.PublishList) 21 | 22 | // extra apis - I 23 | apiRouter.POST("/favorite/action/", controller.FavoriteAction) 24 | apiRouter.GET("/favorite/list/", controller.FavoriteList) 25 | apiRouter.POST("/comment/action/", controller.CommentAction) 26 | apiRouter.GET("/comment/list/", controller.CommentList) 27 | 28 | // extra apis - II 29 | apiRouter.POST("/relation/action/", controller.RelationAction) 30 | apiRouter.GET("/relation/follow/list/", controller.FollowList) 31 | apiRouter.GET("/relation/follower/list/", controller.FollowerList) 32 | apiRouter.GET("/relation/friend/list/", controller.FriendList) 33 | apiRouter.GET("/message/chat/", controller.MessageChat) 34 | apiRouter.POST("/message/action/", controller.MessageAction) 35 | } 36 | -------------------------------------------------------------------------------- /service/message.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/RaymondCode/simple-demo/controller" 7 | "io" 8 | "net" 9 | "sync" 10 | ) 11 | 12 | var chatConnMap = sync.Map{} 13 | 14 | func RunMessageServer() { 15 | listen, err := net.Listen("tcp", "127.0.0.1:9090") 16 | if err != nil { 17 | fmt.Printf("Run message sever failed: %v\n", err) 18 | return 19 | } 20 | 21 | for { 22 | conn, err := listen.Accept() 23 | if err != nil { 24 | fmt.Printf("Accept conn failed: %v\n", err) 25 | continue 26 | } 27 | 28 | go process(conn) 29 | } 30 | } 31 | 32 | func process(conn net.Conn) { 33 | defer conn.Close() 34 | 35 | var buf [256]byte 36 | for { 37 | n, err := conn.Read(buf[:]) 38 | if n == 0 { 39 | if err == io.EOF { 40 | break 41 | } 42 | fmt.Printf("Read message failed: %v\n", err) 43 | continue 44 | } 45 | 46 | var event = controller.MessageSendEvent{} 47 | _ = json.Unmarshal(buf[:n], &event) 48 | fmt.Printf("Receive Message:%+v\n", event) 49 | 50 | fromChatKey := fmt.Sprintf("%d_%d", event.UserId, event.ToUserId) 51 | if len(event.MsgContent) == 0 { 52 | chatConnMap.Store(fromChatKey, conn) 53 | continue 54 | } 55 | 56 | toChatKey := fmt.Sprintf("%d_%d", event.ToUserId, event.UserId) 57 | writeConn, exist := chatConnMap.Load(toChatKey) 58 | if !exist { 59 | fmt.Printf("User %d offline\n", event.ToUserId) 60 | continue 61 | } 62 | 63 | pushEvent := controller.MessagePushEvent{ 64 | FromUserId: event.UserId, 65 | MsgContent: event.MsgContent, 66 | } 67 | pushData, _ := json.Marshal(pushEvent) 68 | _, err = writeConn.(net.Conn).Write(pushData) 69 | if err != nil { 70 | fmt.Printf("Push message failed: %v\n", err) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /test/base_api_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "net/http" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestFeed(t *testing.T) { 12 | e := newExpect(t) 13 | 14 | feedResp := e.GET("/douyin/feed/").Expect().Status(http.StatusOK).JSON().Object() 15 | feedResp.Value("status_code").Number().Equal(0) 16 | feedResp.Value("video_list").Array().Length().Gt(0) 17 | 18 | for _, element := range feedResp.Value("video_list").Array().Iter() { 19 | video := element.Object() 20 | video.ContainsKey("id") 21 | video.ContainsKey("author") 22 | video.Value("play_url").String().NotEmpty() 23 | video.Value("cover_url").String().NotEmpty() 24 | } 25 | } 26 | 27 | func TestUserAction(t *testing.T) { 28 | e := newExpect(t) 29 | 30 | rand.Seed(time.Now().UnixNano()) 31 | registerValue := fmt.Sprintf("douyin%d", rand.Intn(65536)) 32 | 33 | registerResp := e.POST("/douyin/user/register/"). 34 | WithQuery("username", registerValue).WithQuery("password", registerValue). 35 | WithFormField("username", registerValue).WithFormField("password", registerValue). 36 | Expect(). 37 | Status(http.StatusOK). 38 | JSON().Object() 39 | registerResp.Value("status_code").Number().Equal(0) 40 | registerResp.Value("user_id").Number().Gt(0) 41 | registerResp.Value("token").String().Length().Gt(0) 42 | 43 | loginResp := e.POST("/douyin/user/login/"). 44 | WithQuery("username", registerValue).WithQuery("password", registerValue). 45 | WithFormField("username", registerValue).WithFormField("password", registerValue). 46 | Expect(). 47 | Status(http.StatusOK). 48 | JSON().Object() 49 | loginResp.Value("status_code").Number().Equal(0) 50 | loginResp.Value("user_id").Number().Gt(0) 51 | loginResp.Value("token").String().Length().Gt(0) 52 | 53 | token := loginResp.Value("token").String().Raw() 54 | userResp := e.GET("/douyin/user/"). 55 | WithQuery("token", token). 56 | Expect(). 57 | Status(http.StatusOK). 58 | JSON().Object() 59 | userResp.Value("status_code").Number().Equal(0) 60 | userInfo := userResp.Value("user").Object() 61 | userInfo.NotEmpty() 62 | userInfo.Value("id").Number().Gt(0) 63 | userInfo.Value("name").String().Length().Gt(0) 64 | } 65 | 66 | func TestPublish(t *testing.T) { 67 | e := newExpect(t) 68 | 69 | userId, token := getTestUserToken(testUserA, e) 70 | 71 | publishResp := e.POST("/douyin/publish/action/"). 72 | WithMultipart(). 73 | WithFile("data", "../public/bear.mp4"). 74 | WithFormField("token", token). 75 | WithFormField("title", "Bear"). 76 | Expect(). 77 | Status(http.StatusOK). 78 | JSON().Object() 79 | publishResp.Value("status_code").Number().Equal(0) 80 | 81 | publishListResp := e.GET("/douyin/publish/list/"). 82 | WithQuery("user_id", userId).WithQuery("token", token). 83 | Expect(). 84 | Status(http.StatusOK). 85 | JSON().Object() 86 | publishListResp.Value("status_code").Number().Equal(0) 87 | publishListResp.Value("video_list").Array().Length().Gt(0) 88 | 89 | for _, element := range publishListResp.Value("video_list").Array().Iter() { 90 | video := element.Object() 91 | video.ContainsKey("id") 92 | video.ContainsKey("author") 93 | video.Value("play_url").String().NotEmpty() 94 | video.Value("cover_url").String().NotEmpty() 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /test/common.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/gavv/httpexpect/v2" 5 | "net/http" 6 | "testing" 7 | ) 8 | 9 | var serverAddr = "http://localhost:8080" 10 | var testUserA = "douyinTestUserA" 11 | var testUserB = "douyinTestUserB" 12 | 13 | func newExpect(t *testing.T) *httpexpect.Expect { 14 | return httpexpect.WithConfig(httpexpect.Config{ 15 | Client: http.DefaultClient, 16 | BaseURL: serverAddr, 17 | Reporter: httpexpect.NewAssertReporter(t), 18 | Printers: []httpexpect.Printer{ 19 | httpexpect.NewDebugPrinter(t, true), 20 | }, 21 | }) 22 | } 23 | 24 | func getTestUserToken(user string, e *httpexpect.Expect) (int, string) { 25 | registerResp := e.POST("/douyin/user/register/"). 26 | WithQuery("username", user).WithQuery("password", user). 27 | WithFormField("username", user).WithFormField("password", user). 28 | Expect(). 29 | Status(http.StatusOK). 30 | JSON().Object() 31 | 32 | userId := 0 33 | token := registerResp.Value("token").String().Raw() 34 | if len(token) == 0 { 35 | loginResp := e.POST("/douyin/user/login/"). 36 | WithQuery("username", user).WithQuery("password", user). 37 | WithFormField("username", user).WithFormField("password", user). 38 | Expect(). 39 | Status(http.StatusOK). 40 | JSON().Object() 41 | loginToken := loginResp.Value("token").String() 42 | loginToken.Length().Gt(0) 43 | token = loginToken.Raw() 44 | userId = int(loginResp.Value("user_id").Number().Raw()) 45 | } else { 46 | userId = int(registerResp.Value("user_id").Number().Raw()) 47 | } 48 | return userId, token 49 | } 50 | -------------------------------------------------------------------------------- /test/interact_api_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "net/http" 6 | "testing" 7 | ) 8 | 9 | func TestFavorite(t *testing.T) { 10 | e := newExpect(t) 11 | 12 | feedResp := e.GET("/douyin/feed/").Expect().Status(http.StatusOK).JSON().Object() 13 | feedResp.Value("status_code").Number().Equal(0) 14 | feedResp.Value("video_list").Array().Length().Gt(0) 15 | firstVideo := feedResp.Value("video_list").Array().First().Object() 16 | videoId := firstVideo.Value("id").Number().Raw() 17 | 18 | userId, token := getTestUserToken(testUserA, e) 19 | 20 | favoriteResp := e.POST("/douyin/favorite/action/"). 21 | WithQuery("token", token).WithQuery("video_id", videoId).WithQuery("action_type", 1). 22 | WithFormField("token", token).WithFormField("video_id", videoId).WithFormField("action_type", 1). 23 | Expect(). 24 | Status(http.StatusOK). 25 | JSON().Object() 26 | favoriteResp.Value("status_code").Number().Equal(0) 27 | 28 | favoriteListResp := e.GET("/douyin/favorite/list/"). 29 | WithQuery("token", token).WithQuery("user_id", userId). 30 | WithFormField("token", token).WithFormField("user_id", userId). 31 | Expect(). 32 | Status(http.StatusOK). 33 | JSON().Object() 34 | favoriteListResp.Value("status_code").Number().Equal(0) 35 | for _, element := range favoriteListResp.Value("video_list").Array().Iter() { 36 | video := element.Object() 37 | video.ContainsKey("id") 38 | video.ContainsKey("author") 39 | video.Value("play_url").String().NotEmpty() 40 | video.Value("cover_url").String().NotEmpty() 41 | } 42 | } 43 | 44 | func TestComment(t *testing.T) { 45 | e := newExpect(t) 46 | 47 | feedResp := e.GET("/douyin/feed/").Expect().Status(http.StatusOK).JSON().Object() 48 | feedResp.Value("status_code").Number().Equal(0) 49 | feedResp.Value("video_list").Array().Length().Gt(0) 50 | firstVideo := feedResp.Value("video_list").Array().First().Object() 51 | videoId := firstVideo.Value("id").Number().Raw() 52 | 53 | _, token := getTestUserToken(testUserA, e) 54 | 55 | addCommentResp := e.POST("/douyin/comment/action/"). 56 | WithQuery("token", token).WithQuery("video_id", videoId).WithQuery("action_type", 1).WithQuery("comment_text", "测试评论"). 57 | WithFormField("token", token).WithFormField("video_id", videoId).WithFormField("action_type", 1).WithFormField("comment_text", "测试评论"). 58 | Expect(). 59 | Status(http.StatusOK). 60 | JSON().Object() 61 | addCommentResp.Value("status_code").Number().Equal(0) 62 | addCommentResp.Value("comment").Object().Value("id").Number().Gt(0) 63 | commentId := int(addCommentResp.Value("comment").Object().Value("id").Number().Raw()) 64 | 65 | commentListResp := e.GET("/douyin/comment/list/"). 66 | WithQuery("token", token).WithQuery("video_id", videoId). 67 | WithFormField("token", token).WithFormField("video_id", videoId). 68 | Expect(). 69 | Status(http.StatusOK). 70 | JSON().Object() 71 | commentListResp.Value("status_code").Number().Equal(0) 72 | containTestComment := false 73 | for _, element := range commentListResp.Value("comment_list").Array().Iter() { 74 | comment := element.Object() 75 | comment.ContainsKey("id") 76 | comment.ContainsKey("user") 77 | comment.Value("content").String().NotEmpty() 78 | comment.Value("create_date").String().NotEmpty() 79 | if int(comment.Value("id").Number().Raw()) == commentId { 80 | containTestComment = true 81 | } 82 | } 83 | 84 | assert.True(t, containTestComment, "Can't find test comment in list") 85 | 86 | delCommentResp := e.POST("/douyin/comment/action/"). 87 | WithQuery("token", token).WithQuery("video_id", videoId).WithQuery("action_type", 2).WithQuery("comment_id", commentId). 88 | WithFormField("token", token).WithFormField("video_id", videoId).WithFormField("action_type", 2).WithFormField("comment_id", commentId). 89 | Expect(). 90 | Status(http.StatusOK). 91 | JSON().Object() 92 | delCommentResp.Value("status_code").Number().Equal(0) 93 | } 94 | -------------------------------------------------------------------------------- /test/social_api_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "net/http" 6 | "testing" 7 | ) 8 | 9 | func TestRelation(t *testing.T) { 10 | e := newExpect(t) 11 | 12 | userIdA, tokenA := getTestUserToken(testUserA, e) 13 | userIdB, tokenB := getTestUserToken(testUserB, e) 14 | 15 | relationResp := e.POST("/douyin/relation/action/"). 16 | WithQuery("token", tokenA).WithQuery("to_user_id", userIdB).WithQuery("action_type", 1). 17 | WithFormField("token", tokenA).WithFormField("to_user_id", userIdB).WithFormField("action_type", 1). 18 | Expect(). 19 | Status(http.StatusOK). 20 | JSON().Object() 21 | relationResp.Value("status_code").Number().Equal(0) 22 | 23 | followListResp := e.GET("/douyin/relation/follow/list/"). 24 | WithQuery("token", tokenA).WithQuery("user_id", userIdA). 25 | WithFormField("token", tokenA).WithFormField("user_id", userIdA). 26 | Expect(). 27 | Status(http.StatusOK). 28 | JSON().Object() 29 | followListResp.Value("status_code").Number().Equal(0) 30 | 31 | containTestUserB := false 32 | for _, element := range followListResp.Value("user_list").Array().Iter() { 33 | user := element.Object() 34 | user.ContainsKey("id") 35 | if int(user.Value("id").Number().Raw()) == userIdB { 36 | containTestUserB = true 37 | } 38 | } 39 | assert.True(t, containTestUserB, "Follow test user failed") 40 | 41 | followerListResp := e.GET("/douyin/relation/follower/list/"). 42 | WithQuery("token", tokenB).WithQuery("user_id", userIdB). 43 | WithFormField("token", tokenB).WithFormField("user_id", userIdB). 44 | Expect(). 45 | Status(http.StatusOK). 46 | JSON().Object() 47 | followerListResp.Value("status_code").Number().Equal(0) 48 | 49 | containTestUserA := false 50 | for _, element := range followerListResp.Value("user_list").Array().Iter() { 51 | user := element.Object() 52 | user.ContainsKey("id") 53 | if int(user.Value("id").Number().Raw()) == userIdA { 54 | containTestUserA = true 55 | } 56 | } 57 | assert.True(t, containTestUserA, "Follower test user failed") 58 | } 59 | 60 | func TestChat(t *testing.T) { 61 | e := newExpect(t) 62 | 63 | userIdA, tokenA := getTestUserToken(testUserA, e) 64 | userIdB, tokenB := getTestUserToken(testUserB, e) 65 | 66 | messageResp := e.POST("/douyin/message/action/"). 67 | WithQuery("token", tokenA).WithQuery("to_user_id", userIdB).WithQuery("action_type", 1).WithQuery("content", "Send to UserB"). 68 | WithFormField("token", tokenA).WithFormField("to_user_id", userIdB).WithFormField("action_type", 1).WithQuery("content", "Send to UserB"). 69 | Expect(). 70 | Status(http.StatusOK). 71 | JSON().Object() 72 | messageResp.Value("status_code").Number().Equal(0) 73 | 74 | chatResp := e.GET("/douyin/message/chat/"). 75 | WithQuery("token", tokenA).WithQuery("to_user_id", userIdB). 76 | WithFormField("token", tokenA).WithFormField("to_user_id", userIdB). 77 | Expect(). 78 | Status(http.StatusOK). 79 | JSON().Object() 80 | chatResp.Value("status_code").Number().Equal(0) 81 | chatResp.Value("message_list").Array().Length().Gt(0) 82 | 83 | chatResp = e.GET("/douyin/message/chat/"). 84 | WithQuery("token", tokenB).WithQuery("to_user_id", userIdA). 85 | WithFormField("token", tokenB).WithFormField("to_user_id", userIdA). 86 | Expect(). 87 | Status(http.StatusOK). 88 | JSON().Object() 89 | chatResp.Value("status_code").Number().Equal(0) 90 | chatResp.Value("message_list").Array().Length().Gt(0) 91 | } 92 | --------------------------------------------------------------------------------