├── .github ├── semantic.yml └── workflows │ ├── build.yml │ └── ci.yml ├── .realeaserc.json ├── LICENSE ├── README.md ├── app ├── comment │ ├── cmd │ │ └── main.go │ ├── dao │ │ ├── comment.go │ │ ├── init.go │ │ └── migration.go │ └── service │ │ └── comment.go ├── favorite │ ├── cmd │ │ └── main.go │ ├── dao │ │ ├── favorite.go │ │ ├── init.go │ │ └── migration.go │ ├── script │ │ └── favorite.go │ └── service │ │ └── favorite.go ├── gateway │ ├── cmd │ │ └── main.go │ ├── http │ │ ├── comment.go │ │ ├── favorite.go │ │ ├── message.go │ │ ├── relation.go │ │ ├── user.go │ │ └── video.go │ ├── middleware │ │ ├── jaeger.go │ │ └── jwt.go │ ├── router │ │ └── router.go │ ├── rpc │ │ ├── comment.go │ │ ├── favorite.go │ │ ├── init.go │ │ ├── message.go │ │ ├── relation.go │ │ ├── user.go │ │ └── video.go │ └── wrapper │ │ ├── comment.go │ │ ├── favorite.go │ │ ├── jaeger.go │ │ ├── message.go │ │ ├── relation.go │ │ ├── user.go │ │ └── video.go ├── message │ ├── cmd │ │ └── main.go │ ├── dao │ │ ├── init.go │ │ ├── message.go │ │ └── migration.go │ └── service │ │ └── message.go ├── relation │ ├── cmd │ │ └── main.go │ ├── dao │ │ ├── init.go │ │ ├── migration.go │ │ └── relation.go │ └── service │ │ └── relation.go ├── user │ ├── cmd │ │ └── main.go │ ├── dao │ │ ├── init.go │ │ ├── migration.go │ │ └── user.go │ └── service │ │ └── user.go └── video │ ├── cmd │ └── main.go │ ├── dao │ ├── init.go │ ├── migration.go │ └── video.go │ ├── script │ └── video.go │ ├── service │ └── video.go │ └── tmp │ └── README.md ├── build.sh ├── config ├── config.go └── config.ini ├── consts └── mq.go ├── dockerfile ├── go.mod ├── go.sum ├── idl ├── comment │ ├── commentPb │ │ ├── commentService.pb.go │ │ └── commentService.pb.micro.go │ └── commentService.proto ├── favorite │ ├── favoritePb │ │ ├── favoriteService.pb.go │ │ └── favoriteService.pb.micro.go │ └── favoriteService.proto ├── message │ ├── messagePb │ │ ├── messageService.pb.go │ │ └── messageService.pb.micro.go │ └── messageService.proto ├── relation │ ├── relationPb │ │ ├── relationService.pb.go │ │ └── relationService.pb.micro.go │ └── relationService.proto ├── user │ ├── userPb │ │ ├── userService.pb.go │ │ └── userService.pb.micro.go │ └── userService.proto └── video │ ├── videoPb │ ├── videoService.pb.go │ └── videoService.pb.micro.go │ └── videoService.proto ├── model ├── comment.go ├── favorite.go ├── follow.go ├── message.go ├── user.go └── video.go ├── mq ├── consumer.go ├── init.go └── producer.go ├── run.sh ├── start.sh ├── test ├── comment_test.go ├── favorite_test.go ├── feed_test.go ├── login_test.go ├── message_test.go ├── publish_test.go ├── register_test.go ├── relation_test.go └── user_test.go └── util ├── array_change.go ├── fail_request.go ├── ffmpeg.go ├── md5.go ├── sort.go ├── token.go ├── upload.go └── uuid.go /.github/semantic.yml: -------------------------------------------------------------------------------- 1 | # Always validate the PR title AND all the commits 2 | titleAndCommits: true 3 | # Require at least one commit to be valid 4 | # this is only relevant when using commitsOnly: true or titleAndCommits: true, 5 | # which validate all commits by default 6 | anyCommit: true 7 | # Allow use of Merge commits (eg on github: "Merge branch 'master' into feature/ride-unicorns") 8 | # this is only relevant when using commitsOnly: true (or titleAndCommits: true) 9 | allowMergeCommits: false 10 | # Allow use of Revert commits (eg on github: "Revert "feat: ride unicorns"") 11 | # this is only relevant when using commitsOnly: true (or titleAndCommits: true) 12 | allowRevertCommits: false -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | 7 | go-tests: 8 | name: Running Go tests 9 | runs-on: ubuntu-latest 10 | services: 11 | mysql: 12 | image: mysql:5.7 13 | env: 14 | MYSQL_DATABASE: tiktok 15 | MYSQL_ROOT_PASSWORD: 123456 16 | ports: 17 | - 3306:3306 18 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 19 | steps: 20 | - uses: actions/checkout@v3 21 | - uses: actions/setup-go@v4 22 | with: 23 | go-version: '^1.16.5' 24 | cache-dependency-path: ./go.mod 25 | - name: Tests 26 | run: | 27 | # go test -v $(go list ./...) -tags skipCi 28 | working-directory: ./ 29 | 30 | release-and-push: 31 | name: Release And Push 32 | runs-on: ubuntu-latest 33 | if: github.repository == 'UESTCByteDance/ByteRhythm' && github.event_name == 'push' 34 | steps: 35 | - name: Checkout 36 | uses: actions/checkout@v3 37 | with: 38 | fetch-depth: -1 39 | - name: Setup Node.js 40 | uses: actions/setup-node@v3 41 | with: 42 | node-version: 20 43 | 44 | - name: Fetch Previous version 45 | id: get-previous-tag 46 | uses: actions-ecosystem/action-get-latest-tag@v1.6.0 47 | 48 | - name: Release 49 | run: yarn global add semantic-release@17.4.4 && semantic-release 50 | env: 51 | GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }} 52 | 53 | - name: Fetch Current version 54 | id: get-current-tag 55 | uses: actions-ecosystem/action-get-latest-tag@v1.6.0 56 | 57 | - name: Decide Should_Push Or Not 58 | id: should_push 59 | run: | 60 | old_version=${{steps.get-previous-tag.outputs.tag}} 61 | new_version=${{steps.get-current-tag.outputs.tag }} 62 | 63 | old_array=(${old_version//\./ }) 64 | new_array=(${new_version//\./ }) 65 | 66 | if [ ${old_array[0]} != ${new_array[0]} ] 67 | then 68 | echo ::set-output name=push::'true' 69 | elif [ ${old_array[1]} != ${new_array[1]} ] 70 | then 71 | echo ::set-output name=push::'true' 72 | 73 | else 74 | echo ::set-output name=push::'false' 75 | 76 | fi 77 | 78 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | 9 | test: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Set up Go 13 | uses: actions/setup-go@v2 14 | with: 15 | go-version: 1.18 16 | 17 | - uses: actions/checkout@v2 18 | 19 | semantic-release: 20 | needs: [ test ] 21 | runs-on: ubuntu-latest 22 | steps: 23 | 24 | - uses: actions/checkout@v2 25 | - name: Run semantic-release 26 | if: github.repository == 'UESTCByteDance/ByteRhythm' && github.event_name == 'push' 27 | run: | 28 | npm install --save-dev semantic-release@17.2.4 29 | npx semantic-release 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | -------------------------------------------------------------------------------- /.realeaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "debug": true, 3 | "branches": [ 4 | "+([0-9])?(.{+([0-9]),x}).x", 5 | "master", 6 | { 7 | "name": "beta", 8 | "prerelease": true 9 | } 10 | ], 11 | "plugins": [ 12 | "@semantic-release/commit-analyzer", 13 | "@semantic-release/release-notes-generator", 14 | "@semantic-release/github" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

ByteRhythm

2 |

第六届字节跳动青训营后端进阶班大项目优秀奖获得者😎

3 |

本项目利用 Golang 以及相关技术如 Gorm、MySQL、Redis、JWT、RabbitMQ、Hystrix、七牛云 等构建了基于 Gin 和 Go-micro的微服务应用,实现了视频处理、对象存储、限流、降级熔断、负载均衡等功能,并通过 Opentracing、Jaeger 等工具进行监控与追踪,Testify进行单元测试,Docker进行容器化部署,形成高可用高性能的分布式服务。

4 |
5 | 6 | semantic-release 7 | 8 | 9 | Godoc 10 | 11 | 12 | license 13 | 14 | 15 | GitHub issues 16 | 17 | 18 | GitHub stars 19 | 20 | 21 | GitHub forks 22 | 23 | 24 | Release 25 | 26 | 27 | language 28 | 29 | 30 | last commit 31 | 32 | 33 | 访问量统计 34 | 35 |
36 | 37 | #### 👀仓库地址: 38 | 39 | #### 📚文档地址: 40 | 41 | #### 🥽视频地址:[https://www.bilibili.com/video/BV1Y14y1k7gG](https://www.bilibili.com/video/BV1JF41167UL) 42 | 43 | #### 🎶PPT地址: 44 | 45 | 46 | 47 | # 使用说明 48 | 本项目有v1、v2两个版本,可前往[Releases]()下载使用,前者是传统的单体架构,用beego实现,后者是微服务架构,由gin+go-micro实现。 49 | 50 | 下面介绍v2版的使用: 51 | 52 | 如果不使用docker进行容器化部署(docker部署参照文末),可以参考以下步骤进行本地部署。建议使用环境为`Ubuntu20.04`。 53 | 54 | ## 1.克隆到本地 55 | 56 | ```bash 57 | git clone https://github.com/UESTCByteDance/ByteRhythm.git 58 | ``` 59 | 60 | ## 2.安装依赖 61 | 62 | ```bash 63 | go mod tidy 64 | ``` 65 | 66 | ## 3.数据库配置 67 | 68 | 打开`config.ini`,修改以下内容: 69 | 70 | ```ini 71 | DBHost = 127.0.0.1 72 | DBPort = 3306 73 | DBUser = root 74 | DBPassWord = 123456 75 | DBName = tiktok 76 | ``` 77 | 78 | 确保你的`Ubuntu20.04`已经装了`MySQL`,并且能够连接上,然后新建数据库`tiktok` 79 | 80 | ## 4.配置`ffmpeg`环境 81 | 82 | 打开终端,依次执行下列命令(逐条执行): 83 | 84 | ```bash 85 | sudo apt-get -y install autoconf automake build-essential libass-dev libfreetype6-dev libsdl1.2-dev libtheora-dev libtool libva-dev libvdpau-dev libvorbis-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev pkg-config texi2html zlib1g-dev 86 | 87 | sudo apt install -y libavdevice-dev libavfilter-dev libswscale-dev libavcodec-dev libavformat-dev libswresample-dev libavutil-dev 88 | 89 | sudo apt-get install yasm 90 | 91 | export FFMPEG_ROOT=$HOME/ffmpeg 92 | export CGO_LDFLAGS="-L$FFMPEG_ROOT/lib/ -lavcodec -lavformat -lavutil -lswscale -lswresample -lavdevice -lavfilter" 93 | export CGO_CFLAGS="-I$FFMPEG_ROOT/include" 94 | export LD_LIBRARY_PATH=$HOME/ffmpeg/lib 95 | ``` 96 | 97 | ## 5.启动etcd 98 | 99 | 如果未安装,前往官方网站:下载适合你系统的安装包并解压。 100 | 101 | 按需修改配置: 102 | 103 | ```ini 104 | EtcdHost = 127.0.0.1 105 | EtcdPort = 2379 106 | ``` 107 | 108 | 在对应终端执行: 109 | 110 | ```bash 111 | ./etcd 112 | ``` 113 | 114 | 如果权限不够,可以使用`chmod +x etcd`赋予可执行权限再执行`./etcd`。 115 | 可以安装`etcdkeeper`进入UI界面进行查看。 116 | 117 | ## 6.启动Jaeger 118 | 119 | 如果未安装,前往官方网站:下载适合你系统的安装包并解压。 120 | 121 | 按需修改配置: 122 | 123 | ```ini 124 | JaegerHost = 127.0.0.1 125 | JaegerPort = 6831 126 | ``` 127 | 128 | 在对应终端执行: 129 | 130 | ```bash 131 | ./jaeger-all-in-one --collector.zipkin.host-port=:9411 132 | ``` 133 | 134 | 如果权限不够,可以使用`chmod +x jaeger-all-in-one` 135 | 赋予可执行权限再执行`./jaeger-all-in-one --collector.zipkin.host-port=:9411`。 136 | 可以访问:进入UI界面。 137 | 138 | ## 7.启动RabbitMQ 139 | 140 | 如果未安装,前往官方网站:下载安装。 141 | 142 | 按需修改配置: 143 | 144 | ```ini 145 | RabbitMQ = amqp 146 | RabbitMQHost = 127.0.0.1 147 | RabbitMQPort = 5672 148 | RabbitMQUser = guest 149 | RabbitMQPassWord = guest 150 | ``` 151 | 152 | 确保RabbitMQ能在本地运行。 153 | 154 | ## 8.配置Redis 155 | 156 | 如果未安装,打开终端,依次执行下列命令(逐条执行): 157 | 158 | ```bash 159 | sudo apt update 160 | sudo apt install redis-server 161 | ``` 162 | 163 | 按需修改配置: 164 | 165 | ```ini 166 | RedisHost = 127.0.0.1 167 | RedisPort = 6379 168 | ``` 169 | 170 | 确保Redis能在本地运行。 171 | 172 | ## 9.配置七牛云 173 | 174 | 根据你的七牛云账户信息,修改以下配置: 175 | 176 | ```ini 177 | Bucket = your bucket 178 | AccessKey = your access key 179 | SecretKey = your secret key 180 | Domain = your domain 181 | ``` 182 | ## 10.运行项目 183 | 184 | ```bash 185 | //构建项目 186 | chmod +x build.sh 187 | ./build.sh 188 | 189 | //运行项目 190 | chmod +x run.sh 191 | ./run.sh 192 | ``` 193 | 如果你是将项目克隆到Windows系统里,再转移到Ubuntu系统里,很可能会遇到Windows和Linux换行不兼容的问题,建议先执行命令`sed -i -e 's/\r$//' build.sh run.sh`,再执行上述命令。 194 | ## 11.运行测试 195 | ```bash 196 | cd test 197 | go test -v 198 | ``` 199 | 注:测试文件的参数可能会需要根据实际情况更改。 200 | 201 | ## 12.docker 运行 202 | tips:由于服务器性能限制,因此将所有微服务放在同一个容器中,后续可用docker-compose部署为7个业务容器 203 | ### (1)拉取 mysql 镜像并运行 204 | ```sh 205 | docker run -d -p 3306:3306 --name tiktok-mysql -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=tiktok mysql/mysql-server:latest 206 | ``` 207 | ### (2)拉取 byterhythm:v2.1 镜像并运行 208 | ```sh 209 | docker run -it -p 8080:8080/tcp -p 16686:16686/tcp --name byterhythm david945/byterhythm:v2.1 210 | ``` 211 | # 项目贡献者 212 | * [palp1tate](https://github.com/palp1tate) 213 | 214 | * [youyou0805](https://github.com/youyou0805) 215 | 216 | * [Chiba-little-black-cat](https://github.com/Chiba-little-black-cat) 217 | 218 | * [DavidHGS](https://github.com/DavidHGS) 219 | 220 | * [tangyiheng](https://github.com/tangyiheng) 221 | 222 | * [woniu-huang](https://github.com/woniu-huang) 223 | 224 | * [janjiang005](https://github.com/janjiang005) 225 | 226 | # 获奖 227 | 228 | ![](https://cdn.jsdelivr.net/gh/palp1tate/ImgPicGo/img/jyzs.png) 229 | 230 | ![](https://cdn.jsdelivr.net/gh/palp1tate/ImgPicGo/img/dxmhj.png) 231 | 232 | # Stargazers over time 233 | [![Stargazers over time](https://starchart.cc/UESTCByteDance/ByteRhythm.svg?variant=light)](https://starchart.cc/UESTCByteDance/ByteRhythm) 234 | 235 | 236 | -------------------------------------------------------------------------------- /app/comment/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "ByteRhythm/app/comment/dao" 5 | "ByteRhythm/app/comment/service" 6 | "ByteRhythm/app/gateway/wrapper" 7 | "ByteRhythm/config" 8 | "ByteRhythm/idl/comment/commentPb" 9 | "fmt" 10 | "os" 11 | 12 | "github.com/go-micro/plugins/v4/registry/etcd" 13 | ratelimit "github.com/go-micro/plugins/v4/wrapper/ratelimiter/uber" 14 | "github.com/go-micro/plugins/v4/wrapper/select/roundrobin" 15 | "github.com/go-micro/plugins/v4/wrapper/trace/opentracing" 16 | 17 | "go-micro.dev/v4" 18 | "go-micro.dev/v4/registry" 19 | ) 20 | 21 | func main() { 22 | config.Init() 23 | dao.InitMySQL() 24 | dao.InitRedis() 25 | 26 | defer dao.RedisNo1Client.Close() 27 | defer dao.RedisNo2Client.Close() 28 | 29 | // etcd注册件 30 | etcdReg := etcd.NewRegistry( 31 | registry.Addrs(fmt.Sprintf("%s:%s", config.EtcdHost, config.EtcdPort)), 32 | ) 33 | 34 | // 链路追踪 35 | tracer, closer, err := wrapper.InitJaeger("CommentService", fmt.Sprintf("%s:%s", config.JaegerHost, config.JaegerPort)) 36 | if err != nil { 37 | fmt.Printf("new tracer err: %+v\n", err) 38 | os.Exit(-1) 39 | } 40 | defer closer.Close() 41 | 42 | // 得到一个微服务实例 43 | microService := micro.NewService( 44 | micro.Name("CommentService"), // 微服务名字 45 | micro.Address(config.CommentServiceAddress), 46 | micro.Registry(etcdReg), // etcd注册件 47 | micro.WrapHandler(ratelimit.NewHandlerWrapper(50000)), //限流处理 48 | micro.WrapClient(roundrobin.NewClientWrapper()), // 负载均衡 49 | micro.WrapHandler(opentracing.NewHandlerWrapper(tracer)), // 链路追踪 50 | micro.WrapClient(opentracing.NewClientWrapper(tracer)), // 链路追踪 51 | ) 52 | 53 | // 结构命令行参数,初始化 54 | microService.Init() 55 | // 服务注册 56 | _ = commentPb.RegisterCommentServiceHandler(microService.Server(), service.GetCommentSrv()) 57 | // 启动微服务 58 | _ = microService.Run() 59 | } 60 | -------------------------------------------------------------------------------- /app/comment/dao/comment.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "ByteRhythm/model" 5 | "ByteRhythm/util" 6 | "context" 7 | 8 | "gorm.io/gorm" 9 | ) 10 | 11 | type CommentDao struct { 12 | *gorm.DB 13 | } 14 | 15 | func NewCommentDao(ctx context.Context) *CommentDao { 16 | if ctx == nil { 17 | ctx = context.Background() 18 | } 19 | return &CommentDao{NewDBClient(ctx)} 20 | } 21 | 22 | func (c *CommentDao) CreateComment(comment *model.Comment) (err error) { 23 | err = c.Model(&model.Comment{}).Create(&comment).Error 24 | if err != nil { 25 | return 26 | } 27 | return nil 28 | } 29 | 30 | func (c *CommentDao) DeleteComment(comment *model.Comment) (err error) { 31 | err = c.Model(&model.Comment{}).Where(&comment).Delete(&comment).Error 32 | if err != nil { 33 | return 34 | } 35 | return nil 36 | } 37 | 38 | func (c *CommentDao) GetCommentListByVideoId(vid int64) (comments []*model.Comment, err error) { 39 | err = c.Model(&model.Comment{}).Where("video_id = ?", vid).Order("id desc").Find(&comments).Error 40 | if err != nil { 41 | return 42 | } 43 | return 44 | } 45 | 46 | func (c *CommentDao) GetUsernameByUid(uid int64) (username string, err error) { 47 | err = c.Model(&model.User{}).Where("id = ?", uid).Select("username").Find(&username).Error 48 | if err != nil { 49 | return 50 | } 51 | return 52 | } 53 | 54 | func (c *CommentDao) GetAvatarByUid(uid int64) (avatar string, err error) { 55 | err = c.Model(&model.User{}).Where("id = ?", uid).Select("avatar").Find(&avatar).Error 56 | if err != nil { 57 | return 58 | } 59 | return 60 | } 61 | 62 | func (c *CommentDao) GetFollowCount(uid int) (count int64, err error) { 63 | err = c.Model(&model.Follow{}).Where("followed_user_id = ?", uid).Count(&count).Error 64 | if err != nil { 65 | return 66 | } 67 | return 68 | } 69 | 70 | func (c *CommentDao) GetFollowerCount(uid int) (count int64, err error) { 71 | err = c.Model(&model.Follow{}).Where("user_id = ?", uid).Count(&count).Error 72 | if err != nil { 73 | return 74 | } 75 | return 76 | } 77 | 78 | func (c *CommentDao) GetWorkCount(uid int) (count int64, err error) { 79 | err = c.Model(&model.Video{}).Where("author_id = ?", uid).Count(&count).Error 80 | if err != nil { 81 | return 82 | } 83 | return 84 | } 85 | 86 | func (c *CommentDao) GetUserFavoriteCount(uid int) (count int64, err error) { 87 | err = c.Model(&model.Favorite{}).Where("user_id = ?", uid).Count(&count).Error 88 | if err != nil { 89 | return 90 | } 91 | return 92 | } 93 | 94 | func (c *CommentDao) GetFavoriteCount(vid int) (count int64, err error) { 95 | err = c.Model(&model.Favorite{}).Where("video_id = ?", vid).Count(&count).Error 96 | if err != nil { 97 | return 98 | } 99 | return 100 | } 101 | 102 | func (c *CommentDao) GetTotalFavorited(uid int) (count int64, err error) { 103 | var videos []*model.Video 104 | err = c.Model(&model.Video{}).Where("author_id = ?", uid).Find(&videos).Error 105 | if err != nil { 106 | return 107 | } 108 | for _, video := range videos { 109 | var favoriteCount int64 110 | err = c.Model(&model.Favorite{}).Where("video_id = ?", video.ID).Count(&favoriteCount).Error 111 | if err != nil { 112 | return 113 | } 114 | count += favoriteCount 115 | } 116 | return 117 | } 118 | 119 | func (c *CommentDao) GetIsFollowed(uid int, token string) (isFollowed bool, err error) { 120 | 121 | baseID, err := util.GetUserIdFromToken(token) 122 | if err != nil { 123 | return 124 | } 125 | var follow model.Follow 126 | err = c.Model(&model.Follow{}).Where("user_id = ?", baseID).Where("followed_user_id = ?", uid).Limit(1).Find(&follow).Error 127 | if err != nil { 128 | return 129 | } 130 | if follow.ID != 0 { 131 | isFollowed = true 132 | } else { 133 | isFollowed = false 134 | } 135 | return 136 | } 137 | -------------------------------------------------------------------------------- /app/comment/dao/init.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/go-redis/redis/v8" 7 | "time" 8 | 9 | "github.com/gin-gonic/gin" 10 | "gorm.io/driver/mysql" 11 | "gorm.io/gorm" 12 | "gorm.io/gorm/logger" 13 | "gorm.io/gorm/schema" 14 | 15 | "ByteRhythm/config" 16 | ) 17 | 18 | var db *gorm.DB 19 | 20 | var RedisNo2Client *redis.Client 21 | var RedisNo1Client *redis.Client 22 | 23 | func InitRedis() { 24 | // 初始化 Redis 客户端 25 | host := config.RedisHost 26 | port := config.RedisPort 27 | RedisNo2Client = redis.NewClient(&redis.Options{ 28 | Addr: host + ":" + port, // Redis 服务器地址 29 | Password: "", // Redis 访问密码(如果有的话) 30 | DB: 2, // Redis 数据库索引 31 | }) 32 | 33 | RedisNo1Client = redis.NewClient(&redis.Options{ 34 | Addr: host + ":" + port, // Redis 服务器地址 35 | Password: "", // Redis 访问密码(如果有的话) 36 | DB: 1, // Redis 数据库索引 37 | }) 38 | } 39 | 40 | func InitMySQL() { 41 | host := config.DBHost 42 | port := config.DBPort 43 | database := config.DBName 44 | username := config.DBUser 45 | password := config.DBPassWord 46 | charset := config.Charset 47 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=True&loc=Local", username, password, host, port, database, charset) 48 | err := Database(dsn) 49 | if err != nil { 50 | fmt.Println(err) 51 | } 52 | } 53 | 54 | func Database(connString string) error { 55 | var ormLogger logger.Interface 56 | if gin.Mode() == "debug" { 57 | ormLogger = logger.Default.LogMode(logger.Info) 58 | } else { 59 | ormLogger = logger.Default 60 | } 61 | DB, err := gorm.Open(mysql.New(mysql.Config{ 62 | DSN: connString, // DSN data source name 63 | DefaultStringSize: 256, // string 类型字段的默认长度 64 | DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持 65 | DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引 66 | DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列 67 | SkipInitializeWithVersion: false, // 根据版本自动配置 68 | }), &gorm.Config{ 69 | Logger: ormLogger, 70 | NamingStrategy: schema.NamingStrategy{ 71 | SingularTable: true, 72 | }, 73 | }) 74 | if err != nil { 75 | panic(err) 76 | } 77 | sqlDB, _ := DB.DB() 78 | sqlDB.SetMaxIdleConns(20) 79 | sqlDB.SetMaxOpenConns(100) 80 | sqlDB.SetConnMaxLifetime(time.Second * 30) 81 | db = DB 82 | migration() 83 | return err 84 | } 85 | 86 | func NewDBClient(ctx context.Context) *gorm.DB { 87 | DB := db 88 | return DB.WithContext(ctx) 89 | } 90 | -------------------------------------------------------------------------------- /app/comment/dao/migration.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "ByteRhythm/model" 5 | "log" 6 | ) 7 | 8 | func migration() { 9 | err := db.Set(`gorm:table_options`, "charset=utf8mb4"). 10 | AutoMigrate(&model.User{}, &model.Follow{}, &model.Video{}, &model.Favorite{}, &model.Comment{}) 11 | if err != nil { 12 | log.Fatal(err) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/favorite/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "ByteRhythm/app/favorite/dao" 5 | "ByteRhythm/app/favorite/script" 6 | "ByteRhythm/app/favorite/service" 7 | "ByteRhythm/app/gateway/wrapper" 8 | "ByteRhythm/config" 9 | "ByteRhythm/idl/favorite/favoritePb" 10 | "ByteRhythm/mq" 11 | "context" 12 | "fmt" 13 | "os" 14 | 15 | "github.com/go-micro/plugins/v4/registry/etcd" 16 | ratelimit "github.com/go-micro/plugins/v4/wrapper/ratelimiter/uber" 17 | "github.com/go-micro/plugins/v4/wrapper/select/roundrobin" 18 | "github.com/go-micro/plugins/v4/wrapper/trace/opentracing" 19 | 20 | "go-micro.dev/v4" 21 | "go-micro.dev/v4/registry" 22 | ) 23 | 24 | func main() { 25 | config.Init() 26 | dao.InitMySQL() 27 | dao.InitRedis() 28 | mq.InitRabbitMQ() 29 | loadingScript() 30 | 31 | defer dao.RedisClient.Close() 32 | 33 | // etcd注册件 34 | etcdReg := etcd.NewRegistry( 35 | registry.Addrs(fmt.Sprintf("%s:%s", config.EtcdHost, config.EtcdPort)), 36 | ) 37 | 38 | // 链路追踪 39 | tracer, closer, err := wrapper.InitJaeger("FavoriteService", fmt.Sprintf("%s:%s", config.JaegerHost, config.JaegerPort)) 40 | if err != nil { 41 | fmt.Printf("new tracer err: %+v\n", err) 42 | os.Exit(-1) 43 | } 44 | defer closer.Close() 45 | 46 | // 得到一个微服务实例 47 | microService := micro.NewService( 48 | micro.Name("FavoriteService"), // 微服务名字 49 | micro.Address(config.FavoriteServiceAddress), 50 | micro.Registry(etcdReg), // etcd注册件 51 | micro.WrapHandler(ratelimit.NewHandlerWrapper(50000)), //限流处理 52 | micro.WrapClient(roundrobin.NewClientWrapper()), // 负载均衡 53 | micro.WrapHandler(opentracing.NewHandlerWrapper(tracer)), // 链路追踪 54 | micro.WrapClient(opentracing.NewClientWrapper(tracer)), // 链路追踪 55 | ) 56 | 57 | // 结构命令行参数,初始化 58 | microService.Init() 59 | // 服务注册 60 | _ = favoritePb.RegisterFavoriteServiceHandler(microService.Server(), service.GetFavoriteSrv()) 61 | // 启动微服务 62 | _ = microService.Run() 63 | } 64 | 65 | func loadingScript() { 66 | ctx := context.Background() 67 | go script.FavoriteCreateSync(ctx) 68 | go script.FavoriteDeleteSync(ctx) 69 | } 70 | -------------------------------------------------------------------------------- /app/favorite/dao/favorite.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "ByteRhythm/model" 5 | "ByteRhythm/util" 6 | "context" 7 | 8 | "gorm.io/gorm" 9 | ) 10 | 11 | type FavoriteDao struct { 12 | *gorm.DB 13 | } 14 | 15 | func NewFavoriteDao(ctx context.Context) *FavoriteDao { 16 | if ctx == nil { 17 | ctx = context.Background() 18 | } 19 | return &FavoriteDao{NewDBClient(ctx)} 20 | } 21 | 22 | func (f *FavoriteDao) CreateFavorite(favorite *model.Favorite) (err error) { 23 | err = f.Model(&model.Favorite{}).Create(&favorite).Error 24 | if err != nil { 25 | return 26 | } 27 | return nil 28 | } 29 | 30 | func (f *FavoriteDao) DeleteFavorite(favorite *model.Favorite) (err error) { 31 | err = f.Model(&model.Favorite{}).Where(&favorite).Delete(&favorite).Error 32 | if err != nil { 33 | return 34 | } 35 | return nil 36 | } 37 | 38 | func (f *FavoriteDao) GetFavoriteListByUserId(uid int64) (favorites []*model.Favorite, err error) { 39 | err = f.Model(&model.Favorite{}).Where("user_id = ?", uid).Find(&favorites).Error 40 | if err != nil { 41 | return 42 | } 43 | return 44 | } 45 | 46 | func (f *FavoriteDao) GetIsFavoriteByUserIdAndVid(uid int64, vid int64) (isFavorite bool, err error) { 47 | var count int64 48 | err = f.Model(&model.Favorite{}).Where("user_id = ?", uid).Where("video_id = ?", vid).Count(&count).Error 49 | if err != nil { 50 | return false, err 51 | } 52 | if count > 0 { 53 | return true, nil 54 | } 55 | return false, nil 56 | } 57 | 58 | func (f *FavoriteDao) GetFollowCount(uid int) (count int64, err error) { 59 | err = f.Model(&model.Follow{}).Where("followed_user_id = ?", uid).Count(&count).Error 60 | if err != nil { 61 | return 62 | } 63 | return 64 | } 65 | 66 | func (f *FavoriteDao) GetFollowerCount(uid int) (count int64, err error) { 67 | err = f.Model(&model.Follow{}).Where("user_id = ?", uid).Count(&count).Error 68 | if err != nil { 69 | return 70 | } 71 | return 72 | } 73 | 74 | func (f *FavoriteDao) GetWorkCount(uid int) (count int64, err error) { 75 | err = f.Model(&model.Video{}).Where("author_id = ?", uid).Count(&count).Error 76 | if err != nil { 77 | return 78 | } 79 | return 80 | } 81 | 82 | func (f *FavoriteDao) GetUserFavoriteCount(uid int) (count int64, err error) { 83 | err = f.Model(&model.Favorite{}).Where("user_id = ?", uid).Count(&count).Error 84 | if err != nil { 85 | return 86 | } 87 | return 88 | } 89 | 90 | func (f *FavoriteDao) GetFavoriteCount(vid int) (count int64, err error) { 91 | err = f.Model(&model.Favorite{}).Where("video_id = ?", vid).Count(&count).Error 92 | if err != nil { 93 | return 94 | } 95 | return 96 | } 97 | 98 | func (f *FavoriteDao) GetTotalFavorited(uid int) (count int64, err error) { 99 | var videos []*model.Video 100 | err = f.Model(&model.Video{}).Where("author_id = ?", uid).Find(&videos).Error 101 | if err != nil { 102 | return 103 | } 104 | for _, video := range videos { 105 | var favoriteCount int64 106 | err = f.Model(&model.Favorite{}).Where("video_id = ?", video.ID).Count(&favoriteCount).Error 107 | if err != nil { 108 | return 109 | } 110 | count += favoriteCount 111 | } 112 | return 113 | } 114 | 115 | func (f *FavoriteDao) GetIsFollowed(uid int, token string) (isFollowed bool, err error) { 116 | 117 | baseID, err := util.GetUserIdFromToken(token) 118 | if err != nil { 119 | return 120 | } 121 | var follow model.Follow 122 | err = f.Model(&model.Follow{}).Where("user_id = ?", baseID).Where("followed_user_id = ?", uid).Limit(1).Find(&follow).Error 123 | if err != nil { 124 | return 125 | } 126 | if follow.ID != 0 { 127 | isFollowed = true 128 | } else { 129 | isFollowed = false 130 | } 131 | return 132 | } 133 | 134 | func (f *FavoriteDao) GetPlayUrlByVid(vid int) (playUrl string, err error) { 135 | err = f.Model(&model.Video{}).Where("id = ?", vid).Select("play_url").First(&playUrl).Error 136 | if err != nil { 137 | return 138 | } 139 | return 140 | } 141 | 142 | func (f *FavoriteDao) GetCoverUrlByVid(vid int) (playUrl string, err error) { 143 | err = f.Model(&model.Video{}).Where("id = ?", vid).Select("cover_url").First(&playUrl).Error 144 | if err != nil { 145 | return 146 | } 147 | return 148 | } 149 | -------------------------------------------------------------------------------- /app/favorite/dao/init.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/go-redis/redis/v8" 10 | "gorm.io/driver/mysql" 11 | "gorm.io/gorm" 12 | "gorm.io/gorm/logger" 13 | "gorm.io/gorm/schema" 14 | 15 | "ByteRhythm/config" 16 | ) 17 | 18 | var db *gorm.DB 19 | 20 | var RedisClient *redis.Client 21 | 22 | func InitMySQL() { 23 | host := config.DBHost 24 | port := config.DBPort 25 | database := config.DBName 26 | username := config.DBUser 27 | password := config.DBPassWord 28 | charset := config.Charset 29 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=True&loc=Local", username, password, host, port, database, charset) 30 | err := Database(dsn) 31 | if err != nil { 32 | fmt.Println(err) 33 | } 34 | } 35 | 36 | func InitRedis() { 37 | // 初始化 Redis 客户端 38 | host := config.RedisHost 39 | port := config.RedisPort 40 | RedisClient = redis.NewClient(&redis.Options{ 41 | Addr: host + ":" + port, // Redis 服务器地址 42 | Password: "", // Redis 访问密码(如果有的话) 43 | DB: 1, // Redis 数据库索引 44 | }) 45 | } 46 | 47 | func Database(connString string) error { 48 | var ormLogger logger.Interface 49 | if gin.Mode() == "debug" { 50 | ormLogger = logger.Default.LogMode(logger.Info) 51 | } else { 52 | ormLogger = logger.Default 53 | } 54 | DB, err := gorm.Open(mysql.New(mysql.Config{ 55 | DSN: connString, // DSN data source name 56 | DefaultStringSize: 256, // string 类型字段的默认长度 57 | DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持 58 | DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引 59 | DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列 60 | SkipInitializeWithVersion: false, // 根据版本自动配置 61 | }), &gorm.Config{ 62 | Logger: ormLogger, 63 | NamingStrategy: schema.NamingStrategy{ 64 | SingularTable: true, 65 | }, 66 | }) 67 | if err != nil { 68 | panic(err) 69 | } 70 | sqlDB, _ := DB.DB() 71 | sqlDB.SetMaxIdleConns(20) 72 | sqlDB.SetMaxOpenConns(100) 73 | sqlDB.SetConnMaxLifetime(time.Second * 30) 74 | db = DB 75 | migration() 76 | return err 77 | } 78 | 79 | func NewDBClient(ctx context.Context) *gorm.DB { 80 | DB := db 81 | return DB.WithContext(ctx) 82 | } 83 | -------------------------------------------------------------------------------- /app/favorite/dao/migration.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "ByteRhythm/model" 5 | "log" 6 | ) 7 | 8 | func migration() { 9 | err := db.Set(`gorm:table_options`, "charset=utf8mb4"). 10 | AutoMigrate(&model.Follow{}, &model.Video{}, &model.Favorite{}, &model.Comment{}) 11 | if err != nil { 12 | log.Fatal(err) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/favorite/script/favorite.go: -------------------------------------------------------------------------------- 1 | package script 2 | 3 | import ( 4 | "ByteRhythm/app/favorite/service" 5 | "ByteRhythm/idl/favorite/favoritePb" 6 | "ByteRhythm/mq" 7 | "context" 8 | "encoding/json" 9 | ) 10 | 11 | func FavoriteCreateSync(ctx context.Context) { 12 | Sync := new(SyncFavorite) 13 | err := Sync.SyncFavoriteCreate(ctx) 14 | if err != nil { 15 | return 16 | } 17 | } 18 | 19 | func FavoriteDeleteSync(ctx context.Context) { 20 | Sync := new(SyncFavorite) 21 | err := Sync.SyncFavoriteDelete(ctx) 22 | if err != nil { 23 | return 24 | } 25 | } 26 | 27 | type SyncFavorite struct { 28 | } 29 | 30 | func (s *SyncFavorite) SyncFavoriteCreate(ctx context.Context) error { 31 | RabbitMQName := "favorite-create-queue" 32 | msg, err := mq.ConsumeMessage(ctx, RabbitMQName) 33 | if err != nil { 34 | return err 35 | } 36 | var forever chan struct{} 37 | go func() { 38 | for d := range msg { 39 | // 落库 40 | var req *favoritePb.FavoriteActionRequest 41 | err = json.Unmarshal(d.Body, &req) 42 | if err != nil { 43 | return 44 | } 45 | err = service.FavoriteMQ2DB(ctx, req) 46 | if err != nil { 47 | return 48 | } 49 | d.Ack(false) 50 | } 51 | }() 52 | <-forever 53 | return nil 54 | } 55 | 56 | func (s *SyncFavorite) SyncFavoriteDelete(ctx context.Context) error { 57 | RabbitMQName := "favorite-delete-queue" 58 | msg, err := mq.ConsumeMessage(ctx, RabbitMQName) 59 | if err != nil { 60 | return err 61 | } 62 | var forever chan struct{} 63 | go func() { 64 | for d := range msg { 65 | // 落库 66 | var req *favoritePb.FavoriteActionRequest 67 | err = json.Unmarshal(d.Body, &req) 68 | if err != nil { 69 | return 70 | } 71 | err = service.FavoriteMQ2DB(ctx, req) 72 | if err != nil { 73 | return 74 | } 75 | d.Ack(false) 76 | } 77 | }() 78 | <-forever 79 | return nil 80 | } 81 | -------------------------------------------------------------------------------- /app/favorite/service/favorite.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "ByteRhythm/app/favorite/dao" 5 | "ByteRhythm/consts" 6 | "ByteRhythm/idl/favorite/favoritePb" 7 | "ByteRhythm/model" 8 | "ByteRhythm/mq" 9 | "ByteRhythm/util" 10 | "context" 11 | "encoding/json" 12 | "fmt" 13 | "sync" 14 | "time" 15 | 16 | "github.com/go-redis/redis/v8" 17 | ) 18 | 19 | type FavoriteSrv struct { 20 | } 21 | 22 | var FavoriteSrvIns *FavoriteSrv 23 | var FavoriteSrvOnce sync.Once 24 | 25 | // GetFavoriteSrv 懒汉式的单例模式 lazy-loading --> 懒汉式 26 | func GetFavoriteSrv() *FavoriteSrv { 27 | FavoriteSrvOnce.Do(func() { 28 | FavoriteSrvIns = &FavoriteSrv{} 29 | }) 30 | return FavoriteSrvIns 31 | } 32 | 33 | func (c FavoriteSrv) FavoriteAction(ctx context.Context, req *favoritePb.FavoriteActionRequest, res *favoritePb.FavoriteActionResponse) error { 34 | actionType := req.ActionType 35 | vid := req.VideoId 36 | uid, _ := util.GetUserIdFromToken(req.Token) 37 | 38 | body, _ := json.Marshal(&req) 39 | 40 | // 点赞 41 | if actionType == 1 { 42 | // 不能重复点赞 43 | isFavorite, _ := dao.NewFavoriteDao(ctx).GetIsFavoriteByUserIdAndVid(int64(uid), vid) 44 | 45 | if isFavorite { 46 | FavoriteActionResponseData(res, 1, "重复点赞") 47 | return nil 48 | } 49 | 50 | //修改redis 51 | key := fmt.Sprintf("%d", vid) 52 | redisResult, err := dao.RedisClient.Get(ctx, key).Result() 53 | if err != nil && err != redis.Nil { 54 | FavoriteActionResponseData(res, 1, "点赞失败") 55 | return err 56 | } 57 | 58 | if err != redis.Nil { // 在redis中找到了视频信息 59 | var video favoritePb.Video 60 | err = json.Unmarshal([]byte(redisResult), &video) 61 | if err != nil { 62 | FavoriteActionResponseData(res, 1, "点赞失败") 63 | return err 64 | } 65 | 66 | video.FavoriteCount += 1 67 | 68 | videoJson, _ := json.Marshal(&video) 69 | dao.RedisClient.Set(ctx, key, videoJson, time.Hour) 70 | } 71 | 72 | // 加入消息队列 73 | err = mq.SendMessage2MQ(body, consts.CreateFavorite2MQ) 74 | if err != nil { 75 | FavoriteActionResponseData(res, 1, "点赞失败") 76 | return err 77 | } 78 | } 79 | 80 | // 取消点赞 81 | if actionType == 2 { 82 | //修改redis 83 | key := fmt.Sprintf("%d", vid) 84 | redisResult, err := dao.RedisClient.Get(ctx, key).Result() 85 | if err != nil && err != redis.Nil { 86 | FavoriteActionResponseData(res, 1, "取消点赞失败") 87 | return err 88 | } 89 | 90 | if err != redis.Nil { // 在redis中找到了视频信息 91 | var video favoritePb.Video 92 | err = json.Unmarshal([]byte(redisResult), &video) 93 | if err != nil { 94 | FavoriteActionResponseData(res, 1, "取消点赞失败") 95 | return err 96 | } 97 | 98 | video.FavoriteCount -= 1 99 | 100 | videoJson, _ := json.Marshal(&video) 101 | dao.RedisClient.Set(ctx, key, videoJson, time.Hour) 102 | } 103 | 104 | // 加入消息队列 105 | err = mq.SendMessage2MQ(body, consts.DeleteFavorite2MQ) 106 | if err != nil { 107 | FavoriteActionResponseData(res, 1, "取消点赞失败") 108 | return err 109 | } 110 | } 111 | 112 | return nil 113 | } 114 | 115 | func (c FavoriteSrv) FavoriteList(ctx context.Context, req *favoritePb.FavoriteListRequest, res *favoritePb.FavoriteListResponse) error { 116 | uid := req.UserId 117 | 118 | favorites, err := dao.NewFavoriteDao(ctx).GetFavoriteListByUserId(uid) 119 | 120 | if err != nil { 121 | return err 122 | } 123 | 124 | res.StatusCode = 0 125 | res.StatusMsg = "获取喜欢列表成功" 126 | 127 | for _, favorite := range favorites { 128 | vid := favorite.VideoID 129 | res.VideoList = append(res.VideoList, BuildVideoPbModelByVid(ctx, vid)) 130 | } 131 | return nil 132 | } 133 | 134 | func BuildVideoPbModelByVid(ctx context.Context, vid int) *favoritePb.Video { 135 | FavoriteCount, _ := dao.NewFavoriteDao(ctx).GetFavoriteCount(vid) 136 | PlayUrl, _ := dao.NewFavoriteDao(ctx).GetPlayUrlByVid(vid) 137 | CoverUrl, _ := dao.NewFavoriteDao(ctx).GetCoverUrlByVid(vid) 138 | 139 | return &favoritePb.Video{ 140 | Id: int64(vid), 141 | PlayUrl: PlayUrl, 142 | CoverUrl: CoverUrl, 143 | FavoriteCount: FavoriteCount, 144 | } 145 | } 146 | 147 | func BuildUserPbModel(ctx context.Context, user *model.User, token string) *favoritePb.User { 148 | uid := int(user.ID) 149 | FollowCount, _ := dao.NewFavoriteDao(ctx).GetFollowCount(uid) 150 | FollowerCount, _ := dao.NewFavoriteDao(ctx).GetFollowerCount(uid) 151 | WorkCount, _ := dao.NewFavoriteDao(ctx).GetWorkCount(uid) 152 | FavoriteCount, _ := dao.NewFavoriteDao(ctx).GetFavoriteCount(uid) 153 | TotalFavorited, _ := dao.NewFavoriteDao(ctx).GetTotalFavorited(uid) 154 | IsFollow, _ := dao.NewFavoriteDao(ctx).GetIsFollowed(uid, token) 155 | return &favoritePb.User{ 156 | Id: int64(uid), 157 | Name: user.Username, 158 | Avatar: user.Avatar, 159 | BackgroundImage: user.BackgroundImage, 160 | Signature: user.Signature, 161 | FollowCount: FollowCount, 162 | FollowerCount: FollowerCount, 163 | WorkCount: WorkCount, 164 | FavoriteCount: FavoriteCount, 165 | TotalFavorited: TotalFavorited, 166 | IsFollow: IsFollow, 167 | } 168 | } 169 | 170 | func FavoriteActionResponseData(res *favoritePb.FavoriteActionResponse, StatusCode int32, StatusMsg string) { 171 | res.StatusCode = StatusCode 172 | res.StatusMsg = StatusMsg 173 | } 174 | 175 | func FavoriteMQ2DB(ctx context.Context, req *favoritePb.FavoriteActionRequest) error { 176 | token := req.Token 177 | videoId := req.VideoId 178 | actionType := req.ActionType 179 | 180 | // 解析token 181 | user, _ := util.GetUserFromToken(token) 182 | 183 | // 点赞 184 | if actionType == 1 { 185 | //加入redis 186 | //加入校队列 187 | 188 | favorite := model.Favorite{ 189 | UserID: int(user.ID), // uint to int 190 | VideoID: int(videoId), // int64 to int 191 | } 192 | if err := dao.NewFavoriteDao(ctx).CreateFavorite(&favorite); err != nil { 193 | return err 194 | } 195 | 196 | return nil 197 | } 198 | 199 | // 取消点赞 200 | favorite := model.Favorite{ 201 | UserID: int(user.ID), // uint to int 202 | VideoID: int(videoId), // int64 to int 203 | } 204 | 205 | if err := dao.NewFavoriteDao(ctx).DeleteFavorite(&favorite); err != nil { 206 | return err 207 | } 208 | 209 | return nil 210 | } 211 | -------------------------------------------------------------------------------- /app/gateway/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "ByteRhythm/app/gateway/router" 5 | "ByteRhythm/app/gateway/rpc" 6 | "ByteRhythm/config" 7 | "fmt" 8 | "time" 9 | 10 | "github.com/go-micro/plugins/v4/registry/etcd" 11 | "go-micro.dev/v4/registry" 12 | "go-micro.dev/v4/web" 13 | ) 14 | 15 | func main() { 16 | config.Init() 17 | rpc.InitRPC() 18 | etcdReg := etcd.NewRegistry( 19 | registry.Addrs(fmt.Sprintf("%s:%s", config.EtcdHost, config.EtcdPort)), 20 | ) 21 | 22 | // 得到一个微服务实例 23 | webService := web.NewService( 24 | web.Name("HttpService"), // 微服务名字 25 | web.Address(fmt.Sprintf("%s:%s", config.HttpHost, config.HttpPort)), 26 | web.Registry(etcdReg), // etcd注册件 27 | web.Handler(router.NewRouter()), // 路由 28 | web.RegisterTTL(time.Second*30), // 服务注册时间 29 | web.Metadata(map[string]string{"protocol": "http"}), 30 | ) 31 | 32 | _ = webService.Init() 33 | _ = webService.Run() 34 | } 35 | -------------------------------------------------------------------------------- /app/gateway/http/comment.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "ByteRhythm/app/gateway/rpc" 5 | "ByteRhythm/app/gateway/wrapper" 6 | "ByteRhythm/idl/comment/commentPb" 7 | "ByteRhythm/util" 8 | "github.com/afex/hystrix-go/hystrix" 9 | "net/http" 10 | "strconv" 11 | 12 | "github.com/gin-gonic/gin" 13 | ) 14 | 15 | func CommentActionHandler(ctx *gin.Context) { 16 | var req commentPb.CommentActionRequest 17 | req.Token = ctx.Query("token") 18 | vid, _ := strconv.Atoi(ctx.Query("video_id")) 19 | req.VideoId = int64(vid) 20 | ActionType, _ := strconv.Atoi(ctx.Query("action_type")) 21 | req.ActionType = int32(ActionType) 22 | CommentId, _ := strconv.Atoi(ctx.Query("comment_id")) 23 | req.CommentText = ctx.Query("comment_text") 24 | req.CommentId = int64(CommentId) 25 | 26 | var res *commentPb.CommentActionResponse 27 | 28 | hystrix.ConfigureCommand("CommentAction", wrapper.CommentActionFuseConfig) 29 | err := hystrix.Do("CommentAction", func() (err error) { 30 | res, err = rpc.CommentAction(ctx, &req) 31 | if err != nil { 32 | return err 33 | } 34 | return err 35 | }, func(err error) error { 36 | return err 37 | }) 38 | 39 | if err != nil { 40 | ctx.JSON(http.StatusInternalServerError, util.FailRequest(err.Error())) 41 | return 42 | } 43 | ctx.JSON(http.StatusOK, gin.H{ 44 | "status_code": res.StatusCode, 45 | "status_msg": res.StatusMsg, 46 | "comment": res.Comment, 47 | }) 48 | } 49 | 50 | func CommentListHandler(ctx *gin.Context) { 51 | var req commentPb.CommentListRequest 52 | req.Token = ctx.Query("token") 53 | vid, _ := strconv.Atoi(ctx.Query("video_id")) 54 | req.VideoId = int64(vid) 55 | 56 | var res *commentPb.CommentListResponse 57 | 58 | hystrix.ConfigureCommand("CommentList", wrapper.CommentListFuseConfig) 59 | err := hystrix.Do("CommentList", func() (err error) { 60 | res, err = rpc.CommentList(ctx, &req) 61 | if err != nil { 62 | return err 63 | } 64 | return err 65 | }, func(err error) error { 66 | return err 67 | }) 68 | 69 | if err != nil { 70 | ctx.JSON(http.StatusInternalServerError, util.FailRequest(err.Error())) 71 | return 72 | } 73 | ctx.JSON(http.StatusOK, gin.H{ 74 | "status_code": res.StatusCode, 75 | "status_msg": res.StatusMsg, 76 | "comment_list": res.CommentList, 77 | }) 78 | } 79 | -------------------------------------------------------------------------------- /app/gateway/http/favorite.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "ByteRhythm/app/gateway/rpc" 5 | "ByteRhythm/app/gateway/wrapper" 6 | "ByteRhythm/idl/favorite/favoritePb" 7 | "ByteRhythm/util" 8 | "github.com/afex/hystrix-go/hystrix" 9 | "net/http" 10 | "strconv" 11 | 12 | "github.com/gin-gonic/gin" 13 | ) 14 | 15 | func FavoriteActionHandler(ctx *gin.Context) { 16 | var req favoritePb.FavoriteActionRequest 17 | req.Token = ctx.Query("token") 18 | vid, _ := strconv.Atoi(ctx.Query("video_id")) 19 | req.VideoId = int64(vid) 20 | ActionType, _ := strconv.Atoi(ctx.Query("action_type")) 21 | req.ActionType = int32(ActionType) 22 | 23 | var res *favoritePb.FavoriteActionResponse 24 | 25 | hystrix.ConfigureCommand("FavoriteAction", wrapper.FavoriteActionFuseConfig) 26 | err := hystrix.Do("FavoriteAction", func() (err error) { 27 | res, err = rpc.FavoriteAction(ctx, &req) 28 | if err != nil { 29 | return err 30 | } 31 | return err 32 | }, func(err error) error { 33 | return err 34 | }) 35 | 36 | if err != nil { 37 | ctx.JSON(http.StatusInternalServerError, util.FailRequest(err.Error())) 38 | return 39 | } 40 | 41 | ctx.JSON(http.StatusOK, gin.H{ 42 | "status_code": res.StatusCode, 43 | "status_msg": res.StatusMsg, 44 | }) 45 | } 46 | 47 | func FavoriteListHandler(ctx *gin.Context) { 48 | var req favoritePb.FavoriteListRequest 49 | uid, _ := strconv.Atoi(ctx.Query("user_id")) 50 | req.UserId = int64(uid) 51 | req.Token = ctx.Query("token") 52 | 53 | var res *favoritePb.FavoriteListResponse 54 | 55 | hystrix.ConfigureCommand("FavoriteList", wrapper.FavoriteListFuseConfig) 56 | err := hystrix.Do("FavoriteList", func() (err error) { 57 | res, err = rpc.FavoriteList(ctx, &req) 58 | if err != nil { 59 | return err 60 | } 61 | return err 62 | }, func(err error) error { 63 | return err 64 | }) 65 | 66 | if err != nil { 67 | ctx.JSON(http.StatusInternalServerError, util.FailRequest(err.Error())) 68 | return 69 | } 70 | ctx.JSON(http.StatusOK, gin.H{ 71 | "status_code": res.StatusCode, 72 | "status_msg": res.StatusMsg, 73 | "video_list": res.VideoList, 74 | }) 75 | } 76 | -------------------------------------------------------------------------------- /app/gateway/http/message.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "ByteRhythm/app/gateway/rpc" 5 | "ByteRhythm/app/gateway/wrapper" 6 | "ByteRhythm/idl/message/messagePb" 7 | "ByteRhythm/util" 8 | "github.com/afex/hystrix-go/hystrix" 9 | "github.com/gin-gonic/gin" 10 | "net/http" 11 | "strconv" 12 | ) 13 | 14 | func ActionMessageHandler(ctx *gin.Context) { 15 | var req messagePb.MessageActionRequest 16 | req.Token = ctx.Query("token") 17 | toUserId, _ := strconv.Atoi(ctx.Query("to_user_id")) 18 | req.ToUserId = int64(toUserId) 19 | actionType, _ := strconv.Atoi(ctx.Query("action_type")) 20 | req.ActionType = int32(actionType) 21 | req.Content = ctx.Query("content") 22 | var res *messagePb.MessageActionResponse 23 | hystrix.ConfigureCommand("ActionMessage", wrapper.ActionMessageFuseConfig) 24 | err := hystrix.Do("ActionMessage", func() (err error) { 25 | res, err = rpc.ActionMessage(ctx, &req) 26 | return err 27 | }, func(err error) error { 28 | return err 29 | }) 30 | if err != nil { 31 | ctx.JSON(http.StatusInternalServerError, util.FailRequest(err.Error())) 32 | return 33 | } 34 | ctx.JSON(http.StatusOK, gin.H{ 35 | "status_code": res.StatusCode, 36 | "status_msg": res.StatusMsg, 37 | }) 38 | } 39 | 40 | func ChatMessageHandler(ctx *gin.Context) { 41 | var req messagePb.MessageChatRequest 42 | req.Token = ctx.Query("token") 43 | toUserId, _ := strconv.Atoi(ctx.Query("to_user_id")) 44 | req.ToUserId = int64(toUserId) 45 | var res *messagePb.MessageChatResponse 46 | hystrix.ConfigureCommand("ChatMessage", wrapper.ChatMessageFuseConfig) 47 | err := hystrix.Do("ChatMessage", func() (err error) { 48 | res, err = rpc.ChatMessage(ctx, &req) 49 | return err 50 | }, func(err error) error { 51 | return err 52 | }) 53 | if err != nil { 54 | ctx.JSON(http.StatusInternalServerError, util.FailRequest(err.Error())) 55 | return 56 | } 57 | ctx.JSON(http.StatusOK, gin.H{ 58 | "status_code": res.StatusCode, 59 | "status_msg": res.StatusMsg, 60 | "message_list": res.MessageList, 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /app/gateway/http/relation.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "ByteRhythm/app/gateway/rpc" 5 | "ByteRhythm/app/gateway/wrapper" 6 | "ByteRhythm/idl/relation/relationPb" 7 | "ByteRhythm/util" 8 | "github.com/afex/hystrix-go/hystrix" 9 | "net/http" 10 | "strconv" 11 | 12 | "github.com/gin-gonic/gin" 13 | ) 14 | 15 | func ActionRelationHandler(ctx *gin.Context) { 16 | var req relationPb.RelationActionRequest 17 | req.Token = ctx.Query("token") 18 | toUserId, _ := strconv.Atoi(ctx.Query("to_user_id")) 19 | req.ToUserId = int64(toUserId) 20 | actionType, _ := strconv.Atoi(ctx.Query("action_type")) 21 | req.ActionType = int32(actionType) 22 | var res *relationPb.RelationActionResponse 23 | hystrix.ConfigureCommand("ActionRelation", wrapper.ActionRelationFuseConfig) 24 | err := hystrix.Do("ActionRelation", func() (err error) { 25 | res, err = rpc.ActionRelation(ctx, &req) 26 | return err 27 | }, func(err error) error { 28 | return err 29 | }) 30 | if err != nil { 31 | ctx.JSON(http.StatusInternalServerError, util.FailRequest(err.Error())) 32 | return 33 | } 34 | ctx.JSON(http.StatusOK, gin.H{ 35 | "status_code": res.StatusCode, 36 | "status_msg": res.StatusMsg, 37 | }) 38 | } 39 | 40 | func ListFollowRelationHandler(ctx *gin.Context) { 41 | var req relationPb.RelationFollowRequest 42 | uid, _ := strconv.Atoi(ctx.Query("user_id")) 43 | req.UserId = int64(uid) 44 | req.Token = ctx.Query("token") 45 | var res *relationPb.RelationFollowResponse 46 | hystrix.ConfigureCommand("ListFollowRelation", wrapper.ListFollowRelationFuseConfig) 47 | err := hystrix.Do("ListFollowRelation", func() (err error) { 48 | res, err = rpc.ListFollowRelation(ctx, &req) 49 | return err 50 | }, func(err error) error { 51 | return err 52 | }) 53 | if err != nil { 54 | ctx.JSON(http.StatusInternalServerError, util.FailRequest(err.Error())) 55 | return 56 | } 57 | ctx.JSON(http.StatusOK, gin.H{ 58 | "status_code": res.StatusCode, 59 | "status_msg": res.StatusMsg, 60 | "user_list": res.UserList, 61 | }) 62 | } 63 | 64 | func ListFollowerRelationHandler(ctx *gin.Context) { 65 | var req relationPb.RelationFollowerRequest 66 | uid, _ := strconv.Atoi(ctx.Query("user_id")) 67 | req.UserId = int64(uid) 68 | req.Token = ctx.Query("token") 69 | var res *relationPb.RelationFollowerResponse 70 | hystrix.ConfigureCommand("ListFollowerRelation", wrapper.ListFollowerRelationFuseConfig) 71 | err := hystrix.Do("ListFollowerRelation", func() (err error) { 72 | res, err = rpc.ListFollowerRelation(ctx, &req) 73 | return err 74 | }, func(err error) error { 75 | return err 76 | }) 77 | if err != nil { 78 | ctx.JSON(http.StatusInternalServerError, util.FailRequest(err.Error())) 79 | return 80 | } 81 | ctx.JSON(http.StatusOK, gin.H{ 82 | "status_code": res.StatusCode, 83 | "status_msg": res.StatusMsg, 84 | "user_list": res.UserList, 85 | }) 86 | } 87 | 88 | func ListFriendRelationHandler(ctx *gin.Context) { 89 | var req relationPb.RelationFriendRequest 90 | uid, _ := strconv.Atoi(ctx.Query("user_id")) 91 | req.UserId = int64(uid) 92 | req.Token = ctx.Query("token") 93 | var res *relationPb.RelationFriendResponse 94 | hystrix.ConfigureCommand("ListFriendRelation", wrapper.ListFriendRelationFuseConfig) 95 | err := hystrix.Do("ListFriendRelation", func() (err error) { 96 | res, err = rpc.ListFriendRelation(ctx, &req) 97 | return err 98 | }, func(err error) error { 99 | return err 100 | }) 101 | if err != nil { 102 | ctx.JSON(http.StatusInternalServerError, util.FailRequest(err.Error())) 103 | return 104 | } 105 | ctx.JSON(http.StatusOK, gin.H{ 106 | "status_code": res.StatusCode, 107 | "status_msg": res.StatusMsg, 108 | "user_list": res.UserList, 109 | }) 110 | } 111 | -------------------------------------------------------------------------------- /app/gateway/http/user.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "ByteRhythm/app/gateway/rpc" 5 | "ByteRhythm/app/gateway/wrapper" 6 | "ByteRhythm/idl/user/userPb" 7 | "ByteRhythm/util" 8 | "net/http" 9 | "strconv" 10 | 11 | "github.com/afex/hystrix-go/hystrix" 12 | 13 | "github.com/gin-gonic/gin" 14 | ) 15 | 16 | func RegisterHandler(ctx *gin.Context) { 17 | var req userPb.UserRequest 18 | req.Username = ctx.Query("username") 19 | req.Password = ctx.Query("password") 20 | var res *userPb.UserResponse 21 | hystrix.ConfigureCommand("Register", wrapper.RegisterFuseConfig) 22 | err := hystrix.Do("Register", func() (err error) { 23 | res, err = rpc.Register(ctx, &req) 24 | return err 25 | }, func(err error) error { 26 | return err 27 | }) 28 | if err != nil { 29 | ctx.JSON(http.StatusInternalServerError, util.FailRequest(err.Error())) 30 | return 31 | } 32 | ctx.JSON(http.StatusOK, gin.H{ 33 | "status_code": res.StatusCode, 34 | "status_msg": res.StatusMsg, 35 | "user_id": res.UserId, 36 | "token": res.Token, 37 | }) 38 | } 39 | 40 | func LoginHandler(ctx *gin.Context) { 41 | var req userPb.UserRequest 42 | req.Username = ctx.Query("username") 43 | req.Password = ctx.Query("password") 44 | var res *userPb.UserResponse 45 | hystrix.ConfigureCommand("Login", wrapper.LoginFuseConfig) 46 | err := hystrix.Do("Login", func() (err error) { 47 | res, err = rpc.Login(ctx, &req) 48 | return err 49 | }, func(err error) error { 50 | return err 51 | }) 52 | if err != nil { 53 | ctx.JSON(http.StatusInternalServerError, util.FailRequest(err.Error())) 54 | return 55 | } 56 | ctx.JSON(http.StatusOK, gin.H{ 57 | "status_code": res.StatusCode, 58 | "status_msg": res.StatusMsg, 59 | "user_id": res.UserId, 60 | "token": res.Token, 61 | }) 62 | } 63 | 64 | func UserInfoHandler(ctx *gin.Context) { 65 | var req userPb.UserInfoRequest 66 | uid, _ := strconv.Atoi(ctx.Query("user_id")) 67 | req.UserId = int64(uid) 68 | req.Token = ctx.Query("token") 69 | var res *userPb.UserInfoResponse 70 | hystrix.ConfigureCommand("UserInfo", wrapper.UserInfoFuseConfig) 71 | err := hystrix.Do("UserInfo", func() (err error) { 72 | res, err = rpc.UserInfo(ctx, &req) 73 | return err 74 | }, func(err error) error { 75 | return err 76 | }) 77 | if err != nil { 78 | ctx.JSON(http.StatusInternalServerError, util.FailRequest(err.Error())) 79 | return 80 | } 81 | ctx.JSON(http.StatusOK, gin.H{ 82 | "status_code": res.StatusCode, 83 | "status_msg": res.StatusMsg, 84 | "user": res.User, 85 | }) 86 | } 87 | -------------------------------------------------------------------------------- /app/gateway/http/video.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "ByteRhythm/app/gateway/rpc" 5 | "ByteRhythm/app/gateway/wrapper" 6 | "ByteRhythm/idl/video/videoPb" 7 | "ByteRhythm/util" 8 | "bytes" 9 | "io" 10 | "net/http" 11 | "strconv" 12 | 13 | "github.com/afex/hystrix-go/hystrix" 14 | 15 | "github.com/gin-gonic/gin" 16 | ) 17 | 18 | func FeedHandler(ctx *gin.Context) { 19 | var req videoPb.FeedRequest 20 | LatestTime, _ := strconv.Atoi(ctx.Query("latest_time")) 21 | req.LatestTime = int64(LatestTime) 22 | req.Token = ctx.Query("token") 23 | var res *videoPb.FeedResponse 24 | hystrix.ConfigureCommand("Feed", wrapper.FeedFuseConfig) 25 | err := hystrix.Do("Feed", func() (err error) { 26 | res, err = rpc.Feed(ctx, &req) 27 | return err 28 | }, func(err error) error { 29 | //降级处理 30 | wrapper.DefaultFeed(res) 31 | return err 32 | }) 33 | if err != nil { 34 | ctx.JSON(http.StatusInternalServerError, util.FailRequest(err.Error())) 35 | return 36 | } 37 | ctx.JSON(http.StatusOK, gin.H{ 38 | "status_code": res.StatusCode, 39 | "status_msg": res.StatusMsg, 40 | "video_list": res.VideoList, 41 | }) 42 | } 43 | 44 | func PublishHandler(ctx *gin.Context) { 45 | var req videoPb.PublishRequest 46 | req.Title = ctx.PostForm("title") 47 | req.Token = ctx.PostForm("token") 48 | //将获得的文件转为[]byte类型 49 | data, err := ctx.FormFile("data") 50 | if err != nil { 51 | ctx.JSON(http.StatusInternalServerError, util.FailRequest(err.Error())) 52 | } 53 | file, err := data.Open() 54 | defer file.Close() 55 | if err != nil { 56 | ctx.JSON(http.StatusInternalServerError, util.FailRequest(err.Error())) 57 | } 58 | // 使用缓冲区逐块读取文件内容并写入 req.Data 59 | var buffer bytes.Buffer 60 | _, err = io.Copy(&buffer, file) 61 | if err != nil { 62 | ctx.JSON(http.StatusInternalServerError, util.FailRequest(err.Error())) 63 | return 64 | } 65 | req.Data = buffer.Bytes() 66 | var res *videoPb.PublishResponse 67 | hystrix.ConfigureCommand("Publish", wrapper.PublishFuseConfig) 68 | err = hystrix.Do("Publish", func() (err error) { 69 | res, err = rpc.Publish(ctx, &req) 70 | return err 71 | }, func(err error) error { 72 | return err 73 | }) 74 | if err != nil { 75 | ctx.JSON(http.StatusInternalServerError, util.FailRequest(err.Error())) 76 | return 77 | } 78 | ctx.JSON(http.StatusOK, gin.H{ 79 | "status_code": res.StatusCode, 80 | "status_msg": res.StatusMsg, 81 | }) 82 | } 83 | 84 | func PublishListHandler(ctx *gin.Context) { 85 | var req videoPb.PublishListRequest 86 | uid, _ := strconv.Atoi(ctx.Query("user_id")) 87 | req.UserId = int64(uid) 88 | req.Token = ctx.Query("token") 89 | var res *videoPb.PublishListResponse 90 | hystrix.ConfigureCommand("PublishList", wrapper.PublishListFuseConfig) 91 | err := hystrix.Do("PublishList", func() (err error) { 92 | res, err = rpc.PublishList(ctx, &req) 93 | return err 94 | }, func(err error) error { 95 | return err 96 | }) 97 | if err != nil { 98 | ctx.JSON(http.StatusInternalServerError, util.FailRequest(err.Error())) 99 | return 100 | } 101 | ctx.JSON(http.StatusOK, gin.H{ 102 | "status_code": res.StatusCode, 103 | "status_msg": res.StatusMsg, 104 | "video_list": res.VideoList, 105 | }) 106 | } 107 | -------------------------------------------------------------------------------- /app/gateway/middleware/jaeger.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/opentracing/opentracing-go" 8 | "go-micro.dev/v4/metadata" 9 | ) 10 | 11 | func Jaeger(tracer opentracing.Tracer) gin.HandlerFunc { 12 | return func(ctx *gin.Context) { 13 | var md = make(metadata.Metadata, 1) 14 | opName := ctx.Request.URL.Path + "-" + ctx.Request.Method 15 | parentSpan := tracer.StartSpan(opName) 16 | defer parentSpan.Finish() 17 | injectErr := tracer.Inject(parentSpan.Context(), opentracing.TextMap, opentracing.TextMapCarrier(md)) 18 | if injectErr != nil { 19 | log.Fatalf("%s: Couldn't inject metadata", injectErr) 20 | } 21 | newCtx := metadata.NewContext(ctx.Request.Context(), md) 22 | ctx.Request = ctx.Request.WithContext(newCtx) 23 | ctx.Next() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/gateway/middleware/jwt.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "ByteRhythm/util" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | type Request struct { 11 | Token string `json:"token" form:"token" binding:"required"` 12 | } 13 | 14 | func JWT() gin.HandlerFunc { 15 | return func(c *gin.Context) { 16 | if c.Request.URL.Path != "/douyin/user/register" && c.Request.URL.Path != "/douyin/user/login" { 17 | var request Request 18 | c.ShouldBind(&request) 19 | token := request.Token 20 | if token != "" { 21 | if err := util.ValidateToken(token); err != nil { 22 | if c.Request.URL.Path != "/douyin/feed" { 23 | c.JSON(http.StatusForbidden, util.FailRequest("token验证失败,请重新登录")) 24 | c.Abort() 25 | } 26 | } 27 | } 28 | } 29 | c.Next() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/gateway/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "ByteRhythm/app/gateway/http" 5 | "ByteRhythm/app/gateway/middleware" 6 | "ByteRhythm/app/gateway/wrapper" 7 | "ByteRhythm/config" 8 | "fmt" 9 | 10 | "github.com/gin-contrib/cors" 11 | "github.com/gin-gonic/gin" 12 | "go-micro.dev/v4/logger" 13 | ) 14 | 15 | func NewRouter() *gin.Engine { 16 | config.Init() 17 | r := gin.Default() 18 | jaeger, closer, err := wrapper.InitJaeger("HttpService", fmt.Sprintf("%s:%s", config.JaegerHost, config.JaegerPort)) 19 | defer closer.Close() 20 | if err != nil { 21 | logger.Info("HttpService init jaeger failed, err:", err) 22 | } 23 | r.Use( 24 | middleware.JWT(), 25 | cors.Default(), 26 | middleware.Jaeger(jaeger), 27 | ) 28 | v1 := r.Group("/douyin") 29 | { 30 | v1.GET("/feed", http.FeedHandler) 31 | 32 | v2 := v1.Group("/user") 33 | { 34 | v2.POST("/register/", http.RegisterHandler) 35 | v2.POST("/login/", http.LoginHandler) 36 | v2.GET("/", http.UserInfoHandler) 37 | } 38 | 39 | v2 = v1.Group("/publish") 40 | { 41 | v2.POST("/action/", http.PublishHandler) 42 | v2.GET("/list/", http.PublishListHandler) 43 | } 44 | 45 | v2 = v1.Group("/relation") 46 | { 47 | v2.POST("/action/", http.ActionRelationHandler) 48 | v2.GET("/follow/list/", http.ListFollowRelationHandler) 49 | v2.GET("/follower/list/", http.ListFollowerRelationHandler) 50 | v2.GET("/friend/list/", http.ListFriendRelationHandler) 51 | } 52 | 53 | v2 = v1.Group("/message") 54 | { 55 | v2.POST("/action/", http.ActionMessageHandler) 56 | v2.GET("/chat/", http.ChatMessageHandler) 57 | } 58 | 59 | v2 = v1.Group("/comment") 60 | { 61 | v2.POST("/action/", http.CommentActionHandler) 62 | v2.GET("/list/", http.CommentListHandler) 63 | } 64 | 65 | v2 = v1.Group("/favorite") 66 | { 67 | v2.POST("/action/", http.FavoriteActionHandler) 68 | v2.GET("/list/", http.FavoriteListHandler) 69 | } 70 | 71 | } 72 | return r 73 | 74 | } 75 | -------------------------------------------------------------------------------- /app/gateway/rpc/comment.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "ByteRhythm/idl/comment/commentPb" 5 | "context" 6 | ) 7 | 8 | func CommentAction(ctx context.Context, req *commentPb.CommentActionRequest) (res *commentPb.CommentActionResponse, err error) { 9 | res, err = CommentService.CommentAction(ctx, req) 10 | if err != nil { 11 | return 12 | } 13 | return 14 | } 15 | 16 | func CommentList(ctx context.Context, req *commentPb.CommentListRequest) (res *commentPb.CommentListResponse, err error) { 17 | res, err = CommentService.CommentList(ctx, req) 18 | if err != nil { 19 | return 20 | } 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /app/gateway/rpc/favorite.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "ByteRhythm/idl/favorite/favoritePb" 5 | "context" 6 | ) 7 | 8 | func FavoriteAction(ctx context.Context, req *favoritePb.FavoriteActionRequest) (res *favoritePb.FavoriteActionResponse, err error) { 9 | res, err = FavoriteService.FavoriteAction(ctx, req) 10 | if err != nil { 11 | return 12 | } 13 | return 14 | } 15 | 16 | func FavoriteList(ctx context.Context, req *favoritePb.FavoriteListRequest) (res *favoritePb.FavoriteListResponse, err error) { 17 | res, err = FavoriteService.FavoriteList(ctx, req) 18 | if err != nil { 19 | return 20 | } 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /app/gateway/rpc/init.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "ByteRhythm/idl/comment/commentPb" 5 | "ByteRhythm/idl/favorite/favoritePb" 6 | "ByteRhythm/idl/message/messagePb" 7 | "ByteRhythm/idl/relation/relationPb" 8 | "ByteRhythm/idl/user/userPb" 9 | "ByteRhythm/idl/video/videoPb" 10 | 11 | "go-micro.dev/v4" 12 | ) 13 | 14 | var ( 15 | UserService userPb.UserService 16 | VideoService videoPb.VideoService 17 | MessageService messagePb.MessageService 18 | RelationService relationPb.RelationService 19 | CommentService commentPb.CommentService 20 | FavoriteService favoritePb.FavoriteService 21 | ) 22 | 23 | func InitRPC() { 24 | UserMicroService := micro.NewService(micro.Name("UserService.client")) 25 | userService := userPb.NewUserService("UserService", UserMicroService.Client()) 26 | UserService = userService 27 | 28 | VideoMicroService := micro.NewService(micro.Name("VideoService.client")) 29 | videoService := videoPb.NewVideoService("VideoService", VideoMicroService.Client()) 30 | VideoService = videoService 31 | 32 | MessageMicroService := micro.NewService(micro.Name("MessageService.client")) 33 | messageService := messagePb.NewMessageService("MessageService", MessageMicroService.Client()) 34 | MessageService = messageService 35 | 36 | RelationMicroService := micro.NewService(micro.Name("RelationService.client")) 37 | relationService := relationPb.NewRelationService("RelationService", RelationMicroService.Client()) 38 | RelationService = relationService 39 | 40 | CommentMicroService := micro.NewService(micro.Name("CommentService.client")) 41 | commentService := commentPb.NewCommentService("CommentService", CommentMicroService.Client()) 42 | CommentService = commentService 43 | 44 | FavoriteMicroService := micro.NewService(micro.Name("FavoriteService.client")) 45 | favoriteService := favoritePb.NewFavoriteService("FavoriteService", FavoriteMicroService.Client()) 46 | FavoriteService = favoriteService 47 | } 48 | -------------------------------------------------------------------------------- /app/gateway/rpc/message.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "ByteRhythm/idl/message/messagePb" 5 | "context" 6 | ) 7 | 8 | func ActionMessage(ctx context.Context, req *messagePb.MessageActionRequest) (res *messagePb.MessageActionResponse, err error) { 9 | res, err = MessageService.ActionMessage(ctx, req) 10 | if err != nil { 11 | return 12 | } 13 | return 14 | } 15 | 16 | func ChatMessage(ctx context.Context, req *messagePb.MessageChatRequest) (res *messagePb.MessageChatResponse, err error) { 17 | res, err = MessageService.ChatMessage(ctx, req) 18 | if err != nil { 19 | return 20 | } 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /app/gateway/rpc/relation.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "ByteRhythm/idl/relation/relationPb" 5 | "context" 6 | ) 7 | 8 | func ActionRelation(ctx context.Context, req *relationPb.RelationActionRequest) (res *relationPb.RelationActionResponse, err error) { 9 | res, err = RelationService.ActionRelation(ctx, req) 10 | if err != nil { 11 | return 12 | } 13 | return 14 | } 15 | 16 | func ListFollowRelation(ctx context.Context, req *relationPb.RelationFollowRequest) (res *relationPb.RelationFollowResponse, err error) { 17 | res, err = RelationService.ListFollowRelation(ctx, req) 18 | if err != nil { 19 | return 20 | } 21 | return 22 | } 23 | 24 | func ListFollowerRelation(ctx context.Context, req *relationPb.RelationFollowerRequest) (res *relationPb.RelationFollowerResponse, err error) { 25 | res, err = RelationService.ListFollowerRelation(ctx, req) 26 | if err != nil { 27 | return 28 | } 29 | return 30 | } 31 | 32 | func ListFriendRelation(ctx context.Context, req *relationPb.RelationFriendRequest) (res *relationPb.RelationFriendResponse, err error) { 33 | res, err = RelationService.ListFriendRelation(ctx, req) 34 | if err != nil { 35 | return 36 | } 37 | return 38 | } 39 | -------------------------------------------------------------------------------- /app/gateway/rpc/user.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "ByteRhythm/idl/user/userPb" 5 | "context" 6 | ) 7 | 8 | func Login(ctx context.Context, req *userPb.UserRequest) (res *userPb.UserResponse, err error) { 9 | res, err = UserService.Login(ctx, req) 10 | if err != nil { 11 | return 12 | } 13 | return 14 | } 15 | 16 | func Register(ctx context.Context, req *userPb.UserRequest) (res *userPb.UserResponse, err error) { 17 | res, err = UserService.Register(ctx, req) 18 | if err != nil { 19 | return 20 | } 21 | return 22 | } 23 | 24 | func UserInfo(ctx context.Context, req *userPb.UserInfoRequest) (res *userPb.UserInfoResponse, err error) { 25 | res, err = UserService.UserInfo(ctx, req) 26 | if err != nil { 27 | return 28 | } 29 | return 30 | } 31 | -------------------------------------------------------------------------------- /app/gateway/rpc/video.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "ByteRhythm/idl/video/videoPb" 5 | "context" 6 | ) 7 | 8 | func Feed(ctx context.Context, req *videoPb.FeedRequest) (res *videoPb.FeedResponse, err error) { 9 | res, err = VideoService.Feed(ctx, req) 10 | if err != nil { 11 | return 12 | } 13 | return 14 | 15 | } 16 | 17 | func Publish(ctx context.Context, req *videoPb.PublishRequest) (res *videoPb.PublishResponse, err error) { 18 | res, err = VideoService.Publish(ctx, req) 19 | if err != nil { 20 | return 21 | } 22 | return 23 | } 24 | 25 | func PublishList(ctx context.Context, req *videoPb.PublishListRequest) (res *videoPb.PublishListResponse, err error) { 26 | res, err = VideoService.PublishList(ctx, req) 27 | if err != nil { 28 | return 29 | } 30 | return 31 | } 32 | -------------------------------------------------------------------------------- /app/gateway/wrapper/comment.go: -------------------------------------------------------------------------------- 1 | package wrapper 2 | 3 | import "github.com/afex/hystrix-go/hystrix" 4 | 5 | var CommentActionFuseConfig = hystrix.CommandConfig{ 6 | Timeout: 1000, 7 | RequestVolumeThreshold: 5000, // 10秒内的请求量,默认值是20,如果超过20那么就判断是否熔断 8 | ErrorPercentThreshold: 50, // 错误百分比,当错误超过百分比时,直接进行降级处理,直至熔断器再次 开启,默认50% 9 | SleepWindow: 5000, // 过多长时间,熔断器再次检测是否开启,单位毫秒ms(默认5秒) 10 | MaxConcurrentRequests: 50000, 11 | } 12 | 13 | var CommentListFuseConfig = hystrix.CommandConfig{ 14 | Timeout: 1000, 15 | RequestVolumeThreshold: 5000, // 熔断器请求阈值,默认20,意思是有20个请求才能进行错误百分比计算 16 | ErrorPercentThreshold: 50, // 错误百分比,当错误超过百分比时,直接进行降级处理,直至熔断器再次 开启,默认50% 17 | SleepWindow: 5000, // 过多长时间,熔断器再次检测是否开启,单位毫秒ms(默认5秒) 18 | MaxConcurrentRequests: 50000, 19 | } 20 | -------------------------------------------------------------------------------- /app/gateway/wrapper/favorite.go: -------------------------------------------------------------------------------- 1 | package wrapper 2 | 3 | import "github.com/afex/hystrix-go/hystrix" 4 | 5 | var FavoriteActionFuseConfig = hystrix.CommandConfig{ 6 | Timeout: 1000, 7 | RequestVolumeThreshold: 5000, // 10秒内的请求量,默认值是20,如果超过20那么就判断是否熔断 8 | ErrorPercentThreshold: 50, // 错误百分比,当错误超过百分比时,直接进行降级处理,直至熔断器再次 开启,默认50% 9 | SleepWindow: 5000, // 过多长时间,熔断器再次检测是否开启,单位毫秒ms(默认5秒) 10 | MaxConcurrentRequests: 50000, 11 | } 12 | 13 | var FavoriteListFuseConfig = hystrix.CommandConfig{ 14 | Timeout: 1000, 15 | RequestVolumeThreshold: 5000, // 熔断器请求阈值,默认20,意思是有20个请求才能进行错误百分比计算 16 | ErrorPercentThreshold: 50, // 错误百分比,当错误超过百分比时,直接进行降级处理,直至熔断器再次 开启,默认50% 17 | SleepWindow: 5000, // 过多长时间,熔断器再次检测是否开启,单位毫秒ms(默认5秒) 18 | MaxConcurrentRequests: 50000, 19 | } 20 | -------------------------------------------------------------------------------- /app/gateway/wrapper/jaeger.go: -------------------------------------------------------------------------------- 1 | package wrapper 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "time" 7 | 8 | "github.com/opentracing/opentracing-go" 9 | "github.com/uber/jaeger-client-go" 10 | "github.com/uber/jaeger-client-go/config" 11 | ) 12 | 13 | var Tracer opentracing.Tracer 14 | 15 | func InitJaeger(serviceName string, jaegerHostPort string) (opentracing.Tracer, io.Closer, error) { 16 | cfg := config.Configuration{ 17 | ServiceName: serviceName, 18 | Sampler: &config.SamplerConfig{ 19 | Type: jaeger.SamplerTypeConst, 20 | Param: 1, 21 | }, 22 | Reporter: &config.ReporterConfig{ 23 | LogSpans: true, 24 | BufferFlushInterval: 1 * time.Second, 25 | LocalAgentHostPort: jaegerHostPort, 26 | }, 27 | } 28 | var closer io.Closer 29 | var err error 30 | Tracer, closer, err = cfg.NewTracer( 31 | config.Logger(jaeger.StdLogger), 32 | ) 33 | if err != nil { 34 | panic(fmt.Sprintf("ERROR: cannot init Jaeger: %v\n", err)) 35 | } 36 | opentracing.SetGlobalTracer(Tracer) 37 | return Tracer, closer, err 38 | } 39 | -------------------------------------------------------------------------------- /app/gateway/wrapper/message.go: -------------------------------------------------------------------------------- 1 | package wrapper 2 | 3 | import "github.com/afex/hystrix-go/hystrix" 4 | 5 | var ActionMessageFuseConfig = hystrix.CommandConfig{ 6 | Timeout: 1000, 7 | RequestVolumeThreshold: 5000, // 10秒内的请求量,默认值是20,如果超过20那么就判断是否熔断 8 | ErrorPercentThreshold: 50, // 错误百分比,当错误超过百分比时,直接进行降级处理,直至熔断器再次 开启,默认50% 9 | SleepWindow: 5000, // 过多长时间,熔断器再次检测是否开启,单位毫秒ms(默认5秒) 10 | MaxConcurrentRequests: 50000, 11 | } 12 | 13 | var ChatMessageFuseConfig = hystrix.CommandConfig{ 14 | Timeout: 1000, 15 | RequestVolumeThreshold: 5000, // 熔断器请求阈值,默认20,意思是有20个请求才能进行错误百分比计算 16 | ErrorPercentThreshold: 50, // 错误百分比,当错误超过百分比时,直接进行降级处理,直至熔断器再次 开启,默认50% 17 | SleepWindow: 5000, // 过多长时间,熔断器再次检测是否开启,单位毫秒ms(默认5秒) 18 | MaxConcurrentRequests: 50000, 19 | } 20 | -------------------------------------------------------------------------------- /app/gateway/wrapper/relation.go: -------------------------------------------------------------------------------- 1 | package wrapper 2 | 3 | import "github.com/afex/hystrix-go/hystrix" 4 | 5 | var ActionRelationFuseConfig = hystrix.CommandConfig{ 6 | Timeout: 1000, 7 | RequestVolumeThreshold: 5000, // 10秒内的请求量,默认值是20,如果超过20那么就判断是否熔断 8 | ErrorPercentThreshold: 50, // 错误百分比,当错误超过百分比时,直接进行降级处理,直至熔断器再次 开启,默认50% 9 | SleepWindow: 5000, // 过多长时间,熔断器再次检测是否开启,单位毫秒ms(默认5秒) 10 | MaxConcurrentRequests: 50000, 11 | } 12 | 13 | var ListFollowRelationFuseConfig = hystrix.CommandConfig{ 14 | Timeout: 1000, 15 | RequestVolumeThreshold: 5000, // 熔断器请求阈值,默认20,意思是有20个请求才能进行错误百分比计算 16 | ErrorPercentThreshold: 50, // 错误百分比,当错误超过百分比时,直接进行降级处理,直至熔断器再次 开启,默认50% 17 | SleepWindow: 5000, // 过多长时间,熔断器再次检测是否开启,单位毫秒ms(默认5秒) 18 | MaxConcurrentRequests: 50000, 19 | } 20 | 21 | var ListFollowerRelationFuseConfig = hystrix.CommandConfig{ 22 | Timeout: 1000, 23 | RequestVolumeThreshold: 5000, // 熔断器请求阈值,默认20,意思是有20个请求才能进行错误百分比计算 24 | ErrorPercentThreshold: 50, // 错误百分比,当错误超过百分比时,直接进行降级处理,直至熔断器再次 开启,默认50% 25 | SleepWindow: 5000, // 过多长时间,熔断器再次检测是否开启,单位毫秒ms(默认5秒) 26 | MaxConcurrentRequests: 50000, 27 | } 28 | 29 | var ListFriendRelationFuseConfig = hystrix.CommandConfig{ 30 | Timeout: 1000, 31 | RequestVolumeThreshold: 5000, // 熔断器请求阈值,默认20,意思是有20个请求才能进行错误百分比计算 32 | ErrorPercentThreshold: 50, // 错误百分比,当错误超过百分比时,直接进行降级处理,直至熔断器再次 开启,默认50% 33 | SleepWindow: 5000, // 过多长时间,熔断器再次检测是否开启,单位毫秒ms(默认5秒) 34 | MaxConcurrentRequests: 50000, 35 | } 36 | -------------------------------------------------------------------------------- /app/gateway/wrapper/user.go: -------------------------------------------------------------------------------- 1 | package wrapper 2 | 3 | import "github.com/afex/hystrix-go/hystrix" 4 | 5 | var RegisterFuseConfig = hystrix.CommandConfig{ 6 | Timeout: 1000, 7 | RequestVolumeThreshold: 5000, // 10秒内的请求量,默认值是20,如果超过20那么就判断是否熔断 8 | ErrorPercentThreshold: 50, // 错误百分比,当错误超过百分比时,直接进行降级处理,直至熔断器再次 开启,默认50% 9 | SleepWindow: 5000, // 过多长时间,熔断器再次检测是否开启,单位毫秒ms(默认5秒) 10 | MaxConcurrentRequests: 50000, 11 | } 12 | 13 | var LoginFuseConfig = hystrix.CommandConfig{ 14 | Timeout: 1000, 15 | RequestVolumeThreshold: 5000, // 熔断器请求阈值,默认20,意思是有20个请求才能进行错误百分比计算 16 | ErrorPercentThreshold: 50, // 错误百分比,当错误超过百分比时,直接进行降级处理,直至熔断器再次 开启,默认50% 17 | SleepWindow: 5000, // 过多长时间,熔断器再次检测是否开启,单位毫秒ms(默认5秒) 18 | MaxConcurrentRequests: 50000, 19 | } 20 | var UserInfoFuseConfig = hystrix.CommandConfig{ 21 | Timeout: 1000, 22 | RequestVolumeThreshold: 5000, // 熔断器请求阈值,默认20,意思是有20个请求才能进行错误百分比计算 23 | ErrorPercentThreshold: 50, // 错误百分比,当错误超过百分比时,直接进行降级处理,直至熔断器再次 开启,默认50% 24 | SleepWindow: 5000, // 过多长时间,熔断器再次检测是否开启,单位毫秒ms(默认5秒) 25 | MaxConcurrentRequests: 50000, 26 | } 27 | -------------------------------------------------------------------------------- /app/gateway/wrapper/video.go: -------------------------------------------------------------------------------- 1 | package wrapper 2 | 3 | import ( 4 | "ByteRhythm/idl/video/videoPb" 5 | "strconv" 6 | 7 | "github.com/afex/hystrix-go/hystrix" 8 | ) 9 | 10 | var FeedFuseConfig = hystrix.CommandConfig{ 11 | Timeout: 1000, 12 | RequestVolumeThreshold: 5000, // 熔断器请求阈值,默认20,意思是有20个请求才能进行错误百分比计算 13 | ErrorPercentThreshold: 50, // 错误百分比,当错误超过百分比时,直接进行降级处理,直至熔断器再次 开启,默认50% 14 | SleepWindow: 5000, // 过多长时间,熔断器再次检测是否开启,单位毫秒ms(默认5秒) 15 | MaxConcurrentRequests: 50000, 16 | } 17 | 18 | var PublishFuseConfig = hystrix.CommandConfig{ 19 | Timeout: 5000, 20 | RequestVolumeThreshold: 5000, // 熔断器请求阈值,默认20,意思是有20个请求才能进行错误百分比计算 21 | ErrorPercentThreshold: 50, // 错误百分比,当错误超过百分比时,直接进行降级处理,直至熔断器再次 开启,默认50% 22 | SleepWindow: 5000, // 过多长时间,熔断器再次检测是否开启,单位毫秒ms(默认5秒) 23 | MaxConcurrentRequests: 50000, 24 | } 25 | var PublishListFuseConfig = hystrix.CommandConfig{ 26 | Timeout: 1000, 27 | RequestVolumeThreshold: 5000, // 熔断器请求阈值,默认20,意思是有20个请求才能进行错误百分比计算 28 | ErrorPercentThreshold: 50, // 错误百分比,当错误超过百分比时,直接进行降级处理,直至熔断器再次 开启,默认50% 29 | SleepWindow: 5000, // 过多长时间,熔断器再次检测是否开启,单位毫秒ms(默认5秒) 30 | MaxConcurrentRequests: 50000, 31 | } 32 | 33 | func NewVideo(vid int64, title string) *videoPb.Video { 34 | return &videoPb.Video{ 35 | Id: vid, 36 | Author: nil, 37 | Title: title, 38 | PlayUrl: "", 39 | CoverUrl: "", 40 | FavoriteCount: 0, 41 | CommentCount: 0, 42 | IsFavorite: false, 43 | } 44 | } 45 | 46 | // DefaultFeed 降级函数 47 | func DefaultFeed(res interface{}) { 48 | models := make([]*videoPb.Video, 0) 49 | for i := 0; i < 30; i++ { 50 | models = append(models, NewVideo(int64(i), "降级视频流"+strconv.Itoa(i))) 51 | } 52 | result := res.(*videoPb.FeedResponse) 53 | result.VideoList = models 54 | } 55 | -------------------------------------------------------------------------------- /app/message/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "ByteRhythm/app/gateway/wrapper" 5 | "ByteRhythm/app/message/dao" 6 | "ByteRhythm/app/message/service" 7 | "ByteRhythm/config" 8 | "ByteRhythm/idl/message/messagePb" 9 | "fmt" 10 | "os" 11 | 12 | "github.com/go-micro/plugins/v4/registry/etcd" 13 | ratelimit "github.com/go-micro/plugins/v4/wrapper/ratelimiter/uber" 14 | "github.com/go-micro/plugins/v4/wrapper/select/roundrobin" 15 | "github.com/go-micro/plugins/v4/wrapper/trace/opentracing" 16 | 17 | "go-micro.dev/v4" 18 | "go-micro.dev/v4/registry" 19 | ) 20 | 21 | func main() { 22 | config.Init() 23 | dao.InitMySQL() 24 | dao.InitRedis() 25 | 26 | defer dao.RedisClient.Close() 27 | 28 | // etcd注册件 29 | etcdReg := etcd.NewRegistry( 30 | registry.Addrs(fmt.Sprintf("%s:%s", config.EtcdHost, config.EtcdPort)), 31 | ) 32 | // 链路追踪 33 | tracer, closer, err := wrapper.InitJaeger("MessageService", fmt.Sprintf("%s:%s", config.JaegerHost, config.JaegerPort)) 34 | if err != nil { 35 | fmt.Printf("new tracer err: %+v\n", err) 36 | os.Exit(-1) 37 | } 38 | defer closer.Close() 39 | // 得到一个微服务实例 40 | microService := micro.NewService( 41 | micro.Name("MessageService"), // 微服务名字 42 | micro.Address(config.MessageServiceAddress), 43 | micro.Registry(etcdReg), // etcd注册件 44 | micro.WrapHandler(ratelimit.NewHandlerWrapper(50000)), //限流处理 45 | micro.WrapClient(roundrobin.NewClientWrapper()), // 负载均衡 46 | micro.WrapHandler(opentracing.NewHandlerWrapper(tracer)), // 链路追踪 47 | micro.WrapClient(opentracing.NewClientWrapper(tracer)), // 链路追踪 48 | ) 49 | 50 | // 结构命令行参数,初始化 51 | microService.Init() 52 | // 服务注册 53 | _ = messagePb.RegisterMessageServiceHandler(microService.Server(), service.GetMessageSrv()) 54 | // 启动微服务 55 | _ = microService.Run() 56 | } 57 | -------------------------------------------------------------------------------- /app/message/dao/init.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/go-redis/redis/v8" 9 | 10 | "github.com/gin-gonic/gin" 11 | "gorm.io/driver/mysql" 12 | "gorm.io/gorm" 13 | "gorm.io/gorm/logger" 14 | "gorm.io/gorm/schema" 15 | 16 | "ByteRhythm/config" 17 | ) 18 | 19 | var db *gorm.DB 20 | var RedisClient *redis.Client 21 | 22 | func InitMySQL() { 23 | host := config.DBHost 24 | port := config.DBPort 25 | database := config.DBName 26 | username := config.DBUser 27 | password := config.DBPassWord 28 | charset := config.Charset 29 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=True&loc=Local", username, password, host, port, database, charset) 30 | err := Database(dsn) 31 | if err != nil { 32 | fmt.Println(err) 33 | } 34 | } 35 | 36 | func InitRedis() { 37 | // 初始化 Redis 客户端 38 | host := config.RedisHost 39 | port := config.RedisPort 40 | RedisClient = redis.NewClient(&redis.Options{ 41 | Addr: host + ":" + port, // Redis 服务器地址 42 | Password: "", // Redis 访问密码(如果有的话) 43 | DB: 0, // Redis 数据库索引 44 | }) 45 | } 46 | 47 | func Database(connString string) error { 48 | var ormLogger logger.Interface 49 | if gin.Mode() == "debug" { 50 | ormLogger = logger.Default.LogMode(logger.Info) 51 | } else { 52 | ormLogger = logger.Default 53 | } 54 | DB, err := gorm.Open(mysql.New(mysql.Config{ 55 | DSN: connString, // DSN data source name 56 | DefaultStringSize: 256, // string 类型字段的默认长度 57 | DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持 58 | DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引 59 | DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列 60 | SkipInitializeWithVersion: false, // 根据版本自动配置 61 | }), &gorm.Config{ 62 | Logger: ormLogger, 63 | NamingStrategy: schema.NamingStrategy{ 64 | SingularTable: true, 65 | }, 66 | }) 67 | if err != nil { 68 | panic(err) 69 | } 70 | sqlDB, _ := DB.DB() 71 | sqlDB.SetMaxIdleConns(20) 72 | sqlDB.SetMaxOpenConns(100) 73 | sqlDB.SetConnMaxLifetime(time.Second * 30) 74 | db = DB 75 | migration() 76 | return err 77 | } 78 | 79 | func NewDBClient(ctx context.Context) *gorm.DB { 80 | DB := db 81 | return DB.WithContext(ctx) 82 | } 83 | -------------------------------------------------------------------------------- /app/message/dao/message.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "ByteRhythm/model" 5 | "context" 6 | 7 | "gorm.io/gorm" 8 | ) 9 | 10 | type MessageDao struct { 11 | *gorm.DB 12 | } 13 | 14 | func NewMessageDao(ctx context.Context) *MessageDao { 15 | if ctx == nil { 16 | ctx = context.Background() 17 | } 18 | return &MessageDao{NewDBClient(ctx)} 19 | } 20 | 21 | func (m *MessageDao) CreateMessage(message *model.Message) (id int64, err error) { 22 | err = m.Model(&model.Message{}).Create(&message).Error 23 | if err != nil { 24 | return 25 | } 26 | return int64(message.ID), nil 27 | } 28 | 29 | func (m *MessageDao) FindAllMessages(fromUserID int64, toUserID int64) (messages []*model.Message, err error) { 30 | err = m.Model(&model.Message{}).Where("(from_user_id = ? AND to_user_id = ?) OR (from_user_id = ? AND to_user_id = ?)", fromUserID, toUserID, toUserID, fromUserID).Order("id ASC").Find(&messages).Error 31 | if err != nil { 32 | return 33 | } 34 | return messages, nil 35 | } 36 | -------------------------------------------------------------------------------- /app/message/dao/migration.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "ByteRhythm/model" 5 | "log" 6 | ) 7 | 8 | func migration() { 9 | err := db.Set(`gorm:table_options`, "charset=utf8mb4"). 10 | AutoMigrate(&model.Message{}, &model.User{}) 11 | if err != nil { 12 | log.Fatal(err) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/message/service/message.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "ByteRhythm/app/message/dao" 5 | "ByteRhythm/idl/message/messagePb" 6 | "ByteRhythm/model" 7 | "ByteRhythm/util" 8 | "context" 9 | "encoding/json" 10 | "strconv" 11 | "sync" 12 | "time" 13 | 14 | "github.com/go-redis/redis/v8" 15 | ) 16 | 17 | type MessageSrv struct { 18 | } 19 | 20 | var MessageSrvIns *MessageSrv 21 | var MessageSrvOnce sync.Once 22 | 23 | // GetMessageSrv 懒汉式的单例模式 lazy-loading --> 懒汉式 24 | func GetMessageSrv() *MessageSrv { 25 | MessageSrvOnce.Do(func() { 26 | MessageSrvIns = &MessageSrv{} 27 | }) 28 | return MessageSrvIns 29 | } 30 | 31 | func (m MessageSrv) ChatMessage(ctx context.Context, req *messagePb.MessageChatRequest, res *messagePb.MessageChatResponse) error { 32 | token := req.Token 33 | toUserId := req.ToUserId 34 | 35 | fromUserId, err := util.GetUserIdFromToken(token) 36 | if err != nil { 37 | return nil 38 | } 39 | 40 | if int64(fromUserId) == toUserId { 41 | MessageChatResponseData(res, 1, "不能查看与自己的聊天记录!") 42 | return nil 43 | } 44 | 45 | // 构建 Redis 键 46 | var redisKey string 47 | if strconv.Itoa(fromUserId) < strconv.Itoa(int(toUserId)) { 48 | redisKey = "chat_messages:" + strconv.Itoa(fromUserId) + ":" + strconv.Itoa(int(toUserId)) 49 | } else { 50 | redisKey = "chat_messages:" + strconv.Itoa(int(toUserId)) + ":" + strconv.Itoa(fromUserId) 51 | } 52 | 53 | // 尝试从 Redis 缓存中获取数据 54 | redisResult, err := dao.RedisClient.Get(ctx, redisKey).Result() 55 | if err != nil && err != redis.Nil { 56 | MessageChatResponseData(res, 1, "获取聊天记录失败!") 57 | return err 58 | } 59 | 60 | if redisResult != "" { 61 | // 如果缓存中存在数据,则解码并直接返回缓存数据 62 | err = json.Unmarshal([]byte(redisResult), &res.MessageList) 63 | if err != nil { 64 | MessageChatResponseData(res, 1, "获取聊天记录失败!") 65 | return err 66 | } 67 | 68 | MessageChatResponseData(res, 0, "获取聊天记录成功!") 69 | return nil 70 | } 71 | 72 | // 缓存中不存在数据,则从数据库获取聊天记录 73 | messages, err := dao.NewMessageDao(ctx).FindAllMessages(int64(fromUserId), toUserId) 74 | if err != nil { 75 | MessageChatResponseData(res, 1, "获取聊天记录失败!") 76 | return err 77 | } 78 | 79 | for _, message := range messages { 80 | res.MessageList = append(res.MessageList, BuildMessagePbModel(message)) 81 | } 82 | 83 | // 将结果存入 Redis 缓存 84 | jsonBytes, err := json.Marshal(&res.MessageList) 85 | if err != nil { 86 | MessageChatResponseData(res, 1, "获取聊天记录失败!") 87 | return err 88 | } 89 | 90 | err = dao.RedisClient.Set(ctx, redisKey, string(jsonBytes), time.Hour).Err() 91 | if err != nil { 92 | MessageChatResponseData(res, 1, "获取聊天记录失败!") 93 | return err 94 | } 95 | 96 | MessageChatResponseData(res, 0, "获取聊天记录成功!") 97 | return nil 98 | } 99 | 100 | func (m MessageSrv) ActionMessage(ctx context.Context, req *messagePb.MessageActionRequest, res *messagePb.MessageActionResponse) error { 101 | token := req.Token 102 | fromUserID, err := util.GetUserIdFromToken(token) 103 | if err != nil { 104 | return err 105 | } 106 | 107 | actionType := req.ActionType 108 | if actionType == 1 { 109 | toUserID := req.ToUserId 110 | content := req.Content 111 | 112 | message := BuildMessageModel(fromUserID, int(toUserID), content) 113 | id, err := dao.NewMessageDao(ctx).CreateMessage(&message) 114 | if err != nil { 115 | MessageActionResponseData(res, 1, "发送消息失败!") 116 | return err 117 | } 118 | message.ID = uint(id) 119 | 120 | // 构建 Redis 键 121 | var redisKey string 122 | if strconv.Itoa(fromUserID) < strconv.Itoa(int(toUserID)) { 123 | redisKey = "chat_messages:" + strconv.Itoa(fromUserID) + ":" + strconv.Itoa(int(toUserID)) 124 | } else { 125 | redisKey = "chat_messages:" + strconv.Itoa(int(toUserID)) + ":" + strconv.Itoa(fromUserID) 126 | } 127 | 128 | // 尝试从 Redis 缓存中获取数据 129 | redisResult, err := dao.RedisClient.Get(ctx, redisKey).Result() 130 | if err != nil && err != redis.Nil { 131 | MessageActionResponseData(res, 1, "操作失败!") 132 | return err 133 | } 134 | 135 | var messageList []*messagePb.Message 136 | // 如果缓存中存在数据,则解码并合并到 messageList 中 137 | if redisResult != "" { 138 | // 解码 Redis 结果 139 | err = json.Unmarshal([]byte(redisResult), &messageList) 140 | if err != nil { 141 | MessageActionResponseData(res, 1, "操作失败!") 142 | return err 143 | } 144 | messageList = append(messageList, BuildMessagePbModel(&message)) 145 | } else { 146 | // 如果缓存中不存在数据,则创建新的 messageList 切片 147 | messageList = []*messagePb.Message{} 148 | } 149 | 150 | // 将结果存入 Redis 缓存 151 | jsonBytes, err := json.Marshal(&messageList) 152 | if err != nil { 153 | MessageActionResponseData(res, 1, "操作失败!") 154 | return err 155 | } 156 | 157 | err = dao.RedisClient.Set(ctx, redisKey, string(jsonBytes), time.Hour).Err() 158 | if err != nil { 159 | MessageActionResponseData(res, 1, "操作失败!") 160 | return err 161 | } 162 | 163 | MessageActionResponseData(res, 0, "发送消息成功!") 164 | return nil 165 | 166 | } else { 167 | MessageActionResponseData(res, 1, "非发送消息操作!") 168 | return nil 169 | } 170 | } 171 | 172 | func MessageChatResponseData(res *messagePb.MessageChatResponse, StatusCode int32, StatusMsg string, params ...interface{}) { 173 | res.StatusCode = StatusCode 174 | res.StatusMsg = StatusMsg 175 | if len(params) != 0 { 176 | res.MessageList = params[0].([]*messagePb.Message) 177 | } 178 | } 179 | 180 | func MessageActionResponseData(res *messagePb.MessageActionResponse, StatusCode int32, StatusMsg string) { 181 | res.StatusCode = StatusCode 182 | res.StatusMsg = StatusMsg 183 | } 184 | 185 | func BuildMessageModel(fromUserID int, toUserID int, content string) model.Message { 186 | return model.Message{ 187 | FromUserID: fromUserID, 188 | ToUserID: toUserID, 189 | Content: content, 190 | CreatedAt: time.Now(), 191 | } 192 | } 193 | 194 | func BuildMessagePbModel(message *model.Message) *messagePb.Message { 195 | return &messagePb.Message{ 196 | Id: int64(message.ID), 197 | FromUserId: int64(message.FromUserID), 198 | ToUserId: int64(message.ToUserID), 199 | Content: message.Content, 200 | //CreateTime: message.CreatedAt.Format("2006-01-02 15:04:05"), 201 | //CreateTime: time.ParseInLocation("2006-01-02 15:04:05", message.CreatedAt, time.Local), 202 | CreateTime: "0", 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /app/relation/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "ByteRhythm/app/gateway/wrapper" 5 | "ByteRhythm/app/relation/dao" 6 | "ByteRhythm/app/relation/service" 7 | "ByteRhythm/config" 8 | "ByteRhythm/idl/relation/relationPb" 9 | "fmt" 10 | "os" 11 | 12 | "github.com/go-micro/plugins/v4/registry/etcd" 13 | ratelimit "github.com/go-micro/plugins/v4/wrapper/ratelimiter/uber" 14 | "github.com/go-micro/plugins/v4/wrapper/select/roundrobin" 15 | "github.com/go-micro/plugins/v4/wrapper/trace/opentracing" 16 | 17 | "go-micro.dev/v4" 18 | "go-micro.dev/v4/registry" 19 | ) 20 | 21 | func main() { 22 | config.Init() 23 | dao.InitMySQL() 24 | dao.InitRedis() 25 | 26 | defer dao.RedisClient.Close() 27 | 28 | // etcd注册件 29 | etcdReg := etcd.NewRegistry( 30 | registry.Addrs(fmt.Sprintf("%s:%s", config.EtcdHost, config.EtcdPort)), 31 | ) 32 | // 链路追踪 33 | tracer, closer, err := wrapper.InitJaeger("RelationService", fmt.Sprintf("%s:%s", config.JaegerHost, config.JaegerPort)) 34 | if err != nil { 35 | fmt.Printf("new tracer err: %+v\n", err) 36 | os.Exit(-1) 37 | } 38 | defer closer.Close() 39 | // 得到一个微服务实例 40 | microService := micro.NewService( 41 | micro.Name("RelationService"), // 微服务名字 42 | micro.Address(config.RelationServiceAddress), 43 | micro.Registry(etcdReg), // etcd注册件 44 | micro.WrapHandler(ratelimit.NewHandlerWrapper(50000)), //限流处理 45 | micro.WrapClient(roundrobin.NewClientWrapper()), // 负载均衡 46 | micro.WrapHandler(opentracing.NewHandlerWrapper(tracer)), // 链路追踪 47 | micro.WrapClient(opentracing.NewClientWrapper(tracer)), // 链路追踪 48 | ) 49 | 50 | // 结构命令行参数,初始化 51 | microService.Init() 52 | // 服务注册 53 | _ = relationPb.RegisterRelationServiceHandler(microService.Server(), service.GetRelationSrv()) 54 | // 启动微服务 55 | _ = microService.Run() 56 | } 57 | -------------------------------------------------------------------------------- /app/relation/dao/init.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/go-redis/redis/v8" 7 | "time" 8 | 9 | "github.com/gin-gonic/gin" 10 | "gorm.io/driver/mysql" 11 | "gorm.io/gorm" 12 | "gorm.io/gorm/logger" 13 | "gorm.io/gorm/schema" 14 | 15 | "ByteRhythm/config" 16 | ) 17 | 18 | var db *gorm.DB 19 | var RedisClient *redis.Client 20 | 21 | func InitMySQL() { 22 | host := config.DBHost 23 | port := config.DBPort 24 | database := config.DBName 25 | username := config.DBUser 26 | password := config.DBPassWord 27 | charset := config.Charset 28 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=True&loc=Local", username, password, host, port, database, charset) 29 | err := Database(dsn) 30 | if err != nil { 31 | fmt.Println(err) 32 | } 33 | } 34 | 35 | func InitRedis() { 36 | // 初始化 Redis 客户端 37 | host := config.RedisHost 38 | port := config.RedisPort 39 | RedisClient = redis.NewClient(&redis.Options{ 40 | Addr: host + ":" + port, // Redis 服务器地址 41 | Password: "", // Redis 访问密码(如果有的话) 42 | DB: 1, // Redis 数据库索引 43 | }) 44 | } 45 | 46 | func Database(connString string) error { 47 | var ormLogger logger.Interface 48 | if gin.Mode() == "debug" { 49 | ormLogger = logger.Default.LogMode(logger.Info) 50 | } else { 51 | ormLogger = logger.Default 52 | } 53 | DB, err := gorm.Open(mysql.New(mysql.Config{ 54 | DSN: connString, // DSN data source name 55 | DefaultStringSize: 256, // string 类型字段的默认长度 56 | DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持 57 | DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引 58 | DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列 59 | SkipInitializeWithVersion: false, // 根据版本自动配置 60 | }), &gorm.Config{ 61 | Logger: ormLogger, 62 | NamingStrategy: schema.NamingStrategy{ 63 | SingularTable: true, 64 | }, 65 | }) 66 | if err != nil { 67 | panic(err) 68 | } 69 | sqlDB, _ := DB.DB() 70 | sqlDB.SetMaxIdleConns(20) 71 | sqlDB.SetMaxOpenConns(100) 72 | sqlDB.SetConnMaxLifetime(time.Second * 30) 73 | db = DB 74 | migration() 75 | return err 76 | } 77 | 78 | func NewDBClient(ctx context.Context) *gorm.DB { 79 | DB := db 80 | return DB.WithContext(ctx) 81 | } 82 | -------------------------------------------------------------------------------- /app/relation/dao/migration.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "ByteRhythm/model" 5 | "log" 6 | ) 7 | 8 | func migration() { 9 | err := db.Set(`gorm:table_options`, "charset=utf8mb4"). 10 | AutoMigrate(&model.User{}, &model.Follow{}) 11 | if err != nil { 12 | log.Fatal(err) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/relation/dao/relation.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "ByteRhythm/model" 5 | "ByteRhythm/util" 6 | "context" 7 | 8 | "gorm.io/gorm" 9 | ) 10 | 11 | type RelationDao struct { 12 | *gorm.DB 13 | } 14 | 15 | func NewRelationDao(ctx context.Context) *RelationDao { 16 | if ctx == nil { 17 | ctx = context.Background() 18 | } 19 | return &RelationDao{NewDBClient(ctx)} 20 | } 21 | 22 | func (r *RelationDao) FindUserById(uid int) (user *model.User, err error) { 23 | err = r.Model(&model.User{}).Where("id = ?", uid).Limit(1).Find(&user).Error 24 | if err != nil { 25 | return 26 | } 27 | return 28 | } 29 | 30 | func (r *RelationDao) FindAllFollow(uid int) (follows []*model.Follow, err error) { 31 | err = r.Model(&model.Follow{}).Where("followed_user_id = ?", uid).Find(&follows).Error 32 | if err != nil { 33 | return 34 | } 35 | return follows, nil 36 | } 37 | 38 | func (r *RelationDao) FindAllFollower(uid int) (follows []*model.Follow, err error) { 39 | err = r.Model(&model.Follow{}).Where("user_id = ?", uid).Find(&follows).Error 40 | if err != nil { 41 | return 42 | } 43 | return follows, nil 44 | } 45 | 46 | func (r *RelationDao) AddFollow(follow *model.Follow) (id int64, err error) { 47 | result := r.Model(&model.Follow{}).Where("user_id = ?", follow.UserID).Where("followed_user_id=?", follow.FollowedUserID).FirstOrCreate(&follow) 48 | if result.Error != nil { 49 | return 50 | } 51 | return result.RowsAffected, nil 52 | } 53 | 54 | func (r *RelationDao) CancelFollow(follow *model.Follow) (relation *model.Follow, err error) { 55 | err = r.Model(&model.Follow{}).Where("user_id = ?", follow.UserID).Where("followed_user_id=?", follow.FollowedUserID).Delete(&follow).Error 56 | if err != nil { 57 | return 58 | } 59 | return 60 | } 61 | 62 | func (r *RelationDao) GetFriendCount(userId int, followedUserId int) (count int64, err error) { 63 | err = r.Model(&model.Follow{}).Where("user_id = ?", userId).Where("followed_user_id=?", followedUserId).Count(&count).Error 64 | if err != nil { 65 | return 66 | } 67 | return 68 | } 69 | 70 | func (r *RelationDao) GetFollowCount(uid int) (count int64, err error) { 71 | err = r.Model(&model.Follow{}).Where("followed_user_id = ?", uid).Count(&count).Error 72 | if err != nil { 73 | return 74 | } 75 | return 76 | } 77 | 78 | func (r *RelationDao) GetFollowerCount(uid int) (count int64, err error) { 79 | err = r.Model(&model.Follow{}).Where("user_id = ?", uid).Count(&count).Error 80 | if err != nil { 81 | return 82 | } 83 | return 84 | } 85 | 86 | func (r *RelationDao) GetWorkCount(uid int) (count int64, err error) { 87 | err = r.Model(&model.Video{}).Where("author_id = ?", uid).Count(&count).Error 88 | if err != nil { 89 | return 90 | } 91 | return 92 | } 93 | 94 | func (r *RelationDao) GetFavoriteCount(uid int) (count int64, err error) { 95 | err = r.Model(&model.Favorite{}).Where("user_id = ?", uid).Count(&count).Error 96 | if err != nil { 97 | return 98 | } 99 | return 100 | } 101 | 102 | func (r *RelationDao) GetTotalFavorited(uid int) (count int64, err error) { 103 | var videos []*model.Video 104 | err = r.Model(&model.Video{}).Where("author_id = ?", uid).Find(&videos).Error 105 | if err != nil { 106 | return 107 | } 108 | for _, video := range videos { 109 | var favoriteCount int64 110 | err = r.Model(&model.Favorite{}).Where("video_id = ?", video.ID).Count(&favoriteCount).Error 111 | if err != nil { 112 | return 113 | } 114 | count += favoriteCount 115 | } 116 | return 117 | } 118 | 119 | func (r *RelationDao) GetIsFollowed(uid int, token string) (isFollowed bool, err error) { 120 | 121 | baseId, err := util.GetUserIdFromToken(token) 122 | if err != nil { 123 | return 124 | } 125 | var follow model.Follow 126 | err = r.Model(&model.Follow{}).Where("user_id = ?", uid).Where("followed_user_id = ?", baseId).Limit(1).Find(&follow).Error 127 | if err != nil { 128 | return 129 | } 130 | if follow.ID != 0 { 131 | isFollowed = true 132 | } else { 133 | isFollowed = false 134 | } 135 | return 136 | } 137 | -------------------------------------------------------------------------------- /app/user/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "ByteRhythm/app/gateway/wrapper" 5 | "ByteRhythm/app/user/dao" 6 | "ByteRhythm/app/user/service" 7 | "ByteRhythm/config" 8 | "ByteRhythm/idl/user/userPb" 9 | "fmt" 10 | "os" 11 | 12 | "github.com/go-micro/plugins/v4/registry/etcd" 13 | ratelimit "github.com/go-micro/plugins/v4/wrapper/ratelimiter/uber" 14 | "github.com/go-micro/plugins/v4/wrapper/select/roundrobin" 15 | "github.com/go-micro/plugins/v4/wrapper/trace/opentracing" 16 | "go-micro.dev/v4" 17 | "go-micro.dev/v4/registry" 18 | ) 19 | 20 | func main() { 21 | config.Init() 22 | dao.InitMySQL() 23 | 24 | //etcd注册件 25 | etcdReg := etcd.NewRegistry( 26 | registry.Addrs(fmt.Sprintf("%s:%s", config.EtcdHost, config.EtcdPort)), 27 | ) 28 | // 链路追踪 29 | tracer, closer, err := wrapper.InitJaeger("UserService", fmt.Sprintf("%s:%s", config.JaegerHost, config.JaegerPort)) 30 | if err != nil { 31 | fmt.Printf("new tracer err: %+v\n", err) 32 | os.Exit(-1) 33 | } 34 | defer closer.Close() 35 | // 得到一个微服务实例 36 | microService := micro.NewService( 37 | micro.Name("UserService"), // 微服务名字 38 | micro.Address(config.UserServiceAddress), 39 | micro.Registry(etcdReg), // etcd注册件 40 | micro.WrapHandler(ratelimit.NewHandlerWrapper(50000)), //限流处理 41 | micro.WrapClient(roundrobin.NewClientWrapper()), // 负载均衡 42 | micro.WrapHandler(opentracing.NewHandlerWrapper(tracer)), // 链路追踪 43 | micro.WrapClient(opentracing.NewClientWrapper(tracer)), // 链路追踪 44 | ) 45 | 46 | // 结构命令行参数,初始化 47 | microService.Init() 48 | // 服务注册 49 | _ = userPb.RegisterUserServiceHandler(microService.Server(), service.GetUserSrv()) 50 | // 启动微服务 51 | _ = microService.Run() 52 | } 53 | -------------------------------------------------------------------------------- /app/user/dao/init.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/gin-gonic/gin" 7 | "gorm.io/driver/mysql" 8 | "gorm.io/gorm" 9 | "gorm.io/gorm/logger" 10 | "gorm.io/gorm/schema" 11 | "time" 12 | 13 | "ByteRhythm/config" 14 | ) 15 | 16 | var db *gorm.DB 17 | 18 | func InitMySQL() { 19 | host := config.DBHost 20 | port := config.DBPort 21 | database := config.DBName 22 | username := config.DBUser 23 | password := config.DBPassWord 24 | charset := config.Charset 25 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=True&loc=Local", username, password, host, port, database, charset) 26 | err := Database(dsn) 27 | if err != nil { 28 | fmt.Println(err) 29 | } 30 | } 31 | 32 | func Database(connString string) error { 33 | var ormLogger logger.Interface 34 | if gin.Mode() == "debug" { 35 | ormLogger = logger.Default.LogMode(logger.Info) 36 | } else { 37 | ormLogger = logger.Default 38 | } 39 | DB, err := gorm.Open(mysql.New(mysql.Config{ 40 | DSN: connString, // DSN data source name 41 | DefaultStringSize: 256, // string 类型字段的默认长度 42 | DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持 43 | DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引 44 | DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列 45 | SkipInitializeWithVersion: false, // 根据版本自动配置 46 | }), &gorm.Config{ 47 | Logger: ormLogger, 48 | NamingStrategy: schema.NamingStrategy{ 49 | SingularTable: true, 50 | }, 51 | }) 52 | if err != nil { 53 | panic(err) 54 | } 55 | sqlDB, _ := DB.DB() 56 | sqlDB.SetMaxIdleConns(20) 57 | sqlDB.SetMaxOpenConns(100) 58 | sqlDB.SetConnMaxLifetime(time.Second * 30) 59 | db = DB 60 | migration() 61 | return err 62 | } 63 | 64 | func NewDBClient(ctx context.Context) *gorm.DB { 65 | DB := db 66 | return DB.WithContext(ctx) 67 | } 68 | -------------------------------------------------------------------------------- /app/user/dao/migration.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "ByteRhythm/model" 5 | "log" 6 | ) 7 | 8 | func migration() { 9 | err := db.Set(`gorm:table_options`, "charset=utf8mb4"). 10 | AutoMigrate(&model.User{}, &model.Follow{}, &model.Video{}, &model.Favorite{}) 11 | if err != nil { 12 | log.Fatal(err) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/user/dao/user.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "ByteRhythm/model" 5 | "ByteRhythm/util" 6 | "context" 7 | 8 | "gorm.io/gorm" 9 | ) 10 | 11 | type UserDao struct { 12 | *gorm.DB 13 | } 14 | 15 | func NewUserDao(ctx context.Context) *UserDao { 16 | if ctx == nil { 17 | ctx = context.Background() 18 | } 19 | return &UserDao{NewDBClient(ctx)} 20 | } 21 | 22 | func (u *UserDao) FindUserByUserName(username string) (user *model.User, err error) { 23 | //查看该用户是否存在 24 | err = u.Model(&model.User{}).Where("username = ?", username).Limit(1).Find(&user).Error 25 | if err != nil { 26 | return 27 | } 28 | return 29 | } 30 | func (u *UserDao) CreateUser(user *model.User) (id int64, err error) { 31 | err = u.Model(&model.User{}).Create(&user).Error 32 | if err != nil { 33 | return 34 | } 35 | return int64(user.ID), nil 36 | } 37 | 38 | func (u *UserDao) FindUserById(uid int) (user *model.User, err error) { 39 | err = u.Model(&model.User{}).Where("id = ?", uid).Limit(1).Find(&user).Error 40 | if err != nil { 41 | return 42 | } 43 | return 44 | } 45 | 46 | func (u *UserDao) GetFollowCount(uid int) (count int64, err error) { 47 | err = u.Model(&model.Follow{}).Where("followed_user_id = ?", uid).Count(&count).Error 48 | if err != nil { 49 | return 50 | } 51 | return 52 | } 53 | 54 | func (u *UserDao) GetFollowerCount(uid int) (count int64, err error) { 55 | err = u.Model(&model.Follow{}).Where("user_id = ?", uid).Count(&count).Error 56 | if err != nil { 57 | return 58 | } 59 | return 60 | } 61 | 62 | func (u *UserDao) GetWorkCount(uid int) (count int64, err error) { 63 | err = u.Model(&model.Video{}).Where("author_id = ?", uid).Count(&count).Error 64 | if err != nil { 65 | return 66 | } 67 | return 68 | } 69 | 70 | func (u *UserDao) GetFavoriteCount(uid int) (count int64, err error) { 71 | err = u.Model(&model.Favorite{}).Where("user_id = ?", uid).Count(&count).Error 72 | if err != nil { 73 | return 74 | } 75 | return 76 | } 77 | 78 | func (u *UserDao) GetTotalFavorited(uid int) (count int64, err error) { 79 | var videos []*model.Video 80 | err = u.Model(&model.Video{}).Where("author_id = ?", uid).Find(&videos).Error 81 | if err != nil { 82 | return 83 | } 84 | for _, video := range videos { 85 | var favoriteCount int64 86 | err = u.Model(&model.Favorite{}).Where("video_id = ?", video.ID).Count(&favoriteCount).Error 87 | if err != nil { 88 | return 89 | } 90 | count += favoriteCount 91 | } 92 | return 93 | } 94 | 95 | func (u *UserDao) GetIsFollowed(uid int, token string) (isFollowed bool, err error) { 96 | 97 | baseID, err := util.GetUserIdFromToken(token) 98 | if err != nil { 99 | return 100 | } 101 | var follow model.Follow 102 | err = u.Model(&model.Follow{}).Where("user_id = ?", uid).Where("followed_user_id = ?", baseID).Limit(1).Find(&follow).Error 103 | if err != nil { 104 | return 105 | } 106 | if follow.ID != 0 { 107 | isFollowed = true 108 | } else { 109 | isFollowed = false 110 | } 111 | return 112 | } 113 | -------------------------------------------------------------------------------- /app/user/service/user.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "ByteRhythm/app/user/dao" 5 | "ByteRhythm/config" 6 | "ByteRhythm/idl/user/userPb" 7 | "ByteRhythm/model" 8 | 9 | "ByteRhythm/util" 10 | "context" 11 | "sync" 12 | ) 13 | 14 | type UserSrv struct { 15 | } 16 | 17 | var UserSrvIns *UserSrv 18 | var UserSrvOnce sync.Once 19 | 20 | func GetUserSrv() *UserSrv { 21 | UserSrvOnce.Do(func() { 22 | UserSrvIns = &UserSrv{} 23 | }) 24 | return UserSrvIns 25 | } 26 | 27 | func (u *UserSrv) Login(ctx context.Context, req *userPb.UserRequest, res *userPb.UserResponse) (err error) { 28 | user, err := dao.NewUserDao(ctx).FindUserByUserName(req.Username) 29 | if user.ID == 0 || util.Md5(req.Password) != user.Password { 30 | UserResponseData(res, 1, "用户名或密码错误") 31 | return nil 32 | } 33 | token := util.GenerateToken(user, 0) 34 | uid := int64(user.ID) 35 | UserResponseData(res, 0, "登录成功", uid, token) 36 | return nil 37 | } 38 | 39 | func (u *UserSrv) Register(ctx context.Context, req *userPb.UserRequest, res *userPb.UserResponse) (err error) { 40 | username := req.Username 41 | password := req.Password 42 | if len(username) > 32 || len(password) > 32 { 43 | UserResponseData(res, 1, "用户名或密码不能超过32位") 44 | return nil 45 | } 46 | if user, _ := dao.NewUserDao(ctx).FindUserByUserName(username); user.ID != 0 { 47 | UserResponseData(res, 1, "用户名已存在") 48 | return nil 49 | } 50 | 51 | user := BuildUserModel(username, password) 52 | if id, err := dao.NewUserDao(ctx).CreateUser(&user); err != nil { 53 | UserResponseData(res, 1, "注册失败") 54 | return err 55 | } else { 56 | token := util.GenerateToken(&user, 0) 57 | UserResponseData(res, 0, "注册成功", id, token) 58 | return nil 59 | } 60 | } 61 | 62 | func (u *UserSrv) UserInfo(ctx context.Context, req *userPb.UserInfoRequest, res *userPb.UserInfoResponse) error { 63 | token := req.Token 64 | uid := int(req.UserId) 65 | 66 | user, _ := dao.NewUserDao(ctx).FindUserById(uid) 67 | if user.ID == 0 { 68 | UserInfoResponseData(res, 1, "用户不存在") 69 | return nil 70 | } 71 | 72 | User := BuildUserPbModel(ctx, user, token) 73 | UserInfoResponseData(res, 0, "获取用户信息成功", User) 74 | return nil 75 | } 76 | 77 | func UserResponseData(res *userPb.UserResponse, StatusCode int32, StatusMsg string, params ...interface{}) { 78 | res.StatusCode = StatusCode 79 | res.StatusMsg = StatusMsg 80 | if len(params) != 0 { 81 | res.UserId = params[0].(int64) 82 | res.Token = params[1].(string) 83 | } 84 | } 85 | 86 | func UserInfoResponseData(res *userPb.UserInfoResponse, StatusCode int32, StatusMsg string, params ...interface{}) { 87 | res.StatusCode = StatusCode 88 | res.StatusMsg = StatusMsg 89 | if len(params) != 0 { 90 | res.User = params[0].(*userPb.User) 91 | } 92 | } 93 | 94 | func BuildUserModel(username string, password string) model.User { 95 | config.Init() 96 | avatar := config.Avatar 97 | background := config.Background 98 | signature := config.Signature 99 | return model.User{ 100 | Username: username, 101 | Password: util.Md5(password), 102 | Avatar: avatar, 103 | BackgroundImage: background, 104 | Signature: signature, 105 | } 106 | } 107 | func BuildUserPbModel(ctx context.Context, user *model.User, token string) *userPb.User { 108 | uid := int(user.ID) 109 | FollowCount, _ := dao.NewUserDao(ctx).GetFollowCount(uid) 110 | FollowerCount, _ := dao.NewUserDao(ctx).GetFollowerCount(uid) 111 | WorkCount, _ := dao.NewUserDao(ctx).GetWorkCount(uid) 112 | FavoriteCount, _ := dao.NewUserDao(ctx).GetFavoriteCount(uid) 113 | TotalFavorited, _ := dao.NewUserDao(ctx).GetTotalFavorited(uid) 114 | IsFollow, _ := dao.NewUserDao(ctx).GetIsFollowed(uid, token) 115 | return &userPb.User{ 116 | Id: int64(uid), 117 | Name: user.Username, 118 | Avatar: user.Avatar, 119 | BackgroundImage: user.BackgroundImage, 120 | Signature: user.Signature, 121 | FollowCount: FollowCount, 122 | FollowerCount: FollowerCount, 123 | WorkCount: WorkCount, 124 | FavoriteCount: FavoriteCount, 125 | TotalFavorited: TotalFavorited, 126 | IsFollow: IsFollow, 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /app/video/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "ByteRhythm/app/gateway/wrapper" 5 | "ByteRhythm/app/video/dao" 6 | "ByteRhythm/app/video/script" 7 | "ByteRhythm/app/video/service" 8 | "ByteRhythm/config" 9 | "ByteRhythm/idl/video/videoPb" 10 | "ByteRhythm/mq" 11 | "context" 12 | "fmt" 13 | "os" 14 | 15 | "github.com/go-micro/plugins/v4/registry/etcd" 16 | ratelimit "github.com/go-micro/plugins/v4/wrapper/ratelimiter/uber" 17 | "github.com/go-micro/plugins/v4/wrapper/select/roundrobin" 18 | "github.com/go-micro/plugins/v4/wrapper/trace/opentracing" 19 | "go-micro.dev/v4" 20 | "go-micro.dev/v4/registry" 21 | ) 22 | 23 | func main() { 24 | config.Init() 25 | dao.InitMySQL() 26 | dao.InitRedis() 27 | mq.InitRabbitMQ() 28 | loadingScript() 29 | 30 | defer dao.RedisClient.Close() 31 | 32 | // etcd注册件 33 | etcdReg := etcd.NewRegistry( 34 | registry.Addrs(fmt.Sprintf("%s:%s", config.EtcdHost, config.EtcdPort)), 35 | ) 36 | 37 | // 链路追踪 38 | tracer, closer, err := wrapper.InitJaeger("VideoService", fmt.Sprintf("%s:%s", config.JaegerHost, config.JaegerPort)) 39 | if err != nil { 40 | fmt.Printf("new tracer err: %+v\n", err) 41 | os.Exit(-1) 42 | } 43 | defer closer.Close() 44 | // 得到一个微服务实例 45 | microService := micro.NewService( 46 | micro.Name("VideoService"), // 微服务名字 47 | micro.Address(config.VideoServiceAddress), 48 | micro.Registry(etcdReg), // etcd注册件 49 | micro.WrapHandler(ratelimit.NewHandlerWrapper(50000)), //限流处理 50 | micro.WrapClient(roundrobin.NewClientWrapper()), // 负载均衡 51 | micro.WrapHandler(opentracing.NewHandlerWrapper(tracer)), // 链路追踪 52 | micro.WrapClient(opentracing.NewClientWrapper(tracer)), // 链路追踪 53 | ) 54 | 55 | // 结构命令行参数,初始化 56 | microService.Init() 57 | // 服务注册 58 | _ = videoPb.RegisterVideoServiceHandler(microService.Server(), service.GetVideoSrv()) 59 | // 启动微服务 60 | _ = microService.Run() 61 | } 62 | 63 | func loadingScript() { 64 | ctx := context.Background() 65 | go script.VideoCreateSync(ctx) 66 | go script.Video2RedisSync(ctx) 67 | } 68 | -------------------------------------------------------------------------------- /app/video/dao/init.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "ByteRhythm/config" 5 | "context" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/gin-gonic/gin" 10 | "github.com/go-redis/redis/v8" 11 | "gorm.io/driver/mysql" 12 | "gorm.io/gorm" 13 | "gorm.io/gorm/logger" 14 | "gorm.io/gorm/schema" 15 | ) 16 | 17 | var db *gorm.DB 18 | 19 | var RedisClient *redis.Client 20 | 21 | func InitMySQL() { 22 | host := config.DBHost 23 | port := config.DBPort 24 | database := config.DBName 25 | username := config.DBUser 26 | password := config.DBPassWord 27 | charset := config.Charset 28 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=True&loc=Local", username, password, host, port, database, charset) 29 | err := Database(dsn) 30 | if err != nil { 31 | fmt.Println(err) 32 | } 33 | } 34 | 35 | func InitRedis() { 36 | // 初始化 Redis 客户端 37 | host := config.RedisHost 38 | port := config.RedisPort 39 | RedisClient = redis.NewClient(&redis.Options{ 40 | Addr: host + ":" + port, // Redis 服务器地址 41 | Password: "", // Redis 访问密码(如果有的话) 42 | DB: 1, // Redis 数据库索引 43 | }) 44 | } 45 | func Database(connString string) error { 46 | var ormLogger logger.Interface 47 | if gin.Mode() == "debug" { 48 | ormLogger = logger.Default.LogMode(logger.Info) 49 | } else { 50 | ormLogger = logger.Default 51 | } 52 | DB, err := gorm.Open(mysql.New(mysql.Config{ 53 | DSN: connString, // DSN data source name 54 | DefaultStringSize: 256, // string 类型字段的默认长度 55 | DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持 56 | DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引 57 | DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列 58 | SkipInitializeWithVersion: false, // 根据版本自动配置 59 | }), &gorm.Config{ 60 | Logger: ormLogger, 61 | NamingStrategy: schema.NamingStrategy{ 62 | SingularTable: true, 63 | }, 64 | }) 65 | if err != nil { 66 | panic(err) 67 | } 68 | sqlDB, _ := DB.DB() 69 | sqlDB.SetMaxIdleConns(20) 70 | sqlDB.SetMaxOpenConns(100) 71 | sqlDB.SetConnMaxLifetime(time.Second * 30) 72 | db = DB 73 | migration() 74 | return err 75 | } 76 | 77 | func NewDBClient(ctx context.Context) *gorm.DB { 78 | DB := db 79 | return DB.WithContext(ctx) 80 | } 81 | -------------------------------------------------------------------------------- /app/video/dao/migration.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "ByteRhythm/model" 5 | "log" 6 | ) 7 | 8 | func migration() { 9 | err := db.Set(`gorm:table_options`, "charset=utf8mb4"). 10 | AutoMigrate(&model.User{}, &model.Follow{}, &model.Video{}, &model.Favorite{}, &model.Comment{}) 11 | if err != nil { 12 | log.Fatal(err) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/video/dao/video.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "ByteRhythm/model" 5 | "ByteRhythm/util" 6 | "context" 7 | "time" 8 | 9 | "gorm.io/gorm" 10 | ) 11 | 12 | type VideoDao struct { 13 | *gorm.DB 14 | } 15 | 16 | func NewVideoDao(ctx context.Context) *VideoDao { 17 | if ctx == nil { 18 | ctx = context.Background() 19 | } 20 | return &VideoDao{NewDBClient(ctx)} 21 | } 22 | 23 | func (v *VideoDao) GetVideoListByLatestTime(latestTime time.Time, vidExistArray []int, limit int) (videos []*model.Video, err error) { 24 | if limit != 30 { 25 | err = v.Model(&model.Video{}).Where("created_at <= ?", latestTime).Not("id", vidExistArray).Order("created_at desc").Limit(limit).Find(&videos).Error 26 | if err != nil { 27 | return 28 | } 29 | } 30 | err = v.Model(&model.Video{}).Where("created_at <= ?", latestTime).Order("created_at desc").Limit(limit).Find(&videos).Error 31 | if err != nil { 32 | return 33 | } 34 | return 35 | } 36 | 37 | func (v *VideoDao) GetFavoriteCount(vid int) (count int64, err error) { 38 | err = v.Model(&model.Favorite{}).Where("video_id = ?", vid).Count(&count).Error 39 | if err != nil { 40 | return 41 | } 42 | return 43 | } 44 | 45 | func (v *VideoDao) GetCommentCount(vid int) (count int64, err error) { 46 | err = v.Model(&model.Comment{}).Where("video_id = ?", vid).Count(&count).Error 47 | if err != nil { 48 | return 49 | } 50 | return 51 | } 52 | 53 | func (v *VideoDao) GetIsFavorite(vid int, token string) (isFavorite bool, err error) { 54 | baseID, err := util.GetUserIdFromToken(token) 55 | if err != nil { 56 | return 57 | } 58 | var favorite model.Favorite 59 | err = v.Model(&model.Favorite{}).Where("user_id = ? and video_id = ?", baseID, vid).Limit(1).Find(&favorite).Error 60 | if err != nil { 61 | return 62 | } 63 | if favorite.ID != 0 { 64 | isFavorite = true 65 | } else { 66 | isFavorite = false 67 | } 68 | return 69 | } 70 | 71 | func (v *VideoDao) GetFollowCount(uid int) (count int64, err error) { 72 | err = v.Model(&model.Follow{}).Where("followed_user_id = ?", uid).Count(&count).Error 73 | if err != nil { 74 | return 75 | } 76 | return 77 | } 78 | 79 | func (v *VideoDao) GetFollowerCount(uid int) (count int64, err error) { 80 | err = v.Model(&model.Follow{}).Where("user_id = ?", uid).Count(&count).Error 81 | if err != nil { 82 | return 83 | } 84 | return 85 | } 86 | 87 | func (v *VideoDao) GetWorkCount(uid int) (count int64, err error) { 88 | err = v.Model(&model.Video{}).Where("author_id = ?", uid).Count(&count).Error 89 | if err != nil { 90 | return 91 | } 92 | return 93 | } 94 | 95 | func (v *VideoDao) GetUserFavoriteCount(uid int) (count int64, err error) { 96 | err = v.Model(&model.Favorite{}).Where("user_id = ?", uid).Count(&count).Error 97 | if err != nil { 98 | return 99 | } 100 | return 101 | } 102 | 103 | func (v *VideoDao) GetTotalFavorited(uid int) (count int64, err error) { 104 | var videos []*model.Video 105 | err = v.Model(&model.Video{}).Where("author_id = ?", uid).Find(&videos).Error 106 | if err != nil { 107 | return 108 | } 109 | for _, video := range videos { 110 | var favoriteCount int64 111 | err = v.Model(&model.Favorite{}).Where("video_id = ?", video.ID).Count(&favoriteCount).Error 112 | if err != nil { 113 | return 114 | } 115 | count += favoriteCount 116 | } 117 | return 118 | } 119 | 120 | func (v *VideoDao) GetIsFollowed(uid int, token string) (isFollowed bool, err error) { 121 | 122 | baseID, err := util.GetUserIdFromToken(token) 123 | if err != nil { 124 | return 125 | } 126 | var follow model.Follow 127 | err = v.Model(&model.Follow{}).Where("user_id = ?", uid).Where("followed_user_id = ?", baseID).Limit(1).Find(&follow).Error 128 | if err != nil { 129 | return 130 | } 131 | if follow.ID != 0 { 132 | isFollowed = true 133 | } else { 134 | isFollowed = false 135 | } 136 | return 137 | } 138 | 139 | func (v *VideoDao) GetVideoListByUserId(uid int) (videos []*model.Video, err error) { 140 | err = v.Model(&model.Video{}).Where("author_id = ?", uid).Order("created_at desc").Limit(30).Find(&videos).Error 141 | if err != nil { 142 | return 143 | } 144 | return 145 | } 146 | 147 | func (v *VideoDao) CreateVideo(video *model.Video) (err error) { 148 | err = v.Model(&model.Video{}).Create(&video).Error 149 | if err != nil { 150 | return 151 | } 152 | return 153 | } 154 | 155 | func (v *VideoDao) FindUser(video *model.Video) (user *model.User, err error) { 156 | //gorm通过外键查询 157 | err = v.Model(&video).Association("Author").Find(&user) 158 | if err != nil { 159 | return 160 | } 161 | return 162 | } 163 | -------------------------------------------------------------------------------- /app/video/script/video.go: -------------------------------------------------------------------------------- 1 | package script 2 | 3 | import ( 4 | "ByteRhythm/app/video/service" 5 | "ByteRhythm/consts" 6 | "ByteRhythm/idl/video/videoPb" 7 | "ByteRhythm/mq" 8 | "context" 9 | "encoding/json" 10 | ) 11 | 12 | type SyncVideo struct { 13 | } 14 | 15 | func VideoCreateSync(ctx context.Context) { 16 | Sync := new(SyncVideo) 17 | err := Sync.SyncVideoCreate(ctx, consts.CreateVideoQueue) 18 | if err != nil { 19 | return 20 | } 21 | } 22 | 23 | func Video2RedisSync(ctx context.Context) { 24 | Sync := new(SyncVideo) 25 | err := Sync.SyncVideo2Redis(ctx, consts.Video2RedisQueue) 26 | if err != nil { 27 | return 28 | } 29 | } 30 | 31 | func (s *SyncVideo) SyncVideoCreate(ctx context.Context, queueName string) error { 32 | msg, err := mq.ConsumeMessage(ctx, queueName) 33 | if err != nil { 34 | return err 35 | } 36 | var forever chan struct{} 37 | go func() { 38 | for d := range msg { 39 | // 落库 40 | var req *videoPb.PublishRequest 41 | err = json.Unmarshal(d.Body, &req) 42 | if err != nil { 43 | return 44 | } 45 | err = service.VideoMQ2DB(ctx, req) 46 | if err != nil { 47 | return 48 | } 49 | d.Ack(false) 50 | } 51 | }() 52 | <-forever 53 | return nil 54 | } 55 | 56 | func (s *SyncVideo) SyncVideo2Redis(ctx context.Context, queueName string) error { 57 | msg, err := mq.ConsumeMessage(ctx, queueName) 58 | if err != nil { 59 | return err 60 | } 61 | var forever chan struct{} 62 | go func() { 63 | for d := range msg { 64 | // 落库 65 | var req *videoPb.Video 66 | err = json.Unmarshal(d.Body, &req) 67 | if err != nil { 68 | return 69 | } 70 | err = service.VideoMQ2Redis(ctx, req) 71 | if err != nil { 72 | return 73 | } 74 | d.Ack(false) 75 | } 76 | }() 77 | <-forever 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /app/video/tmp/README.md: -------------------------------------------------------------------------------- 1 | # 存放保存到本地的临时文件 -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | go build -o gateway app/gateway/cmd/main.go 3 | go build -o user app/user/cmd/main.go 4 | go build -o video app/video/cmd/main.go 5 | go build -o favorite app/favorite/cmd/main.go 6 | go build -o comment app/comment/cmd/main.go 7 | go build -o relation app/relation/cmd/main.go 8 | go build -o message app/message/cmd/main.go 9 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "gopkg.in/ini.v1" 8 | ) 9 | 10 | var ( 11 | wg sync.WaitGroup 12 | DBHost string 13 | DBPort string 14 | DBUser string 15 | DBPassWord string 16 | DBName string 17 | Charset string 18 | Avatar string 19 | Background string 20 | Signature string 21 | EtcdHost string 22 | EtcdPort string 23 | RedisHost string 24 | RedisPort string 25 | JaegerHost string 26 | JaegerPort string 27 | HttpHost string 28 | HttpPort string 29 | UserServiceAddress string 30 | VideoServiceAddress string 31 | MessageServiceAddress string 32 | CommentServiceAddress string 33 | RelationServiceAddress string 34 | FavoriteServiceAddress string 35 | Bucket string 36 | AccessKey string 37 | SecretKey string 38 | Domain string 39 | RabbitMQ string 40 | RabbitMQHost string 41 | RabbitMQPort string 42 | RabbitMQUser string 43 | RabbitMQPassWord string 44 | ) 45 | 46 | func Init() { 47 | file, err := ini.Load("./config/config.ini") 48 | if err != nil { 49 | fmt.Printf("load config failed,err:%v\n", err) 50 | } 51 | wg.Add(9) 52 | go LoadMySQL(file) 53 | go LoadUser(file) 54 | go LoadEtcd(file) 55 | go LoadJaeger(file) 56 | go LoadGin(file) 57 | go LoadService(file) 58 | go LoadQiNiuYun(file) 59 | go LoadRabbitMQ(file) 60 | go LoadRedis(file) 61 | wg.Wait() 62 | } 63 | 64 | func LoadMySQL(file *ini.File) { 65 | DBHost = file.Section("MySQL").Key("DBHost").String() 66 | DBPort = file.Section("MySQL").Key("DBPort").String() 67 | DBUser = file.Section("MySQL").Key("DBUser").String() 68 | DBPassWord = file.Section("MySQL").Key("DBPassWord").String() 69 | DBName = file.Section("MySQL").Key("DBName").String() 70 | Charset = file.Section("MySQL").Key("Charset").String() 71 | wg.Done() 72 | } 73 | 74 | func LoadUser(file *ini.File) { 75 | Avatar = file.Section("User").Key("Avatar").String() 76 | Background = file.Section("User").Key("Background").String() 77 | Signature = file.Section("User").Key("Signature").String() 78 | wg.Done() 79 | } 80 | 81 | func LoadEtcd(file *ini.File) { 82 | EtcdHost = file.Section("Etcd").Key("EtcdHost").String() 83 | EtcdPort = file.Section("Etcd").Key("EtcdPort").String() 84 | wg.Done() 85 | } 86 | 87 | func LoadJaeger(file *ini.File) { 88 | JaegerHost = file.Section("Jaeger").Key("JaegerHost").String() 89 | JaegerPort = file.Section("Jaeger").Key("JaegerPort").String() 90 | wg.Done() 91 | } 92 | 93 | func LoadGin(file *ini.File) { 94 | HttpHost = file.Section("Gin").Key("HttpHost").String() 95 | HttpPort = file.Section("Gin").Key("HttpPort").String() 96 | wg.Done() 97 | } 98 | 99 | func LoadService(file *ini.File) { 100 | UserServiceAddress = file.Section("Service").Key("UserServiceAddress").String() 101 | VideoServiceAddress = file.Section("Service").Key("VideoServiceAddress").String() 102 | MessageServiceAddress = file.Section("Service").Key("MessageServiceAddress").String() 103 | CommentServiceAddress = file.Section("Service").Key("CommentServiceAddress").String() 104 | RelationServiceAddress = file.Section("Service").Key("RelationServiceAddress").String() 105 | FavoriteServiceAddress = file.Section("Service").Key("FavoriteServiceAddress").String() 106 | wg.Done() 107 | } 108 | 109 | func LoadQiNiuYun(file *ini.File) { 110 | Bucket = file.Section("QiNiuYun").Key("Bucket").String() 111 | AccessKey = file.Section("QiNiuYun").Key("AccessKey").String() 112 | SecretKey = file.Section("QiNiuYun").Key("SecretKey").String() 113 | Domain = file.Section("QiNiuYun").Key("Domain").String() 114 | wg.Done() 115 | } 116 | 117 | func LoadRabbitMQ(file *ini.File) { 118 | RabbitMQ = file.Section("RabbitMQ").Key("RabbitMQ").String() 119 | RabbitMQHost = file.Section("RabbitMQ").Key("RabbitMQHost").String() 120 | RabbitMQPort = file.Section("RabbitMQ").Key("RabbitMQPort").String() 121 | RabbitMQUser = file.Section("RabbitMQ").Key("RabbitMQUser").String() 122 | RabbitMQPassWord = file.Section("RabbitMQ").Key("RabbitMQPassWord").String() 123 | wg.Done() 124 | } 125 | 126 | func LoadRedis(file *ini.File) { 127 | RedisHost = file.Section("Redis").Key("RedisHost").String() 128 | RedisPort = file.Section("Redis").Key("RedisPort").String() 129 | wg.Done() 130 | } 131 | -------------------------------------------------------------------------------- /config/config.ini: -------------------------------------------------------------------------------- 1 | [Gin] 2 | AppMode = debug 3 | HttpHost = 0.0.0.0 4 | HttpPort = 8080 5 | 6 | [MySQL] 7 | DBHost = 127.0.0.1 8 | DBPort = 3306 9 | DBUser = root 10 | DBPassWord = 123456 11 | DBName = tiktok 12 | Charset = utf8mb4 13 | 14 | [Etcd] 15 | EtcdHost = 127.0.0.1 16 | EtcdPort = 2379 17 | 18 | [Jaeger] 19 | JaegerHost = 127.0.0.1 20 | JaegerPort = 6831 21 | 22 | [Redis] 23 | RedisHost = 127.0.0.1 24 | RedisPort = 6379 25 | 26 | [Service] 27 | UserServiceAddress = 127.0.0.1:8081 28 | VideoServiceAddress = 127.0.0.1:8082 29 | FavoriteServiceAddress = 127.0.0.1:8083 30 | CommentServiceAddress = 127.0.0.1:8084 31 | RelationServiceAddress = 127.0.0.1:8085 32 | MessageServiceAddress = 127.0.0.1:8086 33 | 34 | [RabbitMQ] 35 | RabbitMQ = amqp 36 | RabbitMQHost = 127.0.0.1 37 | RabbitMQPort = 5672 38 | RabbitMQUser = guest 39 | RabbitMQPassWord = guest 40 | 41 | [User] 42 | Avatar = http://rz2n87yck.hn-bkt.clouddn.com/avatar.jpg 43 | Background = http://rz2n87yck.hn-bkt.clouddn.com/background.png 44 | Signature = 又来看我的主页啦~ 45 | 46 | [QiNiuYun] 47 | Bucket = your bucket 48 | AccessKey = your access key 49 | SecretKey = your secret key 50 | Domain = your domain 51 | 52 | -------------------------------------------------------------------------------- /consts/mq.go: -------------------------------------------------------------------------------- 1 | package consts 2 | 3 | const CreateVideoQueue = "video-create-queue" 4 | const Video2RedisQueue = "video-redis-queue" 5 | const CreateFavorite2MQ = "favorite-create-queue" 6 | const DeleteFavorite2MQ = "favorite-delete-queue" 7 | -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- 1 | #第一阶段 2 | FROM ubuntu:20.04 as builder 3 | ## 设置时区 4 | RUN apt-get -y update && DEBIAN_FRONTEND="noninteractive" apt -y install tzdata 5 | ENV TZ=Asia/Shanghai 6 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 7 | 8 | WORKDIR /workspace 9 | 10 | COPY . . 11 | 12 | ENV GO111MODULE=on \ 13 | GOPROXY=https://goproxy.cn,direct 14 | 15 | ## 安装go1.20.7 16 | RUN apt install -y wget 17 | RUN wget https://go.dev/dl/go1.20.7.linux-amd64.tar.gz &&\ 18 | tar -C /usr/local -xzf go1.20.7.linux-amd64.tar.gz &&\ 19 | ## 软链接 20 | ln -s /usr/local/go/bin/* /usr/bin/ 21 | # 配置ffmpeg环境 22 | RUN apt-get install -y autoconf automake build-essential libass-dev libfreetype6-dev libsdl1.2-dev libtheora-dev libtool libva-dev libvdpau-dev libvorbis-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev pkg-config texi2html zlib1g-dev 23 | RUN apt install -y libavdevice-dev libavfilter-dev libswscale-dev libavcodec-dev libavformat-dev libswresample-dev libavutil-dev 24 | RUN apt-get install -y yasm 25 | # 设置环境变量 26 | ENV FFMPEG_ROOT=$HOME/ffmpeg \ 27 | CGO_LDFLAGS="-L$FFMPEG_ROOT/lib/ -lavcodec -lavformat -lavutil -lswscale -lswresample -lavdevice -lavfilter" \ 28 | CGO_CFLAGS="-I$FFMPEG_ROOT/include" \ 29 | LD_LIBRARY_PATH=$HOME/ffmpeg/lib 30 | 31 | 32 | RUN go mod download 33 | 34 | RUN chmod +x build.sh && ./build.sh 35 | 36 | #第二阶段 37 | FROM ubuntu:20.04 AS production 38 | WORKDIR /app 39 | 40 | ## 设置时区 41 | RUN apt-get -y update && DEBIAN_FRONTEND="noninteractive" apt -y install tzdata 42 | ENV TZ=Asia/Shanghai 43 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 44 | 45 | COPY ./*.sh . 46 | 47 | #本机目录不能使用绝对路径,因为它本身就是一个相对路径 48 | #只会复制本机的config目录下所有文件,而不会创建config目录,所以后面需要指定 49 | COPY ./config ./config 50 | # COPY . . 51 | RUN mkdir -p ./app/video/tmp/ 52 | RUN apt install -y wget systemctl 53 | 54 | # 配置ffmpeg环境 55 | RUN apt-get install -y autoconf automake build-essential libass-dev libfreetype6-dev libsdl1.2-dev libtheora-dev libtool libva-dev libvdpau-dev libvorbis-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev pkg-config texi2html zlib1g-dev 56 | RUN apt install -y libavdevice-dev libavfilter-dev libswscale-dev libavcodec-dev libavformat-dev libswresample-dev libavutil-dev 57 | RUN apt-get install -y yasm 58 | # 设置环境变量 59 | ENV FFMPEG_ROOT=$HOME/ffmpeg \ 60 | CGO_LDFLAGS="-L$FFMPEG_ROOT/lib/ -lavcodec -lavformat -lavutil -lswscale -lswresample -lavdevice -lavfilter" \ 61 | CGO_CFLAGS="-I$FFMPEG_ROOT/include" \ 62 | LD_LIBRARY_PATH=$HOME/ffmpeg/lib 63 | 64 | ## 安装 etcd v3.5.9 65 | RUN wget https://github.com/etcd-io/etcd/releases/download/v3.5.9/etcd-v3.5.9-linux-amd64.tar.gz &&\ 66 | tar -zxvf etcd-v3.5.9-linux-amd64.tar.gz &&\ 67 | cd etcd-v3.5.9-linux-amd64 &&\ 68 | chmod +x etcd &&\ 69 | mv ./etcd* /usr/local/bin/ 70 | 71 | 72 | ## 安装 Jaeger v3.5.9 73 | RUN wget -c https://github.com/jaegertracing/jaeger/releases/download/v1.48.0/jaeger-1.48.0-linux-amd64.tar.gz &&\ 74 | tar -zxvf jaeger-1.48.0-linux-amd64.tar.gz &&\ 75 | cd jaeger-1.48.0-linux-amd64 &&\ 76 | chmod a+x jaeger-* &&\ 77 | mv ./jaeger-* /usr/local/bin/ 78 | # nohup ./jaeger-all-in-one --collector.zipkin.host-port=:9411 & 79 | 80 | ## 安装 RabbitMQ 81 | ### 导入 RabbitMQ 的存储库密钥 82 | RUN wget -O- https://github.com/rabbitmq/signing-keys/releases/download/2.0/rabbitmq-release-signing-key.asc | apt-key add - 83 | ### 将存储库添加到系统 84 | RUN apt-get install -y apt-transport-https &&\ 85 | cho "deb https://dl.bintray.com/rabbitmq-erlang/debian focal erlang" | tee /etc/apt/sources.list.d/bintray.erlang.list &&\ 86 | echo "deb https://dl.bintray.com/rabbitmq/debian focal main" | tee /etc/apt/sources.list.d/bintray.rabbitmq.list 87 | ### 安装 RabbitMQ 和 Erlang 88 | RUN apt-get install -y rabbitmq-server 89 | 90 | ## 安装 注意:Redis安装会自动启动 91 | RUN apt install -y redis-server 92 | 93 | 94 | COPY --from=builder /workspace/gateway . 95 | COPY --from=builder /workspace/user . 96 | COPY --from=builder /workspace/video . 97 | COPY --from=builder /workspace/relation . 98 | COPY --from=builder /workspace/favorite . 99 | COPY --from=builder /workspace/comment . 100 | COPY --from=builder /workspace/message . 101 | EXPOSE 8080 16686 102 | 103 | # RUN chmod +x /app/run.sh 等效下面语句 104 | RUN cd /app &&chmod +x start.sh 105 | CMD ["/app/start.sh"] 106 | 107 | 108 | ## docker build -t david945/byterhythm:v2.1 . 109 | ## docker run -it -p 8080:8080/tcp -p 16686:16686/tcp --name byterhythm david945/byterhythm:v2.1 110 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module ByteRhythm 2 | 3 | go 1.20 4 | 5 | replace google.golang.org/grpc => google.golang.org/grpc v1.29.0 6 | 7 | require ( 8 | github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 9 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 10 | github.com/gin-contrib/cors v1.4.0 11 | github.com/gin-gonic/gin v1.9.1 12 | github.com/giorgisio/goav v0.1.0 13 | github.com/go-micro/plugins/v4/registry/etcd v1.2.0 14 | github.com/go-micro/plugins/v4/wrapper/ratelimiter/uber v1.2.0 15 | github.com/go-micro/plugins/v4/wrapper/select/roundrobin v1.2.0 16 | github.com/go-micro/plugins/v4/wrapper/trace/opentracing v1.2.0 17 | github.com/go-redis/redis/v8 v8.11.5 18 | github.com/google/uuid v1.2.0 19 | github.com/opentracing/opentracing-go v1.2.0 20 | github.com/qiniu/go-sdk/v7 v7.17.0 21 | github.com/streadway/amqp v1.1.0 22 | github.com/stretchr/testify v1.8.4 23 | github.com/uber/jaeger-client-go v2.30.0+incompatible 24 | go-micro.dev/v4 v4.9.0 25 | google.golang.org/protobuf v1.30.0 26 | gopkg.in/ini.v1 v1.62.0 27 | gorm.io/driver/mysql v1.5.1 28 | gorm.io/gorm v1.25.4 29 | ) 30 | 31 | require ( 32 | github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect 33 | github.com/Microsoft/go-winio v0.5.0 // indirect 34 | github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect 35 | github.com/acomagu/bufpipe v1.0.3 // indirect 36 | github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect 37 | github.com/bitly/go-simplejson v0.5.0 // indirect 38 | github.com/bytedance/sonic v1.9.1 // indirect 39 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 40 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 41 | github.com/coreos/go-semver v0.3.0 // indirect 42 | github.com/coreos/go-systemd/v22 v22.3.2 // indirect 43 | github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect 44 | github.com/davecgh/go-spew v1.1.1 // indirect 45 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 46 | github.com/emirpasic/gods v1.12.0 // indirect 47 | github.com/evanphx/json-patch/v5 v5.5.0 // indirect 48 | github.com/felixge/httpsnoop v1.0.1 // indirect 49 | github.com/fsnotify/fsnotify v1.4.9 // indirect 50 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 51 | github.com/gin-contrib/sse v0.1.0 // indirect 52 | github.com/go-acme/lego/v4 v4.4.0 // indirect 53 | github.com/go-git/gcfg v1.5.0 // indirect 54 | github.com/go-git/go-billy/v5 v5.3.1 // indirect 55 | github.com/go-git/go-git/v5 v5.4.2 // indirect 56 | github.com/go-playground/locales v0.14.1 // indirect 57 | github.com/go-playground/universal-translator v0.18.1 // indirect 58 | github.com/go-playground/validator/v10 v10.14.0 // indirect 59 | github.com/go-sql-driver/mysql v1.7.0 // indirect 60 | github.com/gobwas/httphead v0.1.0 // indirect 61 | github.com/gobwas/pool v0.2.1 // indirect 62 | github.com/gobwas/ws v1.0.4 // indirect 63 | github.com/goccy/go-json v0.10.2 // indirect 64 | github.com/gogo/protobuf v1.3.2 // indirect 65 | github.com/golang/protobuf v1.5.2 // indirect 66 | github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect 67 | github.com/gorilla/handlers v1.5.1 // indirect 68 | github.com/imdario/mergo v0.3.12 // indirect 69 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 70 | github.com/jinzhu/inflection v1.0.0 // indirect 71 | github.com/jinzhu/now v1.1.5 // indirect 72 | github.com/json-iterator/go v1.1.12 // indirect 73 | github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect 74 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 75 | github.com/leodido/go-urn v1.2.4 // indirect 76 | github.com/mattn/go-isatty v0.0.19 // indirect 77 | github.com/miekg/dns v1.1.43 // indirect 78 | github.com/mitchellh/go-homedir v1.1.0 // indirect 79 | github.com/mitchellh/hashstructure v1.1.0 // indirect 80 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 81 | github.com/modern-go/reflect2 v1.0.2 // indirect 82 | github.com/nxadm/tail v1.4.8 // indirect 83 | github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect 84 | github.com/patrickmn/go-cache v2.1.0+incompatible // indirect 85 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 86 | github.com/pkg/errors v0.9.1 // indirect 87 | github.com/pmezard/go-difflib v1.0.0 // indirect 88 | github.com/russross/blackfriday/v2 v2.0.1 // indirect 89 | github.com/sergi/go-diff v1.1.0 // indirect 90 | github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect 91 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 92 | github.com/uber/jaeger-lib v2.4.1+incompatible // indirect 93 | github.com/ugorji/go/codec v1.2.11 // indirect 94 | github.com/urfave/cli/v2 v2.3.0 // indirect 95 | github.com/xanzy/ssh-agent v0.3.0 // indirect 96 | go.etcd.io/etcd/api/v3 v3.5.2 // indirect 97 | go.etcd.io/etcd/client/pkg/v3 v3.5.2 // indirect 98 | go.etcd.io/etcd/client/v3 v3.5.2 // indirect 99 | go.uber.org/atomic v1.7.0 // indirect 100 | go.uber.org/multierr v1.6.0 // indirect 101 | go.uber.org/ratelimit v0.2.0 // indirect 102 | go.uber.org/zap v1.17.0 // indirect 103 | golang.org/x/arch v0.3.0 // indirect 104 | golang.org/x/crypto v0.9.0 // indirect 105 | golang.org/x/net v0.10.0 // indirect 106 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect 107 | golang.org/x/sys v0.8.0 // indirect 108 | golang.org/x/text v0.9.0 // indirect 109 | google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect 110 | google.golang.org/grpc v1.38.0 // indirect 111 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 112 | gopkg.in/warnings.v0 v0.1.2 // indirect 113 | gopkg.in/yaml.v3 v3.0.1 // indirect 114 | ) 115 | -------------------------------------------------------------------------------- /idl/comment/commentPb/commentService.pb.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: commentService.proto 3 | 4 | package commentPb 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "google.golang.org/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | import ( 13 | context "context" 14 | api "go-micro.dev/v4/api" 15 | client "go-micro.dev/v4/client" 16 | server "go-micro.dev/v4/server" 17 | ) 18 | 19 | // Reference imports to suppress errors if they are not otherwise used. 20 | var _ = proto.Marshal 21 | var _ = fmt.Errorf 22 | var _ = math.Inf 23 | 24 | // Reference imports to suppress errors if they are not otherwise used. 25 | var _ api.Endpoint 26 | var _ context.Context 27 | var _ client.Option 28 | var _ server.Option 29 | 30 | // Api Endpoints for CommentService service 31 | 32 | func NewCommentServiceEndpoints() []*api.Endpoint { 33 | return []*api.Endpoint{} 34 | } 35 | 36 | // Client API for CommentService service 37 | 38 | type CommentService interface { 39 | CommentAction(ctx context.Context, in *CommentActionRequest, opts ...client.CallOption) (*CommentActionResponse, error) 40 | CommentList(ctx context.Context, in *CommentListRequest, opts ...client.CallOption) (*CommentListResponse, error) 41 | } 42 | 43 | type commentService struct { 44 | c client.Client 45 | name string 46 | } 47 | 48 | func NewCommentService(name string, c client.Client) CommentService { 49 | return &commentService{ 50 | c: c, 51 | name: name, 52 | } 53 | } 54 | 55 | func (c *commentService) CommentAction(ctx context.Context, in *CommentActionRequest, opts ...client.CallOption) (*CommentActionResponse, error) { 56 | req := c.c.NewRequest(c.name, "CommentService.CommentAction", in) 57 | out := new(CommentActionResponse) 58 | err := c.c.Call(ctx, req, out, opts...) 59 | if err != nil { 60 | return nil, err 61 | } 62 | return out, nil 63 | } 64 | 65 | func (c *commentService) CommentList(ctx context.Context, in *CommentListRequest, opts ...client.CallOption) (*CommentListResponse, error) { 66 | req := c.c.NewRequest(c.name, "CommentService.CommentList", in) 67 | out := new(CommentListResponse) 68 | err := c.c.Call(ctx, req, out, opts...) 69 | if err != nil { 70 | return nil, err 71 | } 72 | return out, nil 73 | } 74 | 75 | // Server API for CommentService service 76 | 77 | type CommentServiceHandler interface { 78 | CommentAction(context.Context, *CommentActionRequest, *CommentActionResponse) error 79 | CommentList(context.Context, *CommentListRequest, *CommentListResponse) error 80 | } 81 | 82 | func RegisterCommentServiceHandler(s server.Server, hdlr CommentServiceHandler, opts ...server.HandlerOption) error { 83 | type commentService interface { 84 | CommentAction(ctx context.Context, in *CommentActionRequest, out *CommentActionResponse) error 85 | CommentList(ctx context.Context, in *CommentListRequest, out *CommentListResponse) error 86 | } 87 | type CommentService struct { 88 | commentService 89 | } 90 | h := &commentServiceHandler{hdlr} 91 | return s.Handle(s.NewHandler(&CommentService{h}, opts...)) 92 | } 93 | 94 | type commentServiceHandler struct { 95 | CommentServiceHandler 96 | } 97 | 98 | func (h *commentServiceHandler) CommentAction(ctx context.Context, in *CommentActionRequest, out *CommentActionResponse) error { 99 | return h.CommentServiceHandler.CommentAction(ctx, in, out) 100 | } 101 | 102 | func (h *commentServiceHandler) CommentList(ctx context.Context, in *CommentListRequest, out *CommentListResponse) error { 103 | return h.CommentServiceHandler.CommentList(ctx, in, out) 104 | } 105 | -------------------------------------------------------------------------------- /idl/comment/commentService.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package commentService; 3 | option go_package = "./commentPb"; 4 | 5 | message Comment { 6 | // @inject_tag: json:"id" form:"id" 7 | int64 id = 1; // 视频评论id 8 | // @inject_tag: json:"user" form:"user" 9 | User user = 2; // 评论用户信息 10 | // @inject_tag: json:"content" form:"content" 11 | string content = 3; // 评论内容 12 | // @inject_tag: json:"create_date" form:"create_date" 13 | string create_date = 4; // 评论发布日期,格式 mm-dd 14 | } 15 | 16 | message User { 17 | // @inject_tag: json:"id" form:"id" 18 | int64 id = 1; // 用户id 19 | // @inject_tag: json:"name" form:"name" 20 | string name = 2; // 用户名称 21 | // @inject_tag: json:"follow_count" form:"follow_count" 22 | int64 follow_count = 3; // 关注总数 23 | // @inject_tag: json:"follower_count" form:"follower_count" 24 | int64 follower_count = 4; // 粉丝总数 25 | // @inject_tag: json:"is_follow" form:"is_follow" 26 | bool is_follow = 5; // true-已关注,false-未关注 27 | // @inject_tag: json:"avatar" form:"avatar" 28 | string avatar = 6; //用户头像 29 | // @inject_tag: json:"background_image" form:"background_image" 30 | string background_image = 7; //用户个人页顶部大图 31 | // @inject_tag: json:"signature" form:"signature" 32 | string signature = 8; //个人简介 33 | // @inject_tag: json:"total_favorited" form:"total_favorited" 34 | int64 total_favorited = 9; //获赞数量 35 | // @inject_tag: json:"work_count" form:"work_count" 36 | int64 work_count = 10; //作品数量 37 | // @inject_tag: json:"favorite_count" form:"favorite_count" 38 | int64 favorite_count = 11; //点赞数量 39 | } 40 | 41 | message CommentActionRequest { 42 | // @inject_tag: json:"token" form:"token" 43 | string token = 1; // 用户鉴权token 44 | // @inject_tag: json:"video_id" form:"video_id" 45 | int64 video_id = 2; // 视频id 46 | // @inject_tag: json:"action_type" form:"action_type" 47 | int32 action_type = 3; // 1-发布评论,2-删除评论 48 | // @inject_tag: json:"comment_text" form:"comment_text" 49 | string comment_text = 4; // 用户填写的评论内容,在action_type=1的时候使用 50 | // @inject_tag: json:"comment_id" form:"comment_id" 51 | int64 comment_id = 5; // 要删除的评论id,在action_type=2的时候使用 52 | } 53 | 54 | message CommentActionResponse { 55 | // @inject_tag: json:"status_code" form:"status_code" 56 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 57 | // @inject_tag: json:"status_msg" form:"status_msg" 58 | string status_msg = 2; // 返回状态描述 59 | // @inject_tag: json:"comment" form:"comment" 60 | Comment comment = 3; // 评论成功返回评论内容,不需要重新拉取整个列表 61 | } 62 | 63 | message CommentListRequest { 64 | // @inject_tag: json:"token" form:"token" 65 | string token = 1; // 用户鉴权token 66 | // @inject_tag: json:"video_id" form:"video_id" 67 | int64 video_id = 2; // 视频id 68 | } 69 | 70 | message CommentListResponse { 71 | // @inject_tag: json:"status_code" form:"status_code" 72 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 73 | // @inject_tag: json:"status_msg" form:"status_msg" 74 | string status_msg = 2; // 返回状态描述 75 | // @inject_tag: json:"comment_list" form:"comment_list" 76 | repeated Comment comment_list = 3; // 评论列表 77 | } 78 | 79 | service CommentService { 80 | rpc CommentAction(CommentActionRequest) returns (CommentActionResponse) {} 81 | rpc CommentList(CommentListRequest) returns (CommentListResponse) {} 82 | } 83 | -------------------------------------------------------------------------------- /idl/favorite/favoritePb/favoriteService.pb.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: favoriteService.proto 3 | 4 | package favoritePb 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "google.golang.org/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | import ( 13 | context "context" 14 | api "go-micro.dev/v4/api" 15 | client "go-micro.dev/v4/client" 16 | server "go-micro.dev/v4/server" 17 | ) 18 | 19 | // Reference imports to suppress errors if they are not otherwise used. 20 | var _ = proto.Marshal 21 | var _ = fmt.Errorf 22 | var _ = math.Inf 23 | 24 | // Reference imports to suppress errors if they are not otherwise used. 25 | var _ api.Endpoint 26 | var _ context.Context 27 | var _ client.Option 28 | var _ server.Option 29 | 30 | // Api Endpoints for FavoriteService service 31 | 32 | func NewFavoriteServiceEndpoints() []*api.Endpoint { 33 | return []*api.Endpoint{} 34 | } 35 | 36 | // Client API for FavoriteService service 37 | 38 | type FavoriteService interface { 39 | FavoriteAction(ctx context.Context, in *FavoriteActionRequest, opts ...client.CallOption) (*FavoriteActionResponse, error) 40 | FavoriteList(ctx context.Context, in *FavoriteListRequest, opts ...client.CallOption) (*FavoriteListResponse, error) 41 | } 42 | 43 | type favoriteService struct { 44 | c client.Client 45 | name string 46 | } 47 | 48 | func NewFavoriteService(name string, c client.Client) FavoriteService { 49 | return &favoriteService{ 50 | c: c, 51 | name: name, 52 | } 53 | } 54 | 55 | func (c *favoriteService) FavoriteAction(ctx context.Context, in *FavoriteActionRequest, opts ...client.CallOption) (*FavoriteActionResponse, error) { 56 | req := c.c.NewRequest(c.name, "FavoriteService.FavoriteAction", in) 57 | out := new(FavoriteActionResponse) 58 | err := c.c.Call(ctx, req, out, opts...) 59 | if err != nil { 60 | return nil, err 61 | } 62 | return out, nil 63 | } 64 | 65 | func (c *favoriteService) FavoriteList(ctx context.Context, in *FavoriteListRequest, opts ...client.CallOption) (*FavoriteListResponse, error) { 66 | req := c.c.NewRequest(c.name, "FavoriteService.FavoriteList", in) 67 | out := new(FavoriteListResponse) 68 | err := c.c.Call(ctx, req, out, opts...) 69 | if err != nil { 70 | return nil, err 71 | } 72 | return out, nil 73 | } 74 | 75 | // Server API for FavoriteService service 76 | 77 | type FavoriteServiceHandler interface { 78 | FavoriteAction(context.Context, *FavoriteActionRequest, *FavoriteActionResponse) error 79 | FavoriteList(context.Context, *FavoriteListRequest, *FavoriteListResponse) error 80 | } 81 | 82 | func RegisterFavoriteServiceHandler(s server.Server, hdlr FavoriteServiceHandler, opts ...server.HandlerOption) error { 83 | type favoriteService interface { 84 | FavoriteAction(ctx context.Context, in *FavoriteActionRequest, out *FavoriteActionResponse) error 85 | FavoriteList(ctx context.Context, in *FavoriteListRequest, out *FavoriteListResponse) error 86 | } 87 | type FavoriteService struct { 88 | favoriteService 89 | } 90 | h := &favoriteServiceHandler{hdlr} 91 | return s.Handle(s.NewHandler(&FavoriteService{h}, opts...)) 92 | } 93 | 94 | type favoriteServiceHandler struct { 95 | FavoriteServiceHandler 96 | } 97 | 98 | func (h *favoriteServiceHandler) FavoriteAction(ctx context.Context, in *FavoriteActionRequest, out *FavoriteActionResponse) error { 99 | return h.FavoriteServiceHandler.FavoriteAction(ctx, in, out) 100 | } 101 | 102 | func (h *favoriteServiceHandler) FavoriteList(ctx context.Context, in *FavoriteListRequest, out *FavoriteListResponse) error { 103 | return h.FavoriteServiceHandler.FavoriteList(ctx, in, out) 104 | } 105 | -------------------------------------------------------------------------------- /idl/favorite/favoriteService.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package favoriteService; 3 | option go_package = "./favoritePb"; 4 | 5 | message Video { 6 | // @inject_tag: json:"id" form:"id" 7 | int64 id = 1; // 视频唯一标识 8 | // @inject_tag: json:"author" form:"author" 9 | User author = 2; // 视频作者信息 10 | // @inject_tag: json:"play_url" form:"play_url" 11 | string play_url = 3; // 视频播放地址 12 | // @inject_tag: json:"cover_url" form:"cover_url" 13 | string cover_url = 4; // 视频封面地址 14 | // @inject_tag: json:"favorite_count" form:"favorite_count" 15 | int64 favorite_count = 5; // 视频的点赞总数 16 | // @inject_tag: json:"comment_count" form:"comment_count" 17 | int64 comment_count = 6; // 视频的评论总数 18 | // @inject_tag: json:"is_favorite" form:"is_favorite" 19 | bool is_favorite = 7; // true-已点赞,false-未点赞 20 | // @inject_tag: json:"title" form:"title" 21 | string title = 8; // 视频标题 22 | } 23 | 24 | message User { 25 | // @inject_tag: json:"id" form:"id" 26 | int64 id = 1; // 用户id 27 | // @inject_tag: json:"name" form:"name" 28 | string name = 2; // 用户名称 29 | // @inject_tag: json:"follow_count" form:"follow_count" 30 | int64 follow_count = 3; // 关注总数 31 | // @inject_tag: json:"follower_count" form:"follower_count" 32 | int64 follower_count = 4; // 粉丝总数 33 | // @inject_tag: json:"is_follow" form:"is_follow" 34 | bool is_follow = 5; // true-已关注,false-未关注 35 | // @inject_tag: json:"avatar" form:"avatar" 36 | string avatar = 6; //用户头像 37 | // @inject_tag: json:"background_image" form:"background_image" 38 | string background_image = 7; //用户个人页顶部大图 39 | // @inject_tag: json:"signature" form:"signature" 40 | string signature = 8; //个人简介 41 | // @inject_tag: json:"total_favorited" form:"total_favorited" 42 | int64 total_favorited = 9; //获赞数量 43 | // @inject_tag: json:"work_count" form:"work_count" 44 | int64 work_count = 10; //作品数量 45 | // @inject_tag: json:"favorite_count" form:"favorite_count" 46 | int64 favorite_count = 11; //点赞数量 47 | } 48 | 49 | message FavoriteActionRequest { 50 | // @inject_tag: json:"token" form:"token" 51 | string token = 1; // 用户鉴权token 52 | // @inject_tag: json:"video_id" form:"video_id" 53 | int64 video_id = 2; // 视频id 54 | // @inject_tag: json:"action_type" form:"action_type" 55 | int32 action_type = 3; // 1-点赞,2-取消点赞 56 | } 57 | 58 | message FavoriteActionResponse { 59 | // @inject_tag: json:"status_code" form:"status_code" 60 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 61 | // @inject_tag: json:"status_msg" form:"status_msg" 62 | string status_msg = 2; // 返回状态描述 63 | } 64 | 65 | message FavoriteListRequest { 66 | // @inject_tag: json:"user_id" form:"user_id" 67 | int64 user_id = 1; // 视频id 68 | // @inject_tag: json:"token" form:"token" 69 | string token = 2; // 用户鉴权token 70 | } 71 | 72 | message FavoriteListResponse { 73 | // @inject_tag: json:"status_code" form:"status_code" 74 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 75 | // @inject_tag: json:"status_msg" form:"status_msg" 76 | string status_msg = 2; // 返回状态描述 77 | // @inject_tag: json:"video_list" form:"video_list" 78 | repeated Video video_list = 3; // 视频列表 79 | } 80 | 81 | service FavoriteService { 82 | rpc FavoriteAction(FavoriteActionRequest) returns (FavoriteActionResponse) {} 83 | rpc FavoriteList(FavoriteListRequest) returns (FavoriteListResponse) {} 84 | } 85 | -------------------------------------------------------------------------------- /idl/message/messagePb/messageService.pb.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: messageService.proto 3 | 4 | package messagePb 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "google.golang.org/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | import ( 13 | context "context" 14 | api "go-micro.dev/v4/api" 15 | client "go-micro.dev/v4/client" 16 | server "go-micro.dev/v4/server" 17 | ) 18 | 19 | // Reference imports to suppress errors if they are not otherwise used. 20 | var _ = proto.Marshal 21 | var _ = fmt.Errorf 22 | var _ = math.Inf 23 | 24 | // Reference imports to suppress errors if they are not otherwise used. 25 | var _ api.Endpoint 26 | var _ context.Context 27 | var _ client.Option 28 | var _ server.Option 29 | 30 | // Api Endpoints for MessageService service 31 | 32 | func NewMessageServiceEndpoints() []*api.Endpoint { 33 | return []*api.Endpoint{} 34 | } 35 | 36 | // Client API for MessageService service 37 | 38 | type MessageService interface { 39 | ChatMessage(ctx context.Context, in *MessageChatRequest, opts ...client.CallOption) (*MessageChatResponse, error) 40 | ActionMessage(ctx context.Context, in *MessageActionRequest, opts ...client.CallOption) (*MessageActionResponse, error) 41 | } 42 | 43 | type messageService struct { 44 | c client.Client 45 | name string 46 | } 47 | 48 | func NewMessageService(name string, c client.Client) MessageService { 49 | return &messageService{ 50 | c: c, 51 | name: name, 52 | } 53 | } 54 | 55 | func (c *messageService) ChatMessage(ctx context.Context, in *MessageChatRequest, opts ...client.CallOption) (*MessageChatResponse, error) { 56 | req := c.c.NewRequest(c.name, "MessageService.ChatMessage", in) 57 | out := new(MessageChatResponse) 58 | err := c.c.Call(ctx, req, out, opts...) 59 | if err != nil { 60 | return nil, err 61 | } 62 | return out, nil 63 | } 64 | 65 | func (c *messageService) ActionMessage(ctx context.Context, in *MessageActionRequest, opts ...client.CallOption) (*MessageActionResponse, error) { 66 | req := c.c.NewRequest(c.name, "MessageService.ActionMessage", in) 67 | out := new(MessageActionResponse) 68 | err := c.c.Call(ctx, req, out, opts...) 69 | if err != nil { 70 | return nil, err 71 | } 72 | return out, nil 73 | } 74 | 75 | // Server API for MessageService service 76 | 77 | type MessageServiceHandler interface { 78 | ChatMessage(context.Context, *MessageChatRequest, *MessageChatResponse) error 79 | ActionMessage(context.Context, *MessageActionRequest, *MessageActionResponse) error 80 | } 81 | 82 | func RegisterMessageServiceHandler(s server.Server, hdlr MessageServiceHandler, opts ...server.HandlerOption) error { 83 | type messageService interface { 84 | ChatMessage(ctx context.Context, in *MessageChatRequest, out *MessageChatResponse) error 85 | ActionMessage(ctx context.Context, in *MessageActionRequest, out *MessageActionResponse) error 86 | } 87 | type MessageService struct { 88 | messageService 89 | } 90 | h := &messageServiceHandler{hdlr} 91 | return s.Handle(s.NewHandler(&MessageService{h}, opts...)) 92 | } 93 | 94 | type messageServiceHandler struct { 95 | MessageServiceHandler 96 | } 97 | 98 | func (h *messageServiceHandler) ChatMessage(ctx context.Context, in *MessageChatRequest, out *MessageChatResponse) error { 99 | return h.MessageServiceHandler.ChatMessage(ctx, in, out) 100 | } 101 | 102 | func (h *messageServiceHandler) ActionMessage(ctx context.Context, in *MessageActionRequest, out *MessageActionResponse) error { 103 | return h.MessageServiceHandler.ActionMessage(ctx, in, out) 104 | } 105 | -------------------------------------------------------------------------------- /idl/message/messageService.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package messageService; 3 | option go_package = "./messagePb"; 4 | 5 | message Message { 6 | // @inject_tag: json:"id" form:"id" 7 | int64 id = 1; // 消息id 8 | // @inject_tag: json:"to_user_id" form:"to_user_id" 9 | int64 to_user_id = 2; // 该消息接收者的id 10 | // @inject_tag: json:"from_user_id" form:"from_user_id" 11 | int64 from_user_id =3; // 该消息发送者的id 12 | // @inject_tag: json:"content" form:"content" 13 | string content = 4; // 消息内容 14 | // @inject_tag: json:"create_time" form:"create_time" 15 | string create_time = 5; // 消息创建时间 16 | } 17 | 18 | message MessageChatRequest { 19 | // @inject_tag: json:"token" form:"token" 20 | string token = 1; // 用户鉴权token 21 | // @inject_tag: json:"to_user_id" form:"to_user_id" 22 | int64 to_user_id = 2; // 对方用户id 23 | // @inject_tag: json:"pre_msg_time" form:"pre_msg_time" 24 | int64 pre_msg_time=3;//上次最新消息的时间(新增字段-apk更新中) 25 | } 26 | 27 | message MessageChatResponse { 28 | // @inject_tag: json:"status_code" form:"status_code" 29 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 30 | // @inject_tag: json:"status_msg" form:"status_msg" 31 | string status_msg = 2; // 返回状态描述 32 | // @inject_tag: json:"message_list" form:"message_list" 33 | repeated Message message_list = 3; // 消息列表 34 | } 35 | 36 | message MessageActionRequest { 37 | // @inject_tag: json:"token" form:"token" 38 | string token = 1; // 用户鉴权token 39 | // @inject_tag: json:"to_user_id" form:"to_user_id" 40 | int64 to_user_id = 2; // 对方用户id 41 | // @inject_tag: json:"action_type" form:"action_type" 42 | int32 action_type = 3; // 1-发送消息 43 | // @inject_tag: json:"content" form:"content" 44 | string content = 4; // 消息内容 45 | } 46 | 47 | message MessageActionResponse { 48 | // @inject_tag: json:"status_code" form:"status_code" 49 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 50 | // @inject_tag: json:"status_msg" form:"status_msg" 51 | string status_msg = 2; // 返回状态描述 52 | } 53 | 54 | service MessageService { 55 | rpc ChatMessage(MessageChatRequest) returns (MessageChatResponse) {} 56 | rpc ActionMessage(MessageActionRequest) returns (MessageActionResponse) {} 57 | } 58 | 59 | -------------------------------------------------------------------------------- /idl/relation/relationPb/relationService.pb.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: relationService.proto 3 | 4 | package relationPb 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "google.golang.org/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | import ( 13 | context "context" 14 | api "go-micro.dev/v4/api" 15 | client "go-micro.dev/v4/client" 16 | server "go-micro.dev/v4/server" 17 | ) 18 | 19 | // Reference imports to suppress errors if they are not otherwise used. 20 | var _ = proto.Marshal 21 | var _ = fmt.Errorf 22 | var _ = math.Inf 23 | 24 | // Reference imports to suppress errors if they are not otherwise used. 25 | var _ api.Endpoint 26 | var _ context.Context 27 | var _ client.Option 28 | var _ server.Option 29 | 30 | // Api Endpoints for RelationService service 31 | 32 | func NewRelationServiceEndpoints() []*api.Endpoint { 33 | return []*api.Endpoint{} 34 | } 35 | 36 | // Client API for RelationService service 37 | 38 | type RelationService interface { 39 | ActionRelation(ctx context.Context, in *RelationActionRequest, opts ...client.CallOption) (*RelationActionResponse, error) 40 | ListFollowRelation(ctx context.Context, in *RelationFollowRequest, opts ...client.CallOption) (*RelationFollowResponse, error) 41 | ListFollowerRelation(ctx context.Context, in *RelationFollowerRequest, opts ...client.CallOption) (*RelationFollowerResponse, error) 42 | ListFriendRelation(ctx context.Context, in *RelationFriendRequest, opts ...client.CallOption) (*RelationFriendResponse, error) 43 | } 44 | 45 | type relationService struct { 46 | c client.Client 47 | name string 48 | } 49 | 50 | func NewRelationService(name string, c client.Client) RelationService { 51 | return &relationService{ 52 | c: c, 53 | name: name, 54 | } 55 | } 56 | 57 | func (c *relationService) ActionRelation(ctx context.Context, in *RelationActionRequest, opts ...client.CallOption) (*RelationActionResponse, error) { 58 | req := c.c.NewRequest(c.name, "RelationService.ActionRelation", in) 59 | out := new(RelationActionResponse) 60 | err := c.c.Call(ctx, req, out, opts...) 61 | if err != nil { 62 | return nil, err 63 | } 64 | return out, nil 65 | } 66 | 67 | func (c *relationService) ListFollowRelation(ctx context.Context, in *RelationFollowRequest, opts ...client.CallOption) (*RelationFollowResponse, error) { 68 | req := c.c.NewRequest(c.name, "RelationService.ListFollowRelation", in) 69 | out := new(RelationFollowResponse) 70 | err := c.c.Call(ctx, req, out, opts...) 71 | if err != nil { 72 | return nil, err 73 | } 74 | return out, nil 75 | } 76 | 77 | func (c *relationService) ListFollowerRelation(ctx context.Context, in *RelationFollowerRequest, opts ...client.CallOption) (*RelationFollowerResponse, error) { 78 | req := c.c.NewRequest(c.name, "RelationService.ListFollowerRelation", in) 79 | out := new(RelationFollowerResponse) 80 | err := c.c.Call(ctx, req, out, opts...) 81 | if err != nil { 82 | return nil, err 83 | } 84 | return out, nil 85 | } 86 | 87 | func (c *relationService) ListFriendRelation(ctx context.Context, in *RelationFriendRequest, opts ...client.CallOption) (*RelationFriendResponse, error) { 88 | req := c.c.NewRequest(c.name, "RelationService.ListFriendRelation", in) 89 | out := new(RelationFriendResponse) 90 | err := c.c.Call(ctx, req, out, opts...) 91 | if err != nil { 92 | return nil, err 93 | } 94 | return out, nil 95 | } 96 | 97 | // Server API for RelationService service 98 | 99 | type RelationServiceHandler interface { 100 | ActionRelation(context.Context, *RelationActionRequest, *RelationActionResponse) error 101 | ListFollowRelation(context.Context, *RelationFollowRequest, *RelationFollowResponse) error 102 | ListFollowerRelation(context.Context, *RelationFollowerRequest, *RelationFollowerResponse) error 103 | ListFriendRelation(context.Context, *RelationFriendRequest, *RelationFriendResponse) error 104 | } 105 | 106 | func RegisterRelationServiceHandler(s server.Server, hdlr RelationServiceHandler, opts ...server.HandlerOption) error { 107 | type relationService interface { 108 | ActionRelation(ctx context.Context, in *RelationActionRequest, out *RelationActionResponse) error 109 | ListFollowRelation(ctx context.Context, in *RelationFollowRequest, out *RelationFollowResponse) error 110 | ListFollowerRelation(ctx context.Context, in *RelationFollowerRequest, out *RelationFollowerResponse) error 111 | ListFriendRelation(ctx context.Context, in *RelationFriendRequest, out *RelationFriendResponse) error 112 | } 113 | type RelationService struct { 114 | relationService 115 | } 116 | h := &relationServiceHandler{hdlr} 117 | return s.Handle(s.NewHandler(&RelationService{h}, opts...)) 118 | } 119 | 120 | type relationServiceHandler struct { 121 | RelationServiceHandler 122 | } 123 | 124 | func (h *relationServiceHandler) ActionRelation(ctx context.Context, in *RelationActionRequest, out *RelationActionResponse) error { 125 | return h.RelationServiceHandler.ActionRelation(ctx, in, out) 126 | } 127 | 128 | func (h *relationServiceHandler) ListFollowRelation(ctx context.Context, in *RelationFollowRequest, out *RelationFollowResponse) error { 129 | return h.RelationServiceHandler.ListFollowRelation(ctx, in, out) 130 | } 131 | 132 | func (h *relationServiceHandler) ListFollowerRelation(ctx context.Context, in *RelationFollowerRequest, out *RelationFollowerResponse) error { 133 | return h.RelationServiceHandler.ListFollowerRelation(ctx, in, out) 134 | } 135 | 136 | func (h *relationServiceHandler) ListFriendRelation(ctx context.Context, in *RelationFriendRequest, out *RelationFriendResponse) error { 137 | return h.RelationServiceHandler.ListFriendRelation(ctx, in, out) 138 | } 139 | -------------------------------------------------------------------------------- /idl/relation/relationService.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package relationService; 3 | option go_package = "./relationPb"; 4 | 5 | message User { 6 | // @inject_tag: json:"id" form:"id" 7 | int64 id = 1; // 用户id 8 | // @inject_tag: json:"name" form:"name" 9 | string name = 2; // 用户名称 10 | // @inject_tag: json:"follow_count" form:"follow_count" 11 | int64 follow_count = 3; // 关注总数 12 | // @inject_tag: json:"follower_count" form:"follower_count" 13 | int64 follower_count = 4; // 粉丝总数 14 | // @inject_tag: json:"is_follow" form:"is_follow" 15 | bool is_follow = 5; // true-已关注,false-未关注 16 | // @inject_tag: json:"avatar" form:"avatar" 17 | string avatar = 6; //用户头像 18 | // @inject_tag: json:"background_image" form:"background_image" 19 | string background_image = 7; //用户个人页顶部大图 20 | // @inject_tag: json:"signature" form:"signature" 21 | string signature = 8; //个人简介 22 | // @inject_tag: json:"total_favorited" form:"total_favorited" 23 | int64 total_favorited = 9; //获赞数量 24 | // @inject_tag: json:"work_count" form:"work_count" 25 | int64 work_count = 10; //作品数量 26 | // @inject_tag: json:"favorite_count" form:"favorite_count" 27 | int64 favorite_count = 11; //点赞数量 28 | } 29 | 30 | message RelationActionRequest { 31 | // @inject_tag: json:"token" form:"token" 32 | string token = 1; // 用户鉴权token 33 | // @inject_tag: json:"to_user_id" form:"to_user_id" 34 | int64 to_user_id = 2; // 对方用户id 35 | // @inject_tag: json:"action_type" form:"action_type" 36 | int32 action_type = 3; // 1-关注,2-取消关注 37 | } 38 | 39 | message RelationActionResponse { 40 | // @inject_tag: json:"status_code" form:"status_code" 41 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 42 | // @inject_tag: json:"status_msg" form:"status_msg" 43 | string status_msg = 2; // 返回状态描述 44 | } 45 | 46 | message RelationFollowRequest { 47 | // @inject_tag: json:"user_id" form:"user_id" 48 | int64 user_id = 1; // 用户id 49 | // @inject_tag: json:"token" form:"token" 50 | string token = 2; // 用户鉴权token 51 | } 52 | 53 | message RelationFollowResponse { 54 | // @inject_tag: json:"status_code" form:"status_code" 55 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 56 | // @inject_tag: json:"status_msg" form:"status_msg" 57 | string status_msg = 2; // 返回状态描述 58 | // @inject_tag: json:"user_list" form:"user_list" 59 | repeated User user_list = 3; // 用户信息列表 60 | } 61 | 62 | message RelationFollowerRequest { 63 | // @inject_tag: json:"user_id" form:"user_id" 64 | int64 user_id = 1; // 用户id 65 | // @inject_tag: json:"token" form:"token" 66 | string token = 2; // 用户鉴权token 67 | } 68 | 69 | message RelationFollowerResponse { 70 | // @inject_tag: json:"status_code" form:"status_code" 71 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 72 | // @inject_tag: json:"status_msg" form:"status_msg" 73 | string status_msg = 2; // 返回状态描述 74 | // @inject_tag: json:"user_list" form:"user_list" 75 | repeated User user_list = 3; // 用户列表 76 | } 77 | 78 | message RelationFriendRequest { 79 | // @inject_tag: json:"user_id" form:"user_id" 80 | int64 user_id = 1; // 用户id 81 | // @inject_tag: json:"token" form:"token" 82 | string token = 2; // 用户鉴权token 83 | } 84 | 85 | message RelationFriendResponse { 86 | // @inject_tag: json:"status_code" form:"status_code" 87 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 88 | // @inject_tag: json:"status_msg" form:"status_msg" 89 | string status_msg = 2; // 返回状态描述 90 | // @inject_tag: json:"user_list" form:"user_list" 91 | repeated User user_list = 3; // 用户列表 92 | } 93 | 94 | service RelationService { 95 | rpc ActionRelation(RelationActionRequest) returns (RelationActionResponse) {} 96 | rpc ListFollowRelation(RelationFollowRequest) returns (RelationFollowResponse) {} 97 | rpc ListFollowerRelation(RelationFollowerRequest) returns (RelationFollowerResponse) {} 98 | rpc ListFriendRelation(RelationFriendRequest) returns (RelationFriendResponse) {} 99 | } 100 | 101 | -------------------------------------------------------------------------------- /idl/user/userPb/userService.pb.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: userService.proto 3 | 4 | package userPb 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "google.golang.org/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | import ( 13 | context "context" 14 | api "go-micro.dev/v4/api" 15 | client "go-micro.dev/v4/client" 16 | server "go-micro.dev/v4/server" 17 | ) 18 | 19 | // Reference imports to suppress errors if they are not otherwise used. 20 | var _ = proto.Marshal 21 | var _ = fmt.Errorf 22 | var _ = math.Inf 23 | 24 | // Reference imports to suppress errors if they are not otherwise used. 25 | var _ api.Endpoint 26 | var _ context.Context 27 | var _ client.Option 28 | var _ server.Option 29 | 30 | // Api Endpoints for UserService service 31 | 32 | func NewUserServiceEndpoints() []*api.Endpoint { 33 | return []*api.Endpoint{} 34 | } 35 | 36 | // Client API for UserService service 37 | 38 | type UserService interface { 39 | Register(ctx context.Context, in *UserRequest, opts ...client.CallOption) (*UserResponse, error) 40 | Login(ctx context.Context, in *UserRequest, opts ...client.CallOption) (*UserResponse, error) 41 | UserInfo(ctx context.Context, in *UserInfoRequest, opts ...client.CallOption) (*UserInfoResponse, error) 42 | } 43 | 44 | type userService struct { 45 | c client.Client 46 | name string 47 | } 48 | 49 | func NewUserService(name string, c client.Client) UserService { 50 | return &userService{ 51 | c: c, 52 | name: name, 53 | } 54 | } 55 | 56 | func (c *userService) Register(ctx context.Context, in *UserRequest, opts ...client.CallOption) (*UserResponse, error) { 57 | req := c.c.NewRequest(c.name, "UserService.Register", in) 58 | out := new(UserResponse) 59 | err := c.c.Call(ctx, req, out, opts...) 60 | if err != nil { 61 | return nil, err 62 | } 63 | return out, nil 64 | } 65 | 66 | func (c *userService) Login(ctx context.Context, in *UserRequest, opts ...client.CallOption) (*UserResponse, error) { 67 | req := c.c.NewRequest(c.name, "UserService.Login", in) 68 | out := new(UserResponse) 69 | err := c.c.Call(ctx, req, out, opts...) 70 | if err != nil { 71 | return nil, err 72 | } 73 | return out, nil 74 | } 75 | 76 | func (c *userService) UserInfo(ctx context.Context, in *UserInfoRequest, opts ...client.CallOption) (*UserInfoResponse, error) { 77 | req := c.c.NewRequest(c.name, "UserService.UserInfo", in) 78 | out := new(UserInfoResponse) 79 | err := c.c.Call(ctx, req, out, opts...) 80 | if err != nil { 81 | return nil, err 82 | } 83 | return out, nil 84 | } 85 | 86 | // Server API for UserService service 87 | 88 | type UserServiceHandler interface { 89 | Register(context.Context, *UserRequest, *UserResponse) error 90 | Login(context.Context, *UserRequest, *UserResponse) error 91 | UserInfo(context.Context, *UserInfoRequest, *UserInfoResponse) error 92 | } 93 | 94 | func RegisterUserServiceHandler(s server.Server, hdlr UserServiceHandler, opts ...server.HandlerOption) error { 95 | type userService interface { 96 | Register(ctx context.Context, in *UserRequest, out *UserResponse) error 97 | Login(ctx context.Context, in *UserRequest, out *UserResponse) error 98 | UserInfo(ctx context.Context, in *UserInfoRequest, out *UserInfoResponse) error 99 | } 100 | type UserService struct { 101 | userService 102 | } 103 | h := &userServiceHandler{hdlr} 104 | return s.Handle(s.NewHandler(&UserService{h}, opts...)) 105 | } 106 | 107 | type userServiceHandler struct { 108 | UserServiceHandler 109 | } 110 | 111 | func (h *userServiceHandler) Register(ctx context.Context, in *UserRequest, out *UserResponse) error { 112 | return h.UserServiceHandler.Register(ctx, in, out) 113 | } 114 | 115 | func (h *userServiceHandler) Login(ctx context.Context, in *UserRequest, out *UserResponse) error { 116 | return h.UserServiceHandler.Login(ctx, in, out) 117 | } 118 | 119 | func (h *userServiceHandler) UserInfo(ctx context.Context, in *UserInfoRequest, out *UserInfoResponse) error { 120 | return h.UserServiceHandler.UserInfo(ctx, in, out) 121 | } 122 | -------------------------------------------------------------------------------- /idl/user/userService.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package userService; 3 | option go_package = "./userPb"; 4 | 5 | message User { 6 | // @inject_tag: json:"id" form:"id" 7 | int64 id = 1; // 用户id 8 | // @inject_tag: json:"name" form:"name" 9 | string name = 2; // 用户名称 10 | // @inject_tag: json:"follow_count" form:"follow_count" 11 | int64 follow_count = 3; // 关注总数 12 | // @inject_tag: json:"follower_count" form:"follower_count" 13 | int64 follower_count = 4; // 粉丝总数 14 | // @inject_tag: json:"is_follow" form:"is_follow" 15 | bool is_follow = 5; // true-已关注,false-未关注 16 | // @inject_tag: json:"avatar" form:"avatar" 17 | string avatar = 6; //用户头像 18 | // @inject_tag: json:"background_image" form:"background_image" 19 | string background_image = 7; //用户个人页顶部大图 20 | // @inject_tag: json:"signature" form:"signature" 21 | string signature = 8; //个人简介 22 | // @inject_tag: json:"total_favorited" form:"total_favorited" 23 | int64 total_favorited = 9; //获赞数量 24 | // @inject_tag: json:"work_count" form:"work_count" 25 | int64 work_count = 10; //作品数量 26 | // @inject_tag: json:"favorite_count" form:"favorite_count" 27 | int64 favorite_count = 11; //点赞数量 28 | } 29 | 30 | message UserRequest { 31 | // @inject_tag: json:"username" form:"username" 32 | string username = 1; // 注册用户名,最长32个字符 33 | // @inject_tag: json:"password" form:"password" 34 | string password = 2; // 密码,最长32个字符 35 | } 36 | 37 | message UserResponse { 38 | // @inject_tag: json:"status_code" form:"status_code" 39 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 40 | // @inject_tag: json:"status_msg" form:"status_msg" 41 | string status_msg = 2; // 返回状态描述 42 | // @inject_tag: json:"user_id" form:"user_id" 43 | int64 user_id = 3; // 用户id 44 | // @inject_tag: json:"token" form:"token" 45 | string token = 4; // 用户鉴权token 46 | } 47 | 48 | message UserInfoRequest { 49 | int64 user_id = 1; // 用户id 50 | string token = 2; // 用户鉴权token 51 | } 52 | 53 | message UserInfoResponse { 54 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 55 | string status_msg = 2; // 返回状态描述 56 | User user = 3; // 用户信息 57 | } 58 | 59 | service UserService { 60 | rpc Register(UserRequest) returns (UserResponse) {} 61 | rpc Login(UserRequest) returns (UserResponse) {} 62 | rpc UserInfo(UserInfoRequest) returns (UserInfoResponse) {} 63 | } 64 | 65 | -------------------------------------------------------------------------------- /idl/video/videoPb/videoService.pb.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: videoService.proto 3 | 4 | package videoPb 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "google.golang.org/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | import ( 13 | context "context" 14 | api "go-micro.dev/v4/api" 15 | client "go-micro.dev/v4/client" 16 | server "go-micro.dev/v4/server" 17 | ) 18 | 19 | // Reference imports to suppress errors if they are not otherwise used. 20 | var _ = proto.Marshal 21 | var _ = fmt.Errorf 22 | var _ = math.Inf 23 | 24 | // Reference imports to suppress errors if they are not otherwise used. 25 | var _ api.Endpoint 26 | var _ context.Context 27 | var _ client.Option 28 | var _ server.Option 29 | 30 | // Api Endpoints for VideoService service 31 | 32 | func NewVideoServiceEndpoints() []*api.Endpoint { 33 | return []*api.Endpoint{} 34 | } 35 | 36 | // Client API for VideoService service 37 | 38 | type VideoService interface { 39 | Feed(ctx context.Context, in *FeedRequest, opts ...client.CallOption) (*FeedResponse, error) 40 | Publish(ctx context.Context, in *PublishRequest, opts ...client.CallOption) (*PublishResponse, error) 41 | PublishList(ctx context.Context, in *PublishListRequest, opts ...client.CallOption) (*PublishListResponse, error) 42 | } 43 | 44 | type videoService struct { 45 | c client.Client 46 | name string 47 | } 48 | 49 | func NewVideoService(name string, c client.Client) VideoService { 50 | return &videoService{ 51 | c: c, 52 | name: name, 53 | } 54 | } 55 | 56 | func (c *videoService) Feed(ctx context.Context, in *FeedRequest, opts ...client.CallOption) (*FeedResponse, error) { 57 | req := c.c.NewRequest(c.name, "VideoService.Feed", in) 58 | out := new(FeedResponse) 59 | err := c.c.Call(ctx, req, out, opts...) 60 | if err != nil { 61 | return nil, err 62 | } 63 | return out, nil 64 | } 65 | 66 | func (c *videoService) Publish(ctx context.Context, in *PublishRequest, opts ...client.CallOption) (*PublishResponse, error) { 67 | req := c.c.NewRequest(c.name, "VideoService.Publish", in) 68 | out := new(PublishResponse) 69 | err := c.c.Call(ctx, req, out, opts...) 70 | if err != nil { 71 | return nil, err 72 | } 73 | return out, nil 74 | } 75 | 76 | func (c *videoService) PublishList(ctx context.Context, in *PublishListRequest, opts ...client.CallOption) (*PublishListResponse, error) { 77 | req := c.c.NewRequest(c.name, "VideoService.PublishList", in) 78 | out := new(PublishListResponse) 79 | err := c.c.Call(ctx, req, out, opts...) 80 | if err != nil { 81 | return nil, err 82 | } 83 | return out, nil 84 | } 85 | 86 | // Server API for VideoService service 87 | 88 | type VideoServiceHandler interface { 89 | Feed(context.Context, *FeedRequest, *FeedResponse) error 90 | Publish(context.Context, *PublishRequest, *PublishResponse) error 91 | PublishList(context.Context, *PublishListRequest, *PublishListResponse) error 92 | } 93 | 94 | func RegisterVideoServiceHandler(s server.Server, hdlr VideoServiceHandler, opts ...server.HandlerOption) error { 95 | type videoService interface { 96 | Feed(ctx context.Context, in *FeedRequest, out *FeedResponse) error 97 | Publish(ctx context.Context, in *PublishRequest, out *PublishResponse) error 98 | PublishList(ctx context.Context, in *PublishListRequest, out *PublishListResponse) error 99 | } 100 | type VideoService struct { 101 | videoService 102 | } 103 | h := &videoServiceHandler{hdlr} 104 | return s.Handle(s.NewHandler(&VideoService{h}, opts...)) 105 | } 106 | 107 | type videoServiceHandler struct { 108 | VideoServiceHandler 109 | } 110 | 111 | func (h *videoServiceHandler) Feed(ctx context.Context, in *FeedRequest, out *FeedResponse) error { 112 | return h.VideoServiceHandler.Feed(ctx, in, out) 113 | } 114 | 115 | func (h *videoServiceHandler) Publish(ctx context.Context, in *PublishRequest, out *PublishResponse) error { 116 | return h.VideoServiceHandler.Publish(ctx, in, out) 117 | } 118 | 119 | func (h *videoServiceHandler) PublishList(ctx context.Context, in *PublishListRequest, out *PublishListResponse) error { 120 | return h.VideoServiceHandler.PublishList(ctx, in, out) 121 | } 122 | -------------------------------------------------------------------------------- /idl/video/videoService.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package videoService; 3 | option go_package = "./videoPb"; 4 | 5 | 6 | message Video { 7 | // @inject_tag: json:"id" form:"id" 8 | int64 id = 1; // 视频唯一标识 9 | // @inject_tag: json:"author" form:"author" 10 | User author = 2; // 视频作者信息 11 | // @inject_tag: json:"play_url" form:"play_url" 12 | string play_url = 3; // 视频播放地址 13 | // @inject_tag: json:"cover_url" form:"cover_url" 14 | string cover_url = 4; // 视频封面地址 15 | // @inject_tag: json:"favorite_count" form:"favorite_count" 16 | int64 favorite_count = 5; // 视频的点赞总数 17 | // @inject_tag: json:"comment_count" form:"comment_count" 18 | int64 comment_count = 6; // 视频的评论总数 19 | // @inject_tag: json:"is_favorite" form:"is_favorite" 20 | bool is_favorite = 7; // true-已点赞,false-未点赞 21 | // @inject_tag: json:"title" form:"title" 22 | string title = 8; // 视频标题 23 | } 24 | 25 | message User { 26 | // @inject_tag: json:"id" form:"id" 27 | int64 id = 1; // 用户id 28 | // @inject_tag: json:"name" form:"name" 29 | string name = 2; // 用户名称 30 | // @inject_tag: json:"follow_count" form:"follow_count" 31 | int64 follow_count = 3; // 关注总数 32 | // @inject_tag: json:"follower_count" form:"follower_count" 33 | int64 follower_count = 4; // 粉丝总数 34 | // @inject_tag: json:"is_follow" form:"is_follow" 35 | bool is_follow = 5; // true-已关注,false-未关注 36 | // @inject_tag: json:"avatar" form:"avatar" 37 | string avatar = 6; //用户头像 38 | // @inject_tag: json:"background_image" form:"background_image" 39 | string background_image = 7; //用户个人页顶部大图 40 | // @inject_tag: json:"signature" form:"signature" 41 | string signature = 8; //个人简介 42 | // @inject_tag: json:"total_favorited" form:"total_favorited" 43 | int64 total_favorited = 9; //获赞数量 44 | // @inject_tag: json:"work_count" form:"work_count" 45 | int64 work_count = 10; //作品数量 46 | // @inject_tag: json:"favorite_count" form:"favorite_count" 47 | int64 favorite_count = 11; //点赞数量 48 | } 49 | 50 | message FeedRequest { 51 | int64 latest_time = 1; // 可选参数,限制返回视频的最新投稿时间戳,精确到秒,不填表示当前时间 52 | string token = 2; // 可选参数,登录用户设置 53 | } 54 | 55 | message FeedResponse { 56 | // @inject_tag: json:"status_code" form:"status_code" 57 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 58 | // @inject_tag: json:"status_msg" form:"status_msg" 59 | string status_msg = 2; // 返回状态描述 60 | // @inject_tag: json:"video_list" form:"video_list" 61 | repeated Video video_list = 3; // 视频列表 62 | // @inject_tag: json:"next_time" form:"next_time" 63 | int64 next_time = 4; // 本次返回的视频中,发布最早的时间,作为下次请求时的latest_time 64 | } 65 | 66 | 67 | message PublishRequest{ 68 | // @inject_tag: json:"token" form:"token" 69 | string token = 1; // 用户鉴权token 70 | // @inject_tag: json:"data" form:"data" 71 | bytes data = 2; // 视频数据 72 | // @inject_tag: json:"title" form:"title" 73 | string title = 3; // 视频标题 74 | } 75 | 76 | message PublishResponse { 77 | // @inject_tag: json:"status_code" form:"status_code" 78 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 79 | // @inject_tag: json:"status_msg" form:"status_msg" 80 | string status_msg = 2; // 返回状态描述 81 | } 82 | 83 | message PublishListRequest { 84 | // @inject_tag: json:"user_id" form:"user_id" 85 | int64 user_id = 1; // 用户id 86 | // @inject_tag: json:"token" form:"token" 87 | string token = 2; // 用户鉴权token 88 | } 89 | 90 | message PublishListResponse { 91 | // @inject_tag: json:"status_code" form:"status_code" 92 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 93 | // @inject_tag: json:"status_msg" form:"status_msg" 94 | string status_msg = 2; // 返回状态描述 95 | // @inject_tag: json:"video_list" form:"video_list" 96 | repeated Video video_list = 3; // 用户发布的视频列表 97 | } 98 | 99 | service VideoService { 100 | rpc Feed(FeedRequest) returns (FeedResponse) {} 101 | rpc Publish(PublishRequest) returns (PublishResponse) {} 102 | rpc PublishList(PublishListRequest) returns (PublishListResponse) {} 103 | } -------------------------------------------------------------------------------- /model/comment.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | type Comment struct { 6 | ID uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"` 7 | UserID int `gorm:"column:user_id" json:"user_id"` 8 | User User `gorm:"foreignKey:UserID;AssociationForeignKey:ID" json:"user"` 9 | VideoID int `gorm:"column:video_id;index" json:"video_id"` 10 | Video Video `gorm:"foreignKey:VideoID;AssociationForeignKey:ID" json:"video"` 11 | Content string `gorm:"column:content;size:1024" json:"content"` 12 | CreatedAt time.Time ` json:"created_at"` 13 | } 14 | -------------------------------------------------------------------------------- /model/favorite.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | type Favorite struct { 6 | ID uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"` 7 | UserID int `gorm:"column:user_id;index:idx_favorite" json:"user_id"` 8 | User User `gorm:"foreignKey:UserID;AssociationForeignKey:ID" json:"user"` 9 | VideoID int `gorm:"column:video_id;index:idx_favorite_video;index:idx_favorite" json:"video_id"` 10 | Video Video `gorm:"foreignKey:VideoID;AssociationForeignKey:ID" json:"video"` 11 | CreatedAt time.Time ` json:"created_at"` 12 | } 13 | -------------------------------------------------------------------------------- /model/follow.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | type Follow struct { 6 | ID uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"` 7 | UserID int `gorm:"column:user_id;index:idx_follow_user;index:idx_follow" json:"user_id"` 8 | User User `gorm:"foreignKey:UserID;AssociationForeignKey:ID" json:"user"` 9 | FollowedUserID int `gorm:"column:followed_user_id;index:idx_follow_followed;index:idx_follow" json:"followed_user_id"` 10 | FollowedUser User `gorm:"foreignKey:FollowedUserID;AssociationForeignKey:ID" json:"followed_user"` 11 | CreatedAt time.Time `json:"created_at"` 12 | } 13 | -------------------------------------------------------------------------------- /model/message.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | type Message struct { 6 | ID uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"` 7 | FromUserID int `gorm:"column:from_user_id;index:idx_message" json:"from_user_id"` 8 | FromUser User `gorm:"foreignKey:FromUserID;AssociationForeignKey:ID" json:"from_user"` 9 | ToUserID int `gorm:"column:to_user_id;index:idx_message" json:"to_user_id"` 10 | ToUser User `gorm:"foreignKey:ToUserID;AssociationForeignKey:ID" json:"to_user"` 11 | Content string `gorm:"column:content;size:1024" json:"content"` 12 | CreatedAt time.Time ` json:"created_at"` 13 | } 14 | -------------------------------------------------------------------------------- /model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | type User struct { 6 | ID uint `gorm:"column:id;primaryKey;autoIncrement;index" json:"id"` 7 | Username string `gorm:"column:username;unique;index" json:"username"` 8 | Password string `gorm:"column:password" json:"password"` 9 | Avatar string `gorm:"column:avatar" json:"avatar"` 10 | BackgroundImage string `gorm:"column:background_image" json:"background_image"` 11 | Signature string `gorm:"column:signature" json:"signature"` 12 | CreatedAt time.Time `json:"created_at"` 13 | } 14 | -------------------------------------------------------------------------------- /model/video.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | type Video struct { 6 | ID uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"` 7 | AuthorID int `gorm:"column:author_id;index" json:"author_id"` 8 | Author User `gorm:"foreignKey:AuthorID;AssociationForeignKey:ID" json:"author"` 9 | PlayUrl string `gorm:"column:play_url" json:"play_url"` 10 | CoverUrl string `gorm:"column:cover_url" json:"cover_url"` 11 | Title string `gorm:"column:title" json:"title"` 12 | CreatedAt time.Time `json:"created_at"` 13 | } 14 | -------------------------------------------------------------------------------- /mq/consumer.go: -------------------------------------------------------------------------------- 1 | package mq 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/streadway/amqp" 7 | ) 8 | 9 | // ConsumeMessage MQ到mysql 10 | func ConsumeMessage(ctx context.Context, queueName string) (msg <-chan amqp.Delivery, err error) { 11 | ch, err := RabbitMQ.Channel() 12 | if err != nil { 13 | return 14 | } 15 | q, _ := ch.QueueDeclare(queueName, true, false, false, false, nil) 16 | err = ch.Qos(1, 0, false) 17 | return ch.Consume(q.Name, "", false, false, false, false, nil) 18 | } 19 | -------------------------------------------------------------------------------- /mq/init.go: -------------------------------------------------------------------------------- 1 | package mq 2 | 3 | import ( 4 | "ByteRhythm/config" 5 | "fmt" 6 | 7 | "github.com/streadway/amqp" 8 | ) 9 | 10 | var RabbitMQ *amqp.Connection 11 | 12 | func InitRabbitMQ() { 13 | config.Init() 14 | connString := fmt.Sprintf("%s://%s:%s@%s:%s/", 15 | config.RabbitMQ, 16 | config.RabbitMQUser, 17 | config.RabbitMQPassWord, 18 | config.RabbitMQHost, 19 | config.RabbitMQPort, 20 | ) 21 | conn, err := amqp.Dial(connString) 22 | if err != nil { 23 | panic(err) 24 | } 25 | RabbitMQ = conn 26 | } 27 | -------------------------------------------------------------------------------- /mq/producer.go: -------------------------------------------------------------------------------- 1 | package mq 2 | 3 | import ( 4 | "github.com/streadway/amqp" 5 | ) 6 | 7 | func SendMessage2MQ(body []byte, queueName string) (err error) { 8 | ch, err := RabbitMQ.Channel() 9 | if err != nil { 10 | return 11 | } 12 | q, _ := ch.QueueDeclare(queueName, true, false, false, false, nil) 13 | err = ch.Publish("", q.Name, false, false, amqp.Publishing{ 14 | DeliveryMode: amqp.Persistent, 15 | ContentType: "application/json", 16 | Body: body, 17 | }) 18 | if err != nil { 19 | return 20 | } 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ./gateway & 3 | ./user & 4 | ./video & 5 | ./favorite & 6 | ./comment & 7 | ./relation & 8 | ./message & 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd /usr/local/bin/ &&./etcd & 3 | cd /usr/local/bin/ &&./jaeger-all-in-one --collector.zipkin.host-port=:9411 & 4 | systemctl start rabbitmq-server & 5 | systemctl start redis-server 6 | 7 | ./gateway & 8 | ./user & 9 | ./video & 10 | ./favorite & 11 | ./comment & 12 | ./relation & 13 | ./message & 14 | -------------------------------------------------------------------------------- /test/comment_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "ByteRhythm/app/favorite/service" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/stretchr/testify/assert" 8 | "io" 9 | "net/http" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | // -----------------/douyin/comment/action/接口测试-------------------- 15 | // 发布评论 16 | func TestCommentAction(t *testing.T) { 17 | baseUrl := "http://192.168.256.128:8080/douyin/comment/action/?" 18 | token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6OSwidXNlcm5hbWUiOiJ0ZXN0MiIsInBhc3N3b3JkIjoiZTEwYWRjMzk0OWJhNTlhYmJlNTZlMDU3ZjIwZjg4M2UiLCJhdmF0YXIiOiJodHRwOi8vcnoybjg3eWNrLmhuLWJrdC5jbG91ZGRuLmNvbS9hdmF0YXIuanBnIiwiYmFja2dyb3VuZF9pbWFnZSI6Imh0dHA6Ly9yejJuODd5Y2suaG4tYmt0LmNsb3VkZG4uY29tL2JhY2tncm91bmQucG5nIiwic2lnbmF0dXJlIjoi5Y-I5p2l55yL5oiR55qE5Li76aG15ZWmfiIsImNyZWF0ZWRfYXQiOiIyMDIzLTA4LTMwVDAyOjIyOjMzLTA3OjAwIiwiZXhwIjoxNjkzMzg4MDAxLCJpYXQiOjE2OTMzODc5NzEsImlzcyI6InRlc3QyIn0.11RhlMGupHJDoetGndAWSiuNyBeAUHNEIF81VcVJkR0" 19 | video_id := "1" 20 | action_type := "1" //发布评论 21 | comment_text := "只狼太好玩了!" 22 | url := baseUrl + "token=" + token + "&video_id=" + video_id + "&action_type=" + action_type + "&comment_text=" + comment_text 23 | 24 | method := "POST" 25 | client := &http.Client{} 26 | req, err := http.NewRequest(method, url, nil) 27 | assert.Empty(t, err) 28 | 29 | res, err := client.Do(req) 30 | assert.Empty(t, err) 31 | defer func(Body io.ReadCloser) { 32 | err := Body.Close() 33 | assert.Empty(t, err) 34 | }(res.Body) 35 | 36 | body, err := io.ReadAll(res.Body) 37 | str := string(body) 38 | assert.Empty(t, err) 39 | favor := service.GetFavoriteSrv() 40 | err = json.Unmarshal(body, &favor) 41 | assert.Empty(t, err) 42 | fmt.Printf(string(body)) 43 | assert.Equal(t, strings.Contains(str, "评论成功"), true) 44 | 45 | } 46 | 47 | // 删除评论 48 | func TestDeleteCommentAction(t *testing.T) { 49 | baseUrl := "http://192.168.256.128:8080/douyin/comment/action/?" 50 | token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6OSwidXNlcm5hbWUiOiJ0ZXN0MiIsInBhc3N3b3JkIjoiZTEwYWRjMzk0OWJhNTlhYmJlNTZlMDU3ZjIwZjg4M2UiLCJhdmF0YXIiOiJodHRwOi8vcnoybjg3eWNrLmhuLWJrdC5jbG91ZGRuLmNvbS9hdmF0YXIuanBnIiwiYmFja2dyb3VuZF9pbWFnZSI6Imh0dHA6Ly9yejJuODd5Y2suaG4tYmt0LmNsb3VkZG4uY29tL2JhY2tncm91bmQucG5nIiwic2lnbmF0dXJlIjoi5Y-I5p2l55yL5oiR55qE5Li76aG15ZWmfiIsImNyZWF0ZWRfYXQiOiIyMDIzLTA4LTMwVDAyOjIyOjMzLTA3OjAwIiwiZXhwIjoxNjkzMzg4MDAxLCJpYXQiOjE2OTMzODc5NzEsImlzcyI6InRlc3QyIn0.11RhlMGupHJDoetGndAWSiuNyBeAUHNEIF81VcVJkR0" 51 | video_id := "1" 52 | action_type := "2" //删除评论 53 | comment_id := "1" 54 | url := baseUrl + "token=" + token + "&video_id=" + video_id + "&action_type=" + action_type + "&comment_id=" + comment_id 55 | 56 | method := "POST" 57 | client := &http.Client{} 58 | req, err := http.NewRequest(method, url, nil) 59 | assert.Empty(t, err) 60 | 61 | res, err := client.Do(req) 62 | assert.Empty(t, err) 63 | defer func(Body io.ReadCloser) { 64 | err := Body.Close() 65 | assert.Empty(t, err) 66 | }(res.Body) 67 | 68 | body, err := io.ReadAll(res.Body) 69 | str := string(body) 70 | assert.Empty(t, err) 71 | favor := service.GetFavoriteSrv() 72 | err = json.Unmarshal(body, &favor) 73 | assert.Empty(t, err) 74 | fmt.Printf(string(body)) 75 | assert.Equal(t, strings.Contains(str, "评论删除成功"), true) 76 | 77 | } 78 | 79 | //-----------------/douyin/comment/list接口测试-------------------- 80 | 81 | // 获取评论列表成功 82 | func TestCommentList(t *testing.T) { 83 | baseUrl := "http://192.168.256.128:8080/douyin/comment/list/?" 84 | token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6OSwidXNlcm5hbWUiOiJ0ZXN0MiIsInBhc3N3b3JkIjoiZTEwYWRjMzk0OWJhNTlhYmJlNTZlMDU3ZjIwZjg4M2UiLCJhdmF0YXIiOiJodHRwOi8vcnoybjg3eWNrLmhuLWJrdC5jbG91ZGRuLmNvbS9hdmF0YXIuanBnIiwiYmFja2dyb3VuZF9pbWFnZSI6Imh0dHA6Ly9yejJuODd5Y2suaG4tYmt0LmNsb3VkZG4uY29tL2JhY2tncm91bmQucG5nIiwic2lnbmF0dXJlIjoi5Y-I5p2l55yL5oiR55qE5Li76aG15ZWmfiIsImNyZWF0ZWRfYXQiOiIyMDIzLTA4LTMwVDAyOjIyOjMzLTA3OjAwIiwiZXhwIjoxNjkzMzg4MDAxLCJpYXQiOjE2OTMzODc5NzEsImlzcyI6InRlc3QyIn0.11RhlMGupHJDoetGndAWSiuNyBeAUHNEIF81VcVJkR0" 85 | video_id := "1" 86 | url := baseUrl + "token=" + token + "&video_id=" + video_id 87 | 88 | method := "GET" 89 | client := &http.Client{} 90 | req, err := http.NewRequest(method, url, nil) 91 | assert.Empty(t, err) 92 | 93 | res, err := client.Do(req) 94 | assert.Empty(t, err) 95 | defer func(Body io.ReadCloser) { 96 | err := Body.Close() 97 | assert.Empty(t, err) 98 | }(res.Body) 99 | 100 | body, err := io.ReadAll(res.Body) 101 | str := string(body) 102 | assert.Empty(t, err) 103 | favor := service.GetFavoriteSrv() 104 | err = json.Unmarshal(body, &favor) 105 | assert.Empty(t, err) 106 | fmt.Printf(string(body)) 107 | assert.Equal(t, strings.Contains(str, "获取评论列表成功"), true) 108 | 109 | } 110 | -------------------------------------------------------------------------------- /test/favorite_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "ByteRhythm/app/favorite/service" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/stretchr/testify/assert" 8 | "io" 9 | "net/http" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | // -----------------/douyin/favorite/action/接口测试-------------------- 15 | // 点赞成功 16 | func TestFavoriteAction(t *testing.T) { 17 | baseUrl := "http://192.168.256.128:8080/douyin/favorite/action/?" 18 | video_id := "1" 19 | token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTEsInVzZXJuYW1lIjoidGVzdDIiLCJwYXNzd29yZCI6IjIwMmNiOTYyYWM1OTA3NWI5NjRiMDcxNTJkMjM0YjcwIiwiYXZhdGFyIjoiaHR0cDovL3J6Mm44N3ljay5obi1ia3QuY2xvdWRkbi5jb20vYXZhdGFyLmpwZyIsImJhY2tncm91bmRfaW1hZ2UiOiJodHRwOi8vcnoybjg3eWNrLmhuLWJrdC5jbG91ZGRuLmNvbS9iYWNrZ3JvdW5kLnBuZyIsInNpZ25hdHVyZSI6IuWPiOadpeeci-aIkeeahOS4u-mhteWVpn4iLCJjcmVhdGVkX2F0IjoiMjAyMy0wOC0zMFQwNDo1NDo1Mi0wNzowMCIsImV4cCI6MTY5MzM5NjYyMiwiaWF0IjoxNjkzMzk2NTAyLCJpc3MiOiJ0ZXN0MiJ9.6CiMJkVJDk-nSpl28qgQXmTkgGqIobQff-Zg7MBMioc" 20 | action_type := "1" 21 | url := baseUrl + "token=" + token + "&video_id=" + video_id + "&action_type=" + action_type 22 | 23 | method := "POST" 24 | client := &http.Client{} 25 | req, err := http.NewRequest(method, url, nil) 26 | assert.Empty(t, err) 27 | 28 | res, err := client.Do(req) 29 | assert.Empty(t, err) 30 | defer func(Body io.ReadCloser) { 31 | err := Body.Close() 32 | assert.Empty(t, err) 33 | }(res.Body) 34 | 35 | body, err := io.ReadAll(res.Body) 36 | assert.Empty(t, err) 37 | favor := service.GetFavoriteSrv() 38 | err = json.Unmarshal(body, &favor) 39 | assert.Empty(t, err) 40 | fmt.Printf(string(body)) 41 | 42 | } 43 | 44 | // 重复点赞 45 | func TestDuplicateFavoriteAction(t *testing.T) { 46 | baseUrl := "http://192.168.256.128:8080/douyin/favorite/action/?" 47 | video_id := "3" 48 | token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTEsInVzZXJuYW1lIjoidGVzdDIiLCJwYXNzd29yZCI6IjIwMmNiOTYyYWM1OTA3NWI5NjRiMDcxNTJkMjM0YjcwIiwiYXZhdGFyIjoiaHR0cDovL3J6Mm44N3ljay5obi1ia3QuY2xvdWRkbi5jb20vYXZhdGFyLmpwZyIsImJhY2tncm91bmRfaW1hZ2UiOiJodHRwOi8vcnoybjg3eWNrLmhuLWJrdC5jbG91ZGRuLmNvbS9iYWNrZ3JvdW5kLnBuZyIsInNpZ25hdHVyZSI6IuWPiOadpeeci-aIkeeahOS4u-mhteWVpn4iLCJjcmVhdGVkX2F0IjoiMjAyMy0wOC0zMFQwNDo1NDo1Mi0wNzowMCIsImV4cCI6MTY5MzM5NjYyMiwiaWF0IjoxNjkzMzk2NTAyLCJpc3MiOiJ0ZXN0MiJ9.6CiMJkVJDk-nSpl28qgQXmTkgGqIobQff-Zg7MBMioc" 49 | action_type := "1" 50 | url := baseUrl + "token=" + token + "&video_id=" + video_id + "&action_type=" + action_type 51 | 52 | method := "POST" 53 | client := &http.Client{} 54 | req, err := http.NewRequest(method, url, nil) 55 | assert.Empty(t, err) 56 | 57 | res, err := client.Do(req) 58 | assert.Empty(t, err) 59 | defer func(Body io.ReadCloser) { 60 | err := Body.Close() 61 | assert.Empty(t, err) 62 | }(res.Body) 63 | 64 | body, err := io.ReadAll(res.Body) 65 | str := string(body) 66 | assert.Empty(t, err) 67 | favor := service.GetFavoriteSrv() 68 | err = json.Unmarshal(body, &favor) 69 | assert.Empty(t, err) 70 | fmt.Printf(string(body)) 71 | assert.Equal(t, strings.Contains(str, "重复点赞"), true) 72 | } 73 | 74 | // 取消点赞 75 | func TestCancelFavoriteAction(t *testing.T) { 76 | baseUrl := "http://192.168.256.128:8080/douyin/favorite/action/?" 77 | video_id := "3" 78 | token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTEsInVzZXJuYW1lIjoidGVzdDIiLCJwYXNzd29yZCI6IjIwMmNiOTYyYWM1OTA3NWI5NjRiMDcxNTJkMjM0YjcwIiwiYXZhdGFyIjoiaHR0cDovL3J6Mm44N3ljay5obi1ia3QuY2xvdWRkbi5jb20vYXZhdGFyLmpwZyIsImJhY2tncm91bmRfaW1hZ2UiOiJodHRwOi8vcnoybjg3eWNrLmhuLWJrdC5jbG91ZGRuLmNvbS9iYWNrZ3JvdW5kLnBuZyIsInNpZ25hdHVyZSI6IuWPiOadpeeci-aIkeeahOS4u-mhteWVpn4iLCJjcmVhdGVkX2F0IjoiMjAyMy0wOC0zMFQwNDo1NDo1Mi0wNzowMCIsImV4cCI6MTY5MzM5NjYyMiwiaWF0IjoxNjkzMzk2NTAyLCJpc3MiOiJ0ZXN0MiJ9.6CiMJkVJDk-nSpl28qgQXmTkgGqIobQff-Zg7MBMioc" 79 | action_type := "2" 80 | url := baseUrl + "token=" + token + "&video_id=" + video_id + "&action_type=" + action_type 81 | 82 | method := "POST" 83 | client := &http.Client{} 84 | req, err := http.NewRequest(method, url, nil) 85 | assert.Empty(t, err) 86 | 87 | res, err := client.Do(req) 88 | assert.Empty(t, err) 89 | defer func(Body io.ReadCloser) { 90 | err := Body.Close() 91 | assert.Empty(t, err) 92 | }(res.Body) 93 | 94 | body, err := io.ReadAll(res.Body) 95 | assert.Empty(t, err) 96 | favor := service.GetFavoriteSrv() 97 | err = json.Unmarshal(body, &favor) 98 | assert.Empty(t, err) 99 | fmt.Printf(string(body)) 100 | 101 | } 102 | 103 | // -----------------/douyin/favorite/list/接口测试-------------------- 104 | func TestFavoriteList(t *testing.T) { 105 | baseUrl := "http://192.168.256.128:8080/douyin/favorite/list/?" 106 | user_id := "11" 107 | token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTEsInVzZXJuYW1lIjoidGVzdDIiLCJwYXNzd29yZCI6IjIwMmNiOTYyYWM1OTA3NWI5NjRiMDcxNTJkMjM0YjcwIiwiYXZhdGFyIjoiaHR0cDovL3J6Mm44N3ljay5obi1ia3QuY2xvdWRkbi5jb20vYXZhdGFyLmpwZyIsImJhY2tncm91bmRfaW1hZ2UiOiJodHRwOi8vcnoybjg3eWNrLmhuLWJrdC5jbG91ZGRuLmNvbS9iYWNrZ3JvdW5kLnBuZyIsInNpZ25hdHVyZSI6IuWPiOadpeeci-aIkeeahOS4u-mhteWVpn4iLCJjcmVhdGVkX2F0IjoiMjAyMy0wOC0zMFQwNDo1NDo1Mi0wNzowMCIsImV4cCI6MTY5MzM5NjYyMiwiaWF0IjoxNjkzMzk2NTAyLCJpc3MiOiJ0ZXN0MiJ9.6CiMJkVJDk-nSpl28qgQXmTkgGqIobQff-Zg7MBMioc" 108 | url := baseUrl + "user_id=" + user_id + "token=" + token 109 | 110 | method := "GET" 111 | client := &http.Client{} 112 | req, err := http.NewRequest(method, url, nil) 113 | assert.Empty(t, err) 114 | 115 | res, err := client.Do(req) 116 | assert.Empty(t, err) 117 | defer func(Body io.ReadCloser) { 118 | err := Body.Close() 119 | assert.Empty(t, err) 120 | }(res.Body) 121 | 122 | body, err := io.ReadAll(res.Body) 123 | str := string(body) 124 | assert.Empty(t, err) 125 | favor := service.GetFavoriteSrv() 126 | err = json.Unmarshal(body, &favor) 127 | assert.Empty(t, err) 128 | fmt.Printf(string(body)) 129 | assert.Equal(t, strings.Contains(str, "获取喜欢列表成功"), true) 130 | } 131 | -------------------------------------------------------------------------------- /test/feed_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "ByteRhythm/app/video/service" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/stretchr/testify/assert" 8 | "io/ioutil" 9 | "net/http" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | //-----------------/douyin/feed/接口测试-------------------- 15 | 16 | // 获取视频流成功 17 | func TestFeed(t *testing.T) { 18 | 19 | url := "http://192.168.256.128:8080/douyin/feed/?latest_time=2023-08-30%2007%3A56%3A40.213&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJ0ZXN0MSIsInBhc3N3b3JkIjoiZTEwYWRjMzk0OWJhNTlhYmJlNTZlMDU3ZjIwZjg4M2UiLCJhdmF0YXIiOiJodHRwOi8vcnoybjg3eWNrLmhuLWJrdC5jbG91ZGRuLmNvbS9hdmF0YXIuanBnIiwiYmFja2dyb3VuZF9pbWFnZSI6Imh0dHA6Ly9yejJuODd5Y2suaG4tYmt0LmNsb3VkZG4uY29tL2JhY2tncm91bmQucG5nIiwic2lnbmF0dXJlIjoi5Y-I5p2l55yL5oiR55qE5Li76aG15ZWmfiIsImNyZWF0ZWRfYXQiOiIyMDIzLTA4LTMwVDA3OjI5OjAwLTA3OjAwIiwiZXhwIjoxNjkzNDA3NzEyLCJpYXQiOjE2OTM0MDc1OTIsImlzcyI6InRlc3QxIn0.ns00VSFt3dGUYY73h4P_njeP6F17HahTlmLJ7sJZWjM" 20 | method := "GET" 21 | 22 | client := &http.Client{} 23 | req, err := http.NewRequest(method, url, nil) 24 | 25 | if err != nil { 26 | fmt.Println(err) 27 | return 28 | } 29 | res, err := client.Do(req) 30 | if err != nil { 31 | fmt.Println(err) 32 | return 33 | } 34 | defer res.Body.Close() 35 | 36 | body, err := ioutil.ReadAll(res.Body) 37 | str := string(body) 38 | if err != nil { 39 | fmt.Println(err) 40 | return 41 | } 42 | fmt.Println(string(body)) 43 | assert.Empty(t, err) 44 | video := service.GetVideoSrv() 45 | err = json.Unmarshal(body, &video) 46 | assert.Empty(t, err) 47 | assert.Equal(t, strings.Contains(str, "获取视频流成功"), true) 48 | } 49 | -------------------------------------------------------------------------------- /test/login_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "ByteRhythm/app/user/service" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/stretchr/testify/assert" 8 | "io/ioutil" 9 | "net/http" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | //-----------------/douyin/user/login/接口测试-------------------- 15 | 16 | // 登陆成功 17 | func TestLogin(t *testing.T) { 18 | 19 | url := "http://192.168.256.128:8080/douyin/user/login/?username=test1&password=123456" 20 | method := "POST" 21 | client := &http.Client{} 22 | req, err := http.NewRequest(method, url, nil) 23 | 24 | if err != nil { 25 | fmt.Println(err) 26 | return 27 | } 28 | res, err := client.Do(req) 29 | if err != nil { 30 | fmt.Println(err) 31 | return 32 | } 33 | defer res.Body.Close() 34 | 35 | body, err := ioutil.ReadAll(res.Body) 36 | if err != nil { 37 | fmt.Println(err) 38 | return 39 | } 40 | str := string(body) 41 | fmt.Println(str) 42 | user := service.GetUserSrv() 43 | err = json.Unmarshal(body, &user) 44 | assert.Empty(t, err) 45 | assert.Equal(t, strings.Contains(str, "登录成功"), true) //登录成功则为true 46 | } 47 | 48 | // 用户名为空进行登录 49 | func TestLogin_EmptyName(t *testing.T) { 50 | 51 | url := "http://192.168.256.128:8080/douyin/user/login/?username=&password=123456" 52 | method := "POST" 53 | client := &http.Client{} 54 | req, err := http.NewRequest(method, url, nil) 55 | 56 | if err != nil { 57 | fmt.Println(err) 58 | return 59 | } 60 | res, err := client.Do(req) 61 | if err != nil { 62 | fmt.Println(err) 63 | return 64 | } 65 | defer res.Body.Close() 66 | 67 | body, err := ioutil.ReadAll(res.Body) 68 | if err != nil { 69 | fmt.Println(err) 70 | return 71 | } 72 | str := string(body) 73 | fmt.Println(str) 74 | user := service.GetUserSrv() 75 | err = json.Unmarshal(body, &user) 76 | assert.Empty(t, err) 77 | assert.Equal(t, strings.Contains(str, "用户名或密码错误"), true) 78 | } 79 | 80 | // 密码为空进行登录 81 | func TestLogin_EmptyPassword(t *testing.T) { 82 | 83 | url := "http://192.168.256.128:8080/douyin/user/login/?username=test1&password=" 84 | method := "POST" 85 | client := &http.Client{} 86 | req, err := http.NewRequest(method, url, nil) 87 | 88 | if err != nil { 89 | fmt.Println(err) 90 | return 91 | } 92 | res, err := client.Do(req) 93 | if err != nil { 94 | fmt.Println(err) 95 | return 96 | } 97 | defer res.Body.Close() 98 | 99 | body, err := ioutil.ReadAll(res.Body) 100 | if err != nil { 101 | fmt.Println(err) 102 | return 103 | } 104 | str := string(body) 105 | fmt.Println(str) 106 | user := service.GetUserSrv() 107 | err = json.Unmarshal(body, &user) 108 | assert.Empty(t, err) 109 | assert.Equal(t, strings.Contains(str, "用户名或密码错误"), true) 110 | } 111 | 112 | // 未注册的用户名登录 113 | func TestLogin_NoRegister(t *testing.T) { 114 | 115 | url := "http://192.168.256.128:8080/douyin/user/login/?username=test3&password=123" 116 | method := "POST" 117 | client := &http.Client{} 118 | req, err := http.NewRequest(method, url, nil) 119 | 120 | if err != nil { 121 | fmt.Println(err) 122 | return 123 | } 124 | res, err := client.Do(req) 125 | if err != nil { 126 | fmt.Println(err) 127 | return 128 | } 129 | defer res.Body.Close() 130 | 131 | body, err := ioutil.ReadAll(res.Body) 132 | if err != nil { 133 | fmt.Println(err) 134 | return 135 | } 136 | str := string(body) 137 | fmt.Println(str) 138 | user := service.GetUserSrv() 139 | err = json.Unmarshal(body, &user) 140 | assert.Empty(t, err) 141 | assert.Equal(t, strings.Contains(str, "用户名或密码错误"), true) 142 | } 143 | -------------------------------------------------------------------------------- /test/message_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "ByteRhythm/app/favorite/service" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/stretchr/testify/assert" 8 | "io" 9 | "net/http" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | // -----------------/douyin/message/chat/接口测试-------------------- 15 | // 获取聊天记录成功! 16 | func TestChatMessage(t *testing.T) { 17 | baseUrl := "http://192.168.256.128:8080/douyin/message/chat/?" 18 | token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6OSwidXNlcm5hbWUiOiJ0ZXN0MiIsInBhc3N3b3JkIjoiZTEwYWRjMzk0OWJhNTlhYmJlNTZlMDU3ZjIwZjg4M2UiLCJhdmF0YXIiOiJodHRwOi8vcnoybjg3eWNrLmhuLWJrdC5jbG91ZGRuLmNvbS9hdmF0YXIuanBnIiwiYmFja2dyb3VuZF9pbWFnZSI6Imh0dHA6Ly9yejJuODd5Y2suaG4tYmt0LmNsb3VkZG4uY29tL2JhY2tncm91bmQucG5nIiwic2lnbmF0dXJlIjoi5Y-I5p2l55yL5oiR55qE5Li76aG15ZWmfiIsImNyZWF0ZWRfYXQiOiIyMDIzLTA4LTMwVDAyOjIyOjMzLTA3OjAwIiwiZXhwIjoxNjkzMzg4MDAxLCJpYXQiOjE2OTMzODc5NzEsImlzcyI6InRlc3QyIn0.11RhlMGupHJDoetGndAWSiuNyBeAUHNEIF81VcVJkR0" 19 | to_user_id := "2" 20 | pre_msg_time := "2023-08-30%2023%3A32%3A49" 21 | url := baseUrl + "token=" + token + "&to_user_id=" + to_user_id + "&pre_msg_time=" + pre_msg_time 22 | 23 | method := "GET" 24 | client := &http.Client{} 25 | req, err := http.NewRequest(method, url, nil) 26 | assert.Empty(t, err) 27 | 28 | res, err := client.Do(req) 29 | assert.Empty(t, err) 30 | defer func(Body io.ReadCloser) { 31 | err := Body.Close() 32 | assert.Empty(t, err) 33 | }(res.Body) 34 | 35 | body, err := io.ReadAll(res.Body) 36 | str := string(body) 37 | assert.Empty(t, err) 38 | favor := service.GetFavoriteSrv() 39 | err = json.Unmarshal(body, &favor) 40 | assert.Empty(t, err) 41 | fmt.Printf(string(body)) 42 | assert.Equal(t, strings.Contains(str, "获取聊天记录成功!"), true) 43 | 44 | } 45 | 46 | // 不能查看与自己的聊天记录! 47 | func TestChatMessageMyself(t *testing.T) { 48 | baseUrl := "http://192.168.256.128:8080/douyin/message/chat/?" 49 | token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6OSwidXNlcm5hbWUiOiJ0ZXN0MiIsInBhc3N3b3JkIjoiZTEwYWRjMzk0OWJhNTlhYmJlNTZlMDU3ZjIwZjg4M2UiLCJhdmF0YXIiOiJodHRwOi8vcnoybjg3eWNrLmhuLWJrdC5jbG91ZGRuLmNvbS9hdmF0YXIuanBnIiwiYmFja2dyb3VuZF9pbWFnZSI6Imh0dHA6Ly9yejJuODd5Y2suaG4tYmt0LmNsb3VkZG4uY29tL2JhY2tncm91bmQucG5nIiwic2lnbmF0dXJlIjoi5Y-I5p2l55yL5oiR55qE5Li76aG15ZWmfiIsImNyZWF0ZWRfYXQiOiIyMDIzLTA4LTMwVDAyOjIyOjMzLTA3OjAwIiwiZXhwIjoxNjkzMzg4MDAxLCJpYXQiOjE2OTMzODc5NzEsImlzcyI6InRlc3QyIn0.11RhlMGupHJDoetGndAWSiuNyBeAUHNEIF81VcVJkR0" 50 | to_user_id := "1" 51 | pre_msg_time := "2023-08-30%2023%3A32%3A49" 52 | url := baseUrl + "token=" + token + "&to_user_id=" + to_user_id + "&pre_msg_time=" + pre_msg_time 53 | 54 | method := "GET" 55 | client := &http.Client{} 56 | req, err := http.NewRequest(method, url, nil) 57 | assert.Empty(t, err) 58 | 59 | res, err := client.Do(req) 60 | assert.Empty(t, err) 61 | defer func(Body io.ReadCloser) { 62 | err := Body.Close() 63 | assert.Empty(t, err) 64 | }(res.Body) 65 | 66 | body, err := io.ReadAll(res.Body) 67 | str := string(body) 68 | assert.Empty(t, err) 69 | favor := service.GetFavoriteSrv() 70 | err = json.Unmarshal(body, &favor) 71 | assert.Empty(t, err) 72 | fmt.Printf(string(body)) 73 | assert.Equal(t, strings.Contains(str, "不能查看与自己的聊天记录!"), true) 74 | 75 | } 76 | 77 | // -----------------/douyin/message/action/接口测试-------------------- 78 | // 发送消息成功! 79 | func TestActionMessage(t *testing.T) { 80 | baseUrl := "http://192.168.256.128:8080/douyin/message/action/?" 81 | token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6OSwidXNlcm5hbWUiOiJ0ZXN0MiIsInBhc3N3b3JkIjoiZTEwYWRjMzk0OWJhNTlhYmJlNTZlMDU3ZjIwZjg4M2UiLCJhdmF0YXIiOiJodHRwOi8vcnoybjg3eWNrLmhuLWJrdC5jbG91ZGRuLmNvbS9hdmF0YXIuanBnIiwiYmFja2dyb3VuZF9pbWFnZSI6Imh0dHA6Ly9yejJuODd5Y2suaG4tYmt0LmNsb3VkZG4uY29tL2JhY2tncm91bmQucG5nIiwic2lnbmF0dXJlIjoi5Y-I5p2l55yL5oiR55qE5Li76aG15ZWmfiIsImNyZWF0ZWRfYXQiOiIyMDIzLTA4LTMwVDAyOjIyOjMzLTA3OjAwIiwiZXhwIjoxNjkzMzg4MDAxLCJpYXQiOjE2OTMzODc5NzEsImlzcyI6InRlc3QyIn0.11RhlMGupHJDoetGndAWSiuNyBeAUHNEIF81VcVJkR0" 82 | to_user_id := "2" 83 | action_type := "1" 84 | content := "你弦一郎打了多少遍?" 85 | url := baseUrl + "token=" + token + "&to_user_id=" + to_user_id + "&action_type=" + action_type + "&content=" + content 86 | 87 | method := "POST" 88 | client := &http.Client{} 89 | req, err := http.NewRequest(method, url, nil) 90 | assert.Empty(t, err) 91 | 92 | res, err := client.Do(req) 93 | assert.Empty(t, err) 94 | defer func(Body io.ReadCloser) { 95 | err := Body.Close() 96 | assert.Empty(t, err) 97 | }(res.Body) 98 | 99 | body, err := io.ReadAll(res.Body) 100 | str := string(body) 101 | assert.Empty(t, err) 102 | favor := service.GetFavoriteSrv() 103 | err = json.Unmarshal(body, &favor) 104 | assert.Empty(t, err) 105 | fmt.Printf(string(body)) 106 | assert.Equal(t, strings.Contains(str, "发送消息成功!"), true) 107 | 108 | } 109 | -------------------------------------------------------------------------------- /test/publish_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "ByteRhythm/app/video/service" 5 | "bytes" 6 | "encoding/json" 7 | "fmt" 8 | "github.com/stretchr/testify/assert" 9 | "io" 10 | "mime/multipart" 11 | "net/http" 12 | "os" 13 | "strings" 14 | "testing" 15 | ) 16 | 17 | //-----------------/douyin/publish/action/接口测试-------------------- 18 | 19 | // 发布成功 20 | func TestPublish(t *testing.T) { 21 | url := "http://192.168.256.128:8080/douyin/publish/action/" 22 | method := "POST" 23 | filePath := "/home/jan/Downloads/1.png" //这里的视频一定要确保自己电脑上有 24 | client := &http.Client{} 25 | 26 | payload := &bytes.Buffer{} 27 | writer := multipart.NewWriter(payload) 28 | 29 | file, err := os.Open(filePath) 30 | assert.Empty(t, err) 31 | defer func(file *os.File) { 32 | err := file.Close() 33 | assert.Empty(t, err) 34 | }(file) 35 | fileWriter, err := writer.CreateFormFile("data", file.Name()) 36 | fmt.Printf(file.Name()) 37 | assert.Empty(t, err) 38 | 39 | _, err = io.Copy(fileWriter, file) 40 | assert.Empty(t, err) 41 | 42 | _ = writer.WriteField("token", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTYsInVzZXJuYW1lIjoidGVzdDEiLCJwYXNzd29yZCI6ImUxMGFkYzM5NDliYTU5YWJiZTU2ZTA1N2YyMGY4ODNlIiwiYXZhdGFyIjoiaHR0cDovL3J6Mm44N3ljay5obi1ia3QuY2xvdWRkbi5jb20vYXZhdGFyLmpwZyIsImJhY2tncm91bmRfaW1hZ2UiOiJodHRwOi8vcnoybjg3eWNrLmhuLWJrdC5jbG91ZGRuLmNvbS9iYWNrZ3JvdW5kLnBuZyIsInNpZ25hdHVyZSI6IuWPiOadpeeci-aIkeeahOS4u-mhteWVpn4iLCJjcmVhdGVkX2F0IjoiMjAyMy0wOC0zMFQwNjowMzowMy0wNzowMCIsImV4cCI6MTY5MzQwNDY2OCwiaWF0IjoxNjkzNDA0NTQ4LCJpc3MiOiJ0ZXN0MSJ9.2dlgE1GpvyIjvXTK5Jl9d4ayQm2Rd9Czhf1tGMzue4A") 43 | _ = writer.WriteField("title", "只狼") 44 | err = writer.Close() 45 | assert.Empty(t, err) 46 | 47 | req, err := http.NewRequest(method, url, payload) 48 | assert.Empty(t, err) 49 | 50 | req.Header.Set("Content-Type", writer.FormDataContentType()) 51 | 52 | res, err := client.Do(req) 53 | assert.Empty(t, err) 54 | defer func(Body io.ReadCloser) { 55 | err := Body.Close() 56 | assert.Empty(t, err) 57 | }(res.Body) 58 | 59 | body, err := io.ReadAll(res.Body) 60 | str := string(body) 61 | //fmt.Printf(str) 62 | assert.Empty(t, err) 63 | video := service.GetVideoSrv() 64 | err = json.Unmarshal(body, &video) 65 | assert.Empty(t, err) 66 | assert.Equal(t, strings.Contains(str, "发布成功"), true) 67 | 68 | } 69 | 70 | //-----------------/douyin/publish/list/接口测试-------------------- 71 | 72 | // 获取成功 73 | func TestPublishList(t *testing.T) { 74 | url := "http://192.168.256.128:8080/douyin/publish/list/?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJ0ZXN0MSIsInBhc3N3b3JkIjoiZTEwYWRjMzk0OWJhNTlhYmJlNTZlMDU3ZjIwZjg4M2UiLCJhdmF0YXIiOiJodHRwOi8vcnoybjg3eWNrLmhuLWJrdC5jbG91ZGRuLmNvbS9hdmF0YXIuanBnIiwiYmFja2dyb3VuZF9pbWFnZSI6Imh0dHA6Ly9yejJuODd5Y2suaG4tYmt0LmNsb3VkZG4uY29tL2JhY2tncm91bmQucG5nIiwic2lnbmF0dXJlIjoi5Y-I5p2l55yL5oiR55qE5Li76aG15ZWmfiIsImNyZWF0ZWRfYXQiOiIyMDIzLTA4LTMwVDA3OjI5OjAwLTA3OjAwIiwiZXhwIjoxNjkzNDA3ODEwLCJpYXQiOjE2OTM0MDc2OTAsImlzcyI6InRlc3QxIn0.hFbVJbjg-Ec9sdR0P1ufO4SM4goIU4njlPHMVScHZ0s&user_id=1" 75 | method := "GET" 76 | client := &http.Client{} 77 | req, err := http.NewRequest(method, url, nil) 78 | assert.Empty(t, err) 79 | 80 | res, err := client.Do(req) 81 | assert.Empty(t, err) 82 | defer func(Body io.ReadCloser) { 83 | err := Body.Close() 84 | assert.Empty(t, err) 85 | }(res.Body) 86 | 87 | body, err := io.ReadAll(res.Body) 88 | str := string(body) 89 | assert.Empty(t, err) 90 | feed := service.GetVideoSrv() 91 | err = json.Unmarshal(body, &feed) 92 | assert.Empty(t, err) 93 | assert.Equal(t, strings.Contains(str, "获取成功"), true) 94 | } 95 | -------------------------------------------------------------------------------- /test/register_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "ByteRhythm/app/user/service" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/stretchr/testify/assert" 8 | "io/ioutil" 9 | "net/http" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | //-----------------/douyin/user/register/接口测试-------------------- 15 | 16 | // 用用户名为test1,密码为123456进行注册 17 | func TestRegister(t *testing.T) { 18 | url := "http://192.168.256.128:8080/douyin/user/register/?username=test1&password=123456" 19 | method := "POST" 20 | 21 | client := &http.Client{} 22 | req, err := http.NewRequest(method, url, nil) 23 | 24 | if err != nil { 25 | fmt.Println(err) 26 | return 27 | } 28 | res, err := client.Do(req) 29 | if err != nil { 30 | fmt.Println(err) 31 | return 32 | } 33 | defer res.Body.Close() 34 | 35 | body, err := ioutil.ReadAll(res.Body) 36 | if err != nil { 37 | fmt.Println(err) 38 | return 39 | } 40 | str := string(body) 41 | fmt.Println(str) 42 | user := service.GetUserSrv() 43 | err = json.Unmarshal(body, &user) 44 | assert.Empty(t, err) 45 | assert.Equal(t, strings.Contains(str, "注册成功"), true) //如果是第一次注册则为true 46 | } 47 | 48 | // 重复用户名进行注册 49 | func TestDisplayRegister_DuplicatedName(t *testing.T) { 50 | url := "http://192.168.256.128:8080/douyin/user/register/?username=test1&password=123" 51 | method := "POST" 52 | client := &http.Client{} 53 | req, err := http.NewRequest(method, url, nil) 54 | 55 | if err != nil { 56 | fmt.Println(err) 57 | return 58 | } 59 | res, err := client.Do(req) 60 | if err != nil { 61 | fmt.Println(err) 62 | return 63 | } 64 | defer res.Body.Close() 65 | 66 | body, err := ioutil.ReadAll(res.Body) 67 | if err != nil { 68 | fmt.Println(err) 69 | return 70 | } 71 | str := string(body) 72 | fmt.Println(str) 73 | user := service.GetUserSrv() 74 | err = json.Unmarshal(body, &user) 75 | assert.Empty(t, err) 76 | assert.Equal(t, strings.Contains(str, "用户名已存在"), true) //如果该用户名已经注册了则为true 77 | } 78 | 79 | // 用户名超过32位 80 | func TestOver32Name(t *testing.T) { 81 | url := "http://192.168.256.128:8080/douyin/user/register/?username=123456789999999999999999999999999996666666&password=123" 82 | method := "POST" 83 | client := &http.Client{} 84 | req, err := http.NewRequest(method, url, nil) 85 | 86 | if err != nil { 87 | fmt.Println(err) 88 | return 89 | } 90 | res, err := client.Do(req) 91 | if err != nil { 92 | fmt.Println(err) 93 | return 94 | } 95 | defer res.Body.Close() 96 | 97 | body, err := ioutil.ReadAll(res.Body) 98 | if err != nil { 99 | fmt.Println(err) 100 | return 101 | } 102 | str := string(body) 103 | fmt.Println(str) 104 | user := service.GetUserSrv() 105 | err = json.Unmarshal(body, &user) 106 | assert.Empty(t, err) 107 | assert.Equal(t, strings.Contains(str, "用户名或密码不能超过32位"), true) //如果该用户名超过32位则为true 108 | } 109 | 110 | // 密码超过32位 111 | func TestOver32Password(t *testing.T) { 112 | url := "http://192.168.256.128:8080/douyin/user/register/?username=test2&password=12345678999999999999999999999999999" 113 | method := "POST" 114 | client := &http.Client{} 115 | req, err := http.NewRequest(method, url, nil) 116 | 117 | if err != nil { 118 | fmt.Println(err) 119 | return 120 | } 121 | res, err := client.Do(req) 122 | if err != nil { 123 | fmt.Println(err) 124 | return 125 | } 126 | defer res.Body.Close() 127 | 128 | body, err := ioutil.ReadAll(res.Body) 129 | if err != nil { 130 | fmt.Println(err) 131 | return 132 | } 133 | str := string(body) 134 | fmt.Println(str) 135 | user := service.GetUserSrv() 136 | err = json.Unmarshal(body, &user) 137 | assert.Empty(t, err) 138 | assert.Equal(t, strings.Contains(str, "用户名或密码不能超过32位"), true) //如果密码超过32位则为true 139 | } 140 | -------------------------------------------------------------------------------- /test/user_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "ByteRhythm/app/user/service" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/stretchr/testify/assert" 8 | "io/ioutil" 9 | "net/http" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | //--------/douyin/user/接口测试-------------------------- 15 | 16 | // 获取用户信息成功,是test1用户的 17 | func TestUserInfo(t *testing.T) { 18 | url := "http://192.168.256.128:8080/douyin/user/?user_id=10&token=" + 19 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTAsInVzZXJuYW1lIjoidGVzdDEiLCJwYXNzd29yZCI6ImUxMGFkYzM5NDliYTU5YWJiZTU2ZTA1N2YyMGY4ODNlIiwiYXZhdGFyIjoiaHR0cDovL3J6Mm44N3ljay5obi1ia3QuY2xvdWRkbi5jb20vYXZhdGFyLmpwZyIsImJhY2tncm91bmRfaW1hZ2UiOiJodHRwOi8vcnoybjg3eWNrLmhuLWJrdC5jbG91ZGRuLmNvbS9iYWNrZ3JvdW5kLnBuZyIsInNpZ25hdHVyZSI6IuWPiOadpeeci-aIkeeahOS4u-mhteWVpn4iLCJjcmVhdGVkX2F0IjoiMjAyMy0wOC0zMFQwNDo0Njo1OC0wNzowMCIsImV4cCI6MTY5MzM5NjQzOCwiaWF0IjoxNjkzMzk2MzE4LCJpc3MiOiJ0ZXN0MSJ9.NQymZNYRryaBFpbaYApSqBuwKfyYEIIGHLZGRNEn1as" 20 | //token随时都在变,要测的时候再具体修改 21 | method := "GET" 22 | client := &http.Client{} 23 | req, err := http.NewRequest(method, url, nil) 24 | if err != nil { 25 | fmt.Println(err) 26 | return 27 | } 28 | res, err := client.Do(req) 29 | if err != nil { 30 | fmt.Println(err) 31 | return 32 | } 33 | defer res.Body.Close() 34 | 35 | body, err := ioutil.ReadAll(res.Body) 36 | if err != nil { 37 | fmt.Println(err) 38 | return 39 | } 40 | str := string(body) 41 | fmt.Println(str) 42 | user := service.GetUserSrv() 43 | err = json.Unmarshal(body, &user) 44 | assert.Empty(t, err) 45 | assert.Equal(t, strings.Contains(str, "获取用户信息成功"), true) 46 | } 47 | 48 | // 用户不存在 49 | func TestNoUser(t *testing.T) { 50 | url := "http://192.168.256.128:8080/douyin/user/?user_id=14&token=" + 51 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTAsInVzZXJuYW1lIjoidGVzdDEiLCJwYXNzd29yZCI6ImUxMGFkYzM5NDliYTU5YWJiZTU2ZTA1N2YyMGY4ODNlIiwiYXZhdGFyIjoiaHR0cDovL3J6Mm44N3ljay5obi1ia3QuY2xvdWRkbi5jb20vYXZhdGFyLmpwZyIsImJhY2tncm91bmRfaW1hZ2UiOiJodHRwOi8vcnoybjg3eWNrLmhuLWJrdC5jbG91ZGRuLmNvbS9iYWNrZ3JvdW5kLnBuZyIsInNpZ25hdHVyZSI6IuWPiOadpeeci-aIkeeahOS4u-mhteWVpn4iLCJjcmVhdGVkX2F0IjoiMjAyMy0wOC0zMFQwNDo0Njo1OC0wNzowMCIsImV4cCI6MTY5MzM5NjQzOCwiaWF0IjoxNjkzMzk2MzE4LCJpc3MiOiJ0ZXN0MSJ9.NQymZNYRryaBFpbaYApSqBuwKfyYEIIGHLZGRNEn1as" 52 | method := "GET" 53 | client := &http.Client{} 54 | req, err := http.NewRequest(method, url, nil) 55 | if err != nil { 56 | fmt.Println(err) 57 | return 58 | } 59 | res, err := client.Do(req) 60 | if err != nil { 61 | fmt.Println(err) 62 | return 63 | } 64 | defer res.Body.Close() 65 | 66 | body, err := ioutil.ReadAll(res.Body) 67 | if err != nil { 68 | fmt.Println(err) 69 | return 70 | } 71 | str := string(body) 72 | fmt.Println(str) 73 | user := service.GetUserSrv() 74 | err = json.Unmarshal(body, &user) 75 | assert.Empty(t, err) 76 | assert.Equal(t, strings.Contains(str, "用户不存在"), true) 77 | } 78 | -------------------------------------------------------------------------------- /util/array_change.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | ) 7 | 8 | // StringArray2IntArray 将字符串数组转化为整数数组 9 | func StringArray2IntArray(strArray []string) []int { 10 | var intArray []int 11 | for _, str := range strArray { 12 | num, _ := strconv.Atoi(str[strings.Index(str, ":")+1:]) 13 | intArray = append(intArray, num) 14 | } 15 | return intArray 16 | } 17 | -------------------------------------------------------------------------------- /util/fail_request.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | func FailRequest(StatusMsg string) gin.H { 6 | return gin.H{ 7 | "status_code": 1, 8 | "status_msg": StatusMsg, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /util/md5.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | ) 7 | 8 | func Md5(str string) string { 9 | hash := md5.New() 10 | hash.Write([]byte(str)) 11 | return fmt.Sprintf("%x", hash.Sum(nil)) 12 | } 13 | -------------------------------------------------------------------------------- /util/sort.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | func SortKeys(keys []string) []string { 10 | // 对key按照第二个%d排序(%d可能不止一位),并改为降序排序 11 | sort.Slice(keys, func(i, j int) bool { 12 | // 提取第二个%d后的数字进行比较 13 | num1, _ := strconv.Atoi(keys[i][strings.Index(keys[i], ":")+1:]) 14 | num2, _ := strconv.Atoi(keys[j][strings.Index(keys[j], ":")+1:]) 15 | return num1 > num2 16 | }) 17 | 18 | // 只保留前30个视频 19 | if len(keys) > 30 { 20 | keys = keys[:30] 21 | } 22 | 23 | return keys 24 | } 25 | -------------------------------------------------------------------------------- /util/token.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "ByteRhythm/model" 5 | "fmt" 6 | "log" 7 | "time" 8 | 9 | "github.com/dgrijalva/jwt-go" 10 | ) 11 | 12 | const ( 13 | KEY string = "JWT-ARY-STARK" 14 | DefaultExpireSeconds int = 3600 // default 60 minutes 15 | ) 16 | 17 | // MyCustomClaims JWT -- json web token 18 | // HEADER PAYLOAD SIGNATURE 19 | // This struct is the PAYLOAD 20 | type MyCustomClaims struct { 21 | model.User 22 | jwt.StandardClaims 23 | } 24 | 25 | // RefreshToken update expireAt and return a new token 26 | func RefreshToken(tokenString string) (string, error) { 27 | // first get previous token 28 | token, err := jwt.ParseWithClaims( 29 | tokenString, 30 | &MyCustomClaims{}, 31 | func(token *jwt.Token) (interface{}, error) { 32 | return []byte(KEY), nil 33 | }) 34 | claims, ok := token.Claims.(*MyCustomClaims) 35 | if !ok || !token.Valid { 36 | return "", err 37 | } 38 | mySigningKey := []byte(KEY) 39 | expireAt := time.Now().Add(time.Second * time.Duration(DefaultExpireSeconds)).Unix() 40 | newClaims := MyCustomClaims{ 41 | claims.User, 42 | jwt.StandardClaims{ 43 | ExpiresAt: expireAt, 44 | Issuer: claims.User.Username, 45 | IssuedAt: time.Now().Unix(), 46 | }, 47 | } 48 | // generate new token with new claims 49 | newToken := jwt.NewWithClaims(jwt.SigningMethodHS256, newClaims) 50 | tokenStr, err := newToken.SignedString(mySigningKey) 51 | if err != nil { 52 | fmt.Println("generate new fresh json web token failed !! error :", err) 53 | return "", err 54 | } 55 | return tokenStr, err 56 | } 57 | 58 | func ValidateToken(tokenString string) error { 59 | token, err := jwt.ParseWithClaims( 60 | tokenString, 61 | &MyCustomClaims{}, 62 | func(token *jwt.Token) (interface{}, error) { 63 | return []byte(KEY), nil 64 | }) 65 | if err != nil { 66 | log.Println("validate tokenString failed !!!", err) 67 | return err 68 | } 69 | if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid { 70 | //fmt.Printf("%v %v", claims.User, claims.StandardClaims.ExpiresAt) 71 | fmt.Println("token will be expired at ", time.Unix(claims.StandardClaims.ExpiresAt, 0)) 72 | } else { 73 | log.Println("validate tokenString failed !!!", err) 74 | return err 75 | } 76 | return nil 77 | } 78 | 79 | func GenerateToken(user *model.User, expiredSeconds int) (tokenString string) { 80 | if expiredSeconds == 0 { 81 | expiredSeconds = DefaultExpireSeconds 82 | } 83 | // Create the Claims 84 | mySigningKey := []byte(KEY) 85 | expireAt := time.Now().Add(time.Second * time.Duration(expiredSeconds)).Unix() 86 | fmt.Println("token will be expired at ", time.Unix(expireAt, 0)) 87 | // pass parameter to this func or not 88 | 89 | claims := MyCustomClaims{ 90 | *user, 91 | jwt.StandardClaims{ 92 | ExpiresAt: expireAt, 93 | Issuer: user.Username, 94 | IssuedAt: time.Now().Unix(), 95 | }, 96 | } 97 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 98 | tokenStr, err := token.SignedString(mySigningKey) 99 | if err != nil { 100 | log.Println("generate json web token failed !! error :", err) 101 | } 102 | return tokenStr 103 | 104 | } 105 | 106 | func GetUsernameFromToken(tokenString string) (string, error) { 107 | token, err := jwt.ParseWithClaims( 108 | tokenString, 109 | &MyCustomClaims{}, 110 | func(token *jwt.Token) (interface{}, error) { 111 | return []byte(KEY), nil 112 | }) 113 | 114 | if err != nil { 115 | return "", err 116 | } 117 | 118 | claims, ok := token.Claims.(*MyCustomClaims) 119 | if !ok || !token.Valid { 120 | return "", fmt.Errorf("invalid token") 121 | } 122 | 123 | return claims.User.Username, nil 124 | } 125 | 126 | func GetUserFromToken(tokenString string) (*model.User, error) { 127 | token, err := jwt.ParseWithClaims( 128 | tokenString, 129 | &MyCustomClaims{}, 130 | func(token *jwt.Token) (interface{}, error) { 131 | return []byte(KEY), nil 132 | }) 133 | 134 | if err != nil { 135 | return nil, err 136 | } 137 | 138 | claims, ok := token.Claims.(*MyCustomClaims) 139 | if !ok || !token.Valid { 140 | return nil, fmt.Errorf("invalid token") 141 | } 142 | 143 | return &claims.User, nil 144 | } 145 | 146 | func GetUserIdFromToken(tokenString string) (int, error) { 147 | token, err := jwt.ParseWithClaims( 148 | tokenString, 149 | &MyCustomClaims{}, 150 | func(token *jwt.Token) (interface{}, error) { 151 | return []byte(KEY), nil 152 | }) 153 | 154 | if err != nil { 155 | return -1, err 156 | } 157 | 158 | claims, ok := token.Claims.(*MyCustomClaims) 159 | if !ok || !token.Valid { 160 | return -1, fmt.Errorf("invalid token") 161 | } 162 | 163 | return int(claims.User.ID), nil 164 | } 165 | -------------------------------------------------------------------------------- /util/upload.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "ByteRhythm/config" 5 | "bytes" 6 | "context" 7 | "fmt" 8 | "strings" 9 | 10 | "github.com/qiniu/go-sdk/v7/auth/qbox" 11 | "github.com/qiniu/go-sdk/v7/storage" 12 | ) 13 | 14 | func UploadVideo(data []byte) (VideoUrl string, err error) { 15 | config.Init() 16 | size := int64(len(data)) 17 | key := fmt.Sprintf("%s.mp4", GenerateUUID()) 18 | putPolicy := storage.PutPolicy{ 19 | Scope: fmt.Sprintf("%s:%s", config.Bucket, key), 20 | } 21 | mac := qbox.NewMac(config.AccessKey, config.SecretKey) 22 | upToken := putPolicy.UploadToken(mac) 23 | cfg := storage.Config{} 24 | uploader := storage.NewFormUploader(&cfg) 25 | ret := storage.PutRet{} 26 | putExtra := storage.PutExtra{ 27 | Params: map[string]string{ 28 | "x:name": "github logo", 29 | }, 30 | } 31 | err = uploader.Put(context.Background(), &ret, upToken, key, bytes.NewReader(data), size, &putExtra) 32 | if err != nil { 33 | return "", err 34 | } 35 | 36 | return fmt.Sprintf("%s/%s", config.Domain, ret.Key), nil 37 | } 38 | 39 | func UploadJPG(imgPath string, videoUrl string) string { 40 | config.Init() 41 | 42 | videoName := strings.Split(strings.Replace(videoUrl, config.Domain+"/", "", -1), ".")[0] 43 | key := fmt.Sprintf("%s.%s", videoName+"_cover", "jpg") 44 | 45 | putPolicy := storage.PutPolicy{ 46 | Scope: config.Bucket, 47 | } 48 | mac := qbox.NewMac(config.AccessKey, config.SecretKey) 49 | upToken := putPolicy.UploadToken(mac) 50 | cfg := storage.Config{} 51 | 52 | // 构建表单上传的对象 53 | formUploader := storage.NewFormUploader(&cfg) 54 | ret := storage.PutRet{} 55 | 56 | // 可选配置 57 | putExtra := storage.PutExtra{ 58 | Params: map[string]string{ 59 | "x:name": "github logo", 60 | }, 61 | } 62 | err := formUploader.PutFile(context.Background(), &ret, upToken, key, imgPath, &putExtra) 63 | if err != nil { 64 | fmt.Println(err) 65 | return "" 66 | } 67 | return fmt.Sprintf("%s/%s", config.Domain, ret.Key) 68 | } 69 | -------------------------------------------------------------------------------- /util/uuid.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "github.com/google/uuid" 4 | 5 | func GenerateUUID() string { 6 | id := uuid.New() 7 | return id.String() 8 | } 9 | --------------------------------------------------------------------------------