├── .idea
├── .gitignore
├── Tiny-TikTok.iml
├── inspectionProfiles
│ └── Project_Default.xml
├── modules.xml
├── sqldialects.xml
└── vcs.xml
├── README.md
├── api_router
├── cmd
│ └── main.go
├── configs
│ ├── config.go
│ └── config.yaml
├── discovery
│ └── resolver.go
├── go.mod
├── go.sum
├── internal
│ ├── handler
│ │ ├── comment.go
│ │ ├── erros.go
│ │ ├── favorite.go
│ │ ├── follow.go
│ │ ├── message.go
│ │ ├── user.go
│ │ └── video.go
│ └── service
│ │ ├── pb
│ │ ├── socialService.proto
│ │ ├── userService.proto
│ │ └── videoService.proto
│ │ ├── socialService.pb.go
│ │ ├── socialService_grpc.pb.go
│ │ ├── userService.pb.go
│ │ ├── userService_grpc.pb.go
│ │ ├── videoService.pb.go
│ │ └── videoService_grpc.pb.go
├── logs
│ ├── 2023-08-22.log
│ ├── 2023-08-22.log1800
│ ├── 2023-08-27.log
│ ├── 2023-08-27.log1500
│ ├── 2023-08-27.log1800
│ ├── 2023-08-28.log
│ ├── 2023-08-28.log1200
│ ├── 2023-09-04.log
│ ├── 2023-09-04.log0900
│ └── 2023-09-04.log1500
├── pkg
│ ├── auth
│ │ └── jwt.go
│ ├── logger
│ │ ├── log.go
│ │ └── log_test.go
│ ├── res
│ │ └── response.go
│ └── wrapper
│ │ └── hystrix.go
└── router
│ ├── middleware
│ ├── error.go
│ ├── jwt.go
│ └── serve.go
│ └── router.go
├── docs
├── README.md
├── image
│ ├── 总体设计图.png
│ ├── 技术架构图.png
│ ├── 数据库设计图.png
│ ├── 获奖证书.png
│ └── 项目获奖证书.png
└── mysql
│ └── tables.sql
├── social_service
├── cmd
│ └── main.go
├── config
│ ├── config.go
│ ├── config.yaml
│ └── config_test.go
├── discovery
│ └── autoRegister.go
├── go.mod
├── go.sum
├── internal
│ ├── handler
│ │ └── social.go
│ ├── model
│ │ ├── follow.go
│ │ ├── follow_test.go
│ │ ├── init.go
│ │ ├── init_test.go
│ │ ├── message.go
│ │ ├── message_test.go
│ │ └── migration.go
│ └── service
│ │ ├── pb
│ │ └── socialService.proto
│ │ ├── socialService.pb.go
│ │ └── socialService_grpc.pb.go
└── pkg
│ └── cache
│ ├── redis.go
│ ├── redisAutoSync.go
│ └── redis_test.go
├── user_service
├── cmd
│ └── main.go
├── config
│ ├── config.go
│ ├── config.yaml
│ └── config_test.go
├── discovery
│ └── autoRegister.go
├── go.mod
├── go.sum
├── internal
│ ├── handler
│ │ └── user.go
│ ├── model
│ │ ├── init.go
│ │ ├── init_test.go
│ │ ├── migration.go
│ │ ├── user.go
│ │ └── user_test.go
│ └── service
│ │ ├── pb
│ │ └── userService.proto
│ │ ├── userService.pb.go
│ │ └── userService_grpc.pb.go
└── pkg
│ ├── cache
│ ├── redis.go
│ └── redis_test.go
│ └── encryption
│ └── encrypt.go
├── utils
├── etcd
│ ├── discovery.go
│ └── register.go
├── exception
│ ├── statusCode.go
│ └── statusMsg.go
├── go.mod
├── go.sum
└── snowFlake
│ ├── snow.go
│ └── snow_test.go
└── video_service
├── cmd
└── main.go
├── config
├── config.go
└── config.yaml
├── discovery
└── autoRegister.go
├── go.mod
├── go.sum
├── internal
├── handler
│ ├── comment.go
│ ├── favorite.go
│ ├── video.go
│ └── video_test.go
├── model
│ ├── comment.go
│ ├── favorite.go
│ ├── init.go
│ ├── init_test.go
│ ├── migration.go
│ ├── video.go
│ └── video_test.go
└── service
│ ├── pb
│ └── videoService.proto
│ ├── videoService.pb.go
│ └── videoService_grpc.pb.go
├── pkg
├── cache
│ ├── redis.go
│ └── redis_test.go
├── cut
│ ├── ffmpeg.go
│ └── ffmpeg_test.go
└── mq
│ ├── rabbitmq.go
│ └── rabbitmq_test.go
└── third_party
├── oss.go
└── oss_test.go
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/Tiny-TikTok.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/sqldialects.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
Tiny-TikTok
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | ## 1 项目介绍
22 |
23 | 简易抖音项目后端实现,使用 **Gin** 作为web框架,**MySQL** 作为数据存储并使用 Gorm 操作数据库。整个项目分为用户服务、视频服务、社交服务,使用 **Etcd** 作为注册中心,**Grpc** 进行服务之间的通信。 采用 **Redis** 作为缓存,提高读写效率;使用消息中间件 **RabbitMQ**,达到上游服务和下游服务的解耦。
24 |
25 | 
26 |
27 | **简单写一个总结:**
28 |
29 | 青训营结束也过去了一段时间,这次获得了**三等奖**,还算比较满意的结果(自己离还有不少差距,心服口服)。本来两个人完成的这个项目,自己也不是主力golang选手,一路跌跌撞撞,也将近用了完整的三四周时间。
30 |
31 | 收获最大的有两个点。一是只有思维的摩擦,才会有创造,比如缓存redis的设计,一开始只想放在后端和数据库之间,简单降低数据库的压力,仅此而已。但是后面在思考的时候,面对不同的场景,采用不同的数据结构,以及如何保证数据的一致性等等。第二个就是实践,只有真正去写代码了,才能意识到很多问题,理论上能滔滔不绝的说出来,但是实践出来还是有很大的差距,一些细节根本在想的时候抓不住。
32 |
33 | 最后总结一下自己的优势与不足,也算给后续想参加青训的同志们的一些简易吧。本项目对比其余项目,可能(自我感觉)优于其它人的,就是测试比较完整。对于redis、MQ的使用,这些大家基本都会去使用,但是使用的时候一定要往深度去思考,比如redis中数据的一致性(这个问题在最后答辩的时候都会问,有的人会考虑掉或者做得不够好)。然后就是广度,这是我们明显缺失的,对于前面的我最佩服的几个项目,从技术选型,到团队合作(一些团队合作工作),项目部署(自动化部署),服务监控,日志等都几乎完整的实现。而我们部署靠手动,监控只统一处理了日志,还有很大很大的差距。还有技术选型有一点点小问题,因为我参加过上一届青训,但是那次没项目就纯看视频课(没啥用),所以这次的课没有咋看,这次是教了Hertz和kitex的,所以可能会给出一种没好好听课的感觉吧~
34 |
35 | **如果对你有帮助,右上角 Satr 走起 !!!**
36 |
37 | ### 1.1 目录介绍
38 |
39 | **第一部分:项目简介**
40 |
41 | **第二部分:项目概览。包括整个项目的技术选择、整体设计、目录结构设计与数据库设计**
42 |
43 | **第三部分:项目详细设计。包括对项目的思考以及一些关键点的设计思路**
44 |
45 | **第四部分:项目测试**
46 |
47 | **第五部分:启动说明。拿到整个项目,应该如何让它跑起来**
48 |
49 |
50 |
51 | ### 1.2 分支介绍
52 |
53 | 本项目的分支是项目的几个迭代版本,可以根据新老版本查看,由简到难:
54 |
55 | * **main分支**:最新版本,仍在迭代中。修复一些小bug,加入消息队列RabbitMQ
56 |
57 | * **1.0分支**:最老的分支,项目完整的启动,最简单的版本
58 |
59 | * **2.0分支**:在1.0的基础上优化缓存,索引。
60 |
61 |
62 |
63 | ## 2 项目概览
64 |
65 | ### 2.1 技术选型与设计
66 |
67 | 
68 |
69 | * **Gin**:Web框架。高性能、轻量级、简洁,被广泛用于构建RESTful API、网站和其它HTTP服务
70 |
71 | * **JWT**:用于身份的验证和授权,具有跨平台、无状态的优点,不需要再会话中保存任何信息,减轻服务器的负担
72 |
73 | * **Hystrix**:服务熔断,防止由于单个服务的故障导致整个系统的崩溃
74 |
75 | * **Etcd + grpc**:etcd实现服务注册和服务发现,grpc负责通信,构建健壮的分布式系统
76 |
77 | * **OSS**:对象存储服务器,用于存储和管理非结构化数据,比如图片、视频等
78 |
79 | * **FFmpeg**:多媒体开源工具,本项目用于上传视频时封面的截取
80 |
81 | * **Gorm**:ORM库,用于操作数据库
82 |
83 | * **MySQL**:关系型数据库,用于存储结构化数据
84 |
85 | * **Redis**:键值存储数据库,以内存作为数据存储介质,提供高性能读写
86 |
87 | * **RabbitMQ**:消息中间件,用于程序之间传递消息,支持消息的发布和订阅,支持消息异步传递,实现解耦和异步处理
88 |
89 |
90 |
91 | ### 2.2 总体设计
92 |
93 | 
94 |
95 | * 请求到达服务器前会对token进行校验
96 | * 通过Api_Router对外暴露接口,进入服务,网关微服务对其它服务进行服务熔断和服务限流
97 | * 各个服务先注册进入ETCD,api_router对各个服务进行调用,组装信息返回给服务端
98 | * api_router通过gprc实现服务之间的通讯
99 | * 服务操作数据库,将信息返回给上一层
100 |
101 |
102 |
103 | ### 2.3 项目结构设计
104 |
105 | * **总目录结构**
106 |
107 | ```bash
108 | ├─api_router # 路由网关
109 | ├─docs # 项目文档
110 | ├─social_service # 社交服务
111 | ├─user_service # 用户服务
112 | ├─utils # 工具函数包
113 | └─video_service # 视频服务
114 | ```
115 |
116 |
117 |
118 | * **路由网关**
119 |
120 | ```bash
121 | ├─api_router
122 | │ ├─cmd
123 | │ ├─config # 项目配置文件
124 | │ ├─discovery # 服务注册与发现
125 | │ ├─internal
126 | │ │ ├─handler
127 | │ │ └─service
128 | │ │ └─pb
129 | │ ├─pkg
130 | │ │ ├─auth
131 | │ │ └─res
132 | │ └─router # 路由和中间件
133 | │ └─middleware
134 | ```
135 |
136 | **/cmd**:一个项目可以有很多个组件,吧main函数所在文件夹同一放在/cmd目录下
137 |
138 | **/internal**:存放私有应用代码。handler类似三层架构中的控制层,service类似服务层,路由不用操作数据库,所以没有持久层
139 |
140 | **/pkg**:存放可以被外部使用的代码库。auth中存放token鉴权,res存放对服务端的统一返回
141 |
142 |
143 |
144 | * **具体服务**
145 |
146 | ```bash
147 | ├─user_service
148 | │ ├─cmd
149 | │ ├─config
150 | │ ├─discovery
151 | │ ├─internal
152 | │ │ ├─handler
153 | │ │ ├─model
154 | │ │ └─service # 持久化层
155 | │ │ └─pb
156 | │ └─pkg
157 | │ └─encryption # 密码加密
158 | ```
159 |
160 |
161 |
162 | * **工具函数包**
163 |
164 | ```bash
165 | ├─utils
166 | │ ├─etcd # etcd服务注册与发现组件
167 | │ ├─exception
168 | │ └─snowFlake # 雪花算法生成ID
169 | ```
170 |
171 |
172 |
173 | ### 2.4 数据库设计
174 |
175 | 
176 |
177 | **用户表**:用于存储用户名称、密码、头像、背景、用户简介信息,以由雪花算法生成的分布式id作为主键(其余表的ID同理),密码由bcrypt函数进行加密。
178 |
179 | **视频表**:用于存储视频的作者、标题、封面路径、视频路径、获赞数量以及评论数量,以视频作者的id关联用户表。
180 |
181 | **评论表**:用于存储视频的评论信息、评论创建时间、评论状态,通过用户id以及视频id关联用户表和视频表,通过评论状态作为软删除判断当前评论是否存在。
182 |
183 | **消息表**:用于存放用户发送的消息以及消息的创建时间,通过用户id关联用户表,记录消息的发生者和消息的接收者
184 |
185 | **关注表**:用户存放用户的关注信息,通过用户id关联用户表获取关注者和被关注者
186 |
187 | **点赞表**:用于存放视频的点赞信息,通过用户id关联用户表获取点赞的用户,视频id关联视频表获取被点赞的视频
188 |
189 |
190 |
191 | ## 3 详细设计
192 |
193 | 本部分包含:**认证鉴权**、**分布式唯一ID**、**密码加密**、**数据库操作**、**视频上传**、**日志打印**、**服务熔断**、**统一错误处理**、**高性能读写**、**异步解耦**的设计
194 |
195 | 详细设计参考答辩文档第三部分:[说明文档](https://v1rwxew1bdp.feishu.cn/docx/ATJPdobcOouDDLxVHsycpANMnig?from=from_copylink)
196 |
197 |
198 |
199 | ## 4 测试
200 |
201 | 本部分包含:单元测试、接口测试(功能测试)、性能测试(压力测试)
202 |
203 | 测试详情查看测试文档:[测试报告](https://v1rwxew1bdp.feishu.cn/docx/F1aQdSY6AoIzeLx6B8tcVQhgnud?from=from_copylink)
204 |
205 |
206 |
207 | ## 5 启动说明
208 |
209 | ### 5.1 启动之前
210 |
211 | 项目启动之前,了解各个组件的版本,一直觉得版本是一个很大的问题,先搞清楚每一个组件版本,后续才能有条不紊的进行
212 |
213 | | 名称 | 版本 | 作用 |
214 | | :----------: | :---: | :------------------: |
215 | | **Go** | 1.19 | |
216 | | **MySQL** | 8.0 | 关系型数据库 |
217 | | **Etcd** | 3.5.1 | 注册中心 |
218 | | **Redis** | 5.0.7 | 缓存 |
219 | | **RabbitMQ** | 3.7.4 | 消息中间件,异步解耦 |
220 | | **FFmpeg** | 6.0 | 视频剪切为封面 |
221 | | **Protoc** | 3.15 | 生成pb文件 |
222 |
223 | 确保每一个组件安装成功,并将ffmpeg和protoc配置进入环境变量
224 |
225 |
226 |
227 | ### 5.2 拉取项目
228 |
229 | 保证安装了git,直接克隆到本地
230 |
231 | ```bash
232 | git clone https://github.com/marinezz/Tiny-TikTok.git
233 | ```
234 |
235 |
236 |
237 | ### 5.3 拉取依赖
238 |
239 | 将所有需要的依赖拉取到本地,进入每一个文件,拉取依赖。以api_router为例(其余文件操作相同):
240 |
241 | ```bash
242 | cd api_router/
243 | go mod tidy
244 | ```
245 |
246 |
247 |
248 | ### 5.4 启动项目
249 |
250 | **第一步**:启动etcd。确保etcd注册中心启动成功。如果不放心可以下载ectdkeeper(参考网络),后续也可以参看服务是否注册进入注册中心
251 |
252 | **第二步**:建立数据库。建立名为tiny_tiktok的数据库,不用建表,启动时gorm会自动建表
253 |
254 | **第三步**:修改配置文件。参考配置文件example,根据自己的实际情况修改配置文件
255 |
256 | **第四步**:正式启动项目。到每个文件的cmd文件中启动项目,或者使用命令 `go run main.go`
257 |
258 | **第五步**:项目启动成功
259 |
260 |
261 |
262 | ## 6 总结与展望
263 |
264 | 项目的从0到有,经过了一个月的时间,在开发过程中遇到了很多问题,也解决很多问题,得到了很多收获。在不断的实际操作中,才能有新的收获,新的想法。截止青训营的结束,现在还是有很多想法的存在,我们也会把自己的想法付诸实现,完完整整做出自己满意的项目。
265 |
266 |
267 |
268 | 我们还会继续ing.....
269 |
270 |
271 |
272 | **如果对你有帮助的话,希望您不要吝啬你得star哦!!**
273 |
274 | 
275 |
--------------------------------------------------------------------------------
/api_router/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | config "api_router/configs"
5 | "api_router/discovery"
6 | "api_router/pkg/logger"
7 | "api_router/router"
8 | "github.com/spf13/viper"
9 | "net/http"
10 | "time"
11 | )
12 |
13 | func main() {
14 | config.InitConfig()
15 | resolver := discovery.Resolver()
16 | r := router.InitRouter(resolver)
17 | server := &http.Server{
18 | Addr: viper.GetString("server.port"),
19 | Handler: r,
20 | ReadTimeout: 10 * time.Second,
21 | WriteTimeout: 10 * time.Second,
22 | MaxHeaderBytes: 1 << 20,
23 | }
24 |
25 | err := server.ListenAndServe()
26 | if err != nil {
27 | logger.Log.Fatal("启动失败...")
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/api_router/configs/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/spf13/viper"
5 | "path"
6 | "runtime"
7 | "strings"
8 | )
9 |
10 | // InitConfig 读取配置文件
11 | func InitConfig() {
12 | _, filePath, _, _ := runtime.Caller(0)
13 |
14 | currentDir := path.Dir(filePath)
15 |
16 | viper.SetConfigName("config")
17 | viper.SetConfigType("yaml")
18 | viper.AddConfigPath(currentDir)
19 |
20 | if err := viper.ReadInConfig(); err != nil {
21 | panic(err)
22 | }
23 |
24 | }
25 |
26 | // DbDnsInit 拼接链接数据库的DNS
27 | func DbDnsInit() string {
28 | host := viper.GetString("mysql.host")
29 | port := viper.GetString("mysql.port")
30 | username := viper.GetString("mysql.username")
31 | password := viper.GetString("mysql.password")
32 | database := viper.GetString("mysql.database")
33 |
34 | InitConfig()
35 | dns := strings.Join([]string{username, ":", password, "@tcp(", host, ":", port, ")/", database, "?charset=utf8&parseTime=True&loc=Local"}, "")
36 |
37 | return dns
38 | }
39 |
--------------------------------------------------------------------------------
/api_router/configs/config.yaml:
--------------------------------------------------------------------------------
1 | server:
2 | name: api_router # 服务名称
3 | port: :10000
4 | address: 127.0.0.1:10000 # 服务地址
5 | jwtSecret: 123456 # jwt密码
6 |
7 |
8 | mysql:
9 | driver: mysql
10 | host: 127.0.0.1
11 | port: 3306
12 | username: root
13 | password: 123456
14 | database: tiny_tiktok
15 |
16 |
17 | etcd:
18 | address: 127.0.0.1:2379
--------------------------------------------------------------------------------
/api_router/discovery/resolver.go:
--------------------------------------------------------------------------------
1 | // 服务发现,发现所有的服务,返回一个map
2 |
3 | package discovery
4 |
5 | import (
6 | "api_router/internal/service"
7 | "api_router/pkg/logger"
8 | "api_router/pkg/wrapper"
9 | "github.com/spf13/viper"
10 | "google.golang.org/grpc"
11 | "google.golang.org/grpc/credentials/insecure"
12 | "utils/etcd"
13 | )
14 |
15 | func Resolver() map[string]interface{} {
16 | serveInstance := make(map[string]interface{})
17 |
18 | etcdAddress := viper.GetString("etcd.address")
19 | serviceDiscovery, err := etcd.NewServiceDiscovery([]string{etcdAddress})
20 | if err != nil {
21 | logger.Log.Fatal(err)
22 | }
23 | defer serviceDiscovery.Close()
24 |
25 | // 获取用户服务实例
26 | err = serviceDiscovery.ServiceDiscovery("user_service")
27 | if err != nil {
28 | logger.Log.Fatal(err)
29 | }
30 | userServiceAddr, _ := serviceDiscovery.GetService("user_service")
31 | userConn, err := grpc.Dial(userServiceAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
32 | if err != nil {
33 | logger.Log.Fatal(err)
34 | }
35 | userClient := service.NewUserServiceClient(userConn)
36 | logger.Log.Info("获取用户服务实例--成功--")
37 | serveInstance["user_service"] = userClient
38 |
39 | // 获取视频服务实例
40 | err = serviceDiscovery.ServiceDiscovery("video_service")
41 | if err != nil {
42 | logger.Log.Fatal(err)
43 | }
44 | videoServiceAddr, _ := serviceDiscovery.GetService("video_service")
45 | videoConn, err := grpc.Dial(videoServiceAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
46 | if err != nil {
47 | logger.Log.Fatal(err)
48 | }
49 |
50 | videoClient := service.NewVideoServiceClient(videoConn)
51 | logger.Log.Info("获取视频服务实例--成功--")
52 | serveInstance["video_service"] = videoClient
53 |
54 | // 获取社交服务实例
55 | err = serviceDiscovery.ServiceDiscovery("social_service")
56 | if err != nil {
57 | logger.Log.Fatal(err)
58 | }
59 | socialServiceAddr, _ := serviceDiscovery.GetService("social_service")
60 | socialConn, err := grpc.Dial(socialServiceAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
61 | if err != nil {
62 | logger.Log.Fatal(err)
63 | }
64 |
65 | socialClient := service.NewSocialServiceClient(socialConn)
66 | logger.Log.Info("获取社交服务实例--成功--")
67 | serveInstance["social_service"] = socialClient
68 |
69 | wrapper.NewWrapper("user_service")
70 | wrapper.NewWrapper("video_service")
71 | wrapper.NewWrapper("social_service")
72 |
73 | return serveInstance
74 | }
75 |
--------------------------------------------------------------------------------
/api_router/go.mod:
--------------------------------------------------------------------------------
1 | module api_router
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5
7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible
8 | github.com/gin-gonic/gin v1.9.1
9 | github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
10 | github.com/pkg/errors v0.9.1
11 | github.com/rabbitmq/amqp091-go v1.8.1
12 | github.com/sirupsen/logrus v1.9.3
13 | github.com/spf13/viper v1.16.0
14 | google.golang.org/grpc v1.55.0
15 | google.golang.org/protobuf v1.30.0
16 | utils v0.0.0
17 | )
18 |
19 | replace utils => ../utils
20 |
21 | require (
22 | github.com/bytedance/sonic v1.9.1 // indirect
23 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
24 | github.com/coreos/go-semver v0.3.0 // indirect
25 | github.com/coreos/go-systemd/v22 v22.3.2 // indirect
26 | github.com/fsnotify/fsnotify v1.6.0 // indirect
27 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect
28 | github.com/gin-contrib/sse v0.1.0 // indirect
29 | github.com/go-playground/locales v0.14.1 // indirect
30 | github.com/go-playground/universal-translator v0.18.1 // indirect
31 | github.com/go-playground/validator/v10 v10.14.0 // indirect
32 | github.com/goccy/go-json v0.10.2 // indirect
33 | github.com/gogo/protobuf v1.3.2 // indirect
34 | github.com/golang/protobuf v1.5.3 // indirect
35 | github.com/hashicorp/hcl v1.0.0 // indirect
36 | github.com/jonboulle/clockwork v0.4.0 // indirect
37 | github.com/json-iterator/go v1.1.12 // indirect
38 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect
39 | github.com/leodido/go-urn v1.2.4 // indirect
40 | github.com/lestrrat-go/strftime v1.0.6 // indirect
41 | github.com/magiconair/properties v1.8.7 // indirect
42 | github.com/mattn/go-isatty v0.0.19 // indirect
43 | github.com/mitchellh/mapstructure v1.5.0 // indirect
44 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
45 | github.com/modern-go/reflect2 v1.0.2 // indirect
46 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect
47 | github.com/smartystreets/goconvey v1.8.1 // indirect
48 | github.com/spf13/afero v1.9.5 // indirect
49 | github.com/spf13/cast v1.5.1 // indirect
50 | github.com/spf13/jwalterweatherman v1.1.0 // indirect
51 | github.com/spf13/pflag v1.0.5 // indirect
52 | github.com/subosito/gotenv v1.4.2 // indirect
53 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
54 | github.com/ugorji/go/codec v1.2.11 // indirect
55 | go.etcd.io/etcd/api/v3 v3.5.9 // indirect
56 | go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect
57 | go.etcd.io/etcd/client/v3 v3.5.9 // indirect
58 | go.uber.org/atomic v1.9.0 // indirect
59 | go.uber.org/multierr v1.8.0 // indirect
60 | go.uber.org/zap v1.21.0 // indirect
61 | golang.org/x/arch v0.3.0 // indirect
62 | golang.org/x/crypto v0.9.0 // indirect
63 | golang.org/x/net v0.10.0 // indirect
64 | golang.org/x/sys v0.8.0 // indirect
65 | golang.org/x/text v0.9.0 // indirect
66 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
67 | gopkg.in/ini.v1 v1.67.0 // indirect
68 | gopkg.in/yaml.v3 v3.0.1 // indirect
69 | )
70 |
--------------------------------------------------------------------------------
/api_router/internal/handler/comment.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "api_router/internal/service"
5 | "api_router/pkg/res"
6 | "context"
7 | "github.com/gin-gonic/gin"
8 | "net/http"
9 | "strconv"
10 | "utils/exception"
11 | )
12 |
13 | // CommentAction 评论操作
14 | func CommentAction(ctx *gin.Context) {
15 | var commentActionReq service.CommentActionRequest
16 |
17 | userId, _ := ctx.Get("user_id")
18 | commentActionReq.UserId, _ = userId.(int64)
19 |
20 | videoId := ctx.PostForm("video_id")
21 | if videoId == "" {
22 | videoId = ctx.Query("video_id")
23 | }
24 | commentActionReq.VideoId, _ = strconv.ParseInt(videoId, 10, 64)
25 |
26 | actionType := ctx.PostForm("action_type")
27 | if actionType == "" {
28 | actionType = ctx.Query("action_type")
29 | }
30 | actionTypeValue, _ := strconv.Atoi(actionType)
31 | commentActionReq.ActionType = int64(actionTypeValue)
32 |
33 | // 评论操作
34 | if commentActionReq.ActionType == 1 {
35 | commentText := ctx.PostForm("comment_text")
36 | if commentText == "" {
37 | commentText = ctx.Query("comment_text")
38 | }
39 | commentActionReq.CommentText = commentText
40 | } else if commentActionReq.ActionType == 2 {
41 | commentId := ctx.PostForm("comment_id")
42 | if commentId == "" {
43 | commentId = ctx.Query("comment_id")
44 | }
45 | commentActionReq.CommentId, _ = strconv.ParseInt(commentId, 10, 64)
46 | } else {
47 | r := res.FavoriteActionResponse{
48 | StatusCode: exception.ErrOperate,
49 | StatusMsg: exception.GetMsg(exception.ErrOperate),
50 | }
51 |
52 | ctx.JSON(http.StatusOK, r)
53 | return
54 | }
55 |
56 | videoServiceClient := ctx.Keys["video_service"].(service.VideoServiceClient)
57 | videoServiceResp, err := videoServiceClient.CommentAction(context.Background(), &commentActionReq)
58 | if err != nil {
59 | PanicIfCommentError(err)
60 | }
61 |
62 | if actionTypeValue == 1 {
63 | // 构建用户信息
64 | userIds := []int64{userId.(int64)}
65 | userInfos := GetUserInfo(userIds, ctx)
66 |
67 | r := res.CommentActionResponse{
68 | StatusCode: videoServiceResp.StatusCode,
69 | StatusMsg: videoServiceResp.StatusMsg,
70 | Comment: BuildComment(videoServiceResp.Comment, userInfos[0]),
71 | }
72 |
73 | ctx.JSON(http.StatusOK, r)
74 | }
75 | // 如果是删除评论的操作
76 | if actionTypeValue == 2 {
77 | r := res.CommentDeleteResponse{
78 | StatusCode: videoServiceResp.StatusCode,
79 | StatusMsg: videoServiceResp.StatusMsg,
80 | }
81 |
82 | ctx.JSON(http.StatusOK, r)
83 | }
84 | }
85 |
86 | func CommentList(ctx *gin.Context) {
87 | var commentListReq service.CommentListRequest
88 |
89 | videoIdStr := ctx.Query("video_id")
90 | videoId, _ := strconv.ParseInt(videoIdStr, 10, 64)
91 |
92 | commentListReq.VideoId = videoId
93 |
94 | videoServiceClient := ctx.Keys["video_service"].(service.VideoServiceClient)
95 | commentListResp, err := videoServiceClient.CommentList(context.Background(), &commentListReq)
96 | if err != nil {
97 | PanicIfCommentError(err)
98 | }
99 |
100 | // 找到所有的用户Id
101 | var userIds []int64
102 | for _, comment := range commentListResp.CommentList {
103 | userIds = append(userIds, comment.UserId)
104 | }
105 |
106 | userInfos := GetUserInfo(userIds, ctx)
107 |
108 | commentList := BuildCommentList(commentListResp.CommentList, userInfos)
109 |
110 | r := res.CommentListResponse{
111 | StatusCode: commentListResp.StatusCode,
112 | StatusMsg: commentListResp.StatusMsg,
113 | Comments: commentList,
114 | }
115 |
116 | ctx.JSON(http.StatusOK, r)
117 | }
118 |
119 | func BuildComment(comment *service.Comment, userInfo res.User) res.Comment {
120 |
121 | return res.Comment{
122 | Id: comment.Id,
123 | User: userInfo,
124 | Content: comment.Content,
125 | CreateDate: comment.CreateDate,
126 | }
127 | }
128 |
129 | func BuildCommentList(comments []*service.Comment, userInfos []res.User) []res.Comment {
130 | var commentList []res.Comment
131 |
132 | for i, comment := range comments {
133 | commentList = append(commentList, res.Comment{
134 | Id: comment.Id,
135 | User: userInfos[i],
136 | Content: comment.Content,
137 | CreateDate: comment.CreateDate,
138 | })
139 | }
140 |
141 | return commentList
142 | }
143 |
--------------------------------------------------------------------------------
/api_router/internal/handler/erros.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "api_router/pkg/logger"
5 | "github.com/pkg/errors"
6 | )
7 |
8 | // PanicIfVideoError 视频错误处理
9 | func PanicIfVideoError(err error) {
10 | if err != nil {
11 | err = errors.New("videoService--error--" + err.Error())
12 | logger.Log.Info(err)
13 | panic(err)
14 | }
15 | }
16 |
17 | // PanicIfUserError 用户错误处理
18 | func PanicIfUserError(err error) {
19 | if err != nil {
20 | err = errors.New("UserService--error" + err.Error())
21 | logger.Log.Info(err)
22 | panic(err)
23 | }
24 | }
25 |
26 | // PanicIfMessageError 消息错误处理
27 | func PanicIfMessageError(err error) {
28 | if err != nil {
29 | err = errors.New("MessageService--error" + err.Error())
30 | logger.Log.Info(err)
31 | panic(err)
32 | }
33 | }
34 |
35 | // PanicIfFollowError 关注错误处理
36 | func PanicIfFollowError(err error) {
37 | if err != nil {
38 | err = errors.New("FollowService--error" + err.Error())
39 | logger.Log.Info(err)
40 | panic(err)
41 | }
42 | }
43 |
44 | // PanicIfFavoriteError 喜欢错误处理
45 | func PanicIfFavoriteError(err error) {
46 | if err != nil {
47 | err = errors.New("FavoriteService--error" + err.Error())
48 | logger.Log.Info(err)
49 | panic(err)
50 | }
51 | }
52 |
53 | // PanicIfCommentError 评论错误处理
54 | func PanicIfCommentError(err error) {
55 | if err != nil {
56 | err = errors.New("CommentService--error" + err.Error())
57 | logger.Log.Info(err)
58 | panic(err)
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/api_router/internal/handler/favorite.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "api_router/internal/service"
5 | "api_router/pkg/res"
6 | "context"
7 | "github.com/gin-gonic/gin"
8 | "net/http"
9 | "strconv"
10 | "utils/exception"
11 | )
12 |
13 | // FavoriteAction 喜欢操作
14 | func FavoriteAction(ctx *gin.Context) {
15 | var favoriteActionReq service.FavoriteActionRequest
16 |
17 | userId, _ := ctx.Get("user_id")
18 | favoriteActionReq.UserId, _ = userId.(int64)
19 | // string转int64
20 | videoId := ctx.PostForm("video_id")
21 | if videoId == "" {
22 | videoId = ctx.Query("video_id")
23 | }
24 | favoriteActionReq.VideoId, _ = strconv.ParseInt(videoId, 10, 64)
25 |
26 | actionType := ctx.PostForm("action_type")
27 | if actionType == "" {
28 | actionType = ctx.Query("action_type")
29 | }
30 | actionTypeValue, _ := strconv.Atoi(actionType)
31 |
32 | // 异常操作
33 | if actionTypeValue == 1 || actionTypeValue == 2 {
34 | favoriteActionReq.ActionType = int64(actionTypeValue)
35 |
36 | videoServiceClient := ctx.Keys["video_service"].(service.VideoServiceClient)
37 | videoServiceResp, err := videoServiceClient.FavoriteAction(context.Background(), &favoriteActionReq)
38 | if err != nil {
39 | PanicIfFavoriteError(err)
40 | }
41 |
42 | r := res.FavoriteActionResponse{
43 | StatusCode: videoServiceResp.StatusCode,
44 | StatusMsg: videoServiceResp.StatusMsg,
45 | }
46 |
47 | ctx.JSON(http.StatusOK, r)
48 | } else {
49 | r := res.FavoriteActionResponse{
50 | StatusCode: exception.ErrOperate,
51 | StatusMsg: exception.GetMsg(exception.ErrOperate),
52 | }
53 |
54 | ctx.JSON(http.StatusOK, r)
55 | }
56 | }
57 |
58 | func FavoriteList(ctx *gin.Context) {
59 | var favoriteListReq service.FavoriteListRequest
60 |
61 | userIdStr := ctx.Query("user_id")
62 | userId, _ := strconv.ParseInt(userIdStr, 10, 64)
63 |
64 | favoriteListReq.UserId = userId
65 |
66 | videoServiceClient := ctx.Keys["video_service"].(service.VideoServiceClient)
67 | favoriteListResp, err := videoServiceClient.FavoriteList(context.Background(), &favoriteListReq)
68 | if err != nil {
69 | PanicIfFavoriteError(err)
70 | }
71 |
72 | // 找到所有的用户Id
73 | var userIds []int64
74 | for _, video := range favoriteListResp.VideoList {
75 | userIds = append(userIds, video.AuthId)
76 | }
77 |
78 | // 找到所有的用户信息
79 | userInfos := GetUserInfo(userIds, ctx)
80 |
81 | list := BuildVideoList(favoriteListResp.VideoList, userInfos)
82 |
83 | r := res.VideoListResponse{
84 | StatusCode: favoriteListResp.StatusCode,
85 | StatusMsg: favoriteListResp.StatusMsg,
86 | VideoList: list,
87 | }
88 |
89 | ctx.JSON(http.StatusOK, r)
90 | }
91 |
--------------------------------------------------------------------------------
/api_router/internal/handler/follow.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "api_router/internal/service"
5 | "api_router/pkg/res"
6 | "context"
7 | "github.com/gin-gonic/gin"
8 | "net/http"
9 | "strconv"
10 | "utils/exception"
11 | )
12 |
13 | type Follow struct {
14 | IsFollow bool
15 | FollowCount int64
16 | FollowerCount int64
17 | }
18 |
19 | func FollowAction(ctx *gin.Context) {
20 | var followAction service.FollowRequest
21 | userId, _ := ctx.Get("user_id")
22 | followAction.UserId, _ = userId.(int64)
23 | toUserId := ctx.Query("to_user_id")
24 | followAction.ToUserId, _ = strconv.ParseInt(toUserId, 10, 64)
25 | actionType := ctx.Query("action_type")
26 | actionTypeInt64, _ := strconv.ParseInt(actionType, 10, 32)
27 | followAction.ActionType = int32(actionTypeInt64)
28 |
29 | if actionTypeInt64 != 1 && actionTypeInt64 != 2 {
30 | r := res.FavoriteActionResponse{
31 | StatusCode: exception.ErrOperate,
32 | StatusMsg: exception.GetMsg(exception.ErrOperate),
33 | }
34 |
35 | ctx.JSON(http.StatusOK, r)
36 | return
37 | }
38 |
39 | socialServiceClient := ctx.Keys["social_service"].(service.SocialServiceClient)
40 | socialResp, err := socialServiceClient.FollowAction(context.Background(), &followAction)
41 | if err != nil {
42 | PanicIfFollowError(err)
43 | }
44 |
45 | r := res.FollowActionResponse{
46 | StatusCode: socialResp.StatusCode,
47 | StatusMsg: socialResp.StatusMsg,
48 | }
49 |
50 | ctx.JSON(http.StatusOK, r)
51 | }
52 |
53 | func GetFollowList(ctx *gin.Context) {
54 | var followList service.FollowListRequest
55 | userId := ctx.Query("user_id")
56 | followList.UserId, _ = strconv.ParseInt(userId, 10, 64)
57 |
58 | socialServiceClient := ctx.Keys["social_service"].(service.SocialServiceClient)
59 | socialResp, err := socialServiceClient.GetFollowList(context.Background(), &followList)
60 | if err != nil {
61 | PanicIfFollowError(err)
62 | }
63 |
64 | r := res.FollowListResponse{
65 | StatusCode: socialResp.StatusCode,
66 | StatusMsg: socialResp.StatusMsg,
67 | UserList: GetUserInfo(socialResp.UserId, ctx),
68 | }
69 | ctx.JSON(http.StatusOK, r)
70 | }
71 |
72 | func GetFollowerList(ctx *gin.Context) {
73 | var followerList service.FollowListRequest
74 | userId := ctx.Query("user_id")
75 | followerList.UserId, _ = strconv.ParseInt(userId, 10, 64)
76 |
77 | socialServiceClient := ctx.Keys["social_service"].(service.SocialServiceClient)
78 | socialResp, err := socialServiceClient.GetFollowerList(context.Background(), &followerList)
79 | if err != nil {
80 | PanicIfFollowError(err)
81 | }
82 |
83 | r := res.FollowListResponse{
84 | StatusCode: socialResp.StatusCode,
85 | StatusMsg: socialResp.StatusMsg,
86 | UserList: GetUserInfo(socialResp.UserId, ctx),
87 | }
88 | ctx.JSON(http.StatusOK, r)
89 | }
90 |
91 | func GetFriendList(ctx *gin.Context) {
92 | var friendList service.FollowListRequest
93 | userId := ctx.Query("user_id")
94 | friendList.UserId, _ = strconv.ParseInt(userId, 10, 64)
95 |
96 | socialServiceClient := ctx.Keys["social_service"].(service.SocialServiceClient)
97 | socialResp, err := socialServiceClient.GetFriendList(context.Background(), &friendList)
98 | if err != nil {
99 | PanicIfFollowError(err)
100 | }
101 |
102 | r := res.FollowListResponse{
103 | StatusCode: socialResp.StatusCode,
104 | StatusMsg: socialResp.StatusMsg,
105 | UserList: GetUserInfo(socialResp.UserId, ctx),
106 | }
107 | ctx.JSON(http.StatusOK, r)
108 | }
109 |
--------------------------------------------------------------------------------
/api_router/internal/handler/message.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "api_router/internal/service"
5 | "api_router/pkg/res"
6 | "context"
7 | "github.com/gin-gonic/gin"
8 | "net/http"
9 | "strconv"
10 | "utils/exception"
11 | )
12 |
13 | func PostMessage(ctx *gin.Context) {
14 | var postMessage service.PostMessageRequest
15 | userId, _ := ctx.Get("user_id")
16 | postMessage.UserId, _ = userId.(int64)
17 | toUserId := ctx.Query("to_user_id")
18 | postMessage.ToUserId, _ = strconv.ParseInt(toUserId, 10, 64)
19 | actionType := ctx.Query("action_type")
20 | actionTypeInt64, _ := strconv.ParseInt(actionType, 10, 32)
21 |
22 | if actionTypeInt64 != 1 {
23 | r := res.FavoriteActionResponse{
24 | StatusCode: exception.ErrOperate,
25 | StatusMsg: exception.GetMsg(exception.ErrOperate),
26 | }
27 |
28 | ctx.JSON(http.StatusOK, r)
29 | return
30 | }
31 |
32 | postMessage.ActionType = int32(actionTypeInt64)
33 | content := ctx.Query("content")
34 | postMessage.Content = content
35 |
36 | socialServiceClient := ctx.Keys["social_service"].(service.SocialServiceClient)
37 | socialResp, err := socialServiceClient.PostMessage(context.Background(), &postMessage)
38 | if err != nil {
39 | PanicIfMessageError(err)
40 | }
41 |
42 | r := res.PostMessageResponse{
43 | StatusCode: socialResp.StatusCode,
44 | StatusMsg: socialResp.StatusMsg,
45 | }
46 | ctx.JSON(http.StatusOK, r)
47 | }
48 |
49 | func GetMessage(ctx *gin.Context) {
50 | var getMessage service.GetMessageRequest
51 | userId, _ := ctx.Get("user_id")
52 | getMessage.UserId, _ = userId.(int64)
53 | toUserId := ctx.Query("to_user_id")
54 | getMessage.ToUserId, _ = strconv.ParseInt(toUserId, 10, 64)
55 | PreMsgTime := ctx.Query("pre_msg_time")
56 | getMessage.PreMsgTime, _ = strconv.ParseInt(PreMsgTime, 10, 64)
57 |
58 | socialServiceClient := ctx.Keys["social_service"].(service.SocialServiceClient)
59 | socialResp, err := socialServiceClient.GetMessage(context.Background(), &getMessage)
60 |
61 | if err != nil {
62 | PanicIfMessageError(err)
63 | }
64 |
65 | r := new(res.GetMessageResponse)
66 | r.StatusCode = socialResp.StatusCode
67 | r.StatusMsg = socialResp.StatusMsg
68 | for _, message := range socialResp.Message {
69 | messageResp := res.Message{
70 | Id: message.Id,
71 | ToUserId: message.ToUserId,
72 | FromUserID: message.UserId,
73 | Content: message.Content,
74 | CreateTime: message.CreatedAt,
75 | }
76 | r.MessageList = append(r.MessageList, messageResp)
77 | }
78 |
79 | ctx.JSON(http.StatusOK, r)
80 | }
81 |
--------------------------------------------------------------------------------
/api_router/internal/handler/user.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "api_router/internal/service"
5 | "api_router/pkg/auth"
6 | "api_router/pkg/res"
7 | "context"
8 | "github.com/gin-gonic/gin"
9 | "net/http"
10 | "strconv"
11 | "sync"
12 | "utils/exception"
13 | )
14 |
15 | // UserRegister 用户注册
16 | func UserRegister(ctx *gin.Context) {
17 | var userReq service.UserRequest
18 |
19 | userName := ctx.PostForm("username")
20 | if userName == "" {
21 | userName = ctx.Query("username")
22 | }
23 | userReq.Username = userName
24 |
25 | passWord := ctx.PostForm("username")
26 | if passWord == "" {
27 | passWord = ctx.Query("password")
28 | }
29 | userReq.Password = passWord
30 |
31 | userServiceClient := ctx.Keys["user_service"].(service.UserServiceClient)
32 | userResp, err := userServiceClient.UserRegister(context.Background(), &userReq)
33 | if err != nil {
34 | PanicIfUserError(err)
35 | }
36 |
37 | token := ""
38 | if userResp.UserId != 0 {
39 | token, _ = auth.GenerateToken(userResp.UserId)
40 | }
41 |
42 | r := res.UserResponse{
43 | StatusCode: userResp.StatusCode,
44 | StatusMsg: userResp.StatusMsg,
45 | UserId: userResp.UserId,
46 | Token: token,
47 | }
48 |
49 | ctx.JSON(http.StatusOK, r)
50 | }
51 |
52 | // UserLogin 用户登录
53 | func UserLogin(ctx *gin.Context) {
54 | var userReq service.UserRequest
55 | //err := ctx.Bind(&userReq)
56 | //if err != nil {
57 | // PanicIfUserError(err)
58 | //}
59 | userName := ctx.Query("username")
60 | if userName == "" {
61 | userName = ctx.PostForm("username")
62 | }
63 | userReq.Username = userName
64 |
65 | passWord := ctx.Query("password")
66 | if userName == "" {
67 | passWord = ctx.PostForm("password")
68 | }
69 | userReq.Password = passWord
70 |
71 | userServiceClient := ctx.Keys["user_service"].(service.UserServiceClient)
72 | userResp, err := userServiceClient.UserLogin(context.Background(), &userReq)
73 | if err != nil {
74 | PanicIfUserError(err)
75 | }
76 |
77 | token := ""
78 | if userResp.UserId != 0 {
79 | token, _ = auth.GenerateToken(userResp.UserId)
80 | }
81 |
82 | r := res.UserResponse{
83 | StatusCode: userResp.StatusCode,
84 | StatusMsg: userResp.StatusMsg,
85 | UserId: userResp.UserId,
86 | Token: token,
87 | }
88 |
89 | ctx.JSON(http.StatusOK, r)
90 | }
91 |
92 | // UserInfo 用户信息列表
93 | func UserInfo(ctx *gin.Context) {
94 | var userIds []int64
95 |
96 | // jwt中间件会解析token,然后把user_id放入context中,所以用两种方式都可以获取到user_id
97 | userIdStr := ctx.Query("user_id")
98 | userId, _ := strconv.ParseInt(userIdStr, 10, 64)
99 |
100 | userIds = append(userIds, userId)
101 |
102 | r := res.UserInfoResponse{
103 | StatusCode: exception.SUCCESS,
104 | StatusMsg: exception.GetMsg(exception.SUCCESS),
105 | User: GetUserInfo(userIds, ctx)[0],
106 | }
107 |
108 | ctx.JSON(http.StatusOK, r)
109 | }
110 |
111 | // GetUserInfo 根据用户id,去调取三个服务,拼接出所有的用户信息
112 | func GetUserInfo(userIds []int64, ctx *gin.Context) (userInfos []res.User) {
113 | var err error
114 | // 构建三个服务的请求
115 | var userInfoReq service.UserInfoRequest
116 | var countInfoReq service.CountRequest
117 | var followInfoReq service.FollowInfoRequest
118 |
119 | userInfoReq.UserIds = userIds
120 | countInfoReq.UserIds = userIds
121 | followInfoReq.ToUserId = userIds
122 |
123 | // 创建接收三个响应
124 | var userResp *service.UserInfoResponse
125 | var countInfoResp *service.CountResponse
126 | var followInfoResp *service.FollowInfoResponse
127 |
128 | // 分别去调用三个服务
129 | var wg sync.WaitGroup
130 | wg.Add(3)
131 | go func() {
132 | defer wg.Done()
133 | userServiceClient := ctx.Keys["user_service"].(service.UserServiceClient)
134 | userResp, err = userServiceClient.UserInfo(context.Background(), &userInfoReq)
135 | if err != nil {
136 | PanicIfUserError(err)
137 | }
138 | }()
139 |
140 | go func() {
141 | defer wg.Done()
142 | videoServiceClient := ctx.Keys["video_service"].(service.VideoServiceClient)
143 | countInfoResp, err = videoServiceClient.CountInfo(context.Background(), &countInfoReq)
144 | if err != nil {
145 | PanicIfVideoError(err)
146 | }
147 | }()
148 |
149 | go func() {
150 | defer wg.Done()
151 |
152 | // 拿到当前用户的id,在is_follow找对应关系
153 | userIdStr, _ := ctx.Get("user_id")
154 | userId, _ := userIdStr.(int64)
155 | followInfoReq.UserId = userId
156 | socialServiceClient := ctx.Keys["social_service"].(service.SocialServiceClient)
157 | followInfoResp, err = socialServiceClient.GetFollowInfo(context.Background(), &followInfoReq)
158 | if err != nil {
159 | PanicIfFollowError(err)
160 | }
161 | }()
162 | wg.Wait()
163 |
164 | // 构建信息userResp.Users[0], countInfoResp.Counts[0])
165 | for index, _ := range userIds {
166 | userInfos = append(userInfos, BuildUser(userResp.Users[index], countInfoResp.Counts[index], followInfoResp.FollowInfo[index]))
167 | }
168 |
169 | return userInfos
170 | }
171 |
172 | // BuildUser 构建用户信息
173 | func BuildUser(user *service.User, count *service.Count, follow *service.FollowInfo) res.User {
174 | return res.User{
175 | Id: user.Id,
176 | Name: user.Name,
177 |
178 | FollowCount: follow.FollowCount,
179 | FollowerCount: follow.FollowerCount,
180 | IsFollow: follow.IsFollow,
181 |
182 | Avatar: user.Avatar,
183 | BackgroundImage: user.BackgroundImage,
184 | Signature: user.Signature,
185 |
186 | TotalFavorited: strconv.FormatInt(count.TotalFavorited, 10), // 将int64转换成string
187 | WorkCount: count.WorkCount,
188 | FavoriteCount: count.FavoriteCount,
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/api_router/internal/handler/video.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "api_router/internal/service"
5 | "api_router/pkg/res"
6 | "context"
7 |
8 | "github.com/gin-gonic/gin"
9 | "io"
10 |
11 | "net/http"
12 | "strconv"
13 | )
14 |
15 | // Feed 视频流
16 | func Feed(ctx *gin.Context) {
17 | var feedReq service.FeedRequest
18 |
19 | // 判断是否带有参数
20 | token := ctx.Query("token")
21 | if token == "" {
22 | feedReq.UserId = -1
23 | } else {
24 | userId, _ := ctx.Get("user_id")
25 | feedReq.UserId, _ = userId.(int64)
26 | }
27 |
28 | latestTime := ctx.Query("latest_time")
29 | if latestTime == "" || latestTime == "0" {
30 | feedReq.LatestTime = -1
31 | } else {
32 | timePoint, _ := strconv.ParseInt(latestTime, 10, 64)
33 | feedReq.LatestTime = timePoint
34 | }
35 |
36 | videoServiceClient := ctx.Keys["video_service"].(service.VideoServiceClient)
37 | feedResp, err := videoServiceClient.Feed(context.Background(), &feedReq)
38 | if err != nil {
39 | PanicIfVideoError(err)
40 | }
41 |
42 | var userIds []int64
43 | for _, video := range feedResp.VideoList {
44 | userIds = append(userIds, video.AuthId)
45 | }
46 |
47 | // 找到所有的用户信息
48 | userInfos := GetUserInfo(userIds, ctx)
49 |
50 | list := BuildVideoList(feedResp.VideoList, userInfos)
51 |
52 | r := res.FeedResponse{
53 | StatusCode: feedResp.StatusCode,
54 | StatusMsg: feedResp.StatusMsg,
55 | NextTime: feedResp.NextTime,
56 | VideoList: list,
57 | }
58 |
59 | ctx.JSON(http.StatusOK, r)
60 | }
61 |
62 | // PublishAction 发布视频
63 | func PublishAction(ctx *gin.Context) {
64 | var publishActionReq service.PublishActionRequest
65 |
66 | userId, _ := ctx.Get("user_id")
67 | publishActionReq.UserId = userId.(int64)
68 |
69 | publishActionReq.Title = ctx.PostForm("title")
70 |
71 | formFile, _ := ctx.FormFile("data")
72 | file, err := formFile.Open()
73 | if err != nil {
74 | PanicIfVideoError(err)
75 | }
76 | defer file.Close()
77 | buf, err := io.ReadAll(file) // 将文件读取到字节切片buf中
78 | if err != nil {
79 | PanicIfVideoError(err)
80 | }
81 | publishActionReq.Data = buf
82 |
83 | videoServiceClient := ctx.Keys["video_service"].(service.VideoServiceClient)
84 | videoServiceResp, err := videoServiceClient.PublishAction(context.Background(), &publishActionReq)
85 | if err != nil {
86 | PanicIfVideoError(err)
87 | }
88 |
89 | r := res.PublishActionResponse{
90 | StatusCode: videoServiceResp.StatusCode,
91 | StatusMsg: videoServiceResp.StatusMsg,
92 | }
93 |
94 | ctx.JSON(http.StatusOK, r)
95 | }
96 |
97 | // PublishList 发布列表
98 | func PublishList(ctx *gin.Context) {
99 | var pulishListReq service.PublishListRequest
100 |
101 | userIdStr := ctx.Query("user_id")
102 | userId, _ := strconv.ParseInt(userIdStr, 10, 64)
103 |
104 | pulishListReq.UserId = userId
105 |
106 | videoServiceClient := ctx.Keys["video_service"].(service.VideoServiceClient)
107 | publishListResp, err := videoServiceClient.PublishList(context.Background(), &pulishListReq)
108 | if err != nil {
109 | PanicIfVideoError(err)
110 | }
111 |
112 | var userIds []int64
113 | for _, video := range publishListResp.VideoList {
114 | userIds = append(userIds, video.AuthId)
115 | }
116 |
117 | // 找到所有的用户信息
118 | userInfos := GetUserInfo(userIds, ctx)
119 |
120 | list := BuildVideoList(publishListResp.VideoList, userInfos)
121 |
122 | r := res.VideoListResponse{
123 | StatusCode: publishListResp.StatusCode,
124 | StatusMsg: publishListResp.StatusMsg,
125 | VideoList: list,
126 | }
127 |
128 | ctx.JSON(http.StatusOK, r)
129 | }
130 |
131 | // BuildVideoList 构建视频列表
132 | func BuildVideoList(videos []*service.Video, userInfos []res.User) []res.Video {
133 |
134 | var videoList []res.Video
135 |
136 | for i, video := range videos {
137 | videoList = append(videoList, res.Video{
138 | Id: video.Id,
139 | Author: userInfos[i],
140 | PlayUrl: video.PlayUrl,
141 | CoverUrl: video.CoverUrl,
142 | FavoriteCount: video.FavoriteCount,
143 | CommentCount: video.CommentCount,
144 | IsFavorite: video.IsFavorite,
145 | Title: video.Title,
146 | })
147 | }
148 |
149 | return videoList
150 | }
151 |
--------------------------------------------------------------------------------
/api_router/internal/service/pb/socialService.proto:
--------------------------------------------------------------------------------
1 | syntax="proto3";
2 | package pb;
3 | option go_package = "../;service"; // 此处格式为<生成的文件存放位置;生成文件的包名>
4 |
5 | // protoc -I internal/service/pb --go_out=./internal/service/ --go_opt=paths=source_relative --go-grpc_out=./internal/service/ --go-grpc_opt=paths=source_relative internal/service/pb/*.proto
6 | // 或者分开使用
7 | // protoc -I internal/service/pb/ --go_out=./ internal/service/pb/*.proto
8 | // protoc -I internal/service/pb/ --go-grpc_out=./ internal/service/pb/*.proto
9 |
10 | message FollowRequest{
11 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
12 | int64 UserId = 1;
13 | // @gotags:json:"to_user_id" form:"to_user_id" uri:"to_user_id"
14 | int64 ToUserId = 2;
15 | // @gotags:json:"action_type" form:"action_type" uri:"action_type"
16 | int32 ActionType = 3;
17 | }
18 |
19 | message FollowResponse{
20 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
21 | int32 StatusCode = 1;
22 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
23 | string StatusMsg = 2;
24 | }
25 |
26 | message FollowListRequest{
27 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
28 | int64 UserId = 1;
29 | }
30 |
31 | message FollowListResponse{
32 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
33 | int32 StatusCode = 1;
34 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
35 | string StatusMsg = 2;
36 | // @gotags:json:"user_list" form:"user_list" uri:"user_list"
37 | repeated int64 UserId = 3;
38 | }
39 |
40 | message FollowInfoRequest{
41 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
42 | int64 UserId = 1;
43 | // @gotags:json:"to_user_id" form:"to_user_id" uri:"to_user_id"
44 | repeated int64 ToUserId = 2;
45 | }
46 |
47 | message FollowInfo{
48 | // @gotags:json:"is_follow" form:"is_follow" uri:"is_follow"
49 | bool IsFollow = 1;
50 | // @gotags:json:"follow_count" form:"follow_count" uri:"follow_count"
51 | int64 FollowCount = 2;
52 | // @gotags:json:"follower_count" form:"follower_count" uri:"follower_count"
53 | int64 FollowerCount = 3;
54 | // @gotags:json:"to_user_id" form:"to_user_id" uri:"to_user_id"
55 | int64 ToUserId = 4;
56 | }
57 |
58 | message FollowInfoResponse{
59 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
60 | int32 StatusCode = 1;
61 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
62 | string StatusMsg = 2;
63 | // @gotags:json:"follow_info" form:"follow_info" uri:"follow_info"
64 | repeated FollowInfo FollowInfo = 3;
65 | }
66 |
67 | message PostMessageRequest{
68 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
69 | int64 UserId = 1;
70 | // @gotags:json:"to_user_id" form:"to_user_id" uri:"to_user_id"
71 | int64 ToUserId = 2;
72 | // @gotags:json:"action_type" form:"action_type" uri:"action_type"
73 | int32 ActionType = 3;
74 | // @gotags:json:"content" form:"content" uri:"content"
75 | string Content = 4;
76 | }
77 |
78 | message PostMessageResponse{
79 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
80 | int32 StatusCode = 1;
81 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
82 | string StatusMsg = 2;
83 | }
84 |
85 | message GetMessageRequest{
86 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
87 | int64 UserId = 1;
88 | // @gotags:json:"to_user_id" form:"to_user_id" uri:"to_user_id"
89 | int64 ToUserId = 2;
90 | // @gotags:json:"pre_msg_time" form:"pre_msg_time" uri:"pre_msg_time"
91 | int64 preMsgTime=3;
92 | }
93 |
94 | message Message{
95 | // @gotags:json:"id" form:"id" uri:"id"
96 | int64 Id = 1;
97 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
98 | int64 UserId = 2;
99 | // @gotags:json:"to_user_id" form:"to_user_id" uri:"to_user_id"
100 | int64 ToUserId = 3;
101 | // @gotags:json:"content" form:"content" uri:"content"
102 | string Content = 4;
103 | // @gotags:json:"created_at" form:"created_at" uri:"created_at"
104 | int64 CreatedAt = 5;
105 | }
106 |
107 | message GetMessageResponse{
108 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
109 | int32 StatusCode = 1;
110 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
111 | string StatusMsg = 2;
112 | // @gotags:json:"message_list" form:"message_list" uri:"message_list"
113 | repeated Message message = 3;
114 | }
115 |
116 | service SocialService{
117 | // 关注服务
118 | rpc FollowAction(FollowRequest) returns(FollowResponse);
119 | rpc GetFollowList(FollowListRequest) returns(FollowListResponse);
120 | rpc GetFollowerList(FollowListRequest) returns(FollowListResponse);
121 | rpc GetFriendList(FollowListRequest) returns(FollowListResponse);
122 | rpc GetFollowInfo(FollowInfoRequest) returns(FollowInfoResponse);
123 |
124 | // 消息服务
125 | rpc PostMessage(PostMessageRequest) returns(PostMessageResponse);
126 | rpc GetMessage(GetMessageRequest) returns(GetMessageResponse);
127 |
128 | }
--------------------------------------------------------------------------------
/api_router/internal/service/pb/userService.proto:
--------------------------------------------------------------------------------
1 | syntax="proto3";
2 | package pb;
3 | option go_package = "../;service"; // 此处格式为<生成的文件存放位置;生成文件的包名>
4 |
5 | // protoc -I internal/service/pb --go_out=./internal/service/ --go_opt=paths=source_relative --go-grpc_out=./internal/service/ --go-grpc_opt=paths=source_relative internal/service/pb/*.proto
6 | // 或者分开使用
7 | // protoc -I internal/service/pb/ --go_out=./ internal/service/pb/*.proto
8 | // protoc -I internal/service/pb/ --go-grpc_out=./ internal/service/pb/*.proto
9 |
10 | message UserRequest{
11 | // @gotags:json:"username" form:"username" uri:"username"
12 | string Username = 1; // 账号
13 | // @gotags:json:"password" form:"password" uri:"password"
14 | string Password = 2; // 密码
15 | }
16 |
17 | message UserResponse{
18 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
19 | int64 StatusCode = 1;
20 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
21 | string StatusMsg = 2;
22 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
23 | int64 UserId = 3; // 用户id
24 | }
25 |
26 | message User {
27 | // @gotags:json:"id" form:"id" uri:"id"
28 | int64 Id = 1;
29 | // @gotags:json:"name" form:"name" uri:"name"
30 | string Name = 2;
31 | // @gotags:json:"avatar" form:"avatar" uri:"avatar"
32 | string Avatar = 3; // 用户头像
33 | // @gotags:json:"background_image" form:"background_image" uri:"background_image"
34 | string BackgroundImage = 4; // 用户背景图
35 | // @gotags:json:"signature" form:"signature" uri:"signature"
36 | string Signature = 5; // 用户签名
37 | }
38 |
39 | message UserInfoRequest {
40 | // @gotags:json:"user_ids" form:"user_ids" uri:"user_ids"
41 | repeated int64 UserIds = 1; // 传入一个userId切片
42 | }
43 |
44 | message UserInfoResponse {
45 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
46 | int64 StatusCode = 1;
47 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
48 | string StatusMsg = 2;
49 | // @gotags:json:"users" form:"users" uri:"users"
50 | repeated User Users = 3; // 用户信息
51 | }
52 |
53 |
54 | service UserService{
55 | rpc UserRegister(UserRequest) returns(UserResponse);
56 | rpc UserLogin(UserRequest) returns(UserResponse);
57 | rpc UserInfo(UserInfoRequest) returns(UserInfoResponse);
58 | }
--------------------------------------------------------------------------------
/api_router/internal/service/pb/videoService.proto:
--------------------------------------------------------------------------------
1 | syntax="proto3";
2 | package pb;
3 | option go_package = "../;service"; // 此处格式为<生成的文件存放位置;生成文件的包名>
4 |
5 |
6 | message Video {
7 | // @gotags:json:"id" form:"id" uri:"id"
8 | int64 Id = 1;
9 | // @gotags:json:"auth_id" form:"auth_id" uri:"auth_id"
10 | int64 AuthId = 2;
11 | // @gotags:json:"play_url" form:"play_rul" uri:"play_url"
12 | string PlayUrl = 3; // 视频播放地址
13 | // @gotasgs:json:"cover_url" form:"cover_url" uri:"cover_url"
14 | string CoverUrl = 4;
15 | // @gotags:json:"favorite_count" form:"favorite_count" uri:"favorite_count"
16 | int64 FavoriteCount = 5;
17 | // @gotags:json:"comment_count" form:"comment_count" uri:"comment_count"
18 | int64 CommentCount = 6;
19 | // @gotags:json:"is_favorite" form:"is_favorite" uri:"is_favorite"
20 | bool IsFavorite = 7;
21 | // @gotags:json:"title" form:"title" uri:"title"
22 | string Title = 8;
23 | }
24 |
25 | message Comment {
26 | // @gotags:json:"id" form:"id" uri:"id"
27 | int64 Id = 1;
28 | // @gatags:json:"user_id" form:"user_id" uri:"user_id"
29 | int64 UserId = 2;
30 | // @gotags:json:"content" form:"content" uri:"content"
31 | string Content = 3; // 评论内容
32 | // @gotags:json:"create_date" form:"create_date" uri:"create_date"
33 | string CreateDate = 4;
34 | }
35 |
36 | // 视频流
37 | message FeedRequest {
38 | // @gotags:json:"latest_time" form:"latest_time" uri:"latest_time"
39 | int64 LatestTime = 1; // 可选参数,限制返回视频的最新投稿时间戳,精确到秒,不填表示当前时间
40 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
41 | int64 UserId = 2;
42 | }
43 |
44 | message FeedResponse {
45 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
46 | int64 StatusCode = 1;
47 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
48 | string StatusMsg = 2;
49 | // @gotags:json:"video_list" form:"video_list" uri:"video_list"
50 | repeated Video VideoList = 3;
51 | // @gotags:json:"next_time" form:"next_time" uri:"next_time"
52 | int64 NextTime = 4;
53 | }
54 |
55 | // 发布视频
56 | message PublishActionRequest {
57 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
58 | int64 UserId = 1;
59 | // @gotags:json:"data" form:"data" uri:"data"
60 | bytes Data = 2;
61 | // @gotags:json:"title" form:"title" uri:"title"
62 | string Title = 3;
63 | }
64 |
65 | message PublishActionResponse {
66 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
67 | int64 StatusCode = 1;
68 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
69 | string StatusMsg = 2;
70 | }
71 |
72 | // 发布列表
73 | message PublishListRequest {
74 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
75 | int64 UserId = 1;
76 | }
77 |
78 | message PublishListResponse {
79 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
80 | int64 StatusCode = 1;
81 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
82 | string StatusMsg = 2;
83 | // @gotags:json:"video_list" form:"video_list" uri:"video_list"
84 | repeated Video VideoList = 3;
85 | }
86 |
87 | // 赞操作
88 | message FavoriteActionRequest {
89 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
90 | int64 UserId = 1;
91 | // @gotags:json:"video_id" form:"video_id" uri:"video_id"
92 | int64 VideoId = 2;
93 | // @gotags:json:"action_type" form:"action_type" uri:"action_type"
94 | int64 ActionType = 3; // 1-点赞,2-取消点赞
95 | }
96 |
97 | message FavoriteActionResponse {
98 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
99 | int64 StatusCode = 1;
100 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
101 | string StatusMsg = 2;
102 | }
103 |
104 |
105 | // 喜欢列表
106 | message FavoriteListRequest {
107 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
108 | int64 UserId = 1;
109 | }
110 |
111 | message FavoriteListResponse {
112 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
113 | int64 StatusCode = 1;
114 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
115 | string StatusMsg = 2;
116 | // @gotags:json:"video_list" form:"video_list" uri:"video_list"
117 | repeated Video VideoList = 3;
118 | }
119 |
120 |
121 | // 评论操作
122 | message CommentActionRequest {
123 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
124 | int64 UserId = 1;
125 | // @gotags:json:"video_id" form:"video_id" uri:"video_id"
126 | int64 VideoId = 2;
127 | // @gotags:json:"action_type" form:"action_type" uri:"action_type"
128 | int64 ActionType = 3; //1-发布评论,2-删除评论
129 | // @gotags:json:"comment_text" form:"comment_text" uri:"comment_text"
130 | string CommentText = 4; // 可选,用户填写的评论内容,在action_type=1的时候使用
131 | // @gotags:json:"comment_id" form:"comment_id" uri:"comment_id"
132 | int64 CommentId = 5; // 可选,要删除的评论id,在action_type=2的时候使用
133 | }
134 |
135 | message CommentActionResponse {
136 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
137 | int64 StatusCode = 1;
138 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
139 | string StatusMsg = 2;
140 | // @gotags:json:"comment" form:"comment" uri:"comment"
141 | Comment Comment = 3;
142 | }
143 |
144 | // 评论列表
145 | message CommentListRequest {
146 | // @gotags:json:"video_id" form:"video_id" uri:"video_id"
147 | int64 VideoId = 1;
148 | }
149 |
150 | message CommentListResponse {
151 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
152 | int64 StatusCode = 1;
153 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
154 | string StatusMsg = 2;
155 | // @gotags:json:"comment_list" form:"comment_list" uri:"comment_list"
156 | repeated Comment CommentList = 3;
157 | }
158 |
159 | // 视频计数
160 | message Count {
161 | // @gotags:json:"total_favorited" form:"total_favorited" uri:"total_favorited"
162 | int64 TotalFavorited = 1; // 获赞数量
163 | // @gotags:json:"work_count" form:"work_count" uri:"work_count"
164 | int64 WorkCount = 2; // 作品数量
165 | // @gotags:json:"favorite_count" form:"favorite_count" uri:"favorite_count"
166 | int64 FavoriteCount = 3; // 喜欢数量
167 | }
168 |
169 | // 视频总计数请求
170 | message CountRequest {
171 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
172 | repeated int64 UserIds = 1; // 传入一个userId切片
173 | }
174 |
175 | message CountResponse {
176 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
177 | int64 StatusCode = 1;
178 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
179 | string StatusMsg = 2;
180 | // @gotags:json:"counts" form:"counts" uri:"counts"
181 | repeated Count counts = 3;
182 | }
183 |
184 | service VideoService {
185 | rpc Feed(FeedRequest) returns(FeedResponse);
186 | rpc PublishAction(PublishActionRequest) returns (PublishActionResponse);
187 | rpc PublishList(PublishListRequest) returns(PublishListResponse);
188 |
189 | rpc FavoriteAction(FavoriteActionRequest) returns(FavoriteActionResponse);
190 | rpc FavoriteList(FavoriteListRequest) returns(FavoriteListResponse);
191 |
192 | rpc CommentAction(CommentActionRequest) returns(CommentActionResponse);
193 | rpc CommentList(CommentListRequest) returns(CommentListResponse);
194 |
195 | // 根据user_id切片,返回计数信息
196 | rpc CountInfo(CountRequest) returns(CountResponse);
197 | }
198 |
199 |
--------------------------------------------------------------------------------
/api_router/internal/service/userService_grpc.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
2 | // versions:
3 | // - protoc-gen-go-grpc v1.3.0
4 | // - protoc v3.15.5
5 | // source: userService.proto
6 |
7 | package service
8 |
9 | import (
10 | context "context"
11 | grpc "google.golang.org/grpc"
12 | codes "google.golang.org/grpc/codes"
13 | status "google.golang.org/grpc/status"
14 | )
15 |
16 | // This is a compile-time assertion to ensure that this generated file
17 | // is compatible with the grpc package it is being compiled against.
18 | // Requires gRPC-Go v1.32.0 or later.
19 | const _ = grpc.SupportPackageIsVersion7
20 |
21 | const (
22 | UserService_UserRegister_FullMethodName = "/pb.UserService/UserRegister"
23 | UserService_UserLogin_FullMethodName = "/pb.UserService/UserLogin"
24 | UserService_UserInfo_FullMethodName = "/pb.UserService/UserInfo"
25 | )
26 |
27 | // UserServiceClient is the client API for UserService service.
28 | //
29 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
30 | type UserServiceClient interface {
31 | UserRegister(ctx context.Context, in *UserRequest, opts ...grpc.CallOption) (*UserResponse, error)
32 | UserLogin(ctx context.Context, in *UserRequest, opts ...grpc.CallOption) (*UserResponse, error)
33 | UserInfo(ctx context.Context, in *UserInfoRequest, opts ...grpc.CallOption) (*UserInfoResponse, error)
34 | }
35 |
36 | type userServiceClient struct {
37 | cc grpc.ClientConnInterface
38 | }
39 |
40 | func NewUserServiceClient(cc grpc.ClientConnInterface) UserServiceClient {
41 | return &userServiceClient{cc}
42 | }
43 |
44 | func (c *userServiceClient) UserRegister(ctx context.Context, in *UserRequest, opts ...grpc.CallOption) (*UserResponse, error) {
45 | out := new(UserResponse)
46 | err := c.cc.Invoke(ctx, UserService_UserRegister_FullMethodName, in, out, opts...)
47 | if err != nil {
48 | return nil, err
49 | }
50 | return out, nil
51 | }
52 |
53 | func (c *userServiceClient) UserLogin(ctx context.Context, in *UserRequest, opts ...grpc.CallOption) (*UserResponse, error) {
54 | out := new(UserResponse)
55 | err := c.cc.Invoke(ctx, UserService_UserLogin_FullMethodName, in, out, opts...)
56 | if err != nil {
57 | return nil, err
58 | }
59 | return out, nil
60 | }
61 |
62 | func (c *userServiceClient) UserInfo(ctx context.Context, in *UserInfoRequest, opts ...grpc.CallOption) (*UserInfoResponse, error) {
63 | out := new(UserInfoResponse)
64 | err := c.cc.Invoke(ctx, UserService_UserInfo_FullMethodName, in, out, opts...)
65 | if err != nil {
66 | return nil, err
67 | }
68 | return out, nil
69 | }
70 |
71 | // UserServiceServer is the server API for UserService service.
72 | // All implementations must embed UnimplementedUserServiceServer
73 | // for forward compatibility
74 | type UserServiceServer interface {
75 | UserRegister(context.Context, *UserRequest) (*UserResponse, error)
76 | UserLogin(context.Context, *UserRequest) (*UserResponse, error)
77 | UserInfo(context.Context, *UserInfoRequest) (*UserInfoResponse, error)
78 | mustEmbedUnimplementedUserServiceServer()
79 | }
80 |
81 | // UnimplementedUserServiceServer must be embedded to have forward compatible implementations.
82 | type UnimplementedUserServiceServer struct {
83 | }
84 |
85 | func (UnimplementedUserServiceServer) UserRegister(context.Context, *UserRequest) (*UserResponse, error) {
86 | return nil, status.Errorf(codes.Unimplemented, "method UserRegister not implemented")
87 | }
88 | func (UnimplementedUserServiceServer) UserLogin(context.Context, *UserRequest) (*UserResponse, error) {
89 | return nil, status.Errorf(codes.Unimplemented, "method UserLogin not implemented")
90 | }
91 | func (UnimplementedUserServiceServer) UserInfo(context.Context, *UserInfoRequest) (*UserInfoResponse, error) {
92 | return nil, status.Errorf(codes.Unimplemented, "method UserInfo not implemented")
93 | }
94 | func (UnimplementedUserServiceServer) mustEmbedUnimplementedUserServiceServer() {}
95 |
96 | // UnsafeUserServiceServer may be embedded to opt out of forward compatibility for this service.
97 | // Use of this interface is not recommended, as added methods to UserServiceServer will
98 | // result in compilation errors.
99 | type UnsafeUserServiceServer interface {
100 | mustEmbedUnimplementedUserServiceServer()
101 | }
102 |
103 | func RegisterUserServiceServer(s grpc.ServiceRegistrar, srv UserServiceServer) {
104 | s.RegisterService(&UserService_ServiceDesc, srv)
105 | }
106 |
107 | func _UserService_UserRegister_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
108 | in := new(UserRequest)
109 | if err := dec(in); err != nil {
110 | return nil, err
111 | }
112 | if interceptor == nil {
113 | return srv.(UserServiceServer).UserRegister(ctx, in)
114 | }
115 | info := &grpc.UnaryServerInfo{
116 | Server: srv,
117 | FullMethod: UserService_UserRegister_FullMethodName,
118 | }
119 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
120 | return srv.(UserServiceServer).UserRegister(ctx, req.(*UserRequest))
121 | }
122 | return interceptor(ctx, in, info, handler)
123 | }
124 |
125 | func _UserService_UserLogin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
126 | in := new(UserRequest)
127 | if err := dec(in); err != nil {
128 | return nil, err
129 | }
130 | if interceptor == nil {
131 | return srv.(UserServiceServer).UserLogin(ctx, in)
132 | }
133 | info := &grpc.UnaryServerInfo{
134 | Server: srv,
135 | FullMethod: UserService_UserLogin_FullMethodName,
136 | }
137 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
138 | return srv.(UserServiceServer).UserLogin(ctx, req.(*UserRequest))
139 | }
140 | return interceptor(ctx, in, info, handler)
141 | }
142 |
143 | func _UserService_UserInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
144 | in := new(UserInfoRequest)
145 | if err := dec(in); err != nil {
146 | return nil, err
147 | }
148 | if interceptor == nil {
149 | return srv.(UserServiceServer).UserInfo(ctx, in)
150 | }
151 | info := &grpc.UnaryServerInfo{
152 | Server: srv,
153 | FullMethod: UserService_UserInfo_FullMethodName,
154 | }
155 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
156 | return srv.(UserServiceServer).UserInfo(ctx, req.(*UserInfoRequest))
157 | }
158 | return interceptor(ctx, in, info, handler)
159 | }
160 |
161 | // UserService_ServiceDesc is the grpc.ServiceDesc for UserService service.
162 | // It's only intended for direct use with grpc.RegisterService,
163 | // and not to be introspected or modified (even as a copy)
164 | var UserService_ServiceDesc = grpc.ServiceDesc{
165 | ServiceName: "pb.UserService",
166 | HandlerType: (*UserServiceServer)(nil),
167 | Methods: []grpc.MethodDesc{
168 | {
169 | MethodName: "UserRegister",
170 | Handler: _UserService_UserRegister_Handler,
171 | },
172 | {
173 | MethodName: "UserLogin",
174 | Handler: _UserService_UserLogin_Handler,
175 | },
176 | {
177 | MethodName: "UserInfo",
178 | Handler: _UserService_UserInfo_Handler,
179 | },
180 | },
181 | Streams: []grpc.StreamDesc{},
182 | Metadata: "userService.proto",
183 | }
184 |
--------------------------------------------------------------------------------
/api_router/logs/2023-08-22.log:
--------------------------------------------------------------------------------
1 | 2023-08-22.log1800
--------------------------------------------------------------------------------
/api_router/logs/2023-08-22.log1800:
--------------------------------------------------------------------------------
1 | time="2023-08-22T19:15:10+08:00" level=info msg=222
2 | time="2023-08-22T19:15:28+08:00" level=info msg=222
3 | time="2023-08-22T19:52:58+08:00" level=info msg="获取用户服务实例--成功--"
4 | time="2023-08-22T19:52:58+08:00" level=info msg="获取视频服务实例--成功--"
5 | time="2023-08-22T19:52:58+08:00" level=info msg="获取社交服务实例--成功--"
6 | time="2023-08-22T19:55:13+08:00" level=info msg="获取用户服务实例--成功--"
7 | time="2023-08-22T19:55:13+08:00" level=info msg="获取视频服务实例--成功--"
8 | time="2023-08-22T19:55:13+08:00" level=info msg="获取社交服务实例--成功--"
9 | time="2023-08-22T19:55:13+08:00" level=info msg="请求过快,服务熔断"
10 | time="2023-08-22T19:55:13+08:00" level=info msg="请求太快拉..."
11 | time="2023-08-22T19:58:00+08:00" level=info msg="获取用户服务实例--成功--"
12 | time="2023-08-22T19:58:00+08:00" level=info msg="获取视频服务实例--成功--"
13 | time="2023-08-22T19:58:00+08:00" level=info msg="获取社交服务实例--成功--"
14 | time="2023-08-22T19:58:00+08:00" level=info msg="请求过快,服务熔断user_service"
15 | time="2023-08-22T19:58:00+08:00" level=info msg="请求太快拉..."
16 | time="2023-08-22T20:06:27+08:00" level=info msg="获取用户服务实例--成功--"
17 | time="2023-08-22T20:06:27+08:00" level=info msg="获取视频服务实例--成功--"
18 | time="2023-08-22T20:06:27+08:00" level=info msg="获取社交服务实例--成功--"
19 | time="2023-08-22T20:06:27+08:00" level=info msg="请求过快---user_service服务熔断"
20 | time="2023-08-22T20:07:35+08:00" level=info msg="获取用户服务实例--成功--"
21 | time="2023-08-22T20:07:35+08:00" level=info msg="获取视频服务实例--成功--"
22 | time="2023-08-22T20:07:35+08:00" level=info msg="获取社交服务实例--成功--"
23 | time="2023-08-22T20:07:35+08:00" level=info msg="请求过快---user_service服务熔断"
24 | time="2023-08-22T20:07:35+08:00" level=info msg="请求过快---video_service服务熔断"
25 | time="2023-08-22T20:07:35+08:00" level=info msg="请求过快---social_service服务熔断"
26 |
--------------------------------------------------------------------------------
/api_router/logs/2023-08-27.log:
--------------------------------------------------------------------------------
1 | 2023-08-27.log1500
--------------------------------------------------------------------------------
/api_router/logs/2023-08-27.log1500:
--------------------------------------------------------------------------------
1 | time="2023-08-27T15:09:23+08:00" level=fatal msg="failed to build resolver: passthrough: received empty target in Build()"
2 | time="2023-08-27T15:09:35+08:00" level=info msg="获取用户服务实例--成功--"
3 | time="2023-08-27T15:09:35+08:00" level=info msg="获取视频服务实例--成功--"
4 | time="2023-08-27T15:09:35+08:00" level=info msg="获取社交服务实例--成功--"
5 | time="2023-08-27T15:09:35+08:00" level=info msg="请求过快---user_service服务熔断"
6 | time="2023-08-27T15:09:35+08:00" level=info msg="请求过快---video_service服务熔断"
7 | time="2023-08-27T15:09:35+08:00" level=info msg="请求过快---social_service服务熔断"
8 | time="2023-08-27T15:12:57+08:00" level=info msg="获取用户服务实例--成功--"
9 | time="2023-08-27T15:12:57+08:00" level=info msg="获取视频服务实例--成功--"
10 | time="2023-08-27T15:12:57+08:00" level=info msg="获取社交服务实例--成功--"
11 | time="2023-08-27T15:12:57+08:00" level=info msg="请求过快---user_service服务熔断"
12 | time="2023-08-27T15:12:57+08:00" level=info msg="请求过快---video_service服务熔断"
13 | time="2023-08-27T15:12:57+08:00" level=info msg="请求过快---social_service服务熔断"
14 |
--------------------------------------------------------------------------------
/api_router/logs/2023-08-27.log1800:
--------------------------------------------------------------------------------
1 | time="2023-08-27T20:40:40+08:00" level=fatal msg="failed to build resolver: passthrough: received empty target in Build()"
2 | time="2023-08-27T20:40:46+08:00" level=fatal msg="failed to build resolver: passthrough: received empty target in Build()"
3 | time="2023-08-27T20:41:12+08:00" level=info msg="获取用户服务实例--成功--"
4 | time="2023-08-27T20:41:12+08:00" level=info msg="获取视频服务实例--成功--"
5 | time="2023-08-27T20:41:12+08:00" level=info msg="获取社交服务实例--成功--"
6 | time="2023-08-27T20:41:12+08:00" level=info msg="请求过快---user_service服务熔断"
7 | time="2023-08-27T20:41:12+08:00" level=info msg="请求过快---video_service服务熔断"
8 | time="2023-08-27T20:41:12+08:00" level=info msg="请求过快---social_service服务熔断"
9 | time="2023-08-27T20:53:22+08:00" level=info msg="获取用户服务实例--成功--"
10 | time="2023-08-27T20:53:22+08:00" level=info msg="获取视频服务实例--成功--"
11 | time="2023-08-27T20:53:22+08:00" level=info msg="获取社交服务实例--成功--"
12 | time="2023-08-27T20:53:22+08:00" level=info msg="请求过快---user_service服务熔断"
13 | time="2023-08-27T20:53:22+08:00" level=info msg="请求过快---video_service服务熔断"
14 | time="2023-08-27T20:53:22+08:00" level=info msg="请求过快---social_service服务熔断"
15 |
--------------------------------------------------------------------------------
/api_router/logs/2023-08-28.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marinezz/Tiny-TikTok/d78369dabfa86fd1d59dce7140d0f7e9afaf70b8/api_router/logs/2023-08-28.log
--------------------------------------------------------------------------------
/api_router/logs/2023-08-28.log1200:
--------------------------------------------------------------------------------
1 | time="2023-08-28T13:30:56+08:00" level=info msg="获取用户服务实例--成功--"
2 | time="2023-08-28T13:30:56+08:00" level=info msg="获取视频服务实例--成功--"
3 | time="2023-08-28T13:30:56+08:00" level=info msg="获取社交服务实例--成功--"
4 | time="2023-08-28T13:30:56+08:00" level=info msg="请求过快---user_service服务熔断"
5 | time="2023-08-28T13:30:56+08:00" level=info msg="请求过快---video_service服务熔断"
6 | time="2023-08-28T13:30:56+08:00" level=info msg="请求过快---social_service服务熔断"
7 | time="2023-08-28T13:35:17+08:00" level=info msg="获取用户服务实例--成功--"
8 | time="2023-08-28T13:35:17+08:00" level=info msg="获取视频服务实例--成功--"
9 | time="2023-08-28T13:35:17+08:00" level=info msg="获取社交服务实例--成功--"
10 | time="2023-08-28T13:35:17+08:00" level=info msg="请求过快---user_service服务熔断"
11 | time="2023-08-28T13:35:17+08:00" level=info msg="请求过快---video_service服务熔断"
12 | time="2023-08-28T13:35:17+08:00" level=info msg="请求过快---social_service服务熔断"
13 | time="2023-08-28T13:36:50+08:00" level=info msg="获取用户服务实例--成功--"
14 | time="2023-08-28T13:36:50+08:00" level=info msg="获取视频服务实例--成功--"
15 | time="2023-08-28T13:36:50+08:00" level=info msg="获取社交服务实例--成功--"
16 | time="2023-08-28T13:36:50+08:00" level=info msg="请求过快---user_service服务熔断"
17 | time="2023-08-28T13:36:50+08:00" level=info msg="请求过快---video_service服务熔断"
18 | time="2023-08-28T13:36:50+08:00" level=info msg="请求过快---social_service服务熔断"
19 | time="2023-08-28T13:38:38+08:00" level=info msg="获取用户服务实例--成功--"
20 | time="2023-08-28T13:38:38+08:00" level=info msg="获取视频服务实例--成功--"
21 | time="2023-08-28T13:38:38+08:00" level=info msg="获取社交服务实例--成功--"
22 | time="2023-08-28T13:38:38+08:00" level=info msg="请求过快---user_service服务熔断"
23 | time="2023-08-28T13:38:38+08:00" level=info msg="请求过快---video_service服务熔断"
24 | time="2023-08-28T13:38:38+08:00" level=info msg="请求过快---social_service服务熔断"
25 | time="2023-08-28T13:42:57+08:00" level=info msg="获取用户服务实例--成功--"
26 | time="2023-08-28T13:42:57+08:00" level=info msg="获取视频服务实例--成功--"
27 | time="2023-08-28T13:42:57+08:00" level=info msg="获取社交服务实例--成功--"
28 | time="2023-08-28T13:42:57+08:00" level=info msg="请求过快---user_service服务熔断"
29 | time="2023-08-28T13:42:57+08:00" level=info msg="请求过快---video_service服务熔断"
30 | time="2023-08-28T13:42:57+08:00" level=info msg="请求过快---social_service服务熔断"
31 | time="2023-08-28T13:57:53+08:00" level=info msg="获取用户服务实例--成功--"
32 | time="2023-08-28T13:57:53+08:00" level=info msg="获取视频服务实例--成功--"
33 | time="2023-08-28T13:57:53+08:00" level=info msg="获取社交服务实例--成功--"
34 | time="2023-08-28T13:57:53+08:00" level=info msg="请求过快---user_service服务熔断"
35 | time="2023-08-28T13:57:53+08:00" level=info msg="请求过快---video_service服务熔断"
36 | time="2023-08-28T13:57:53+08:00" level=info msg="请求过快---social_service服务熔断"
37 |
--------------------------------------------------------------------------------
/api_router/logs/2023-09-04.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marinezz/Tiny-TikTok/d78369dabfa86fd1d59dce7140d0f7e9afaf70b8/api_router/logs/2023-09-04.log
--------------------------------------------------------------------------------
/api_router/logs/2023-09-04.log0900:
--------------------------------------------------------------------------------
1 | time="2023-09-04T11:16:56+08:00" level=info msg="获取用户服务实例--成功--"
2 | time="2023-09-04T11:16:56+08:00" level=info msg="获取视频服务实例--成功--"
3 | time="2023-09-04T11:16:56+08:00" level=info msg="获取社交服务实例--成功--"
4 | time="2023-09-04T11:16:56+08:00" level=info msg="请求过快---user_service服务熔断"
5 | time="2023-09-04T11:16:56+08:00" level=info msg="请求过快---video_service服务熔断"
6 | time="2023-09-04T11:16:56+08:00" level=info msg="请求过快---social_service服务熔断"
7 | time="2023-09-04T11:19:39+08:00" level=info msg="FollowService--errorrpc error: code = Unavailable desc = error reading from server: read tcp 127.0.0.1:9108->127.0.0.1:10003: wsarecv: An existing connection was forcibly closed by the remote host."
8 | time="2023-09-04T11:23:12+08:00" level=info msg="FollowService--errorrpc error: code = Unavailable desc = error reading from server: read tcp 127.0.0.1:14985->127.0.0.1:10003: wsarecv: An existing connection was forcibly closed by the remote host."
9 | time="2023-09-04T11:31:04+08:00" level=info msg="FollowService--errorrpc error: code = Unavailable desc = connection error: desc = \"transport: Error while dialing: dial tcp 127.0.0.1:10003: connectex: No connection could be made because the target machine actively refused it.\""
10 |
--------------------------------------------------------------------------------
/api_router/logs/2023-09-04.log1500:
--------------------------------------------------------------------------------
1 | time="2023-09-04T15:16:00+08:00" level=info msg="获取用户服务实例--成功--"
2 | time="2023-09-04T15:16:00+08:00" level=info msg="获取视频服务实例--成功--"
3 | time="2023-09-04T15:16:00+08:00" level=info msg="获取社交服务实例--成功--"
4 | time="2023-09-04T15:16:00+08:00" level=info msg="请求过快---user_service服务熔断"
5 | time="2023-09-04T15:16:00+08:00" level=info msg="请求过快---video_service服务熔断"
6 | time="2023-09-04T15:16:00+08:00" level=info msg="请求过快---social_service服务熔断"
7 |
--------------------------------------------------------------------------------
/api_router/pkg/auth/jwt.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "errors"
5 | "github.com/dgrijalva/jwt-go"
6 | "github.com/spf13/viper"
7 | "time"
8 | )
9 |
10 | type Claims struct {
11 | UserId int64 `json:"user_id"`
12 | jwt.StandardClaims
13 | }
14 |
15 | const TokenExpireDuration = time.Hour * 24 * 30 // 设置过期时间
16 |
17 | var Secret = []byte(viper.GetString("server.jwtSecret")) // 设置密码,配置文件中读取
18 |
19 | // GenerateToken 签发Token
20 | func GenerateToken(userId int64) (string, error) {
21 | now := time.Now()
22 | // 创建一个自己的声明
23 | claims := Claims{
24 | UserId: userId,
25 | StandardClaims: jwt.StandardClaims{
26 | ExpiresAt: now.Add(TokenExpireDuration).Unix(), // 过期时间: 当前时间 + 过期时间
27 | Issuer: "admin", // 签发人
28 | },
29 | }
30 | // 创建签名对象
31 | tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
32 | // 获取token
33 | token, err := tokenClaims.SignedString(Secret)
34 |
35 | return token, err
36 | }
37 |
38 | // ParseToken 解析token
39 | func ParseToken(token string) (*Claims, error) {
40 | tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
41 | return Secret, nil
42 | })
43 | if err != nil {
44 | return nil, err
45 | }
46 |
47 | if tokenClaims != nil {
48 | if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
49 | return claims, nil
50 | }
51 | }
52 | return nil, errors.New("invalid token")
53 | }
54 |
--------------------------------------------------------------------------------
/api_router/pkg/logger/log.go:
--------------------------------------------------------------------------------
1 | package logger
2 |
3 | import (
4 | rotatelogs "github.com/lestrrat-go/file-rotatelogs"
5 | "github.com/sirupsen/logrus"
6 | "log"
7 | "os"
8 | "path"
9 | "path/filepath"
10 | "runtime"
11 | "time"
12 | )
13 |
14 | var Log *logrus.Logger
15 |
16 | func init() {
17 | // 如果实例存在则不用新建
18 | if Log != nil {
19 | fileName := getFileDir()
20 | witter := rotateLog(fileName)
21 | Log.Out = witter
22 | return
23 | }
24 |
25 | logger := logrus.New()
26 | fileName := getFileDir()
27 | witter := rotateLog(fileName)
28 |
29 | // 设置输出文件
30 | logger.Out = witter
31 | // 设置日志级别
32 | logger.SetLevel(logrus.DebugLevel)
33 |
34 | Log = logger
35 | }
36 |
37 | // 获取日志输出路径
38 | func getFileDir() string {
39 | now := time.Now()
40 | // 获取指定路径
41 | _, filePath, _, _ := runtime.Caller(0)
42 | logsPath := filepath.Join(filePath, "..", "..", "..", "logs")
43 |
44 | // 文件名称
45 | logFileName := now.Format("2006-01-02") + ".log"
46 | fileName := path.Join(logsPath, logFileName)
47 |
48 | // 查看文件是否存在,不存在则创建
49 | if _, err := os.Stat(fileName); err != nil {
50 | if _, err := os.Create(fileName); err != nil {
51 | log.Println(err.Error())
52 | }
53 | }
54 |
55 | return fileName
56 | }
57 |
58 | // 日志本地文件分割
59 | func rotateLog(fileName string) *rotatelogs.RotateLogs {
60 | witter, _ := rotatelogs.New(
61 | fileName+"%H%M",
62 | rotatelogs.WithLinkName(fileName),
63 | // 日志最长保留时间
64 | rotatelogs.WithMaxAge(time.Duration(12)*time.Hour),
65 | // 日志轮转的时间间隔
66 | rotatelogs.WithRotationTime(time.Duration(3)*time.Hour),
67 | )
68 |
69 | return witter
70 | }
71 |
--------------------------------------------------------------------------------
/api_router/pkg/logger/log_test.go:
--------------------------------------------------------------------------------
1 | package logger
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestLog(t *testing.T) {
8 |
9 | //_, filePath, _, _ := runtime.Caller(0)
10 | //join := filepath.Join(filePath, "..", "..", "..", "logs")
11 | //print(join)
12 |
13 | Log.Info("222")
14 | }
15 |
--------------------------------------------------------------------------------
/api_router/pkg/res/response.go:
--------------------------------------------------------------------------------
1 | package res
2 |
3 | type User struct {
4 | // 用户服务
5 | Id int64 `json:"id"`
6 | Name string `json:"name"`
7 | // 社交服务
8 | FollowCount int64 `json:"follow_count"`
9 | FollowerCount int64 `json:"follower_count"`
10 | IsFollow bool `json:"is_follow"`
11 | // 用户服务
12 | Avatar string `json:"avatar"`
13 | BackgroundImage string `json:"background_image"`
14 | Signature string `json:"signature"`
15 | // 视频服务
16 | TotalFavorited string `json:"total_favorited"` // 获赞数量
17 | WorkCount int64 `json:"work_count"` // 作品数量
18 | FavoriteCount int64 `json:"favorite_count"` // 喜欢数量
19 | }
20 |
21 | type Comment struct {
22 | Id int64 `json:"id"` // 评论id
23 | User User `json:"user"`
24 | Content string `json:"content"` // 评论内容
25 | CreateDate string `json:"create_date"` // 评论发布日期,格式 mm-dd
26 | }
27 |
28 | type Video struct {
29 | Id int64 `json:"id"`
30 | Author User `json:"author"`
31 | PlayUrl string `json:"play_url"`
32 | CoverUrl string `json:"cover_url"`
33 | FavoriteCount int64 `json:"favorite_count"`
34 | CommentCount int64 `json:"comment_count"`
35 | IsFavorite bool `json:"is_favorite"`
36 | Title string `json:"title"`
37 | }
38 |
39 | type FeedResponse struct {
40 | StatusCode int64 `json:"status_code"`
41 | NextTime int64 `json:"next_time"`
42 | StatusMsg string `json:"status_msg"`
43 | VideoList []Video `json:"video_list"`
44 | }
45 |
46 | type UserResponse struct {
47 | StatusCode int64 `json:"status_code"`
48 | StatusMsg string `json:"status_msg,omitempty"`
49 | UserId int64 `json:"user_id"`
50 | Token string `json:"token"`
51 | }
52 |
53 | type UserInfoResponse struct {
54 | StatusCode int64 `json:"status_code"`
55 | StatusMsg string `json:"status_msg,omitempty"`
56 | User User `json:"user"`
57 | }
58 |
59 | type VideoListResponse struct {
60 | StatusCode int64 `json:"status_code"` // 状态码,0-成功,其他值-失败
61 | StatusMsg string `json:"status_msg"` // 返回状态描述
62 | VideoList []Video `json:"video_list"` // 用户点赞视频列表
63 | }
64 |
65 | type PublishActionResponse struct {
66 | StatusCode int64 `json:"status_code"`
67 | StatusMsg string `json:"status_msg,omitempty"`
68 | }
69 |
70 | type FavoriteActionResponse struct {
71 | StatusCode int64 `json:"status_code"`
72 | StatusMsg string `json:"status_msg"`
73 | }
74 |
75 | type CommentActionResponse struct {
76 | StatusCode int64 `json:"status_code"`
77 | StatusMsg string `json:"status_msg"`
78 | Comment Comment `json:"comment"` // 评论成功返回评论内容,不需要重新拉取整个列表
79 | }
80 |
81 | type CommentDeleteResponse struct {
82 | StatusCode int64 `json:"status_code"`
83 | StatusMsg string `json:"status_msg"`
84 | }
85 |
86 | type CommentListResponse struct {
87 | StatusCode int64 `json:"status_code"`
88 | StatusMsg string `json:"status_msg"`
89 | Comments []Comment `json:"comment_list"`
90 | }
91 |
92 | type FollowActionResponse struct {
93 | StatusCode int32 `json:"status_code"` // 状态码,0-成功,其他值-失败
94 | StatusMsg string `json:"status_msg"` // 返回状态描述
95 | }
96 |
97 | type FollowListResponse struct {
98 | StatusCode int32 `json:"status_code"` // 状态码,0-成功,其他值-失败
99 | StatusMsg string `json:"status_msg"` // 返回状态描述
100 | UserList []User `json:"user_list"`
101 | }
102 |
103 | type PostMessageResponse struct {
104 | StatusCode int32 `json:"status_code"` // 状态码,0-成功,其他值-失败
105 | StatusMsg string `json:"status_msg"` // 返回状态描述
106 | }
107 |
108 | type Message struct {
109 | Id int64 `json:"id"`
110 | ToUserId int64 `json:"to_user_id"`
111 | FromUserID int64 `json:"from_user_id"`
112 | Content string `json:"content"`
113 | CreateTime int64 `json:"create_time"`
114 | }
115 |
116 | type GetMessageResponse struct {
117 | StatusCode int32 `json:"status_code"` // 状态码,0-成功,其他值-失败
118 | StatusMsg string `json:"status_msg"` // 返回状态描述
119 | MessageList []Message `json:"message_list"`
120 | }
121 |
--------------------------------------------------------------------------------
/api_router/pkg/wrapper/hystrix.go:
--------------------------------------------------------------------------------
1 | package wrapper
2 |
3 | import (
4 | "api_router/pkg/logger"
5 | "errors"
6 | "fmt"
7 | "github.com/afex/hystrix-go/hystrix"
8 | )
9 |
10 | func NewWrapper(name string) {
11 | // 设置 Hystrix 配置
12 | hystrix.ConfigureCommand("my_command", hystrix.CommandConfig{
13 | Timeout: 1000, // 超时时间(毫秒)
14 | MaxConcurrentRequests: 10, // 最大并发请求数
15 | RequestVolumeThreshold: 5, // 触发熔断的最小请求数,大于这个值才开始做熔断检测
16 | SleepWindow: 5000, // 熔断后休眠时间(毫秒)
17 | ErrorPercentThreshold: 20, // 错误率阈值
18 | })
19 |
20 | // 使用 Hystrix 执行命令
21 | err := hystrix.Do(name, func() error {
22 | return errors.New("服务熔断")
23 | }, nil)
24 | if err != nil {
25 | logger.Log.Info("请求过快---" + name + "服务熔断")
26 | fmt.Println("err", err)
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/api_router/router/middleware/error.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "net/http"
7 | "utils/exception"
8 | )
9 |
10 | // ErrorMiddleWare 错误处理中间件,捕获panic抛出异常
11 | func ErrorMiddleWare() gin.HandlerFunc {
12 | return func(c *gin.Context) {
13 | defer func() {
14 | r := recover()
15 | if r != nil {
16 | c.JSON(http.StatusOK, gin.H{
17 | "status_code": exception.ERROR,
18 | // 打印具体错误
19 | "status_msg": fmt.Sprintf("%s", r),
20 | })
21 | // 中断
22 | c.Abort()
23 | }
24 | }()
25 | c.Next()
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/api_router/router/middleware/jwt.go:
--------------------------------------------------------------------------------
1 | // Package middleware token校验中间件
2 | package middleware
3 |
4 | import (
5 | "api_router/pkg/auth"
6 | "github.com/gin-gonic/gin"
7 | "net/http"
8 | "time"
9 | "utils/exception"
10 | )
11 |
12 | func JWTMiddleware() gin.HandlerFunc {
13 | return func(c *gin.Context) {
14 | var code int
15 | code = exception.SUCCESS
16 | // token 可能在query中也可能在postForm中
17 | token := c.Query("token")
18 | if token == "" {
19 | token = c.PostForm("token")
20 | }
21 | // token不存在
22 | if token == "" {
23 | code = exception.RequestERROR
24 | }
25 |
26 | // 验证token(验证不通过,或者超时)
27 | claims, err := auth.ParseToken(token)
28 | if err != nil {
29 | code = exception.UnAuth
30 | } else if time.Now().Unix() > claims.ExpiresAt {
31 | code = exception.TokenTimeOut
32 | }
33 |
34 | if code != exception.SUCCESS {
35 | c.JSON(http.StatusOK, gin.H{
36 | "StatusCode": code,
37 | "StatusMsg": exception.GetMsg(code),
38 | })
39 | c.Abort()
40 | return
41 | }
42 | c.Set("user_id", claims.UserId)
43 | c.Next()
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/api_router/router/middleware/serve.go:
--------------------------------------------------------------------------------
1 | // 服务中间件,接收服务实例,并保存到context.Key中
2 |
3 | package middleware
4 |
5 | import (
6 | "github.com/gin-gonic/gin"
7 | )
8 |
9 | func ServeMiddleware(serveInstance map[string]interface{}) gin.HandlerFunc {
10 | return func(c *gin.Context) {
11 | // 如果直接复制,浅拷贝会导致map冲突
12 | c.Keys = make(map[string]interface{})
13 | for key, value := range serveInstance {
14 | c.Keys[key] = value
15 | }
16 | c.Next()
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/api_router/router/router.go:
--------------------------------------------------------------------------------
1 | // Package router 路由
2 | package router
3 |
4 | import (
5 | "api_router/internal/handler"
6 | "api_router/router/middleware"
7 | "github.com/gin-gonic/gin"
8 | "net/http"
9 | )
10 |
11 | func InitRouter(serveInstance map[string]interface{}) *gin.Engine {
12 | r := gin.Default()
13 |
14 | r.Use(middleware.ServeMiddleware(serveInstance), middleware.ErrorMiddleWare())
15 | // 测试
16 | r.GET("/ping", func(c *gin.Context) {
17 | c.JSON(http.StatusOK, gin.H{
18 | "message": "ok",
19 | })
20 | })
21 | baseGroup := r.Group("/douyin")
22 | {
23 | // 视频流
24 | baseGroup.GET("/feed/", handler.Feed)
25 |
26 | // 用户
27 | baseGroup.POST("/user/register/", handler.UserRegister)
28 | baseGroup.POST("/user/login/", handler.UserLogin)
29 | baseGroup.GET("/user/", middleware.JWTMiddleware(), handler.UserInfo)
30 |
31 | // 视频
32 | publishGroup := baseGroup.Group("/publish")
33 | publishGroup.Use(middleware.JWTMiddleware())
34 | {
35 | publishGroup.POST("/action/", handler.PublishAction)
36 | publishGroup.GET("/list/", handler.PublishList)
37 | }
38 | favoriteGroup := baseGroup.Group("favorite")
39 | favoriteGroup.Use(middleware.JWTMiddleware())
40 | {
41 | favoriteGroup.POST("action/", handler.FavoriteAction)
42 | favoriteGroup.GET("list/", handler.FavoriteList)
43 | }
44 | commentGroup := baseGroup.Group("/comment")
45 | commentGroup.Use(middleware.JWTMiddleware())
46 | {
47 | commentGroup.POST("/action/", handler.CommentAction)
48 | commentGroup.GET("/list/", handler.CommentList)
49 | }
50 | // 社交
51 | relationGroup := baseGroup.Group("/relation")
52 | relationGroup.Use(middleware.JWTMiddleware())
53 | {
54 | relationGroup.POST("/action/", handler.FollowAction)
55 | relationGroup.GET("/follow/list/", handler.GetFollowList)
56 | relationGroup.GET("/follower/list/", handler.GetFollowerList)
57 | relationGroup.GET("/friend/list/", handler.GetFriendList)
58 | }
59 | messageGroup := baseGroup.Group("/message")
60 | messageGroup.Use(middleware.JWTMiddleware())
61 | {
62 | messageGroup.POST("/action/", handler.PostMessage)
63 | messageGroup.GET("/chat/", handler.GetMessage)
64 | }
65 | }
66 | return r
67 | }
68 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Tiny-TikTok
2 | ## 1 项目介绍
3 |
4 | ### 1.1 概述
5 |
6 | 简易抖音项目后端实现
7 |
8 | ### 1.2 成员介绍
9 |
10 | Ben
11 |
12 | Marine
13 |
14 | ## 2 项目概览
15 |
16 | ### 2.1 技术选型与设计
17 | [img](https://v1rwxew1bdp.feishu.cn/space/api/box/stream/download/asynccode/?code=MmJlMjRhMzJjZDc0OGNjYTIyY2JhOGQzMGQ2YmM1OGRfT2NKdmZWYkVnalpxbHBmeUtCTE1HUnB3djY2SEtUYW5fVG9rZW46U2kxbGJQOVE3b0hOWnB4M1p4bWNJZ2pKbjliXzE2OTE3NTA4MTg6MTY5MTc1NDQxOF9WNA)
18 |
19 | * Gin:Web框架。高性能、轻量级、简洁,被广泛用于构建RESTful API、网站和其它HTTP服务
20 |
21 | * **JWT:**用于身份的验证和授权,具有跨平台、无状态的优点,不需要再会话中保存任何信息,减轻服务器的负担
22 |
23 | * **Hystrix:**服务熔断,防止由于单个服务的故障导致整个系统的崩溃
24 |
25 | * **Etcd + grpc:**etcd实现服务注册和服务发现,grpc负责通信,构建健壮的分布式系统
26 |
27 | * **OSS:**对象存储服务器,用于存储和管理非结构化数据,比如图片、视频等
28 |
29 | * **FFmpeg:**多媒体开源工具,本项目用于上传视频时封面的截取
30 |
31 | * **Gorm:**ORM库,用于操作数据库
32 |
33 | * **MySQL:**关系型数据库,用于存储结构化数据
34 |
35 | * **Redis:**键值存储数据库,以内存作为数据存储介质,提供高性能读写
36 |
37 | * **RabbitMQ:**消息中间件,用于程序之间传递消息,支持消息的发布和订阅,支持消息异步传递,实现解耦和异步处理
38 |
39 |
40 |
41 | ### 2.2 总体设计
42 |
43 | 
44 |
45 | * 请求到达服务器前会对token进行校验
46 | * 通过Api_Router对外暴露接口,进入服务,网关微服务对其它服务进行服务熔断和服务限流
47 | * 各个服务先注册进入ETCD,api_router对各个服务进行调用,组装信息返回给服务端
48 | * api_router通过gprc实现服务之间的通讯
49 | * 服务操作数据库,将信息返回给上一层
50 |
51 |
52 |
53 | ### 2.3 项目结构设计
54 |
55 | * **总目录结构**
56 |
57 | ```bash
58 | ├─api_router # 路由网关
59 | ├─docs # 项目文档
60 | ├─social_service # 社交服务
61 | ├─user_service # 用户服务
62 | ├─utils # 工具函数包
63 | └─video_service # 视频服务
64 | ```
65 |
66 |
67 |
68 | * **路由网关**
69 |
70 | ```bash
71 | ├─api_router
72 | │ ├─cmd
73 | │ ├─config # 项目配置文件
74 | │ ├─discovery # 服务注册与发现
75 | │ ├─internal
76 | │ │ ├─handler
77 | │ │ └─service
78 | │ │ └─pb
79 | │ ├─pkg
80 | │ │ ├─auth
81 | │ │ └─res
82 | │ └─router # 路由和中间件
83 | │ └─middleware
84 | ```
85 |
86 | **/cmd:**一个项目可以有很多个组件,吧main函数所在文件夹同一放在/cmd目录下
87 |
88 | **/internal:**存放私有应用代码。handler类似三层架构中的控制层,service类似服务层,路由不用操作数据库,所以没有持久层
89 |
90 | **/pkg:**存放可以被外部使用的代码库。auth中存放token鉴权,res存放对服务端的统一返回
91 |
92 |
93 |
94 | * **具体服务**
95 |
96 | ```bash
97 | ├─user_service
98 | │ ├─cmd
99 | │ ├─config
100 | │ ├─discovery
101 | │ ├─internal
102 | │ │ ├─handler
103 | │ │ ├─model
104 | │ │ └─service # 持久化层
105 | │ │ └─pb
106 | │ └─pkg
107 | │ └─encryption # 密码加密
108 | ```
109 |
110 |
111 |
112 | * **工具函数包**
113 |
114 | ```bash
115 | ├─utils
116 | │ ├─etcd # etcd服务注册与发现组件
117 | │ ├─exception
118 | │ └─snowFlake # 雪花算法生成ID
119 | ```
120 |
121 |
122 |
123 | ### 2.4 数据库设计
124 |
125 | 
126 |
127 | **用户表:**用于存储用户名称、密码、头像、背景、用户简介信息,以由雪花算法生成的分布式id作为主键(其余表的ID同理),密码由bcrypt函数进行加密。
128 |
129 | **视频表:**用于存储视频的作者、标题、封面路径、视频路径、获赞数量以及评论数量,以视频作者的id关联用户表。
130 |
131 | **评论表:**用于存储视频的评论信息、评论创建时间、评论状态,通过用户id以及视频id关联用户表和视频表,通过评论状态作为软删除判断当前评论是否存在。
132 |
133 | **消息表:**用于存放用户发送的消息以及消息的创建时间,通过用户id关联用户表,记录消息的发生者和消息的接收者
134 |
135 | **关注表:**用户存放用户的关注信息,通过用户id关联用户表获取关注者和被关注者
136 |
137 | **点赞表:**用于存放视频的点赞信息,通过用户id关联用户表获取点赞的用户,视频id关联视频表获取被点赞的视频
138 |
139 |
140 |
141 | ## 3 详细设计
142 |
143 | ## 4 测试
144 |
145 | ## 5 项目演示
146 |
147 | ## 6 总结
148 |
149 |
150 |
151 |
152 |
153 |
--------------------------------------------------------------------------------
/docs/image/总体设计图.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marinezz/Tiny-TikTok/d78369dabfa86fd1d59dce7140d0f7e9afaf70b8/docs/image/总体设计图.png
--------------------------------------------------------------------------------
/docs/image/技术架构图.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marinezz/Tiny-TikTok/d78369dabfa86fd1d59dce7140d0f7e9afaf70b8/docs/image/技术架构图.png
--------------------------------------------------------------------------------
/docs/image/数据库设计图.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marinezz/Tiny-TikTok/d78369dabfa86fd1d59dce7140d0f7e9afaf70b8/docs/image/数据库设计图.png
--------------------------------------------------------------------------------
/docs/image/获奖证书.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marinezz/Tiny-TikTok/d78369dabfa86fd1d59dce7140d0f7e9afaf70b8/docs/image/获奖证书.png
--------------------------------------------------------------------------------
/docs/image/项目获奖证书.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marinezz/Tiny-TikTok/d78369dabfa86fd1d59dce7140d0f7e9afaf70b8/docs/image/项目获奖证书.png
--------------------------------------------------------------------------------
/docs/mysql/tables.sql:
--------------------------------------------------------------------------------
1 | /*
2 | 用户表
3 | */
4 | CREATE TABLE `user` (
5 | `id` bigint NOT NULL AUTO_INCREMENT,
6 | `user_name` varchar(256) DEFAULT NULL,
7 | `pass_word` varchar(256) NOT NULL,
8 | `avatar` varchar(256) DEFAULT NULL,
9 | `background_image` varchar(256) DEFAULT NULL,
10 | `signature` varchar(256) DEFAULT '该用户还没有简介',
11 | PRIMARY KEY (`id`),
12 | UNIQUE KEY `user_name` (`user_name`)
13 | ) ENGINE=InnoDB AUTO_INCREMENT=812575311663105 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
14 |
15 |
16 | /*
17 | 视频表
18 | */
19 | CREATE TABLE `video` (
20 | `id` bigint NOT NULL AUTO_INCREMENT,
21 | `auth_id` bigint DEFAULT NULL,
22 | `title` varchar(256) DEFAULT NULL,
23 | `cover_url` varchar(256) DEFAULT NULL,
24 | `play_url` varchar(256) DEFAULT NULL,
25 | `favorite_count` bigint DEFAULT '0',
26 | `comment_count` bigint DEFAULT '0',
27 | `creat_at` datetime DEFAULT NULL,
28 | PRIMARY KEY (`id`)
29 | ) ENGINE=InnoDB AUTO_INCREMENT=2276964627783681 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
30 |
31 |
32 | /*
33 | 消息表
34 | */
35 | CREATE TABLE `message` (
36 | `id` bigint NOT NULL AUTO_INCREMENT,
37 | `user_id` bigint DEFAULT NULL,
38 | `to_user_id` bigint DEFAULT NULL,
39 | `message` varchar(256) DEFAULT NULL,
40 | `created_at` varchar(256) DEFAULT NULL,
41 | PRIMARY KEY (`id`)
42 | ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
43 |
44 |
45 | /*
46 | 评论表
47 | */
48 | CREATE TABLE `comment` (
49 | `id` bigint NOT NULL AUTO_INCREMENT,
50 | `user_id` bigint DEFAULT NULL,
51 | `video_id` bigint DEFAULT NULL,
52 | `creat_at` datetime DEFAULT NULL,
53 | `comment_status` tinyint(1) DEFAULT NULL,
54 | PRIMARY KEY (`id`)
55 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
56 |
57 |
58 | /*
59 | 点赞表
60 | */
61 | CREATE TABLE `favorite` (
62 | `id` bigint NOT NULL AUTO_INCREMENT,
63 | `user_id` bigint DEFAULT NULL,
64 | `video_id` bigint DEFAULT NULL,
65 | `is_favorite` tinyint(1) DEFAULT NULL,
66 | PRIMARY KEY (`id`)
67 | ) ENGINE=InnoDB AUTO_INCREMENT=2277278458191873 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
68 |
69 |
70 | /*
71 | 关注表
72 | */
73 | CREATE TABLE `follow` (
74 | `id` bigint NOT NULL AUTO_INCREMENT,
75 | `user_id` bigint DEFAULT NULL,
76 | `to_user_id` bigint DEFAULT NULL,
77 | `is_follow` int DEFAULT (2),
78 | PRIMARY KEY (`id`)
79 | ) ENGINE=InnoDB AUTO_INCREMENT=7638289369411586 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
80 |
--------------------------------------------------------------------------------
/social_service/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "social/config"
5 | "social/discovery"
6 | "social/internal/model"
7 | "social/pkg/cache"
8 | )
9 |
10 | func main() {
11 | config.InitConfig()
12 | model.InitDb()
13 | cache.InitRedis()
14 | go cache.TimerSync()
15 | discovery.AutoRegister()
16 | }
17 |
--------------------------------------------------------------------------------
/social_service/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/spf13/viper"
5 | "path"
6 | "runtime"
7 | "strings"
8 | )
9 |
10 | // Config 数据结构
11 | type Config struct {
12 | Mysql struct {
13 | Host string
14 | Port string
15 | Username string
16 | Password string
17 | Database string
18 | }
19 | }
20 |
21 | // ConfigData 配置数据变量
22 | var ConfigData *Config
23 |
24 | // InitConfig 读取配置文件
25 | func InitConfig() {
26 | _, filePath, _, _ := runtime.Caller(0)
27 |
28 | currentDir := path.Dir(filePath)
29 |
30 | viper.SetConfigName("config")
31 | viper.SetConfigType("yaml")
32 | viper.AddConfigPath(currentDir)
33 |
34 | if err := viper.ReadInConfig(); err != nil {
35 | panic(err)
36 | }
37 |
38 | }
39 |
40 | // DbDnsInit 拼接链接数据库的DNS
41 | func DbDnsInit() string {
42 | host := viper.GetString("mysql.host")
43 | port := viper.GetString("mysql.port")
44 | username := viper.GetString("mysql.username")
45 | password := viper.GetString("mysql.password")
46 | database := viper.GetString("mysql.database")
47 |
48 | InitConfig()
49 | dns := strings.Join([]string{username, ":", password, "@tcp(", host, ":", port, ")/", database, "?charset=utf8&parseTime=True&loc=Local"}, "")
50 |
51 | return dns
52 | }
53 |
--------------------------------------------------------------------------------
/social_service/config/config.yaml:
--------------------------------------------------------------------------------
1 | server:
2 | name: social_service # 服务名称
3 | address: 127.0.0.1:10003 # 服务地址
4 |
5 |
6 | mysql:
7 | driver: mysql
8 | host: 127.0.0.1
9 | port: 3306
10 | username: root
11 | password: 123456
12 | database: tiny_tiktok
13 |
14 |
15 | etcd:
16 | address: 127.0.0.1:2379
17 |
18 | redis:
19 | address: 127.0.0.1:6379
20 | password: 123456
--------------------------------------------------------------------------------
/social_service/config/config_test.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 | "github.com/spf13/viper"
6 | "path"
7 | "runtime"
8 | "testing"
9 | )
10 |
11 | func TestInitConfig(t *testing.T) {
12 | _, filePath, _, _ := runtime.Caller(0)
13 |
14 | currentDir := path.Dir(filePath)
15 |
16 | fmt.Println(currentDir)
17 | }
18 |
19 | func TestDbDnsInit(t *testing.T) {
20 | InitConfig()
21 | fmt.Printf("host is : %v \n", viper.GetString("mysql.host"))
22 | dns := DbDnsInit()
23 | fmt.Printf("dns is : %v", dns)
24 | }
25 |
--------------------------------------------------------------------------------
/social_service/discovery/autoRegister.go:
--------------------------------------------------------------------------------
1 | package discovery
2 |
3 | import (
4 | "github.com/spf13/viper"
5 | "google.golang.org/grpc"
6 | "log"
7 | "net"
8 | "social/internal/handler"
9 | "social/internal/service"
10 | "utils/etcd"
11 | )
12 |
13 | // AutoRegister etcd自动注册
14 | func AutoRegister() {
15 | etcdAddress := viper.GetString("etcd.address")
16 | etcdRegister, err := etcd.NewEtcdRegister(etcdAddress)
17 |
18 | if err != nil {
19 | log.Fatal(err)
20 | }
21 |
22 | serviceName := viper.GetString("server.name")
23 | serviceAddress := viper.GetString("server.address")
24 | err = etcdRegister.ServiceRegister(serviceName, serviceAddress, 30)
25 | if err != nil {
26 | log.Fatal(err)
27 | }
28 |
29 | listener, err := net.Listen("tcp", serviceAddress)
30 | if err != nil {
31 | log.Fatal(err)
32 | }
33 |
34 | server := grpc.NewServer()
35 | service.RegisterSocialServiceServer(server, handler.NewSocialService())
36 |
37 | err = server.Serve(listener)
38 | if err != nil {
39 | log.Fatal(err)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/social_service/go.mod:
--------------------------------------------------------------------------------
1 | module social
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/gin-gonic/gin v1.9.1
7 | github.com/go-redis/redis/v8 v8.11.5
8 | github.com/spf13/viper v1.16.0
9 | golang.org/x/sync v0.1.0
10 | google.golang.org/grpc v1.57.0
11 | google.golang.org/protobuf v1.31.0
12 | gorm.io/driver/mysql v1.5.1
13 | gorm.io/gorm v1.25.3
14 | utils v0.0.0
15 | )
16 |
17 | replace utils => ../utils
18 |
19 | require (
20 | github.com/bytedance/sonic v1.9.1 // indirect
21 | github.com/cespare/xxhash/v2 v2.2.0 // indirect
22 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
23 | github.com/coreos/go-semver v0.3.0 // indirect
24 | github.com/coreos/go-systemd/v22 v22.3.2 // indirect
25 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
26 | github.com/fsnotify/fsnotify v1.6.0 // indirect
27 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect
28 | github.com/gin-contrib/sse v0.1.0 // indirect
29 | github.com/go-playground/locales v0.14.1 // indirect
30 | github.com/go-playground/universal-translator v0.18.1 // indirect
31 | github.com/go-playground/validator/v10 v10.14.0 // indirect
32 | github.com/go-sql-driver/mysql v1.7.0 // indirect
33 | github.com/goccy/go-json v0.10.2 // indirect
34 | github.com/gogo/protobuf v1.3.2 // indirect
35 | github.com/golang/protobuf v1.5.3 // indirect
36 | github.com/hashicorp/hcl v1.0.0 // indirect
37 | github.com/jinzhu/inflection v1.0.0 // indirect
38 | github.com/jinzhu/now v1.1.5 // indirect
39 | github.com/json-iterator/go v1.1.12 // indirect
40 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect
41 | github.com/leodido/go-urn v1.2.4 // indirect
42 | github.com/magiconair/properties v1.8.7 // indirect
43 | github.com/mattn/go-isatty v0.0.19 // indirect
44 | github.com/mitchellh/mapstructure v1.5.0 // indirect
45 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
46 | github.com/modern-go/reflect2 v1.0.2 // indirect
47 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect
48 | github.com/robfig/cron/v3 v3.0.1 // indirect
49 | github.com/spf13/afero v1.9.5 // indirect
50 | github.com/spf13/cast v1.5.1 // indirect
51 | github.com/spf13/jwalterweatherman v1.1.0 // indirect
52 | github.com/spf13/pflag v1.0.5 // indirect
53 | github.com/subosito/gotenv v1.4.2 // indirect
54 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
55 | github.com/ugorji/go/codec v1.2.11 // indirect
56 | go.etcd.io/etcd/api/v3 v3.5.9 // indirect
57 | go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect
58 | go.etcd.io/etcd/client/v3 v3.5.9 // indirect
59 | go.uber.org/atomic v1.9.0 // indirect
60 | go.uber.org/multierr v1.8.0 // indirect
61 | go.uber.org/zap v1.21.0 // indirect
62 | golang.org/x/arch v0.3.0 // indirect
63 | golang.org/x/crypto v0.12.0 // indirect
64 | golang.org/x/net v0.14.0 // indirect
65 | golang.org/x/sys v0.11.0 // indirect
66 | golang.org/x/text v0.12.0 // indirect
67 | google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect
68 | google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e // indirect
69 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 // indirect
70 | gopkg.in/ini.v1 v1.67.0 // indirect
71 | gopkg.in/yaml.v3 v3.0.1 // indirect
72 | )
73 |
--------------------------------------------------------------------------------
/social_service/internal/handler/social.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "context"
5 | "social/internal/model"
6 | "social/internal/service"
7 | "social/pkg/cache"
8 | "utils/exception"
9 | )
10 |
11 | type SocialService struct {
12 | service.UnimplementedSocialServiceServer // 版本兼容问题
13 | }
14 |
15 | func NewSocialService() *SocialService {
16 | return &SocialService{}
17 | }
18 |
19 | // FollowAction 关注服务
20 | func (*SocialService) FollowAction(ctx context.Context, req *service.FollowRequest) (resp *service.FollowResponse, err error) {
21 | resp = new(service.FollowResponse)
22 |
23 | if req.UserId == req.ToUserId {
24 | resp.StatusCode = exception.FollowSelfErr
25 | resp.StatusMsg = exception.GetMsg(exception.FollowSelfErr)
26 | return resp, nil
27 | }
28 |
29 | resp.StatusCode = exception.ERROR
30 | resp.StatusMsg = exception.GetMsg(exception.ERROR)
31 |
32 | /* mysql操作
33 |
34 | follow := model.Follow{
35 | UserId: req.UserId,
36 | ToUserId: req.ToUserId,
37 | IsFollow: req.ActionType,
38 | }
39 |
40 | err = model.GetFollowInstance().FollowAction(&follow)
41 | */
42 |
43 | // redis操作
44 | err = cache.FollowAction(req.UserId, req.ToUserId, req.ActionType)
45 |
46 | if err != nil {
47 | return resp, nil
48 | }
49 | resp.StatusCode = exception.SUCCESS
50 | resp.StatusMsg = exception.GetMsg(exception.SUCCESS)
51 | return resp, nil
52 | }
53 |
54 | func (*SocialService) GetFollowList(ctx context.Context, req *service.FollowListRequest) (resp *service.FollowListResponse, err error) {
55 | resp = new(service.FollowListResponse)
56 | resp.StatusCode = exception.ERROR
57 | resp.StatusMsg = exception.GetMsg(exception.ERROR)
58 |
59 | // mysql操作
60 | // err = model.GetFollowInstance().GetFollowList(req.UserId, &resp.UserId)
61 |
62 | // redis操作
63 | err = cache.GetFollowList(req.UserId, &resp.UserId)
64 |
65 | if err != nil {
66 | return resp, nil
67 | }
68 | resp.StatusCode = exception.SUCCESS
69 | resp.StatusMsg = exception.GetMsg(exception.SUCCESS)
70 | return resp, nil
71 | }
72 |
73 | func (*SocialService) GetFollowerList(ctx context.Context, req *service.FollowListRequest) (resp *service.FollowListResponse, err error) {
74 | resp = new(service.FollowListResponse)
75 | resp.StatusCode = exception.ERROR
76 | resp.StatusMsg = exception.GetMsg(exception.ERROR)
77 |
78 | // mysql操作
79 | // err = model.GetFollowInstance().GetFollowerList(req.UserId, &resp.UserId)
80 |
81 | // redis操作
82 | err = cache.GetFollowerList(req.UserId, &resp.UserId)
83 | if err != nil {
84 | return resp, nil
85 | }
86 | resp.StatusCode = exception.SUCCESS
87 | resp.StatusMsg = exception.GetMsg(exception.SUCCESS)
88 | return resp, nil
89 | }
90 |
91 | func (*SocialService) GetFriendList(ctx context.Context, req *service.FollowListRequest) (resp *service.FollowListResponse, err error) {
92 | resp = new(service.FollowListResponse)
93 | resp.StatusCode = exception.ERROR
94 | resp.StatusMsg = exception.GetMsg(exception.ERROR)
95 |
96 | // mysql
97 | // err = model.GetFollowInstance().GetFriendList(req.UserId, &resp.UserId)
98 |
99 | // mysql
100 | err = cache.GetFriendList(req.UserId, &resp.UserId)
101 |
102 | if err != nil {
103 | return resp, nil
104 | }
105 | resp.StatusCode = exception.SUCCESS
106 | resp.StatusMsg = exception.GetMsg(exception.SUCCESS)
107 | return resp, nil
108 | }
109 |
110 | func (*SocialService) GetFollowInfo(ctx context.Context, req *service.FollowInfoRequest) (resp *service.FollowInfoResponse, err error) {
111 | resp = new(service.FollowInfoResponse)
112 | for _, toUserId := range req.ToUserId {
113 | /* mysql
114 | res1, err1 := model.GetFollowInstance().IsFollow(req.UserId, toUserId)
115 | cnt2, err2 := model.GetFollowInstance().GetFollowCount(toUserId)
116 | cnt3, err3 := model.GetFollowInstance().GetFollowerCount(toUserId)
117 | */
118 | res1, err1 := cache.IsFollow(req.UserId, toUserId)
119 | cnt2, err2 := cache.GetFollowCount(toUserId)
120 | cnt3, err3 := cache.GetFollowerCount(toUserId)
121 | if err1 != nil || err2 != nil || err3 != nil {
122 | resp.StatusCode = exception.ERROR
123 | resp.StatusMsg = exception.GetMsg(exception.ERROR)
124 | return resp, nil
125 | }
126 | resp.FollowInfo = append(resp.FollowInfo, &service.FollowInfo{
127 | IsFollow: res1,
128 | FollowCount: cnt2,
129 | FollowerCount: cnt3,
130 | ToUserId: toUserId,
131 | })
132 | }
133 | return resp, nil
134 | }
135 |
136 | // PostMessage 消息服务
137 | func (*SocialService) PostMessage(ctx context.Context, req *service.PostMessageRequest) (resp *service.PostMessageResponse, err error) {
138 | resp = new(service.PostMessageResponse)
139 | message := model.Message{
140 | UserId: req.UserId,
141 | ToUserId: req.ToUserId,
142 | Message: req.Content,
143 | }
144 | err = model.GetMessageInstance().PostMessage(&message)
145 | if err != nil {
146 | resp.StatusCode = exception.ERROR
147 | resp.StatusMsg = exception.GetMsg(exception.ERROR)
148 | return resp, nil
149 | }
150 | resp.StatusCode = exception.SUCCESS
151 | resp.StatusMsg = exception.GetMsg(exception.SUCCESS)
152 | return resp, nil
153 | }
154 |
155 | func (*SocialService) GetMessage(ctx context.Context, req *service.GetMessageRequest) (resp *service.GetMessageResponse, err error) {
156 | resp = new(service.GetMessageResponse)
157 | var messages []model.Message
158 | err = model.GetMessageInstance().GetMessage(req.UserId, req.ToUserId, req.PreMsgTime, &messages)
159 | if err != nil {
160 | resp.StatusCode = exception.ERROR
161 | resp.StatusMsg = exception.GetMsg(exception.ERROR)
162 | return resp, nil
163 | }
164 |
165 | for _, message := range messages {
166 | resp.Message = append(resp.Message, &service.Message{
167 | Id: message.Id,
168 | UserId: message.UserId,
169 | ToUserId: message.ToUserId,
170 | Content: message.Message,
171 | CreatedAt: message.CreatedAt,
172 | })
173 | }
174 | resp.StatusCode = exception.SUCCESS
175 | resp.StatusMsg = exception.GetMsg(exception.SUCCESS)
176 | return resp, nil
177 | }
178 |
--------------------------------------------------------------------------------
/social_service/internal/model/follow.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "gorm.io/gorm"
7 | "log"
8 | "strconv"
9 | "sync"
10 | "utils/snowFlake"
11 | )
12 |
13 | type Follow struct {
14 | Id int64 `gorm:"primary_key"`
15 | UserId int64
16 | ToUserId int64
17 | IsFollow int32 `gorm:"default:(2)"`
18 | }
19 |
20 | type FollowModel struct {
21 | }
22 |
23 | var followModel *FollowModel
24 | var followOnce sync.Once // 单例模式
25 |
26 | // GetFollowInstance 获取单例实例
27 | func GetFollowInstance() *FollowModel {
28 | followOnce.Do(
29 | func() {
30 | followModel = &FollowModel{}
31 | },
32 | )
33 | return followModel
34 | }
35 |
36 | // FollowAction 更新关注状态
37 | func (*FollowModel) FollowAction(follow *Follow) error {
38 | isFollow := follow.IsFollow
39 | if err := DB.Where(&Follow{UserId: follow.UserId, ToUserId: follow.ToUserId}).First(&follow).Error; err != nil {
40 | if errors.Is(err, gorm.ErrRecordNotFound) {
41 | flake, _ := snowFlake.NewSnowFlake(7, 3)
42 | follow.Id = flake.NextId()
43 | err = DB.Create(&follow).Error // create new record from newUser
44 | }
45 | if err != nil {
46 | return err
47 | }
48 | }
49 | if follow.Id != 0 && isFollow != follow.IsFollow {
50 | err := DB.Model(&Follow{}).Where(&follow).Update("is_follow", isFollow).Error
51 | if err != nil {
52 | return err
53 | }
54 | }
55 | return nil
56 | }
57 |
58 | func (*FollowModel) IsFollow(UserId int64, ToUserId int64) (bool, error) {
59 | follow := Follow{UserId: UserId, ToUserId: ToUserId}
60 | err := DB.Where(&follow).First(&follow).Error
61 | if errors.Is(err, gorm.ErrRecordNotFound) {
62 | return false, nil
63 | } else if err != nil {
64 | return false, err
65 | }
66 | if follow.IsFollow == 1 {
67 | return true, nil
68 | }
69 | return false, nil
70 | }
71 |
72 | func (*FollowModel) GetFollowList(reqUser int64, UserId *[]int64) error {
73 | var follows []Follow
74 | if err := DB.Where(&Follow{UserId: reqUser, IsFollow: 1}).Find(&follows).Error; err != nil {
75 | return err
76 | }
77 |
78 | for _, follow := range follows {
79 | *UserId = append(*UserId, follow.ToUserId)
80 | }
81 | return nil
82 | }
83 |
84 | func (*FollowModel) GetFollowerList(reqUser int64, UserId *[]int64) error {
85 | var follows []Follow
86 | if err := DB.Where(&Follow{ToUserId: reqUser, IsFollow: 1}).Find(&follows).Error; err != nil {
87 | return err
88 | }
89 |
90 | for _, follow := range follows {
91 | *UserId = append(*UserId, follow.UserId)
92 | }
93 | return nil
94 | }
95 |
96 | func (*FollowModel) GetFriendList(reqUser int64, UserId *[]int64) error {
97 |
98 | if err := DB.Raw("select a.to_user_id from follow as a inner join follow as b on a.user_id = b.to_user_id and a.to_user_id = b.user_id and a.is_follow = 1 and b.is_follow = 1 and a.user_id = ?", reqUser).Scan(UserId).Error; err != nil {
99 | return err
100 | }
101 |
102 | return nil
103 | }
104 |
105 | func (*FollowModel) GetFollowCount(reqUser int64) (int64, error) {
106 | var cnt int64
107 |
108 | if err := DB.Model(&Follow{}).Where(&Follow{UserId: reqUser, IsFollow: 1}).Count(&cnt).Error; err != nil {
109 | return 0, err
110 | }
111 |
112 | return cnt, nil
113 | }
114 |
115 | func (*FollowModel) GetFollowerCount(reqUser int64) (int64, error) {
116 | var cnt int64
117 | if err := DB.Model(&Follow{}).Where(&Follow{ToUserId: reqUser, IsFollow: 1}).Count(&cnt).Error; err != nil {
118 | return 0, err
119 | }
120 | return cnt, nil
121 | }
122 |
123 | func (*FollowModel) RedisToMysql(data string) error {
124 | // 先将所有follow置为2
125 | if err := DB.Model(&Follow{}).Where("is_follow = ?", 1).Update("is_follow", 2).Error; err != nil {
126 | return err
127 | }
128 | // 转换data为map
129 | var follows map[string][]string
130 | if err := json.Unmarshal([]byte(data), &follows); err != nil {
131 | return err
132 | }
133 | // 对每一个关注进行操作
134 | var wg sync.WaitGroup
135 | var errs []error
136 |
137 | for userId, toUserIds := range follows {
138 | userid, _ := strconv.ParseInt(userId, 10, 64)
139 | for _, toUserId := range toUserIds {
140 | touserid, _ := strconv.ParseInt(toUserId, 10, 64)
141 | wg.Add(1)
142 | go func(userId int64, toUserId int64) {
143 | defer wg.Done()
144 | follow := Follow{UserId: userId, ToUserId: toUserId, IsFollow: 1}
145 | err := GetFollowInstance().FollowAction(&follow)
146 | if err != nil {
147 | errs = append(errs, err)
148 | }
149 | }(userid, touserid)
150 |
151 | }
152 | }
153 |
154 | wg.Wait()
155 |
156 | log.Println(errs)
157 |
158 | return nil
159 | }
160 |
--------------------------------------------------------------------------------
/social_service/internal/model/follow_test.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func TestFollowModel_FollowAction(t *testing.T) {
9 | InitDb()
10 | follow := Follow{
11 | UserId: 1,
12 | ToUserId: 2,
13 | IsFollow: 2,
14 | }
15 | err := GetFollowInstance().FollowAction(&follow)
16 | fmt.Println(err)
17 | }
18 |
19 | func TestFollowModel_IsFollow(t *testing.T) {
20 | InitDb()
21 | res, err := GetFollowInstance().IsFollow(5155644223918080, 5155317378584576)
22 | if err != nil {
23 | panic(err)
24 | }
25 | fmt.Println(res)
26 | }
27 |
28 | func TestFollowModel_GetFollowList(t *testing.T) {
29 | InitDb()
30 | var UserId []int64
31 | var reqUser int64
32 | reqUser = 1
33 | if err := GetFollowInstance().GetFollowList(reqUser, &UserId); err != nil {
34 | panic(nil)
35 | }
36 | fmt.Printf("%d的关注有%#v\n", reqUser, UserId)
37 | }
38 |
39 | func TestFollowModel_GetFollowerList(t *testing.T) {
40 | InitDb()
41 | var UserId []int64
42 | var reqUser int64
43 | reqUser = 2
44 | if err := GetFollowInstance().GetFollowerList(reqUser, &UserId); err != nil {
45 | panic(nil)
46 | }
47 | fmt.Printf("%d的粉丝有%#v\n", reqUser, UserId)
48 | }
49 |
50 | func TestFollowModel_GetFriendList(t *testing.T) {
51 | InitDb()
52 | var UserId []int64
53 | var reqUser int64
54 | reqUser = 2
55 | if err := GetFollowInstance().GetFriendList(reqUser, &UserId); err != nil {
56 | panic(nil)
57 | }
58 | fmt.Printf("%d的好友有%#v\n", reqUser, UserId)
59 | }
60 |
61 | func TestFollowModel_GetFollowCount(t *testing.T) {
62 | InitDb()
63 | var cnt int64
64 | var reqUser int64
65 | reqUser = 1
66 | cnt, err := GetFollowInstance().GetFollowCount(reqUser)
67 | if err != nil {
68 | panic(nil)
69 | }
70 | fmt.Printf("%d的关注有%d个\n", reqUser, cnt)
71 | }
72 |
73 | func TestFollowModel_GetFollowerCount(t *testing.T) {
74 | InitDb()
75 | var cnt int64
76 | var reqUser int64
77 | reqUser = 2
78 | cnt, err := GetFollowInstance().GetFollowerCount(reqUser)
79 | if err != nil {
80 | panic(nil)
81 | }
82 | fmt.Printf("%d的粉丝有%d个\n", reqUser, cnt)
83 | }
84 |
--------------------------------------------------------------------------------
/social_service/internal/model/init.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "gorm.io/driver/mysql"
6 | "gorm.io/gorm"
7 | "gorm.io/gorm/logger"
8 | "gorm.io/gorm/schema"
9 | "social/config"
10 | "time"
11 | )
12 |
13 | // DB 创建数据库单例
14 | var DB *gorm.DB
15 |
16 | // InitDb 初始化数据库
17 | func InitDb() {
18 | config.InitConfig()
19 | dns := config.DbDnsInit()
20 | Database(dns)
21 | }
22 |
23 | // Database 初始花数据库链接
24 | func Database(connString string) {
25 | var ormLogger logger.Interface
26 |
27 | // gin中有debug 、 release 、 test 三种模式
28 | // 不指定默认以debug形式启动
29 | // 开发用debug模式、 上线用release模式
30 | // 指定方式 : gin.SetMode(gin.ReleaseMode)
31 |
32 | // 设置gorm的日志模式,可以打印原生SQL语句
33 | if gin.Mode() == "debug" {
34 | ormLogger = logger.Default.LogMode(logger.Info)
35 | } else {
36 | ormLogger = logger.Default
37 | }
38 | db, err := gorm.Open(mysql.New(mysql.Config{
39 | DSN: connString, // DSN data source name
40 | DefaultStringSize: 256, // string 类型字段的默认长度
41 | DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
42 | DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
43 | DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
44 | SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
45 | }), &gorm.Config{
46 | Logger: ormLogger,
47 | NamingStrategy: schema.NamingStrategy{
48 | SingularTable: true, // 默认不加负数
49 | },
50 | })
51 |
52 | if err != nil {
53 | panic(err)
54 | }
55 |
56 | sqlDB, _ := db.DB()
57 |
58 | // 设置连接池
59 | sqlDB.SetMaxIdleConns(20) // 空闲时候的最大连接数
60 | sqlDB.SetMaxOpenConns(100) // 打开时候的最大连接数
61 | sqlDB.SetConnMaxLifetime(time.Second * 20) // 超时时间
62 | DB = db
63 |
64 | migration()
65 | }
66 |
--------------------------------------------------------------------------------
/social_service/internal/model/init_test.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "fmt"
5 | "github.com/spf13/viper"
6 | "social/config"
7 | "testing"
8 | )
9 |
10 | func TestInitDb(t *testing.T) {
11 | config.InitConfig()
12 | dns := config.DbDnsInit()
13 | fmt.Print(dns)
14 | config.InitConfig()
15 | fmt.Printf("host is: %v \n", viper.GetString("mysql.host"))
16 | }
17 |
--------------------------------------------------------------------------------
/social_service/internal/model/message.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "sync"
5 | "time"
6 | "utils/snowFlake"
7 | )
8 |
9 | type Message struct {
10 | Id int64 `gorm:"primary_key"`
11 | UserId int64
12 | ToUserId int64
13 | Message string
14 | CreatedAt int64
15 | }
16 |
17 | type MessageModel struct {
18 | }
19 |
20 | var messageModel *MessageModel
21 | var messageOnce sync.Once // 单例模式
22 |
23 | // GetMessageInstance 获取单例实例
24 | func GetMessageInstance() *MessageModel {
25 | messageOnce.Do(
26 | func() {
27 | messageModel = &MessageModel{}
28 | },
29 | )
30 | return messageModel
31 | }
32 |
33 | func getCurrentTime() (createTime int64) {
34 | createTime = time.Now().Unix()
35 | return
36 | }
37 |
38 | func (*MessageModel) PostMessage(message *Message) error {
39 | message.CreatedAt = getCurrentTime()
40 | flake, _ := snowFlake.NewSnowFlake(7, 3)
41 | message.Id = flake.NextId()
42 | err := DB.Create(&message).Error
43 | return err
44 | }
45 |
46 | func (*MessageModel) GetMessage(UserId int64, ToUserID int64, PreMsgTime int64, messages *[]Message) error {
47 | err := DB.Model(&Message{}).Where(DB.Model(&Message{}).Where(&Message{UserId: UserId, ToUserId: ToUserID}).
48 | Or(&Message{UserId: ToUserID, ToUserId: UserId})).Where("created_at > ?", PreMsgTime).
49 | Order("created_at").Find(messages).Error
50 | return err
51 | }
52 |
--------------------------------------------------------------------------------
/social_service/internal/model/message_test.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func TestMessageModel_GetMessage(t *testing.T) {
9 | InitDb()
10 | var messages []Message
11 | err := GetMessageInstance().GetMessage(5155317378584576, 5155644223918080, 16, &messages)
12 | if err != nil {
13 | panic(err)
14 | }
15 | fmt.Printf("%#v\n", messages)
16 | }
17 |
18 | func TestMessageModel_PostMessage(t *testing.T) {
19 | InitDb()
20 | err := GetMessageInstance().PostMessage(&Message{
21 | UserId: 1,
22 | ToUserId: 2,
23 | Message: "你好",
24 | })
25 | if err != nil {
26 | panic(err)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/social_service/internal/model/migration.go:
--------------------------------------------------------------------------------
1 | // Package model 数据库自动迁移
2 | package model
3 |
4 | import "log"
5 |
6 | func migration() {
7 | // 自动迁移
8 | err := DB.Set("gorm:table_options", "charset=utf8mb4").AutoMigrate(&Follow{})
9 | // Todo 判断error 写入日志
10 | if err != nil {
11 | log.Print("err")
12 | }
13 |
14 | err = DB.Set("gorm:table_options", "charset=utf8mb4").AutoMigrate(&Message{})
15 | // Todo 判断error 写入日志
16 | if err != nil {
17 | log.Print("err")
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/social_service/internal/service/pb/socialService.proto:
--------------------------------------------------------------------------------
1 | syntax="proto3";
2 | package pb;
3 | option go_package = "../;service"; // 此处格式为<生成的文件存放位置;生成文件的包名>
4 |
5 | // protoc -I internal/service/pb --go_out=./internal/service/ --go_opt=paths=source_relative --go-grpc_out=./internal/service/ --go-grpc_opt=paths=source_relative internal/service/pb/*.proto
6 | // 或者分开使用
7 | // protoc -I internal/service/pb/ --go_out=./ internal/service/pb/*.proto
8 | // protoc -I internal/service/pb/ --go-grpc_out=./ internal/service/pb/*.proto
9 |
10 | message FollowRequest{
11 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
12 | int64 UserId = 1;
13 | // @gotags:json:"to_user_id" form:"to_user_id" uri:"to_user_id"
14 | int64 ToUserId = 2;
15 | // @gotags:json:"action_type" form:"action_type" uri:"action_type"
16 | int32 ActionType = 3;
17 | }
18 |
19 | message FollowResponse{
20 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
21 | int32 StatusCode = 1;
22 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
23 | string StatusMsg = 2;
24 | }
25 |
26 | message FollowListRequest{
27 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
28 | int64 UserId = 1;
29 | }
30 |
31 | message FollowListResponse{
32 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
33 | int32 StatusCode = 1;
34 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
35 | string StatusMsg = 2;
36 | // @gotags:json:"user_list" form:"user_list" uri:"user_list"
37 | repeated int64 UserId = 3;
38 | }
39 |
40 | message FollowInfoRequest{
41 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
42 | int64 UserId = 1;
43 | // @gotags:json:"to_user_id" form:"to_user_id" uri:"to_user_id"
44 | repeated int64 ToUserId = 2;
45 | }
46 |
47 | message FollowInfo{
48 | // @gotags:json:"is_follow" form:"is_follow" uri:"is_follow"
49 | bool IsFollow = 1;
50 | // @gotags:json:"follow_count" form:"follow_count" uri:"follow_count"
51 | int64 FollowCount = 2;
52 | // @gotags:json:"follower_count" form:"follower_count" uri:"follower_count"
53 | int64 FollowerCount = 3;
54 | // @gotags:json:"to_user_id" form:"to_user_id" uri:"to_user_id"
55 | int64 ToUserId = 4;
56 | }
57 |
58 | message FollowInfoResponse{
59 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
60 | int32 StatusCode = 1;
61 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
62 | string StatusMsg = 2;
63 | // @gotags:json:"follow_info" form:"follow_info" uri:"follow_info"
64 | repeated FollowInfo FollowInfo = 3;
65 | }
66 |
67 | message PostMessageRequest{
68 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
69 | int64 UserId = 1;
70 | // @gotags:json:"to_user_id" form:"to_user_id" uri:"to_user_id"
71 | int64 ToUserId = 2;
72 | // @gotags:json:"action_type" form:"action_type" uri:"action_type"
73 | int32 ActionType = 3;
74 | // @gotags:json:"content" form:"content" uri:"content"
75 | string Content = 4;
76 | }
77 |
78 | message PostMessageResponse{
79 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
80 | int32 StatusCode = 1;
81 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
82 | string StatusMsg = 2;
83 | }
84 |
85 | message GetMessageRequest{
86 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
87 | int64 UserId = 1;
88 | // @gotags:json:"to_user_id" form:"to_user_id" uri:"to_user_id"
89 | int64 ToUserId = 2;
90 | // @gotags:json:"pre_msg_time" form:"pre_msg_time" uri:"pre_msg_time"
91 | int64 preMsgTime=3;
92 | }
93 |
94 | message Message{
95 | // @gotags:json:"id" form:"id" uri:"id"
96 | int64 Id = 1;
97 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
98 | int64 UserId = 2;
99 | // @gotags:json:"to_user_id" form:"to_user_id" uri:"to_user_id"
100 | int64 ToUserId = 3;
101 | // @gotags:json:"content" form:"content" uri:"content"
102 | string Content = 4;
103 | // @gotags:json:"created_at" form:"created_at" uri:"created_at"
104 | int64 CreatedAt = 5;
105 | }
106 |
107 | message GetMessageResponse{
108 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
109 | int32 StatusCode = 1;
110 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
111 | string StatusMsg = 2;
112 | // @gotags:json:"message_list" form:"message_list" uri:"message_list"
113 | repeated Message message = 3;
114 | }
115 |
116 | service SocialService{
117 | // 关注服务
118 | rpc FollowAction(FollowRequest) returns(FollowResponse);
119 | rpc GetFollowList(FollowListRequest) returns(FollowListResponse);
120 | rpc GetFollowerList(FollowListRequest) returns(FollowListResponse);
121 | rpc GetFriendList(FollowListRequest) returns(FollowListResponse);
122 | rpc GetFollowInfo(FollowInfoRequest) returns(FollowInfoResponse);
123 |
124 | // 消息服务
125 | rpc PostMessage(PostMessageRequest) returns(PostMessageResponse);
126 | rpc GetMessage(GetMessageRequest) returns(GetMessageResponse);
127 |
128 | }
--------------------------------------------------------------------------------
/social_service/pkg/cache/redis.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "context"
5 | "github.com/go-redis/redis/v8"
6 | "github.com/spf13/viper"
7 | "strconv"
8 | )
9 |
10 | var Redis *redis.Client
11 | var ctx = context.Background()
12 |
13 | // InitRedis 连接redis
14 | func InitRedis() {
15 | addr := viper.GetString("redis.address")
16 | Redis = redis.NewClient(&redis.Options{
17 | Addr: addr,
18 | DB: 0, // 存入DB0
19 | })
20 | _, err := Redis.Ping(context.Background()).Result()
21 | if err != nil {
22 | panic(err)
23 | }
24 | }
25 |
26 | func GenerateFollowKey(UserId int64) string {
27 | return "TINYTIKTOK:FOLLOW:" + strconv.FormatInt(UserId, 10)
28 | }
29 |
30 | func GenerateFollowerKey(UserId int64) string {
31 | return "TINYTIKTOK:FOLLOWER:" + strconv.FormatInt(UserId, 10)
32 | }
33 |
34 | func GenerateFriendKey(UserId int64) string {
35 | return "TINYTIKTOK:FRIEND:" + strconv.FormatInt(UserId, 10)
36 | }
37 |
38 | func IsFollow(UserId int64, ToUserId int64) (bool, error) {
39 | result, err := Redis.SIsMember(ctx, GenerateFollowKey(UserId), ToUserId).Result()
40 | return result, err
41 | }
42 |
43 | func FollowAction(UserId int64, ToUserId int64, ActionType int32) error {
44 | // 先查询关注的状态
45 | result, err := Redis.SIsMember(ctx, GenerateFollowKey(UserId), ToUserId).Result()
46 | if err != nil {
47 | return err
48 | }
49 | // 获取操作类型
50 | action := true
51 | if ActionType == 2 {
52 | action = false
53 | }
54 | // 判断操作是否需要执行
55 | if result == action {
56 | return nil
57 | }
58 | // 执行操作
59 | pipe := Redis.TxPipeline()
60 | defer pipe.Close()
61 | if action {
62 | pipe.SAdd(ctx, GenerateFollowKey(UserId), ToUserId)
63 | pipe.SAdd(ctx, GenerateFollowerKey(ToUserId), UserId)
64 | _, err := pipe.Exec(ctx)
65 | if err != nil {
66 | return err
67 | }
68 | } else {
69 | pipe.SRem(ctx, GenerateFollowKey(UserId), ToUserId)
70 | pipe.SRem(ctx, GenerateFollowerKey(ToUserId), UserId)
71 | _, err := pipe.Exec(ctx)
72 | if err != nil {
73 | return err
74 | }
75 | }
76 | return nil
77 | }
78 |
79 | func GetFollowList(reqUser int64, UserId *[]int64) error {
80 | result, err := Redis.SMembers(ctx, GenerateFollowKey(reqUser)).Result()
81 | if err != nil {
82 | return err
83 | }
84 | for _, r := range result {
85 | r64, _ := strconv.ParseInt(r, 10, 64)
86 | *UserId = append(*UserId, r64)
87 | }
88 | return nil
89 | }
90 |
91 | func GetFollowerList(reqUser int64, UserId *[]int64) error {
92 | result, err := Redis.SMembers(ctx, GenerateFollowerKey(reqUser)).Result()
93 | if err != nil {
94 | return err
95 | }
96 | for _, r := range result {
97 | r64, _ := strconv.ParseInt(r, 10, 64)
98 | *UserId = append(*UserId, r64)
99 | }
100 | return nil
101 | }
102 |
103 | func GetFriendList(reqUser int64, UserId *[]int64) error {
104 | _, err := Redis.Do(ctx, "SINTERSTORE", GenerateFriendKey(reqUser), GenerateFollowKey(reqUser), GenerateFollowerKey(reqUser)).Result()
105 | if err != nil {
106 | return err
107 | }
108 | result, err := Redis.SMembers(ctx, GenerateFriendKey(reqUser)).Result()
109 | if err != nil {
110 | return err
111 | }
112 | for _, r := range result {
113 | r64, _ := strconv.ParseInt(r, 10, 64)
114 | *UserId = append(*UserId, r64)
115 | }
116 | return nil
117 | }
118 |
119 | func GetFollowCount(reqUser int64) (int64, error) {
120 | return Redis.SCard(ctx, GenerateFollowKey(reqUser)).Result()
121 | }
122 |
123 | func GetFollowerCount(reqUser int64) (int64, error) {
124 | return Redis.SCard(ctx, GenerateFollowerKey(reqUser)).Result()
125 | }
126 |
--------------------------------------------------------------------------------
/social_service/pkg/cache/redisAutoSync.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/go-redis/redis/v8"
7 | "github.com/robfig/cron/v3"
8 | "golang.org/x/sync/errgroup"
9 | "log"
10 | "os"
11 | "social/internal/model"
12 | "time"
13 | )
14 |
15 | // getAllKeys 获取所有"FOLLOW"表项的key值
16 | func getAllKeys(keys *[]string) {
17 | // 使用正则表达式来匹配键
18 | pattern := "TINYTIKTOK:FOLLOW:*" // 替换为您的正则表达式
19 |
20 | // 初始化SCAN迭代器
21 | iter := Redis.Scan(ctx, 0, pattern, 0).Iterator()
22 |
23 | // 用于并发处理的函数
24 | handleKey := func(key string) error {
25 | *keys = append(*keys, key)
26 | return nil
27 | }
28 |
29 | // 并发处理匹配的键
30 | var g errgroup.Group
31 | for iter.Next(ctx) {
32 | key := iter.Val()
33 | g.Go(func() error {
34 | return handleKey(key)
35 | })
36 | }
37 |
38 | if err := g.Wait(); err != nil {
39 | panic(err)
40 | }
41 | }
42 |
43 | func getAllValueByKeys(keys []string) string {
44 | // 创建管道并将所有的 查询列表操作加入pipe
45 | cmds, err := Redis.Pipelined(ctx, func(pipe redis.Pipeliner) error {
46 | for _, key := range keys {
47 | pipe.SMembers(ctx, key)
48 | }
49 | return nil
50 | })
51 | if err != nil {
52 | panic(err)
53 | }
54 |
55 | followInfo := make(map[string][]string)
56 | for index, cmd := range cmds {
57 | followInfo[keys[index][18:]] = cmd.(*redis.StringSliceCmd).Val()
58 | }
59 | marshal, err := json.Marshal(followInfo)
60 | if err != nil {
61 | return ""
62 | }
63 | return string(marshal)
64 |
65 | }
66 |
67 | /*
68 | func MysqlToRedis() error {
69 | var errs []error
70 | // 所有数据,每次取100条
71 | page := 1
72 | pageSize := 100
73 | for {
74 | var follows []Follow
75 | offset := (page - 1) * pageSize
76 | DB.Limit(pageSize).Offset(offset).Find(&follows)
77 |
78 | // 处理查询结果
79 | if len(follows) == 0 {
80 | break // 没有更多记录可获取,退出循环
81 | }
82 |
83 | for _, follow := range follows {
84 | err := cache.FollowAction(follow.UserId, follow.ToUserId, 1)
85 | errs = append(errs, err)
86 | }
87 |
88 | page++
89 | }
90 |
91 | log.Println(errs)
92 | return nil
93 | */
94 | func AutoSync() {
95 | // 先匹配所有follow的key
96 | var keys []string
97 | getAllKeys(&keys)
98 | res := getAllValueByKeys(keys)
99 | err := model.GetFollowInstance().RedisToMysql(res)
100 | if err != nil {
101 | panic(err)
102 | }
103 | }
104 |
105 | func TimerSync() {
106 | c := cron.New(cron.WithSeconds(), cron.WithChain(cron.SkipIfStillRunning(cron.DefaultLogger)), cron.WithLogger(
107 | cron.VerbosePrintfLogger(log.New(os.Stdout, "cron: ", log.LstdFlags))))
108 |
109 | _, err := c.AddFunc("0 */10 * * * *", func() {
110 | fmt.Println(time.Now(), " 开始同步到mysql数据库")
111 | AutoSync()
112 | })
113 | if err != nil {
114 | panic(err)
115 | }
116 |
117 | c.Start()
118 | select {}
119 | }
120 |
--------------------------------------------------------------------------------
/social_service/pkg/cache/redis_test.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "fmt"
5 | "social/internal/model"
6 | "testing"
7 | )
8 |
9 | func TestFollowAction(t *testing.T) {
10 | InitRedis()
11 | err := FollowAction(3, 4, 1)
12 | fmt.Println(err)
13 | }
14 |
15 | func TestGetFriendListList(t *testing.T) {
16 | InitRedis()
17 | var UserId []int64
18 | err := GetFollowList(1, &UserId)
19 | fmt.Printf("%#v", UserId)
20 | fmt.Println(err)
21 | }
22 |
23 | func TestGetFollowCount(t *testing.T) {
24 | InitRedis()
25 | fmt.Println(GetFollowCount(100))
26 | }
27 |
28 | func TestAutoSync(t *testing.T) {
29 | InitRedis()
30 | model.InitDb()
31 | AutoSync()
32 | }
33 |
--------------------------------------------------------------------------------
/user_service/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "user/config"
5 | "user/discovery"
6 | "user/internal/model"
7 | "user/pkg/cache"
8 | )
9 |
10 | func main() {
11 | config.InitConfig() // 初始话配置文件
12 | cache.InitRedis() // 初始化redis
13 | model.InitDb() // 初始化数据库
14 | discovery.AutoRegister() // 自动注册
15 | }
16 |
--------------------------------------------------------------------------------
/user_service/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/spf13/viper"
5 | "path"
6 | "runtime"
7 | "strings"
8 | )
9 |
10 | // Config 数据结构
11 | type Config struct {
12 | Mysql struct {
13 | Host string
14 | Port string
15 | Username string
16 | Password string
17 | Database string
18 | }
19 | }
20 |
21 | // ConfigData 配置数据变量
22 | var ConfigData *Config
23 |
24 | // InitConfig 读取配置文件
25 | func InitConfig() {
26 | _, filePath, _, _ := runtime.Caller(0)
27 |
28 | currentDir := path.Dir(filePath)
29 |
30 | viper.SetConfigName("config")
31 | viper.SetConfigType("yaml")
32 | viper.AddConfigPath(currentDir)
33 |
34 | if err := viper.ReadInConfig(); err != nil {
35 | panic(err)
36 | }
37 |
38 | }
39 |
40 | // DbDnsInit 拼接链接数据库的DNS
41 | func DbDnsInit() string {
42 | host := viper.GetString("mysql.host")
43 | port := viper.GetString("mysql.port")
44 | username := viper.GetString("mysql.username")
45 | password := viper.GetString("mysql.password")
46 | database := viper.GetString("mysql.database")
47 |
48 | InitConfig()
49 | dns := strings.Join([]string{username, ":", password, "@tcp(", host, ":", port, ")/", database, "?charset=utf8&parseTime=True&loc=Local"}, "")
50 |
51 | return dns
52 | }
53 |
54 | func RedisURLInit() string {
55 | password := viper.GetString("redis.password")
56 | address := viper.GetString("redis.address")
57 | database := viper.GetString("redis.database")
58 |
59 | InitConfig()
60 | RedisURL := "redis://" + password + "@" + address + "/" + database
61 |
62 | return RedisURL
63 | }
64 |
--------------------------------------------------------------------------------
/user_service/config/config.yaml:
--------------------------------------------------------------------------------
1 | server:
2 | name: user_service # 服务名称
3 | address: 127.0.0.1:10001 # 服务地址
4 |
5 |
6 | mysql:
7 | driver: mysql
8 | host: 127.0.0.1
9 | port: 3306
10 | username: root
11 | password: 123456
12 | database: tiny_tiktok
13 |
14 |
15 | etcd:
16 | address: 127.0.0.1:2379
17 |
18 | redis:
19 | address: 127.0.0.1:6379
20 |
--------------------------------------------------------------------------------
/user_service/config/config_test.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 | "github.com/spf13/viper"
6 | "path"
7 | "runtime"
8 | "testing"
9 | )
10 |
11 | func TestInitConfig(t *testing.T) {
12 | _, filePath, _, _ := runtime.Caller(0)
13 |
14 | currentDir := path.Dir(filePath)
15 |
16 | fmt.Println(currentDir)
17 | }
18 |
19 | func TestDbDnsInit(t *testing.T) {
20 | InitConfig()
21 | fmt.Printf("host is : %v \n", viper.GetString("mysql.host"))
22 | dns := DbDnsInit()
23 | fmt.Printf("dns is : %v", dns)
24 | }
25 |
--------------------------------------------------------------------------------
/user_service/discovery/autoRegister.go:
--------------------------------------------------------------------------------
1 | package discovery
2 |
3 | import (
4 | "github.com/spf13/viper"
5 | "google.golang.org/grpc"
6 | "log"
7 | "net"
8 | "user/internal/handler"
9 | "user/internal/service"
10 | "utils/etcd"
11 | )
12 |
13 | // AutoRegister etcd自动注册
14 | func AutoRegister() {
15 | etcdAddress := viper.GetString("etcd.address")
16 | etcdRegister, err := etcd.NewEtcdRegister(etcdAddress)
17 |
18 | if err != nil {
19 | log.Fatal(err)
20 | }
21 |
22 | serviceName := viper.GetString("server.name")
23 | serviceAddress := viper.GetString("server.address")
24 | err = etcdRegister.ServiceRegister(serviceName, serviceAddress, 30)
25 | if err != nil {
26 | log.Fatal(err)
27 | }
28 |
29 | listener, err := net.Listen("tcp", serviceAddress)
30 | if err != nil {
31 | log.Fatal(err)
32 | }
33 |
34 | server := grpc.NewServer()
35 | service.RegisterUserServiceServer(server, handler.NewUserService())
36 |
37 | err = server.Serve(listener)
38 | if err != nil {
39 | log.Fatal(err)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/user_service/go.mod:
--------------------------------------------------------------------------------
1 | module user
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/gin-gonic/gin v1.9.1
7 | github.com/spf13/viper v1.16.0
8 | go.etcd.io/etcd/api/v3 v3.5.9
9 | go.etcd.io/etcd/client/v3 v3.5.9
10 | golang.org/x/crypto v0.9.0
11 | google.golang.org/grpc v1.57.0
12 | google.golang.org/protobuf v1.31.0
13 | gorm.io/driver/mysql v1.5.1
14 | gorm.io/gorm v1.25.2
15 | utils v0.0.0
16 | )
17 |
18 | replace utils => ../utils
19 |
20 | require (
21 | github.com/bytedance/sonic v1.9.1 // indirect
22 | github.com/cespare/xxhash/v2 v2.2.0 // indirect
23 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
24 | github.com/coreos/go-semver v0.3.0 // indirect
25 | github.com/coreos/go-systemd/v22 v22.3.2 // indirect
26 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
27 | github.com/fsnotify/fsnotify v1.6.0 // indirect
28 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect
29 | github.com/gin-contrib/sse v0.1.0 // indirect
30 | github.com/go-playground/locales v0.14.1 // indirect
31 | github.com/go-playground/universal-translator v0.18.1 // indirect
32 | github.com/go-playground/validator/v10 v10.14.0 // indirect
33 | github.com/go-redis/redis/v8 v8.11.5 // indirect
34 | github.com/go-sql-driver/mysql v1.7.0 // indirect
35 | github.com/goccy/go-json v0.10.2 // indirect
36 | github.com/gogo/protobuf v1.3.2 // indirect
37 | github.com/golang/protobuf v1.5.3 // indirect
38 | github.com/hashicorp/hcl v1.0.0 // indirect
39 | github.com/jinzhu/inflection v1.0.0 // indirect
40 | github.com/jinzhu/now v1.1.5 // indirect
41 | github.com/json-iterator/go v1.1.12 // indirect
42 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect
43 | github.com/leodido/go-urn v1.2.4 // indirect
44 | github.com/magiconair/properties v1.8.7 // indirect
45 | github.com/mattn/go-isatty v0.0.19 // indirect
46 | github.com/mitchellh/mapstructure v1.5.0 // indirect
47 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
48 | github.com/modern-go/reflect2 v1.0.2 // indirect
49 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect
50 | github.com/sirupsen/logrus v1.9.3 // indirect
51 | github.com/spf13/afero v1.9.5 // indirect
52 | github.com/spf13/cast v1.5.1 // indirect
53 | github.com/spf13/jwalterweatherman v1.1.0 // indirect
54 | github.com/spf13/pflag v1.0.5 // indirect
55 | github.com/subosito/gotenv v1.4.2 // indirect
56 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
57 | github.com/ugorji/go/codec v1.2.11 // indirect
58 | go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect
59 | go.uber.org/atomic v1.9.0 // indirect
60 | go.uber.org/multierr v1.8.0 // indirect
61 | go.uber.org/zap v1.21.0 // indirect
62 | golang.org/x/arch v0.3.0 // indirect
63 | golang.org/x/net v0.10.0 // indirect
64 | golang.org/x/sys v0.8.0 // indirect
65 | golang.org/x/text v0.9.0 // indirect
66 | google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 // indirect
67 | google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 // indirect
68 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect
69 | gopkg.in/ini.v1 v1.67.0 // indirect
70 | gopkg.in/yaml.v3 v3.0.1 // indirect
71 | )
72 |
--------------------------------------------------------------------------------
/user_service/internal/handler/user.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "strconv"
8 | "time"
9 | "user/internal/model"
10 | "user/internal/service"
11 | "user/pkg/cache"
12 | "utils/exception"
13 | )
14 |
15 | type UserService struct {
16 | service.UnimplementedUserServiceServer // 版本兼容问题
17 | }
18 |
19 | func NewUserService() *UserService {
20 | return &UserService{}
21 | }
22 |
23 | func (*UserService) UserRegister(ctx context.Context, req *service.UserRequest) (resp *service.UserResponse, err error) {
24 | resp = new(service.UserResponse)
25 | var user model.User
26 |
27 | // 检查用户是否已经存在
28 | if exist := model.GetInstance().CheckUserExist(req.Username); !exist {
29 | resp.StatusCode = exception.UserExist
30 | resp.StatusMsg = exception.GetMsg(exception.UserExist)
31 | return resp, err
32 | }
33 |
34 | user.UserName = req.Username
35 | user.PassWord = req.Password
36 |
37 | // 创建用户
38 | err = model.GetInstance().Create(&user)
39 | if err != nil {
40 | resp.StatusCode = exception.DataErr
41 | resp.StatusMsg = exception.GetMsg(exception.DataErr)
42 | return resp, err
43 | }
44 |
45 | // 查询出ID
46 | userName, err := model.GetInstance().FindUserByName(user.UserName)
47 | if err != nil {
48 | resp.StatusCode = exception.UserUnExist
49 | resp.StatusMsg = exception.GetMsg(exception.UserUnExist)
50 | return resp, err
51 | }
52 |
53 | resp.StatusCode = exception.SUCCESS
54 | resp.StatusMsg = exception.GetMsg(exception.SUCCESS)
55 | resp.UserId = userName.Id
56 |
57 | return resp, nil
58 | }
59 |
60 | func (*UserService) UserLogin(ctx context.Context, req *service.UserRequest) (resp *service.UserResponse, err error) {
61 | resp = new(service.UserResponse)
62 |
63 | // 检查用户是否存在
64 | if exist := model.GetInstance().CheckUserExist(req.Username); exist {
65 | resp.StatusCode = exception.UserUnExist
66 | resp.StatusMsg = exception.GetMsg(exception.UserUnExist)
67 | return resp, err
68 | }
69 |
70 | // 检查密码是否正确
71 | user, err := model.GetInstance().FindUserByName(req.Username)
72 | if ok := model.GetInstance().CheckPassWord(req.Password, user.PassWord); !ok {
73 | resp.StatusCode = exception.PasswordError
74 | resp.StatusMsg = exception.GetMsg(exception.PasswordError)
75 | return resp, err
76 | }
77 |
78 | resp.StatusCode = exception.SUCCESS
79 | resp.StatusMsg = exception.GetMsg(exception.SUCCESS)
80 | resp.UserId = user.Id
81 |
82 | return resp, nil
83 | }
84 |
85 | // UserInfo 用户信息
86 | func (*UserService) UserInfo(ctx context.Context, req *service.UserInfoRequest) (resp *service.UserInfoResponse, err error) {
87 | resp = new(service.UserInfoResponse)
88 |
89 | // 根据userId切片查询user信息
90 | userIds := req.UserIds
91 |
92 | for _, userId := range userIds {
93 | // 查看缓存是否存在 需要的信息
94 | var user *model.User
95 | key := fmt.Sprintf("%s:%s:%s", "user", "info", strconv.FormatInt(userId, 10))
96 |
97 | exists, err := cache.Redis.Exists(cache.Ctx, key).Result()
98 | if err != nil {
99 | return nil, fmt.Errorf("缓存错误:%v", err)
100 | }
101 |
102 | if exists > 0 {
103 | // 查询缓存
104 | userString, err := cache.Redis.Get(cache.Ctx, key).Result()
105 | if err != nil {
106 | return nil, fmt.Errorf("缓存错误:%v", err)
107 | }
108 | err = json.Unmarshal([]byte(userString), &user)
109 | if err != nil {
110 | return nil, err
111 | }
112 | } else {
113 | // 查询数据库
114 | user, err = model.GetInstance().FindUserById(userId)
115 | if err != nil {
116 | resp.StatusCode = exception.UserUnExist
117 | resp.StatusMsg = exception.GetMsg(exception.UserUnExist)
118 | return resp, err
119 | }
120 |
121 | // 将查询结果放入缓存中
122 | userJson, _ := json.Marshal(&user)
123 | err = cache.Redis.Set(cache.Ctx, key, userJson, 12*time.Hour).Err()
124 | if err != nil {
125 | return nil, fmt.Errorf("缓存错误:%v", err)
126 | }
127 |
128 | }
129 | resp.Users = append(resp.Users, BuildUser(user))
130 | }
131 |
132 | resp.StatusCode = exception.SUCCESS
133 | resp.StatusMsg = exception.GetMsg(exception.SUCCESS)
134 |
135 | return resp, nil
136 | }
137 |
138 | func BuildUser(u *model.User) *service.User {
139 | user := service.User{
140 | Id: u.Id,
141 | Name: u.UserName,
142 | Avatar: u.Avatar,
143 | BackgroundImage: u.BackgroundImage,
144 | Signature: u.Signature,
145 | }
146 | return &user
147 | }
148 |
--------------------------------------------------------------------------------
/user_service/internal/model/init.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "gorm.io/driver/mysql"
6 | "gorm.io/gorm"
7 | "gorm.io/gorm/logger"
8 | "gorm.io/gorm/schema"
9 | "time"
10 | "user/config"
11 | )
12 |
13 | // DB 创建数据库单例
14 | var DB *gorm.DB
15 |
16 | // InitDb 初始化数据库
17 | func InitDb() {
18 | config.InitConfig()
19 | dns := config.DbDnsInit()
20 | Database(dns)
21 | }
22 |
23 | // Database 初始花数据库链接
24 | func Database(connString string) {
25 | var ormLogger logger.Interface
26 |
27 | // gin中有debug 、 release 、 test 三种模式
28 | // 不指定默认以debug形式启动
29 | // 开发用debug模式、 上线用release模式
30 | // 指定方式 : gin.SetMode(gin.ReleaseMode)
31 |
32 | // 设置gorm的日志模式,可以打印原生SQL语句
33 | if gin.Mode() == "debug" {
34 | ormLogger = logger.Default.LogMode(logger.Info)
35 | } else {
36 | ormLogger = logger.Default
37 | }
38 | db, err := gorm.Open(mysql.New(mysql.Config{
39 | DSN: connString, // DSN data source name
40 | DefaultStringSize: 256, // string 类型字段的默认长度
41 | DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
42 | DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
43 | DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
44 | SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
45 | }), &gorm.Config{
46 | Logger: ormLogger,
47 | NamingStrategy: schema.NamingStrategy{
48 | SingularTable: true, // 默认不加负数
49 | },
50 | })
51 |
52 | if err != nil {
53 | panic(err)
54 | }
55 |
56 | sqlDB, _ := db.DB()
57 |
58 | // 设置连接池
59 | sqlDB.SetMaxIdleConns(20) // 空闲时候的最大连接数
60 | sqlDB.SetMaxOpenConns(100) // 打开时候的最大连接数
61 | sqlDB.SetConnMaxLifetime(time.Second * 20) // 超时时间
62 | DB = db
63 |
64 | migration()
65 | }
66 |
--------------------------------------------------------------------------------
/user_service/internal/model/init_test.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "fmt"
5 | "github.com/spf13/viper"
6 | "testing"
7 | "user/config"
8 | )
9 |
10 | func TestInitDb(t *testing.T) {
11 | config.InitConfig()
12 | dns := config.DbDnsInit()
13 | fmt.Print(dns)
14 | config.InitConfig()
15 | fmt.Printf("host is: %v \n", viper.GetString("mysql.host"))
16 | }
17 |
--------------------------------------------------------------------------------
/user_service/internal/model/migration.go:
--------------------------------------------------------------------------------
1 | // Package model 数据库自动迁移
2 | package model
3 |
4 | import "log"
5 |
6 | func migration() {
7 | // 自动迁移
8 | err := DB.Set("gorm:table_options", "charset=utf8mb4").AutoMigrate(&User{})
9 |
10 | if err != nil {
11 | log.Print("err")
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/user_service/internal/model/user.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "gorm.io/gorm"
5 | "sync"
6 | "user/pkg/encryption"
7 | "utils/snowFlake"
8 | )
9 |
10 | type User struct {
11 | Id int64 `gorm:"primary_key"`
12 | UserName string `gorm:"unique"`
13 | PassWord string `gorm:"notnull"`
14 | Avatar string `gorm:"default:http://tiny-tiktok.oss-cn-chengdu.aliyuncs.com/%E9%BB%98%E8%AE%A4%E5%A4%B4%E5%83%8F.png"` // 用户头像
15 | BackgroundImage string `gorm:"default:http://tiny-tiktok.oss-cn-chengdu.aliyuncs.com/%E9%BB%98%E8%AE%A4%E8%83%8C%E6%99%AF.png"` // 用户背景 // 用户首页顶部图
16 | Signature string `gorm:"default:该用户还没有简介"` // 个人简介
17 | }
18 |
19 | type UserModel struct {
20 | }
21 |
22 | var userModel *UserModel
23 | var userOnce sync.Once // 单例模式
24 |
25 | // GetInstance 获取单例实例
26 | func GetInstance() *UserModel {
27 | userOnce.Do(
28 | func() {
29 | userModel = &UserModel{}
30 | },
31 | )
32 | return userModel
33 | }
34 |
35 | // Create 创建用户
36 | func (*UserModel) Create(user *User) error {
37 | flake, _ := snowFlake.NewSnowFlake(7, 1)
38 | user.Id = flake.NextId()
39 | user.PassWord = encryption.HashPassword(user.PassWord)
40 | DB.Create(&user)
41 | return nil
42 | }
43 |
44 | // FindUserByName 根据用户名称查找用户,并返回对象
45 | func (*UserModel) FindUserByName(username string) (*User, error) {
46 | user := User{}
47 | res := DB.Where("user_name=?", username).First(&user)
48 | if res.Error != nil {
49 | return nil, res.Error
50 | }
51 | return &user, nil
52 | }
53 |
54 | func (*UserModel) FindUserById(userid int64) (*User, error) {
55 | user := User{}
56 | res := DB.Where("id=?", userid).First(&user)
57 | if res.Error != nil {
58 | return nil, res.Error
59 | }
60 |
61 | return &user, nil
62 | }
63 |
64 | // CheckUserExist 检查User是否存在(已经被注册过了)
65 | func (*UserModel) CheckUserExist(username string) bool {
66 | user := User{}
67 | err := DB.Where("user_name=?", username).First(&user).Error
68 | if err == gorm.ErrRecordNotFound {
69 | return true
70 | }
71 |
72 | return false
73 | }
74 |
75 | // CheckPassWord 检查密码是否正确
76 | func (*UserModel) CheckPassWord(password string, storePassword string) bool {
77 | return encryption.VerifyPasswordWithHash(password, storePassword)
78 | }
79 |
--------------------------------------------------------------------------------
/user_service/internal/model/user_test.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func TestUser_Create(t *testing.T) {
9 | InitDb()
10 | user := &User{
11 | UserName: "张三",
12 | PassWord: "123456",
13 | }
14 | GetInstance().Create(user)
15 | }
16 |
17 | func TestUserModel_FindUserByName(t *testing.T) {
18 | InitDb()
19 | user, _ := GetInstance().FindUserByName("ben")
20 | fmt.Print(user)
21 | }
22 |
23 | func TestUserModel_CheckUserExist(t *testing.T) {
24 | InitDb()
25 | exist := GetInstance().CheckUserExist("lisi")
26 | fmt.Println(exist)
27 | }
28 |
29 | func TestUserModel_FindUserById(t *testing.T) {
30 | InitDb()
31 | user, _ := GetInstance().FindUserById(4391185549234176)
32 | fmt.Println(user)
33 | }
34 |
--------------------------------------------------------------------------------
/user_service/internal/service/pb/userService.proto:
--------------------------------------------------------------------------------
1 | syntax="proto3";
2 | package pb;
3 | option go_package = "../;service"; // 此处格式为<生成的文件存放位置;生成文件的包名>
4 |
5 | // protoc -I internal/service/pb --go_out=./internal/service/ --go_opt=paths=source_relative --go-grpc_out=./internal/service/ --go-grpc_opt=paths=source_relative internal/service/pb/*.proto
6 | // 或者分开使用
7 | // protoc -I internal/service/pb/ --go_out=./ internal/service/pb/*.proto
8 | // protoc -I internal/service/pb/ --go-grpc_out=./ internal/service/pb/*.proto
9 |
10 | message UserRequest{
11 | // @gotags:json:"username" form:"username" uri:"username"
12 | string Username = 1; // 账号
13 | // @gotags:json:"password" form:"password" uri:"password"
14 | string Password = 2; // 密码
15 | }
16 |
17 | message UserResponse{
18 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
19 | int64 StatusCode = 1;
20 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
21 | string StatusMsg = 2;
22 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
23 | int64 UserId = 3; // 用户id
24 | }
25 |
26 | message User {
27 | // @gotags:json:"id" form:"id" uri:"id"
28 | int64 Id = 1;
29 | // @gotags:json:"name" form:"name" uri:"name"
30 | string Name = 2;
31 | // @gotags:json:"avatar" form:"avatar" uri:"avatar"
32 | string Avatar = 3; // 用户头像
33 | // @gotags:json:"background_image" form:"background_image" uri:"background_image"
34 | string BackgroundImage = 4; // 用户背景图
35 | // @gotags:json:"signature" form:"signature" uri:"signature"
36 | string Signature = 5; // 用户签名
37 | }
38 |
39 | message UserInfoRequest {
40 | // @gotags:json:"user_ids" form:"user_ids" uri:"user_ids"
41 | repeated int64 UserIds = 1; // 传入一个userId切片
42 | }
43 |
44 | message UserInfoResponse {
45 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
46 | int64 StatusCode = 1;
47 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
48 | string StatusMsg = 2;
49 | // @gotags:json:"users" form:"users" uri:"users"
50 | repeated User Users = 3; // 用户信息
51 | }
52 |
53 |
54 | service UserService{
55 | rpc UserRegister(UserRequest) returns(UserResponse);
56 | rpc UserLogin(UserRequest) returns(UserResponse);
57 | rpc UserInfo(UserInfoRequest) returns(UserInfoResponse);
58 | }
--------------------------------------------------------------------------------
/user_service/internal/service/userService_grpc.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
2 | // versions:
3 | // - protoc-gen-go-grpc v1.3.0
4 | // - protoc v3.15.5
5 | // source: userService.proto
6 |
7 | package service
8 |
9 | import (
10 | context "context"
11 | grpc "google.golang.org/grpc"
12 | codes "google.golang.org/grpc/codes"
13 | status "google.golang.org/grpc/status"
14 | )
15 |
16 | // This is a compile-time assertion to ensure that this generated file
17 | // is compatible with the grpc package it is being compiled against.
18 | // Requires gRPC-Go v1.32.0 or later.
19 | const _ = grpc.SupportPackageIsVersion7
20 |
21 | const (
22 | UserService_UserRegister_FullMethodName = "/pb.UserService/UserRegister"
23 | UserService_UserLogin_FullMethodName = "/pb.UserService/UserLogin"
24 | UserService_UserInfo_FullMethodName = "/pb.UserService/UserInfo"
25 | )
26 |
27 | // UserServiceClient is the client API for UserService service.
28 | //
29 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
30 | type UserServiceClient interface {
31 | UserRegister(ctx context.Context, in *UserRequest, opts ...grpc.CallOption) (*UserResponse, error)
32 | UserLogin(ctx context.Context, in *UserRequest, opts ...grpc.CallOption) (*UserResponse, error)
33 | UserInfo(ctx context.Context, in *UserInfoRequest, opts ...grpc.CallOption) (*UserInfoResponse, error)
34 | }
35 |
36 | type userServiceClient struct {
37 | cc grpc.ClientConnInterface
38 | }
39 |
40 | func NewUserServiceClient(cc grpc.ClientConnInterface) UserServiceClient {
41 | return &userServiceClient{cc}
42 | }
43 |
44 | func (c *userServiceClient) UserRegister(ctx context.Context, in *UserRequest, opts ...grpc.CallOption) (*UserResponse, error) {
45 | out := new(UserResponse)
46 | err := c.cc.Invoke(ctx, UserService_UserRegister_FullMethodName, in, out, opts...)
47 | if err != nil {
48 | return nil, err
49 | }
50 | return out, nil
51 | }
52 |
53 | func (c *userServiceClient) UserLogin(ctx context.Context, in *UserRequest, opts ...grpc.CallOption) (*UserResponse, error) {
54 | out := new(UserResponse)
55 | err := c.cc.Invoke(ctx, UserService_UserLogin_FullMethodName, in, out, opts...)
56 | if err != nil {
57 | return nil, err
58 | }
59 | return out, nil
60 | }
61 |
62 | func (c *userServiceClient) UserInfo(ctx context.Context, in *UserInfoRequest, opts ...grpc.CallOption) (*UserInfoResponse, error) {
63 | out := new(UserInfoResponse)
64 | err := c.cc.Invoke(ctx, UserService_UserInfo_FullMethodName, in, out, opts...)
65 | if err != nil {
66 | return nil, err
67 | }
68 | return out, nil
69 | }
70 |
71 | // UserServiceServer is the server API for UserService service.
72 | // All implementations must embed UnimplementedUserServiceServer
73 | // for forward compatibility
74 | type UserServiceServer interface {
75 | UserRegister(context.Context, *UserRequest) (*UserResponse, error)
76 | UserLogin(context.Context, *UserRequest) (*UserResponse, error)
77 | UserInfo(context.Context, *UserInfoRequest) (*UserInfoResponse, error)
78 | mustEmbedUnimplementedUserServiceServer()
79 | }
80 |
81 | // UnimplementedUserServiceServer must be embedded to have forward compatible implementations.
82 | type UnimplementedUserServiceServer struct {
83 | }
84 |
85 | func (UnimplementedUserServiceServer) UserRegister(context.Context, *UserRequest) (*UserResponse, error) {
86 | return nil, status.Errorf(codes.Unimplemented, "method UserRegister not implemented")
87 | }
88 | func (UnimplementedUserServiceServer) UserLogin(context.Context, *UserRequest) (*UserResponse, error) {
89 | return nil, status.Errorf(codes.Unimplemented, "method UserLogin not implemented")
90 | }
91 | func (UnimplementedUserServiceServer) UserInfo(context.Context, *UserInfoRequest) (*UserInfoResponse, error) {
92 | return nil, status.Errorf(codes.Unimplemented, "method UserInfo not implemented")
93 | }
94 | func (UnimplementedUserServiceServer) mustEmbedUnimplementedUserServiceServer() {}
95 |
96 | // UnsafeUserServiceServer may be embedded to opt out of forward compatibility for this service.
97 | // Use of this interface is not recommended, as added methods to UserServiceServer will
98 | // result in compilation errors.
99 | type UnsafeUserServiceServer interface {
100 | mustEmbedUnimplementedUserServiceServer()
101 | }
102 |
103 | func RegisterUserServiceServer(s grpc.ServiceRegistrar, srv UserServiceServer) {
104 | s.RegisterService(&UserService_ServiceDesc, srv)
105 | }
106 |
107 | func _UserService_UserRegister_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
108 | in := new(UserRequest)
109 | if err := dec(in); err != nil {
110 | return nil, err
111 | }
112 | if interceptor == nil {
113 | return srv.(UserServiceServer).UserRegister(ctx, in)
114 | }
115 | info := &grpc.UnaryServerInfo{
116 | Server: srv,
117 | FullMethod: UserService_UserRegister_FullMethodName,
118 | }
119 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
120 | return srv.(UserServiceServer).UserRegister(ctx, req.(*UserRequest))
121 | }
122 | return interceptor(ctx, in, info, handler)
123 | }
124 |
125 | func _UserService_UserLogin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
126 | in := new(UserRequest)
127 | if err := dec(in); err != nil {
128 | return nil, err
129 | }
130 | if interceptor == nil {
131 | return srv.(UserServiceServer).UserLogin(ctx, in)
132 | }
133 | info := &grpc.UnaryServerInfo{
134 | Server: srv,
135 | FullMethod: UserService_UserLogin_FullMethodName,
136 | }
137 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
138 | return srv.(UserServiceServer).UserLogin(ctx, req.(*UserRequest))
139 | }
140 | return interceptor(ctx, in, info, handler)
141 | }
142 |
143 | func _UserService_UserInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
144 | in := new(UserInfoRequest)
145 | if err := dec(in); err != nil {
146 | return nil, err
147 | }
148 | if interceptor == nil {
149 | return srv.(UserServiceServer).UserInfo(ctx, in)
150 | }
151 | info := &grpc.UnaryServerInfo{
152 | Server: srv,
153 | FullMethod: UserService_UserInfo_FullMethodName,
154 | }
155 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
156 | return srv.(UserServiceServer).UserInfo(ctx, req.(*UserInfoRequest))
157 | }
158 | return interceptor(ctx, in, info, handler)
159 | }
160 |
161 | // UserService_ServiceDesc is the grpc.ServiceDesc for UserService service.
162 | // It's only intended for direct use with grpc.RegisterService,
163 | // and not to be introspected or modified (even as a copy)
164 | var UserService_ServiceDesc = grpc.ServiceDesc{
165 | ServiceName: "pb.UserService",
166 | HandlerType: (*UserServiceServer)(nil),
167 | Methods: []grpc.MethodDesc{
168 | {
169 | MethodName: "UserRegister",
170 | Handler: _UserService_UserRegister_Handler,
171 | },
172 | {
173 | MethodName: "UserLogin",
174 | Handler: _UserService_UserLogin_Handler,
175 | },
176 | {
177 | MethodName: "UserInfo",
178 | Handler: _UserService_UserInfo_Handler,
179 | },
180 | },
181 | Streams: []grpc.StreamDesc{},
182 | Metadata: "userService.proto",
183 | }
184 |
--------------------------------------------------------------------------------
/user_service/pkg/cache/redis.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "context"
5 | "github.com/go-redis/redis/v8"
6 | "github.com/spf13/viper"
7 | )
8 |
9 | var Redis *redis.Client
10 |
11 | var Ctx = context.Background()
12 |
13 | // InitRedis 连接redis
14 | func InitRedis() {
15 | addr := viper.GetString("redis.address")
16 | Redis = redis.NewClient(&redis.Options{
17 | Addr: addr,
18 | Password: "",
19 | DB: 0, // 存入DB0
20 | })
21 | }
22 |
--------------------------------------------------------------------------------
/user_service/pkg/cache/redis_test.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "context"
5 | "testing"
6 | )
7 |
8 | func TestInitRedis(t *testing.T) {
9 | InitRedis()
10 | Redis.Set(context.Background(), "K2", "myValue", 0)
11 | }
12 |
--------------------------------------------------------------------------------
/user_service/pkg/encryption/encrypt.go:
--------------------------------------------------------------------------------
1 | // 密码加密
2 |
3 | package encryption
4 |
5 | import "golang.org/x/crypto/bcrypt"
6 |
7 | const PassWordCost = 12 // 加密难度,相当与hash被迭代2^12次。默认是10 ,推荐使用12
8 |
9 | func HashPassword(password string) string {
10 | hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), PassWordCost)
11 | if err != nil {
12 | return ""
13 | }
14 | return string(hashedPassword)
15 | }
16 |
17 | // VerifyPasswordWithHash 方法验证传入的密码与数据库中的密码哈希是否匹配
18 | func VerifyPasswordWithHash(inputPassword string, storedHash string) bool {
19 | // 将数据库中的密码哈希字符串解析为哈希字节
20 | storedHashBytes := []byte(storedHash)
21 |
22 | // 使用 bcrypt 提供的 CompareHashAndPassword 方法比较密码
23 | err := bcrypt.CompareHashAndPassword(storedHashBytes, []byte(inputPassword))
24 | return err == nil
25 | }
26 |
--------------------------------------------------------------------------------
/utils/etcd/discovery.go:
--------------------------------------------------------------------------------
1 | package etcd
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "go.etcd.io/etcd/api/v3/mvccpb"
7 | clientv3 "go.etcd.io/etcd/client/v3"
8 | "log"
9 | "sync"
10 | "time"
11 | )
12 |
13 | // EtcdDiscovery 服务发现
14 | type EtcdDiscovery struct {
15 | cli *clientv3.Client // etcd连接
16 | serviceMap map[string]string // 服务列表(k-v列表)
17 | lock sync.RWMutex // 读写互斥锁
18 | }
19 |
20 | func NewServiceDiscovery(endpoints []string) (*EtcdDiscovery, error) {
21 | // 创建etcdClient对象
22 | cli, err := clientv3.New(clientv3.Config{
23 | Endpoints: endpoints,
24 | DialTimeout: 5 * time.Second,
25 | })
26 |
27 | if err != nil {
28 | return nil, err
29 | }
30 |
31 | return &EtcdDiscovery{
32 | cli: cli,
33 | serviceMap: make(map[string]string), // 初始化kvMap
34 | }, nil
35 | }
36 |
37 | // ServiceDiscovery 读取etcd的服务并开启协程监听kv变化
38 | func (e *EtcdDiscovery) ServiceDiscovery(prefix string) error {
39 | // 根据服务名称的前缀,获取所有的注册服务
40 | resp, err := e.cli.Get(context.Background(), prefix, clientv3.WithPrefix())
41 | if err != nil {
42 | return err
43 | }
44 |
45 | // 遍历key-value存储到本地map
46 | for _, kv := range resp.Kvs {
47 | e.putService(string(kv.Key), string(kv.Value))
48 | }
49 |
50 | // 开启监听协程,监听prefix的变化
51 | go func() {
52 | watchRespChan := e.cli.Watch(context.Background(), prefix, clientv3.WithPrefix())
53 | log.Printf("watching prefix:%s now...", prefix)
54 | for watchResp := range watchRespChan {
55 | for _, event := range watchResp.Events {
56 | switch event.Type {
57 | case mvccpb.PUT: // 发生了修改或者新增
58 | e.putService(string(event.Kv.Key), string(event.Kv.Value)) // ServiceMap中进行相应的修改或新增
59 | case mvccpb.DELETE: //发生了删除
60 | e.delService(string(event.Kv.Key)) // ServiceMap中进行相应的删除
61 | }
62 | }
63 | }
64 | }()
65 |
66 | return nil
67 | }
68 |
69 | // SetService 新增或修改本地服务
70 | func (s *EtcdDiscovery) putService(key, val string) {
71 | s.lock.Lock()
72 | s.serviceMap[key] = val
73 | s.lock.Unlock()
74 | log.Println("put key :", key, "val:", val)
75 | }
76 |
77 | // DelService 删除本地服务
78 | func (s *EtcdDiscovery) delService(key string) {
79 | s.lock.Lock()
80 | delete(s.serviceMap, key)
81 | s.lock.Unlock()
82 | log.Println("del key:", key)
83 | }
84 |
85 | // GetService 获取本地服务
86 | func (s *EtcdDiscovery) GetService(serviceName string) (string, error) {
87 | s.lock.RLock()
88 | serviceAddr, ok := s.serviceMap[serviceName]
89 | s.lock.RUnlock()
90 | if !ok {
91 | return "", fmt.Errorf("can not get serviceAddr")
92 | }
93 | return serviceAddr, nil
94 | }
95 |
96 | // Close 关闭服务
97 | func (e *EtcdDiscovery) Close() error {
98 | return e.cli.Close()
99 | }
100 |
--------------------------------------------------------------------------------
/utils/etcd/register.go:
--------------------------------------------------------------------------------
1 | package etcd
2 |
3 | import (
4 | "context"
5 | clientv3 "go.etcd.io/etcd/client/v3"
6 | "log"
7 | "time"
8 | )
9 |
10 | // EtcdRegister 服务注册
11 | type EtcdRegister struct {
12 | etcdCli *clientv3.Client // etcdClient对象
13 | leaseId clientv3.LeaseID // 租约id
14 | }
15 |
16 | // CreateLease 创建租约。expire表示有效期(s)
17 | func (e *EtcdRegister) CreateLease(expire int64) error {
18 |
19 | lease, err := e.etcdCli.Grant(context.Background(), expire)
20 | if err != nil {
21 | log.Println(err.Error())
22 | return err
23 | }
24 |
25 | e.leaseId = lease.ID // 记录生成的租约Id
26 | return nil
27 | }
28 |
29 | // BindLease 绑定租约。将租约与对应的key-value绑定
30 | func (e *EtcdRegister) BindLease(key string, value string) error {
31 |
32 | res, err := e.etcdCli.Put(context.Background(), key, value, clientv3.WithLease(e.leaseId))
33 | if err != nil {
34 | log.Println(err.Error())
35 | return err
36 | }
37 |
38 | log.Printf("bind lease success %v \n", res)
39 | return nil
40 | }
41 |
42 | // KeepAlive 获取续约通道 并 持续续租
43 | func (e *EtcdRegister) KeepAlive() error {
44 | keepAliveChan, err := e.etcdCli.KeepAlive(context.Background(), e.leaseId)
45 |
46 | if err != nil {
47 | log.Println(err.Error())
48 | return err
49 | }
50 |
51 | go func(keepAliveChan <-chan *clientv3.LeaseKeepAliveResponse) {
52 | for {
53 | select {
54 | case resp := <-keepAliveChan:
55 | log.Printf("续约成功...leaseID=%d", resp.ID)
56 | }
57 | }
58 | }(keepAliveChan)
59 |
60 | return nil
61 | }
62 |
63 | // Close 关闭服务
64 | func (e *EtcdRegister) Close() error {
65 | log.Printf("close...\n")
66 | // 撤销租约
67 | e.etcdCli.Revoke(context.Background(), e.leaseId)
68 | return e.etcdCli.Close()
69 | }
70 |
71 | // NewEtcdRegister 初始化etcd服务注册对象
72 | func NewEtcdRegister(etcdServerAddr string) (*EtcdRegister, error) {
73 |
74 | client, err := clientv3.New(clientv3.Config{
75 | Endpoints: []string{etcdServerAddr},
76 | DialTimeout: 3 * time.Second,
77 | })
78 |
79 | if err != nil {
80 | log.Println(err.Error())
81 | return nil, err
82 | }
83 |
84 | e := &EtcdRegister{
85 | etcdCli: client,
86 | }
87 | return e, nil
88 | }
89 |
90 | // ServiceRegister 服务注册。expire表示过期时间,serviceName和serviceAddr分别是服务名与服务地址
91 | func (e *EtcdRegister) ServiceRegister(serviceName, serviceAddr string, expire int64) (err error) {
92 |
93 | // 创建租约
94 | err = e.CreateLease(expire)
95 | if err != nil {
96 | return err
97 | }
98 |
99 | // 将租约与k-v绑定
100 | err = e.BindLease(serviceName, serviceAddr)
101 | if err != nil {
102 | return err
103 | }
104 |
105 | // 持续续租
106 | err = e.KeepAlive()
107 | return err
108 | }
109 |
--------------------------------------------------------------------------------
/utils/exception/statusCode.go:
--------------------------------------------------------------------------------
1 | package exception
2 |
3 | //* 错误码:
4 | //* 四位组成
5 | //* 1. 1开头代表用户端错误
6 | //* 2. 2开头代表当前系统异常
7 | //* 3. 3开头代表第三方服务异常
8 | //* 4. 4开头若无法确定具体错误,选择宏观错误
9 | //* 5. 大的错误类间的步长间距预留100
10 |
11 | const (
12 | SUCCESS = 0 // 常规的返回成功
13 | ERROR = -1 // 常规返回失败
14 |
15 | RequestERROR = 1000 // token相关
16 | UnAuth = 1001
17 | TokenTimeOut = 1002
18 |
19 | ErrOperate = 1200 // 异常操作
20 |
21 | DataErr = 2000 // 数据创建
22 | CacheErr = 2001 // 缓存异常
23 |
24 | UserExist = 2100 // 用户相关
25 | UserUnExist = 2101
26 | PasswordError = 2102
27 |
28 | VideoUnExist = 2200 // 视频相关
29 | VideoUploadErr = 2201
30 | VideoFavoriteErr = 2203
31 | UserNoVideo = 2204
32 |
33 | FavoriteErr = 2300 // 点赞相关
34 | CancelFavoriteErr = 2301
35 | VideoNoFavorite = 2302
36 | UserNoFavorite = 2303
37 |
38 | CommentErr = 2400 // 评论失败
39 | CommentUnExist = 2401 // 评论相关
40 | CommentDeleteErr = 2402
41 |
42 | FollowSelfErr = 2500 // 关注相关
43 | )
44 |
--------------------------------------------------------------------------------
/utils/exception/statusMsg.go:
--------------------------------------------------------------------------------
1 | package exception
2 |
3 | var Msg = map[int]string{
4 |
5 | SUCCESS: "OK",
6 | ERROR: "fail",
7 |
8 | RequestERROR: "请求有误,用户未登录",
9 | UnAuth: "Token未经授权",
10 | TokenTimeOut: "Token授权已过期",
11 |
12 | ErrOperate: "错误操作",
13 |
14 | DataErr: "数据创建错误",
15 | CacheErr: "Redis异常",
16 |
17 | UserExist: "用户已经存在",
18 | UserUnExist: "用户不存在,请注册",
19 | PasswordError: "密码错误",
20 |
21 | VideoUnExist: "视频信息不存在",
22 | VideoUploadErr: "视频上传失败",
23 | VideoFavoriteErr: "视频点赞失败",
24 | UserNoVideo: "用户没有发布作品",
25 |
26 | FavoriteErr: "点赞失败",
27 | CancelFavoriteErr: "取消点赞失败",
28 | VideoNoFavorite: "视频没有点赞",
29 | UserNoFavorite: "用户没有点赞",
30 |
31 | CommentErr: "评论失败",
32 | CommentUnExist: "评论不存在",
33 | CommentDeleteErr: "评论删除失败",
34 |
35 | FollowSelfErr: "关注自己",
36 | }
37 |
38 | // GetMsg 根据状态码获取对应信息
39 | func GetMsg(code int) string {
40 | msg, ok := Msg[code]
41 | if ok {
42 | return msg
43 | }
44 |
45 | return Msg[ERROR]
46 | }
47 |
--------------------------------------------------------------------------------
/utils/go.mod:
--------------------------------------------------------------------------------
1 | module utils
2 |
3 | go 1.19
4 |
5 | require (
6 | go.etcd.io/etcd/api/v3 v3.5.9
7 | go.etcd.io/etcd/client/v3 v3.5.9
8 | )
9 |
10 | require (
11 | github.com/coreos/go-semver v0.3.0 // indirect
12 | github.com/coreos/go-systemd/v22 v22.3.2 // indirect
13 | github.com/gogo/protobuf v1.3.2 // indirect
14 | github.com/golang/protobuf v1.5.2 // indirect
15 | go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect
16 | go.uber.org/atomic v1.7.0 // indirect
17 | go.uber.org/multierr v1.6.0 // indirect
18 | go.uber.org/zap v1.17.0 // indirect
19 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
20 | golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 // indirect
21 | golang.org/x/text v0.3.5 // indirect
22 | google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect
23 | google.golang.org/grpc v1.41.0 // indirect
24 | google.golang.org/protobuf v1.26.0 // indirect
25 | )
26 |
--------------------------------------------------------------------------------
/utils/snowFlake/snow.go:
--------------------------------------------------------------------------------
1 | package snowFlake
2 |
3 | import (
4 | "errors"
5 | "sync"
6 | "time"
7 | )
8 |
9 | const (
10 | // 当前时间 2023-08-07 10:47:09 毫秒级别
11 | epoch int64 = 1691376429015
12 | // 序列号位数
13 | numberBit int8 = 12
14 | // 服务ID位数
15 | serveIdBit int8 = 5
16 | // 机器ID位数
17 | machineIdBit int8 = 5
18 | // 服务ID的偏移量
19 | serveIdShift int8 = numberBit
20 | // 机器ID的偏移量
21 | machineIdShift int8 = numberBit + serveIdBit
22 | // 时间戳的偏移量
23 | timestampShift int8 = numberBit + serveIdBit + machineIdBit
24 | // 服务ID的最大值 31
25 | serverIdMax int64 = -1 ^ (-1 << serveIdBit)
26 | // 机器ID的最大值 31
27 | machineIdMax int64 = -1 ^ (-1 << machineIdBit)
28 | // 序列号的最大值 4095
29 | numberMax int64 = -1 ^ (-1 << numberBit)
30 | )
31 |
32 | type SnowFlake struct {
33 | // 每次生产一个id都是一个原子操作
34 | lock sync.Mutex
35 | // 时间戳、机器ID、服务ID、序列号
36 | timestamp int64
37 | machineId int64
38 | serveId int64
39 | number int64
40 | }
41 |
42 | // NewSnowFlake 构造函数,传入机器ID和服务ID
43 | func NewSnowFlake(machineId int64, serveId int64) (*SnowFlake, error) {
44 | if machineId < 0 || machineId > machineIdMax {
45 | return nil, errors.New("mechineId超出限制")
46 | }
47 | if serveId < 0 || serveId > serverIdMax {
48 | return nil, errors.New("serveId超出限制")
49 | }
50 | return &SnowFlake{
51 | timestamp: 0,
52 | machineId: machineId,
53 | serveId: serveId,
54 | number: 0,
55 | }, nil
56 | }
57 |
58 | func (snow *SnowFlake) NextId() int64 {
59 | // 原子操作
60 | snow.lock.Lock()
61 | defer snow.lock.Unlock()
62 |
63 | // 获取当前时间戳
64 | now := time.Now().UnixMilli()
65 |
66 | // 如果时间戳还是当前时间错,则序列号增加
67 | if now == snow.timestamp {
68 | snow.number++
69 | // 如果超过了序列号的最大值,则更新时间戳
70 | if snow.number > numberMax {
71 | for now <= snow.timestamp {
72 | now = time.Now().UnixMilli()
73 | }
74 | }
75 | } else {
76 | snow.number = 0
77 | snow.timestamp = now
78 | }
79 |
80 | // 拼接最后的结果,将不同不服的数值移到指定位置
81 | id := (snow.timestamp-epoch)< ../utils
20 |
21 | require (
22 | github.com/bytedance/sonic v1.9.1 // indirect
23 | github.com/cespare/xxhash/v2 v2.2.0 // indirect
24 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
25 | github.com/coreos/go-semver v0.3.0 // indirect
26 | github.com/coreos/go-systemd/v22 v22.3.2 // indirect
27 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
28 | github.com/fsnotify/fsnotify v1.6.0 // indirect
29 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect
30 | github.com/gin-contrib/sse v0.1.0 // indirect
31 | github.com/go-playground/locales v0.14.1 // indirect
32 | github.com/go-playground/universal-translator v0.18.1 // indirect
33 | github.com/go-playground/validator/v10 v10.14.0 // indirect
34 | github.com/go-sql-driver/mysql v1.7.0 // indirect
35 | github.com/goccy/go-json v0.10.2 // indirect
36 | github.com/gogo/protobuf v1.3.2 // indirect
37 | github.com/golang/protobuf v1.5.3 // indirect
38 | github.com/hashicorp/hcl v1.0.0 // indirect
39 | github.com/jinzhu/inflection v1.0.0 // indirect
40 | github.com/jinzhu/now v1.1.5 // indirect
41 | github.com/json-iterator/go v1.1.12 // indirect
42 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect
43 | github.com/leodido/go-urn v1.2.4 // indirect
44 | github.com/magiconair/properties v1.8.7 // indirect
45 | github.com/mattn/go-isatty v0.0.19 // indirect
46 | github.com/mitchellh/mapstructure v1.5.0 // indirect
47 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
48 | github.com/modern-go/reflect2 v1.0.2 // indirect
49 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect
50 | github.com/spf13/afero v1.9.5 // indirect
51 | github.com/spf13/cast v1.5.1 // indirect
52 | github.com/spf13/jwalterweatherman v1.1.0 // indirect
53 | github.com/spf13/pflag v1.0.5 // indirect
54 | github.com/subosito/gotenv v1.4.2 // indirect
55 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
56 | github.com/ugorji/go/codec v1.2.11 // indirect
57 | go.etcd.io/etcd/api/v3 v3.5.9 // indirect
58 | go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect
59 | go.etcd.io/etcd/client/v3 v3.5.9 // indirect
60 | go.uber.org/atomic v1.9.0 // indirect
61 | go.uber.org/multierr v1.8.0 // indirect
62 | go.uber.org/zap v1.21.0 // indirect
63 | golang.org/x/arch v0.3.0 // indirect
64 | golang.org/x/crypto v0.9.0 // indirect
65 | golang.org/x/net v0.10.0 // indirect
66 | golang.org/x/sys v0.8.0 // indirect
67 | golang.org/x/text v0.9.0 // indirect
68 | golang.org/x/time v0.1.0 // indirect
69 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
70 | gopkg.in/ini.v1 v1.67.0 // indirect
71 | gopkg.in/yaml.v3 v3.0.1 // indirect
72 | )
73 |
--------------------------------------------------------------------------------
/video_service/internal/handler/comment.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/go-redis/redis/v8"
8 | "log"
9 | "strconv"
10 | "time"
11 | "utils/exception"
12 | "video/internal/model"
13 | "video/internal/service"
14 | "video/pkg/cache"
15 | )
16 |
17 | // CommentAction 评论操作
18 | func (*VideoService) CommentAction(ctx context.Context, req *service.CommentActionRequest) (resp *service.CommentActionResponse, err error) {
19 | resp = new(service.CommentActionResponse)
20 | key := fmt.Sprintf("%s:%s:%s", "video", "comment_list", strconv.FormatInt(req.VideoId, 10))
21 | comment := model.Comment{
22 | UserId: req.UserId,
23 | VideoId: req.VideoId,
24 | Content: req.CommentText,
25 | }
26 | action := req.ActionType
27 |
28 | time := time.Now()
29 |
30 | // 发布评论
31 | if action == 1 {
32 | comment.CreatAt = time
33 |
34 | // 事务中执行创建操作
35 | tx := model.DB.Begin()
36 | id, err := model.GetCommentInstance().CreateComment(tx, &comment)
37 | if err != nil {
38 | resp.StatusCode = exception.CommentErr
39 | resp.StatusMsg = exception.GetMsg(exception.CommentErr)
40 | resp.Comment = nil
41 | return resp, err
42 | }
43 | comment.Id = id
44 | comment.CommentStatus = true
45 | commentJson, _ := json.Marshal(comment)
46 |
47 | // 存入缓存中
48 | member := redis.Z{
49 | Score: float64(time.Unix()),
50 | Member: commentJson,
51 | }
52 |
53 | err = cache.Redis.ZAdd(cache.Ctx, key, &member).Err()
54 | if err != nil {
55 | tx.Rollback()
56 | return nil, fmt.Errorf("缓存错误:%v", err)
57 | }
58 |
59 | tx.Commit()
60 |
61 | commentResp := &service.Comment{
62 | Id: id,
63 | Content: req.CommentText,
64 | // 将Time.time转换成字符串形式,格式为mm-dd
65 | CreateDate: time.Format("01-02"),
66 | }
67 |
68 | // 将评论返回
69 | resp.StatusCode = exception.SUCCESS
70 | resp.StatusMsg = exception.GetMsg(exception.SUCCESS)
71 | resp.Comment = commentResp
72 |
73 | return resp, nil
74 | }
75 |
76 | // 删除评论
77 | tx := model.DB.Begin()
78 | commentInstance, err := model.GetCommentInstance().GetComment(tx, req.CommentId)
79 | commentMarshal, _ := json.Marshal(commentInstance)
80 |
81 | err = model.GetCommentInstance().DeleteComment(req.CommentId)
82 | if err != nil {
83 | resp.StatusCode = exception.CommentDeleteErr
84 | resp.StatusMsg = exception.GetMsg(exception.CommentDeleteErr)
85 | resp.Comment = nil
86 |
87 | return resp, err
88 | }
89 | log.Print(commentMarshal)
90 |
91 | // 删除缓存
92 | count, err := cache.Redis.ZRem(cache.Ctx, key, string(commentMarshal)).Result()
93 | log.Printf("删除了: %v 条记录", count)
94 | if err != nil {
95 | tx.Rollback()
96 | return nil, fmt.Errorf("缓存错误:%v", err)
97 | }
98 |
99 | tx.Commit()
100 |
101 | resp.StatusCode = exception.SUCCESS
102 | resp.StatusMsg = exception.GetMsg(exception.SUCCESS)
103 | resp.Comment = nil
104 |
105 | return resp, nil
106 | }
107 |
108 | // CommentList 评论列表
109 | func (*VideoService) CommentList(ctx context.Context, req *service.CommentListRequest) (resp *service.CommentListResponse, err error) {
110 | resp = new(service.CommentListResponse)
111 | var comments []model.Comment
112 | key := fmt.Sprintf("%s:%s:%s", "video", "comment_list", strconv.FormatInt(req.VideoId, 10))
113 |
114 | exist, err := cache.Redis.Exists(cache.Ctx, key).Result()
115 | if err != nil {
116 | log.Print(4)
117 | return nil, fmt.Errorf("缓存错误:%v", err)
118 | }
119 |
120 | if exist == 0 {
121 | err := buildCommentCache(req.VideoId)
122 | if err != nil {
123 | log.Print(3)
124 | return nil, fmt.Errorf("缓存错误:%v", err)
125 | }
126 | }
127 |
128 | // 查询缓存
129 | commentsString, err := cache.Redis.ZRevRange(cache.Ctx, key, 0, -1).Result()
130 | if err != nil {
131 | log.Print(5)
132 | return nil, fmt.Errorf("缓存错误:%v", err)
133 | }
134 |
135 | for _, commentString := range commentsString {
136 | var comment model.Comment
137 | err := json.Unmarshal([]byte(commentString), &comment)
138 | if err != nil {
139 | return nil, err
140 | }
141 | comments = append(comments, comment)
142 | }
143 |
144 | resp.StatusCode = exception.SUCCESS
145 | resp.StatusMsg = exception.GetMsg(exception.SUCCESS)
146 | resp.CommentList = BuildComments(comments)
147 |
148 | return resp, nil
149 | }
150 |
151 | func BuildComments(comments []model.Comment) []*service.Comment {
152 | var commentresp []*service.Comment
153 |
154 | for _, comment := range comments {
155 | commentresp = append(commentresp, &service.Comment{
156 | Id: comment.Id,
157 | UserId: comment.UserId,
158 | Content: comment.Content,
159 | CreateDate: comment.CreatAt.Format("01-02"),
160 | })
161 | }
162 |
163 | return commentresp
164 | }
165 |
166 | // 构建评论列表缓存
167 | func buildCommentCache(videoId int64) error {
168 | key := fmt.Sprintf("%s:%s:%s", "video", "comment_list", strconv.FormatInt(videoId, 10))
169 |
170 | comments, err := model.GetCommentInstance().CommentList(videoId)
171 | if err != nil {
172 | return err
173 | }
174 |
175 | var zMembers []*redis.Z
176 | for _, comment := range comments {
177 | commentJSON, err := json.Marshal(comment)
178 | if err != nil {
179 | fmt.Println("Error encoding comment:", err)
180 | continue
181 | }
182 | zMembers = append(zMembers, &redis.Z{
183 | Score: float64(comment.CreatAt.Unix()),
184 | Member: commentJSON,
185 | })
186 | }
187 |
188 | err = cache.Redis.ZAdd(cache.Ctx, key, zMembers...).Err()
189 | if err != nil {
190 | return err
191 | }
192 |
193 | return nil
194 | }
195 |
196 | // 通过缓存查看视频得评论数量
197 | func getCommentCount(videoId int64) int64 {
198 | key := fmt.Sprintf("%s:%s:%s", "video", "comment_list", strconv.FormatInt(videoId, 10))
199 |
200 | exists, err := cache.Redis.Exists(cache.Ctx, key).Result()
201 | if err != nil {
202 | log.Print(err)
203 | }
204 |
205 | if exists == 0 {
206 | err := buildCommentCache(videoId)
207 | if err != nil {
208 | log.Print(err)
209 | }
210 | }
211 |
212 | count, err := cache.Redis.ZCard(cache.Ctx, key).Result()
213 | if err != nil {
214 | log.Print(err)
215 | }
216 |
217 | return count
218 | }
219 |
--------------------------------------------------------------------------------
/video_service/internal/handler/video_test.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/go-redis/redis/v8"
7 | "log"
8 | "testing"
9 | "time"
10 | "video/config"
11 | "video/internal/model"
12 | "video/pkg/cache"
13 | )
14 |
15 | func TestVideoService_Feed(t *testing.T) {
16 | timePoint := time.Unix(1693130905305/1000, 0)
17 | fmt.Print(timePoint)
18 | }
19 |
20 | func TestCache(t *testing.T) {
21 | model.InitDb()
22 | cache.InitRedis()
23 | key := "video:comment_list:4395719587667968"
24 | tx := model.DB.Begin()
25 | comment, _ := model.GetCommentInstance().GetComment(tx, 5146154783088640)
26 | commentJson, _ := json.Marshal(comment)
27 |
28 | removedCount, err := cache.Redis.ZRem(cache.Ctx, key, string(commentJson)).Result()
29 | if err != nil {
30 | fmt.Println("Redis操作出错:", err)
31 | } else {
32 | fmt.Printf("删除了 %d 个匹配的成员\n", removedCount)
33 | }
34 | }
35 |
36 | func TestFavoriteCache(t *testing.T) {
37 | model.InitDb()
38 | cache.InitRedis()
39 |
40 | key := "video:favorite_video:4410007823982592"
41 |
42 | _, err := cache.Redis.Del(cache.Ctx, key).Result()
43 | if err != nil {
44 | fmt.Println("Error deleting existing Set:", err)
45 | return
46 | }
47 |
48 | _, err = cache.Redis.SAdd(cache.Ctx, key, "tempMember").Result()
49 | if err != nil {
50 | fmt.Println("Error adding member to Set:", err)
51 | return
52 | }
53 |
54 | _, err = cache.Redis.Expire(cache.Ctx, key, 12*time.Hour).Result()
55 | if err != nil {
56 | fmt.Println("Error setting expiration time:", err)
57 | return
58 | }
59 |
60 | // 检查 Set 是否存在
61 | exists, err := cache.Redis.Exists(cache.Ctx, key).Result()
62 | if err != nil {
63 | fmt.Println("Error checking Set existence:", err)
64 | return
65 | }
66 |
67 | if exists == 1 {
68 | fmt.Println("Set exists")
69 | } else {
70 | fmt.Println("Set does not exist")
71 | }
72 |
73 | // 删除临时成员并检查是否删除成功
74 | removedCount, err := cache.Redis.SRem(cache.Ctx, key, "tempMember").Result()
75 | if err != nil {
76 | fmt.Println("Error removing temporary member:", err)
77 | return
78 | }
79 |
80 | if removedCount == 1 {
81 | fmt.Println("Successfully removed temporary member")
82 | } else {
83 | fmt.Println("Temporary member not found in Set")
84 | return
85 | }
86 |
87 | // 检查 Set 是否存在
88 | exists, err = cache.Redis.Exists(cache.Ctx, key).Result()
89 | if err != nil {
90 | fmt.Println("Error checking Set existence:", err)
91 | return
92 | }
93 |
94 | if exists == 1 {
95 | fmt.Println("Set exists")
96 | } else {
97 | fmt.Println("Set does not exist")
98 | }
99 | }
100 |
101 | func TestSet(t *testing.T) {
102 | model.InitDb()
103 | cache.InitRedis()
104 |
105 | key := "video:favorite_video:4410007823982592"
106 |
107 | // 先添加临时成员
108 | _, err := cache.Redis.SAdd(cache.Ctx, key, "tempMember").Result()
109 | if err != nil {
110 | fmt.Println("Error adding member to Set:", err)
111 | return
112 | }
113 |
114 | // 检查 Set 是否存在
115 | exists, err := cache.Redis.Exists(cache.Ctx, key).Result()
116 | if err != nil {
117 | fmt.Println("Error checking Set existence:", err)
118 | return
119 | }
120 |
121 | if exists == 1 {
122 | fmt.Println("Set exists")
123 | } else {
124 | fmt.Println("Set does not exist")
125 | return
126 | }
127 |
128 | // 删除临时成员并检查是否删除成功
129 | removedCount, err := cache.Redis.SRem(cache.Ctx, key, "tempMember").Result()
130 | if err != nil {
131 | fmt.Println("Error removing temporary member:", err)
132 | return
133 | }
134 |
135 | if removedCount == 1 {
136 | fmt.Println("Successfully removed temporary member")
137 | } else {
138 | fmt.Println("Temporary member not found in Set")
139 | return
140 | }
141 |
142 | // 再次检查 Set 是否存在
143 | exists, err = cache.Redis.Exists(cache.Ctx, key).Result()
144 | if err != nil {
145 | fmt.Println("Error checking Set existence:", err)
146 | return
147 | }
148 |
149 | if exists == 1 {
150 | fmt.Println("Set exists")
151 | } else {
152 | fmt.Println("Set does not exist")
153 | }
154 |
155 | }
156 |
157 | func TestZset(t *testing.T) {
158 | model.InitDb()
159 | cache.InitRedis()
160 | key := "video:comment_list:4396317246627840"
161 |
162 | emptyMember := "empty_member"
163 | z := redis.Z{
164 | Member: emptyMember,
165 | Score: 0,
166 | }
167 | count, err := cache.Redis.ZAdd(cache.Ctx, key, &z).Result()
168 | if err != nil {
169 | log.Print(err)
170 | }
171 | log.Print("创建数量", count)
172 |
173 | // 立即删除空成员
174 | count, err = cache.Redis.ZRem(cache.Ctx, key, emptyMember).Result()
175 | if err != nil {
176 | log.Print(err)
177 | }
178 | log.Print("删除数量", count)
179 |
180 | // 检查ZSET是否为空
181 | length, err := cache.Redis.ZCard(cache.Ctx, key).Result()
182 | if err != nil {
183 | log.Print(err)
184 | }
185 |
186 | if length == 0 {
187 | // ZSET为空
188 | println("ZSET为空")
189 | } else {
190 | // ZSET不为空
191 | println("ZSET不为空")
192 | }
193 | }
194 |
195 | func TestMQ(t *testing.T) {
196 | config.InitConfig() // 初始话配置文件
197 | model.InitDb() // 初始化数据库
198 | cache.InitRedis() // 初始化缓
199 | PublishVideo()
200 | }
201 |
--------------------------------------------------------------------------------
/video_service/internal/model/comment.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "errors"
5 | "gorm.io/gorm"
6 | "sync"
7 | "time"
8 | "utils/snowFlake"
9 | )
10 |
11 | type Comment struct {
12 | Id int64 `gorm:"primaryKey"`
13 | UserId int64
14 | VideoId int64
15 | Content string `gorm:"default:(-)"` // 评论内容
16 | CreatAt time.Time
17 | CommentStatus bool `gorm:"default:(-)"`
18 | }
19 |
20 | type CommentModel struct {
21 | }
22 |
23 | var commentModel *CommentModel
24 | var commentOnce sync.Once
25 |
26 | // GetCommentInstance 拿到单例实例
27 | func GetCommentInstance() *CommentModel {
28 | commentOnce.Do(
29 | func() {
30 | commentModel = &CommentModel{}
31 | })
32 | return commentModel
33 | }
34 |
35 | // CreateComment 新增评论
36 | func (*CommentModel) CreateComment(tx *gorm.DB, comment *Comment) (id int64, err error) {
37 | flake, _ := snowFlake.NewSnowFlake(7, 2)
38 | comment.Id = flake.NextId()
39 | comment.CommentStatus = true
40 | comment.CreatAt = time.Now()
41 |
42 | result := tx.Create(&comment)
43 | if result.Error != nil {
44 | return -1, result.Error
45 | }
46 |
47 | return comment.Id, nil
48 | }
49 |
50 | // DeleteComment 删除评论
51 | func (*CommentModel) DeleteComment(commentId int64) error {
52 | var comment Comment
53 | result := DB.First(&comment, commentId)
54 | if result.Error != nil {
55 | return result.Error
56 | }
57 | if comment.CommentStatus == false {
58 | return errors.New("评论不存在!!")
59 | }
60 |
61 | comment.CommentStatus = false
62 | result = DB.Save(&comment)
63 | if result.Error != nil {
64 | return result.Error
65 | }
66 | return nil
67 | }
68 |
69 | // CommentList 根据视频id找到所有的评论
70 | func (*CommentModel) CommentList(videoId int64) ([]Comment, error) {
71 | var comments []Comment
72 |
73 | result := DB.Table("comment").
74 | Where("video_id = ? AND comment_status = ?", videoId, true).
75 | Find(&comments)
76 | if result.Error != nil {
77 | return nil, result.Error
78 | }
79 |
80 | return comments, nil
81 | }
82 |
83 | // GetComment 根据评论ID找到具体评论
84 | func (*CommentModel) GetComment(tx *gorm.DB, commentId int64) (Comment, error) {
85 | comment := Comment{}
86 |
87 | result := tx.Table("comment").
88 | Where("id = ?", commentId).
89 | First(&comment)
90 | if result.Error != nil {
91 | return comment, result.Error
92 | }
93 |
94 | return comment, nil
95 | }
96 |
--------------------------------------------------------------------------------
/video_service/internal/model/favorite.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "errors"
5 | "gorm.io/gorm"
6 | "sync"
7 | "utils/snowFlake"
8 | )
9 |
10 | type Favorite struct {
11 | Id int64 `gorm:"primaryKey"`
12 | UserId int64
13 | VideoId int64
14 | IsFavorite bool `gorm:"default:true"`
15 | }
16 |
17 | type FavoriteModel struct {
18 | }
19 |
20 | var favoriteModel *FavoriteModel
21 | var favoriteOnce sync.Once
22 |
23 | func GetFavoriteInstance() *FavoriteModel {
24 | favoriteOnce.Do(
25 | func() {
26 | favoriteModel = &FavoriteModel{}
27 | })
28 | return favoriteModel
29 | }
30 |
31 | // AddFavorite 创建点赞
32 | func (*FavoriteModel) AddFavorite(tx *gorm.DB, favorite *Favorite) error {
33 | result := tx.Where("user_id=? AND video_id=?", favorite.UserId, favorite.VideoId).First(&favorite)
34 | // 发生除没找到记录的其它错误
35 | if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) {
36 | return result.Error
37 | }
38 |
39 | // 如果找到了记录,更新is_favorite置为0
40 | if result.RowsAffected > 0 {
41 | favorite.IsFavorite = true
42 | result = tx.Save(&favorite)
43 | if result.Error != nil {
44 | return result.Error
45 | }
46 | } else {
47 | flake, _ := snowFlake.NewSnowFlake(7, 2)
48 | favorite.Id = flake.NextId()
49 | result = tx.Create(&favorite)
50 | if result.Error != nil {
51 | return result.Error
52 | }
53 | }
54 |
55 | return nil
56 | }
57 |
58 | // IsFavorite 根据用户id和视频id获取点赞状态
59 | func (*FavoriteModel) IsFavorite(userId int64, videoId int64) (bool, error) {
60 | var isFavorite bool
61 |
62 | result := DB.Table("favorite").
63 | Where("user_id = ? AND video_id = ?", userId, videoId).
64 | Pluck("is_favorite", &isFavorite)
65 | if result.Error != nil {
66 | return true, result.Error
67 | }
68 |
69 | return isFavorite, nil
70 | }
71 |
72 | // DeleteFavorite 删除点赞
73 | func (*FavoriteModel) DeleteFavorite(tx *gorm.DB, favorite *Favorite) error {
74 | result := tx.Where("user_id=? AND video_id=?", favorite.UserId, favorite.VideoId).First(&favorite)
75 | // 发生除没找到记录的其它错误
76 | if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) {
77 | return result.Error
78 | }
79 | // 如果找到了记录,更新is_favorite置为0
80 | if result.RowsAffected > 0 {
81 | favorite.IsFavorite = false
82 | result = tx.Save(&favorite)
83 | if result.Error != nil {
84 | return result.Error
85 | }
86 | }
87 |
88 | return nil
89 | }
90 |
91 | // FavoriteVideoList 根据用户Id获取所有喜欢的视频id
92 | func (*FavoriteModel) FavoriteVideoList(userId int64) ([]int64, error) {
93 | var videoIds []int64
94 |
95 | result := DB.Table("favorite").
96 | Where("user_id = ? AND is_favorite = ?", userId, true).
97 | Pluck("video_id", &videoIds)
98 | if result.Error != nil {
99 | return nil, result.Error
100 | }
101 |
102 | return videoIds, nil
103 | }
104 |
105 | // GetFavoriteCount 获取喜欢数量
106 | func (*FavoriteModel) GetFavoriteCount(userId int64) (int64, error) {
107 | var count int64
108 |
109 | DB.Table("favorite").
110 | Where("user_id=? AND is_favorite=?", userId, true).
111 | Count(&count)
112 |
113 | return count, nil
114 | }
115 |
116 | // GetVideoFavoriteCount 获取视频货站数量
117 | func (*FavoriteModel) GetVideoFavoriteCount(videoId int64) (int64, error) {
118 | var count int64
119 |
120 | DB.Table("favorite").
121 | Where("video_id=? AND is_favorite=?", videoId, true).
122 | Count(&count)
123 |
124 | return count, nil
125 | }
126 |
127 | // FavoriteUserList 根据视频找到所有点赞用户的id
128 | func (*FavoriteModel) FavoriteUserList(videoId int64) ([]int64, error) {
129 | var userIds []int64
130 |
131 | result := DB.Table("favorite").
132 | Where("video_id = ? AND is_favorite = ?", videoId, true).
133 | Pluck("user_id", &userIds)
134 | if result.Error != nil {
135 | return nil, result.Error
136 | }
137 |
138 | return userIds, nil
139 | }
140 |
--------------------------------------------------------------------------------
/video_service/internal/model/init.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "gorm.io/driver/mysql"
6 | "gorm.io/gorm"
7 | "gorm.io/gorm/logger"
8 | "gorm.io/gorm/schema"
9 | "time"
10 | "video/config"
11 | )
12 |
13 | // DB 创建数据库单例
14 | var DB *gorm.DB
15 |
16 | // InitDb 初始化数据库
17 | func InitDb() {
18 | config.InitConfig()
19 | dns := config.DbDnsInit()
20 | Database(dns)
21 | }
22 |
23 | // Database 初始花数据库链接
24 | func Database(connString string) {
25 | var ormLogger logger.Interface
26 |
27 | // gin中有debug 、 release 、 test 三种模式
28 | // 不指定默认以debug形式启动
29 | // 开发用debug模式、 上线用release模式
30 | // 指定方式 : gin.SetMode(gin.ReleaseMode)
31 |
32 | // 设置gorm的日志模式,可以打印原生SQL语句
33 | if gin.Mode() == "debug" {
34 | ormLogger = logger.Default.LogMode(logger.Info)
35 | } else {
36 | ormLogger = logger.Default
37 | }
38 | db, err := gorm.Open(mysql.New(mysql.Config{
39 | DSN: connString, // DSN data source name
40 | DefaultStringSize: 256, // string 类型字段的默认长度
41 | DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
42 | DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
43 | DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
44 | SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
45 | }), &gorm.Config{
46 | Logger: ormLogger,
47 | NamingStrategy: schema.NamingStrategy{
48 | SingularTable: true, // 默认不加负数
49 | },
50 | })
51 |
52 | if err != nil {
53 | panic(err)
54 | }
55 |
56 | sqlDB, _ := db.DB()
57 |
58 | // 设置连接池
59 | sqlDB.SetMaxIdleConns(20) // 空闲时候的最大连接数
60 | sqlDB.SetMaxOpenConns(100) // 打开时候的最大连接数
61 | sqlDB.SetConnMaxLifetime(time.Second * 20) // 超时时间
62 | DB = db
63 |
64 | migration()
65 | }
66 |
--------------------------------------------------------------------------------
/video_service/internal/model/init_test.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "fmt"
5 | "github.com/spf13/viper"
6 | "testing"
7 | "video/config"
8 | )
9 |
10 | func TestInitDb(t *testing.T) {
11 | config.InitConfig()
12 | dns := config.DbDnsInit()
13 | fmt.Print(dns)
14 | config.InitConfig()
15 | fmt.Printf("host is: %v \n", viper.GetString("mysql.host"))
16 | }
17 |
--------------------------------------------------------------------------------
/video_service/internal/model/migration.go:
--------------------------------------------------------------------------------
1 | // Package model 数据库自动迁移
2 | package model
3 |
4 | import "log"
5 |
6 | func migration() {
7 | // 自动迁移
8 | err := DB.Set("gorm:table_options", "charset=utf8mb4").AutoMigrate(&Video{}, &Comment{}, &Favorite{})
9 | if err != nil {
10 | log.Print("err")
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/video_service/internal/model/video.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "gorm.io/gorm"
5 | "sync"
6 | "time"
7 | "utils/snowFlake"
8 | )
9 |
10 | type Video struct {
11 | Id int64 `gorm:"primary_key"`
12 | AuthId int64
13 | Title string
14 | CoverUrl string `gorm:"default:(-)"`
15 | PlayUrl string `gorm:"default:(-)"`
16 | //FavoriteCount int64 `gorm:"default:0"`
17 | //CommentCount int64 `gorm:"default:0"`
18 | CreatAt time.Time
19 | }
20 |
21 | type VideoModel struct {
22 | }
23 |
24 | var videoMedel *VideoModel
25 | var videoOnce sync.Once // 单例模式
26 |
27 | // GetVideoInstance 获取单例的实例
28 | func GetVideoInstance() *VideoModel {
29 | videoOnce.Do(
30 | func() {
31 | videoMedel = &VideoModel{}
32 | })
33 | return videoMedel
34 | }
35 |
36 | // Create 创建视频信息
37 | func (*VideoModel) Create(video *Video) error {
38 | // 服务2
39 | flake, _ := snowFlake.NewSnowFlake(7, 2)
40 | video.Id = flake.NextId()
41 |
42 | DB.Create(&video)
43 |
44 | return nil
45 | }
46 |
47 | // DeleteVideoByUrl 删除视频
48 | func (v *VideoModel) DeleteVideoByUrl(videoUrl string) error {
49 | var video Video
50 | if err := DB.Where("play_url = ?", videoUrl).First(&video).Error; err != nil {
51 | return err
52 | }
53 |
54 | // 删除找到的记录
55 | if err := DB.Delete(&video).Error; err != nil {
56 | return err
57 | }
58 |
59 | return nil
60 | }
61 |
62 | // GetVideoByTime 根据创建时间获取视频
63 | func (*VideoModel) GetVideoByTime(timePoint time.Time) ([]Video, error) {
64 | var videos []Video
65 |
66 | result := DB.Table("video").
67 | Where("creat_at < ?", timePoint).
68 | Order("creat_at DESC").
69 | Limit(30).
70 | Find(&videos)
71 | if result.Error != nil {
72 | return nil, result.Error
73 | }
74 |
75 | // 查询不到数据,就返回当前时间最新的30条数据
76 | if len(videos) == 0 {
77 | timePoint = time.Now()
78 | result := DB.Table("video").
79 | Where("creat_at < ?", timePoint).
80 | Order("creat_at DESC").
81 | Limit(30).
82 | Find(&videos)
83 | if result.Error != nil {
84 | return nil, result.Error
85 | }
86 | return videos, nil
87 | }
88 |
89 | return videos, nil
90 | }
91 |
92 | // GetVideoList 根据视频Id获取视频列表
93 | func (*VideoModel) GetVideoList(videoIds []int64) ([]Video, error) {
94 | var videos []Video
95 |
96 | result := DB.Table("video").
97 | Where("id IN ?", videoIds).
98 | Find(&videos)
99 | if result.Error != nil {
100 | return nil, result.Error
101 | }
102 |
103 | return videos, nil
104 | }
105 |
106 | // GetVideoListByUser 根据用户的id找到视频列表
107 | func (*VideoModel) GetVideoListByUser(userId int64) ([]Video, error) {
108 | var videos []Video
109 |
110 | result := DB.Table("video").
111 | Where("auth_id = ?", userId).
112 | Find(&videos)
113 | if result.Error != nil {
114 | return nil, result.Error
115 | }
116 |
117 | return videos, nil
118 | }
119 |
120 | // AddFavoriteCount 喜欢记录 + 1
121 | func (*VideoModel) AddFavoriteCount(tx *gorm.DB, videoId int64) error {
122 | result := tx.Model(&Video{}).Where("id = ?", videoId).
123 | Update("favorite_count", gorm.Expr("favorite_count + ?", 1))
124 | if result.Error != nil {
125 | return result.Error
126 | }
127 |
128 | return nil
129 | }
130 |
131 | // DeleteFavoriteCount 喜欢记录 - 1
132 | func (*VideoModel) DeleteFavoriteCount(tx *gorm.DB, videoId int64) error {
133 | result := tx.Model(&Video{}).Where("id = ?", videoId).
134 | Update("favorite_count", gorm.Expr("favorite_count - ?", 1))
135 | if result.Error != nil {
136 | return result.Error
137 | }
138 |
139 | return nil
140 | }
141 |
142 | // AddCommentCount 视频评论数量 + 1
143 | func (*VideoModel) AddCommentCount(tx *gorm.DB, videoId int64) error {
144 | result := tx.Model(&Video{}).
145 | Where("id = ?", videoId).
146 | Update("comment_count", gorm.Expr("comment_count + ?", 1))
147 | if result.Error != nil {
148 | return result.Error
149 | }
150 |
151 | return nil
152 | }
153 |
154 | // DeleteCommentCount 视频评论数量 - 1
155 | func (*VideoModel) DeleteCommentCount(tx *gorm.DB, videoId int64) error {
156 | result := tx.Model(&Video{}).
157 | Where("id = ?", videoId).
158 | Update("comment_count", gorm.Expr("comment_count - ?", 1))
159 | if result.Error != nil {
160 | return result.Error
161 | }
162 |
163 | return nil
164 | }
165 |
166 | // GetFavoritedCount 获取用户的获赞数量
167 | func (*VideoModel) GetFavoritedCount(userId int64) (int64, error) {
168 | var count int64
169 |
170 | DB.Table("video").
171 | Where("auth_id=?", userId).
172 | Select("SUM(favorite_count) as count").
173 | Pluck("count", &count)
174 |
175 | return count, nil
176 | }
177 |
178 | // GetWorkCount 获取用户的作品数量
179 | func (*VideoModel) GetWorkCount(userId int64) (int64, error) {
180 | var count int64
181 | DB.Table("video").
182 | Where("auth_id=?", userId).
183 | Count(&count)
184 |
185 | return count, nil
186 | }
187 |
--------------------------------------------------------------------------------
/video_service/internal/model/video_test.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "strconv"
7 | "testing"
8 | "time"
9 | )
10 |
11 | // 测试视频喜欢记录 + 1
12 | func TestVideoModel_AddFavoriteCount(t *testing.T) {
13 | InitDb()
14 | tx := DB.Begin()
15 | GetVideoInstance().AddFavoriteCount(tx, 1949953677991936)
16 | }
17 |
18 | // 测试视频喜欢记录 - 1
19 | func TestFavoriteModel_DeleteFavorite2(t *testing.T) {
20 | InitDb()
21 | tx := DB.Begin()
22 | GetVideoInstance().DeleteFavoriteCount(tx, 1949953677991936)
23 | }
24 |
25 | // 测试新增喜欢记录
26 | func TestFavoriteModel_AddFavorite(t *testing.T) {
27 | InitDb()
28 | tx := DB.Begin()
29 | favorite := Favorite{
30 | UserId: 123,
31 | VideoId: 456,
32 | }
33 | GetFavoriteInstance().AddFavorite(tx, &favorite)
34 | }
35 |
36 | // 测试软删除喜欢记录
37 | func TestFavoriteModel_DeleteFavorite(t *testing.T) {
38 | InitDb()
39 | tx := DB.Begin()
40 | favorite := Favorite{
41 | UserId: 123,
42 | VideoId: 456,
43 | }
44 | GetFavoriteInstance().DeleteFavorite(tx, &favorite)
45 | }
46 |
47 | // 测试创建评论
48 | func TestCommentModel_CreateComment(t *testing.T) {
49 | InitDb()
50 | tx := DB.Begin()
51 | comment := Comment{
52 | UserId: 111,
53 | VideoId: 222,
54 | Content: "喜欢",
55 | }
56 | GetCommentInstance().CreateComment(tx, &comment)
57 | tx.Commit()
58 | }
59 |
60 | // 测试删除评论
61 | func TestCommentModel_DeleteComment(t *testing.T) {
62 | InitDb()
63 | GetCommentInstance().DeleteComment(8361782507610112)
64 |
65 | }
66 |
67 | // 测试删除评论
68 | func TestCommentModel_CommentList(t *testing.T) {
69 | InitDb()
70 | commentList, _ := GetCommentInstance().CommentList(4395719587667968)
71 | fmt.Print(commentList)
72 | }
73 |
74 | func TestTime(t *testing.T) {
75 | currentTime := time.Now()
76 | fmt.Println(currentTime)
77 | timeString := currentTime.Format("2006-01-02 15:04:05")
78 | fmt.Println("Formatted time:", timeString)
79 | }
80 |
81 | // 测试用户获赞数量
82 | func TestVideoModel_GetFavoritedCount(t *testing.T) {
83 | InitDb()
84 | count, _ := GetVideoInstance().GetFavoritedCount(812575311663104)
85 | fmt.Println(count)
86 | }
87 |
88 | // 统计作品数量
89 | func TestVideoModel_GetWorkCount(t *testing.T) {
90 | InitDb()
91 | count, _ := GetVideoInstance().GetWorkCount(812575311663104)
92 | fmt.Println(count)
93 | }
94 |
95 | // 统计喜欢数量
96 | func TestFavorite_GetFavoriteCount(t *testing.T) {
97 | InitDb()
98 | count, _ := GetFavoriteInstance().GetFavoriteCount(812575311663104)
99 | fmt.Println(count)
100 | }
101 |
102 | // 找到喜欢的视频id
103 | func TestFavoriteModel_FavoriteVideoList(t *testing.T) {
104 | InitDb()
105 | list, _ := GetFavoriteInstance().FavoriteVideoList(812575311663104)
106 |
107 | videoList, _ := GetVideoInstance().GetVideoList(list)
108 | fmt.Print(list)
109 | fmt.Print(videoList)
110 | }
111 |
112 | func TestFavoriteModel_IsFavorite(t *testing.T) {
113 | InitDb()
114 | favorite, _ := GetFavoriteInstance().IsFavorite(812575311663104, 2276964627783680)
115 | fmt.Print(favorite)
116 | }
117 |
118 | // 根据时间查找视频列表
119 | func TestVideoModel_GetVideoByTime(t *testing.T) {
120 | InitDb()
121 | videos, _ := GetVideoInstance().GetVideoByTime(time.Now())
122 | fmt.Print(videos)
123 | }
124 |
125 | func TestFmt(t *testing.T) {
126 | userId := int64(111)
127 | key := fmt.Sprintf("%s:%s:%s", "user", "info", strconv.FormatInt(userId, 10))
128 | print(key)
129 | }
130 |
131 | func TestFavoriteModel_FavoriteUserList(t *testing.T) {
132 | InitDb()
133 | list, _ := GetFavoriteInstance().FavoriteUserList(4396360053694464)
134 | fmt.Println(list)
135 | }
136 |
137 | func TestCommentModel_GetComment(t *testing.T) {
138 | InitDb()
139 | tx := DB.Begin()
140 | comment, _ := commentModel.GetComment(tx, 4419719369990144)
141 | log.Print(comment)
142 | }
143 |
--------------------------------------------------------------------------------
/video_service/internal/service/pb/videoService.proto:
--------------------------------------------------------------------------------
1 | syntax="proto3";
2 | package pb;
3 | option go_package = "../;service"; // 此处格式为<生成的文件存放位置;生成文件的包名>
4 |
5 |
6 | message Video {
7 | // @gotags:json:"id" form:"id" uri:"id"
8 | int64 Id = 1;
9 | // @gotags:json:"auth_id" form:"auth_id" uri:"auth_id"
10 | int64 AuthId = 2;
11 | // @gotags:json:"play_url" form:"play_rul" uri:"play_url"
12 | string PlayUrl = 3; // 视频播放地址
13 | // @gotasgs:json:"cover_url" form:"cover_url" uri:"cover_url"
14 | string CoverUrl = 4;
15 | // @gotags:json:"favorite_count" form:"favorite_count" uri:"favorite_count"
16 | int64 FavoriteCount = 5;
17 | // @gotags:json:"comment_count" form:"comment_count" uri:"comment_count"
18 | int64 CommentCount = 6;
19 | // @gotags:json:"is_favorite" form:"is_favorite" uri:"is_favorite"
20 | bool IsFavorite = 7;
21 | // @gotags:json:"title" form:"title" uri:"title"
22 | string Title = 8;
23 | }
24 |
25 | message Comment {
26 | // @gotags:json:"id" form:"id" uri:"id"
27 | int64 Id = 1;
28 | // @gatags:json:"user_id" form:"user_id" uri:"user_id"
29 | int64 UserId = 2;
30 | // @gotags:json:"content" form:"content" uri:"content"
31 | string Content = 3; // 评论内容
32 | // @gotags:json:"create_date" form:"create_date" uri:"create_date"
33 | string CreateDate = 4;
34 | }
35 |
36 | // 视频流
37 | message FeedRequest {
38 | // @gotags:json:"latest_time" form:"latest_time" uri:"latest_time"
39 | int64 LatestTime = 1; // 可选参数,限制返回视频的最新投稿时间戳,精确到秒,不填表示当前时间
40 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
41 | int64 UserId = 2;
42 | }
43 |
44 | message FeedResponse {
45 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
46 | int64 StatusCode = 1;
47 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
48 | string StatusMsg = 2;
49 | // @gotags:json:"video_list" form:"video_list" uri:"video_list"
50 | repeated Video VideoList = 3;
51 | // @gotags:json:"next_time" form:"next_time" uri:"next_time"
52 | int64 NextTime = 4;
53 | }
54 |
55 | // 发布视频
56 | message PublishActionRequest {
57 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
58 | int64 UserId = 1;
59 | // @gotags:json:"data" form:"data" uri:"data"
60 | bytes Data = 2;
61 | // @gotags:json:"title" form:"title" uri:"title"
62 | string Title = 3;
63 | }
64 |
65 | message PublishActionResponse {
66 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
67 | int64 StatusCode = 1;
68 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
69 | string StatusMsg = 2;
70 | }
71 |
72 | // 发布列表
73 | message PublishListRequest {
74 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
75 | int64 UserId = 1;
76 | }
77 |
78 | message PublishListResponse {
79 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
80 | int64 StatusCode = 1;
81 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
82 | string StatusMsg = 2;
83 | // @gotags:json:"video_list" form:"video_list" uri:"video_list"
84 | repeated Video VideoList = 3;
85 | }
86 |
87 | // 赞操作
88 | message FavoriteActionRequest {
89 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
90 | int64 UserId = 1;
91 | // @gotags:json:"video_id" form:"video_id" uri:"video_id"
92 | int64 VideoId = 2;
93 | // @gotags:json:"action_type" form:"action_type" uri:"action_type"
94 | int64 ActionType = 3; // 1-点赞,2-取消点赞
95 | }
96 |
97 | message FavoriteActionResponse {
98 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
99 | int64 StatusCode = 1;
100 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
101 | string StatusMsg = 2;
102 | }
103 |
104 |
105 | // 喜欢列表
106 | message FavoriteListRequest {
107 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
108 | int64 UserId = 1;
109 | }
110 |
111 | message FavoriteListResponse {
112 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
113 | int64 StatusCode = 1;
114 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
115 | string StatusMsg = 2;
116 | // @gotags:json:"video_list" form:"video_list" uri:"video_list"
117 | repeated Video VideoList = 3;
118 | }
119 |
120 |
121 | // 评论操作
122 | message CommentActionRequest {
123 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
124 | int64 UserId = 1;
125 | // @gotags:json:"video_id" form:"video_id" uri:"video_id"
126 | int64 VideoId = 2;
127 | // @gotags:json:"action_type" form:"action_type" uri:"action_type"
128 | int64 ActionType = 3; //1-发布评论,2-删除评论
129 | // @gotags:json:"comment_text" form:"comment_text" uri:"comment_text"
130 | string CommentText = 4; // 可选,用户填写的评论内容,在action_type=1的时候使用
131 | // @gotags:json:"comment_id" form:"comment_id" uri:"comment_id"
132 | int64 CommentId = 5; // 可选,要删除的评论id,在action_type=2的时候使用
133 | }
134 |
135 | message CommentActionResponse {
136 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
137 | int64 StatusCode = 1;
138 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
139 | string StatusMsg = 2;
140 | // @gotags:json:"comment" form:"comment" uri:"comment"
141 | Comment Comment = 3;
142 | }
143 |
144 | // 评论列表
145 | message CommentListRequest {
146 | // @gotags:json:"video_id" form:"video_id" uri:"video_id"
147 | int64 VideoId = 1;
148 | }
149 |
150 | message CommentListResponse {
151 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
152 | int64 StatusCode = 1;
153 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
154 | string StatusMsg = 2;
155 | // @gotags:json:"comment_list" form:"comment_list" uri:"comment_list"
156 | repeated Comment CommentList = 3;
157 | }
158 |
159 | // 视频计数
160 | message Count {
161 | // @gotags:json:"total_favorited" form:"total_favorited" uri:"total_favorited"
162 | int64 TotalFavorited = 1; // 获赞数量
163 | // @gotags:json:"work_count" form:"work_count" uri:"work_count"
164 | int64 WorkCount = 2; // 作品数量
165 | // @gotags:json:"favorite_count" form:"favorite_count" uri:"favorite_count"
166 | int64 FavoriteCount = 3; // 喜欢数量
167 | }
168 |
169 | // 视频总计数请求
170 | message CountRequest {
171 | // @gotags:json:"user_id" form:"user_id" uri:"user_id"
172 | repeated int64 UserIds = 1; // 传入一个userId切片
173 | }
174 |
175 | message CountResponse {
176 | // @gotags:json:"status_code" form:"status_code" uri:"status_code"
177 | int64 StatusCode = 1;
178 | // @gotags:json:"status_msg" form:"status_msg" uri:"status_msg"
179 | string StatusMsg = 2;
180 | // @gotags:json:"counts" form:"counts" uri:"counts"
181 | repeated Count counts = 3;
182 | }
183 |
184 | service VideoService {
185 | rpc Feed(FeedRequest) returns(FeedResponse);
186 | rpc PublishAction(PublishActionRequest) returns (PublishActionResponse);
187 | rpc PublishList(PublishListRequest) returns(PublishListResponse);
188 |
189 | rpc FavoriteAction(FavoriteActionRequest) returns(FavoriteActionResponse);
190 | rpc FavoriteList(FavoriteListRequest) returns(FavoriteListResponse);
191 |
192 | rpc CommentAction(CommentActionRequest) returns(CommentActionResponse);
193 | rpc CommentList(CommentListRequest) returns(CommentListResponse);
194 |
195 | // 根据user_id切片,返回计数信息
196 | rpc CountInfo(CountRequest) returns(CountResponse);
197 | }
198 |
199 |
--------------------------------------------------------------------------------
/video_service/pkg/cache/redis.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "context"
5 | "github.com/go-redis/redis/v8"
6 | "github.com/spf13/viper"
7 | )
8 |
9 | var Redis *redis.Client
10 |
11 | var Ctx = context.Background()
12 |
13 | // InitRedis 连接redis
14 | func InitRedis() {
15 | addr := viper.GetString("redis.address")
16 | Redis = redis.NewClient(&redis.Options{
17 | Addr: addr,
18 | Password: "",
19 | DB: 0, // 存入DB0
20 | })
21 | }
22 |
--------------------------------------------------------------------------------
/video_service/pkg/cache/redis_test.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "context"
5 | "testing"
6 | )
7 |
8 | func TestInitRedis(t *testing.T) {
9 | InitRedis()
10 | Redis.Set(context.Background(), "K2", "myValue", 0)
11 | }
12 |
--------------------------------------------------------------------------------
/video_service/pkg/cut/ffmpeg.go:
--------------------------------------------------------------------------------
1 | package cut
2 |
3 | import (
4 | "bytes"
5 | "os"
6 | "os/exec"
7 | )
8 |
9 | // Cover 截取视频为图片
10 | func Cover(videoURL string, timeOffset string) ([]byte, error) {
11 | cmd := exec.Command(
12 | "ffmpeg", "-i", videoURL, "-ss", timeOffset, "-vframes", "1", "-q:v", "2", "-f", "image2", "pipe:1",
13 | )
14 | cmd.Stderr = os.Stderr
15 |
16 | var outputBuffer bytes.Buffer
17 | cmd.Stdout = &outputBuffer
18 |
19 | err := cmd.Run()
20 | if err != nil {
21 | return nil, err
22 | }
23 |
24 | return outputBuffer.Bytes(), nil
25 | }
26 |
--------------------------------------------------------------------------------
/video_service/pkg/cut/ffmpeg_test.go:
--------------------------------------------------------------------------------
1 | package cut
2 |
3 | import (
4 | "testing"
5 | "video/config"
6 | "video/third_party"
7 | )
8 |
9 | func TestCover(t *testing.T) {
10 | config.InitConfig()
11 | videoURL := "http://tiny-tiktok.oss-cn-chengdu.aliyuncs.com/video1.mp4"
12 |
13 | imageBytes, _ := Cover(videoURL, "00:00:05")
14 |
15 | third_party.Upload("output.jpg", imageBytes)
16 | }
17 |
--------------------------------------------------------------------------------
/video_service/pkg/mq/rabbitmq.go:
--------------------------------------------------------------------------------
1 | package mq
2 |
3 | import (
4 | amqp "github.com/rabbitmq/amqp091-go"
5 | "log"
6 | "video/config"
7 | )
8 |
9 | func failOnError(err error, msg string) {
10 | if err != nil {
11 | log.Printf("%s: %s", msg, err)
12 | }
13 | }
14 |
15 | func InitMQ() *amqp.Connection {
16 | // 连接到RabbitMQ服务器
17 | url := config.InitRabbitMQUrl()
18 | conn, err := amqp.Dial(url)
19 | failOnError(err, "Failed to connect to RabbitMQ")
20 |
21 | return conn
22 | }
23 |
24 | // ConsumeMessage 消费消息
25 | func ConsumeMessage() {
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/video_service/pkg/mq/rabbitmq_test.go:
--------------------------------------------------------------------------------
1 | package mq
2 |
3 | import (
4 | "context"
5 | amqp "github.com/rabbitmq/amqp091-go"
6 | "log"
7 | "strconv"
8 | "testing"
9 | "time"
10 | "video/config"
11 | )
12 |
13 | func TestMQServer(t *testing.T) {
14 | conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
15 | failOnError(err, "Failed to connect to RabbitMQ")
16 | defer conn.Close()
17 |
18 | ch, err := conn.Channel()
19 | failOnError(err, "Failed to open a channel")
20 | defer ch.Close()
21 |
22 | q, err := ch.QueueDeclare(
23 | "rpc_queue", // name
24 | false, // durable
25 | false, // delete when unused
26 | false, // exclusive
27 | false, // no-wait
28 | nil, // arguments
29 | )
30 | failOnError(err, "Failed to declare a queue")
31 |
32 | err = ch.Qos(
33 | 1, // prefetch count
34 | 0, // prefetch size
35 | false, // global
36 | )
37 | failOnError(err, "Failed to set QoS")
38 |
39 | msgs, err := ch.Consume(
40 | q.Name, // queue
41 | "", // consumer
42 | false, // auto-ack
43 | false, // exclusive
44 | false, // no-local
45 | false, // no-wait
46 | nil, // args
47 | )
48 | failOnError(err, "Failed to register a consumer")
49 |
50 | var forever chan struct{}
51 |
52 | go func() {
53 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
54 | defer cancel()
55 | for d := range msgs {
56 | n, err := strconv.Atoi(string(d.Body))
57 | failOnError(err, "Failed to convert body to integer")
58 |
59 | log.Printf(" [.] fib(%d)", n)
60 | response := fib(n)
61 |
62 | err = ch.PublishWithContext(ctx,
63 | "", // exchange
64 | d.ReplyTo, // routing key
65 | false, // mandatory
66 | false, // immediate
67 | amqp.Publishing{
68 | ContentType: "text/plain",
69 | CorrelationId: d.CorrelationId,
70 | Body: []byte(strconv.Itoa(response)),
71 | })
72 | failOnError(err, "Failed to publish a message")
73 |
74 | d.Ack(false)
75 | }
76 | }()
77 |
78 | log.Printf(" [*] Awaiting RPC requests")
79 | <-forever
80 | }
81 |
82 | func TestUrl(t *testing.T) {
83 | config.InitConfig()
84 | url := config.InitRabbitMQUrl()
85 | log.Printf(url)
86 | }
87 |
--------------------------------------------------------------------------------
/video_service/third_party/oss.go:
--------------------------------------------------------------------------------
1 | package third_party
2 |
3 | import (
4 | "bytes"
5 | "github.com/aliyun/aliyun-oss-go-sdk/oss"
6 | "github.com/spf13/viper"
7 | )
8 |
9 | // 创建存储空间
10 | func getBucket() (*oss.Bucket, error) {
11 | bucketName := viper.GetString("oss.bucketName")
12 | endPoint := viper.GetString("oss.endpoint")
13 | accessKeyId := viper.GetString("oss.accessKeyId")
14 | accessKeySecret := viper.GetString("oss.accessKeySecret")
15 |
16 | // 创建OSSClient实例
17 | client, err := oss.New(endPoint, accessKeyId, accessKeySecret)
18 | if err != nil {
19 | return nil, err
20 | }
21 | // 获取存储空间
22 | bucket, err := client.Bucket(bucketName)
23 | if err != nil {
24 | return nil, err
25 | }
26 | return bucket, nil
27 | }
28 |
29 | // Upload 上传
30 | func Upload(fileDir string, fileBytes []byte) error {
31 | bucket, err := getBucket()
32 | if err != nil {
33 | return err
34 | }
35 |
36 | // 上传文件
37 | file := bytes.NewReader(fileBytes)
38 | err = bucket.PutObject(fileDir, file)
39 | return err
40 | }
41 |
42 | // Delete 删除
43 | func Delete(fileDir string) error {
44 | bucket, err := getBucket()
45 | if err != nil {
46 | return err
47 | }
48 |
49 | // 上传文件
50 | err = bucket.DeleteObject(fileDir)
51 | return err
52 | }
53 |
--------------------------------------------------------------------------------
/video_service/third_party/oss_test.go:
--------------------------------------------------------------------------------
1 | package third_party
2 |
3 | import (
4 | "io/ioutil"
5 | "testing"
6 | "video/config"
7 | )
8 |
9 | func TestUpload(t *testing.T) {
10 |
11 | config.InitConfig()
12 | filePath := "D:\\Project\\video\\output_image.jpg"
13 | fileByte, _ := ioutil.ReadFile(filePath)
14 | Upload("hello", fileByte)
15 | }
16 |
--------------------------------------------------------------------------------