├── .gitignore
├── .idea
├── .gitignore
├── TikTok.iml
└── modules.xml
├── LICENSE
├── README.md
├── config
├── config.go
├── doc.go
├── douyin.sql
├── nginx.conf
└── vsftpd.conf
├── controller
├── commentController.go
├── doc.go
├── followController.go
├── likeController.go
├── userController.go
└── videoController.go
├── dao
├── commentDao.go
├── commentDao_test.go
├── doc.go
├── followDao.go
├── followDao_test.go
├── initDao.go
├── initDao_test.go
├── likeDao.go
├── likeDao_test.go
├── userDao.go
├── userDao_test.go
├── videoDao.go
└── videoDao_test.go
├── document
└── sensitiveDict.txt
├── go.mod
├── go.sum
├── images
├── 1.0.png
├── 1.2.png
├── 1.png
├── 10.png
├── 11.png
├── 2.png
├── 3.png
├── 4.png
├── 5.png
├── 6.png
├── 7.png
├── 8.png
├── 9.png
├── awards.jpg
├── awards1.jpg
├── framework.jpg
├── future.png
├── logo1.png
├── logo2.png
├── mysql.jpg
├── rabbitmq.jpg
├── recommend.jpg
├── redis.jpg
└── video.png
├── main.go
├── middleware
├── ffmpeg
│ ├── doc.go
│ └── ffmpeg.go
├── ftp
│ ├── doc.go
│ └── ftp.go
├── jwt
│ ├── auth.go
│ ├── authBody.go
│ └── doc.go
├── rabbitmq
│ ├── commentMQ.go
│ ├── doc.go
│ ├── followMQ.go
│ ├── likeMQ.go
│ └── rabbitMQ.go
└── redis
│ ├── doc.go
│ └── redis.go
├── router.go
├── service
├── commentService.go
├── commentServiceImpl.go
├── commentServiceImpl_test.go
├── commentSub.go
├── doc.go
├── followService.go
├── followServiceImpl.go
├── followServiceImpl_test.go
├── followSub.go
├── likeService.go
├── likeServiceImpl.go
├── likeServiceImpl_test.go
├── likeSub.go
├── userService.go
├── userServiceImpl.go
├── userServiceImpl_test.go
├── userSub.go
├── videoService.go
├── videoServiceImpl.go
├── videoServiceImpl_test.go
└── videoSub.go
└── util
└── sensitiveFilter.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # Dependency directories (remove the comment below to include it)
15 | # vendor/
16 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Datasource local storage ignored files
5 | /dataSources/
6 | /dataSources.local.xml
7 | # Editor-based HTTP Client requests
8 | /httpRequests/
9 |
--------------------------------------------------------------------------------
/.idea/TikTok.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # tiktok
2 |
3 |
4 |
5 | 
6 | 
7 | 
8 | 
9 | [![MIT License][license-shield]][license-url]
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
抖音简洁版
21 |
22 |
23 | 探索本项目的文档 »
24 |
25 |
26 |
27 |
28 |
29 | **Attention:** We always welcome contributors to the project. Before adding your contribution, please carefully read our [Git 分支管理规范](https://ypbg9olvt2.feishu.cn/docs/doccnTMRmh7YgMwL2PgZ5moWUsd)和[注释规范](https://juejin.cn/post/7096881555246678046)。
30 |
31 | ## 目录
32 | - [荣誉展示](#荣誉展示)
33 | - [上手指南](#上手指南)
34 | - [开发前的配置要求](#开发前的配置要求)
35 | - [安装步骤](#安装步骤)
36 | - [演示界面](#演示界面)
37 | - [演示视频](#演示视频)
38 | - [文件目录说明](#文件目录说明)
39 | - [开发的整体设计](#开发的整体设计)
40 | - [整体的架构图](#整体的架构图)
41 | - [数据库的设计](#数据库的设计)
42 | - [Redis架构的设计](#Redis架构的设计)
43 | - [RabbitMQ架构的设计](#RabbitMQ架构的设计)
44 | - [服务模块的设计](#服务模块的设计)
45 | - [视频模块的设计](#视频模块的设计)
46 | - [点赞模块的设计](#点赞模块的设计)
47 | - [关注模块的设计](#关注模块的设计)
48 | - [用户模块的设计](#用户模块的设计)
49 | - [评论模块的设计](#评论模块的设计)
50 | - [性能测试](#性能测试)
51 | - [编译项目到linux](#编译项目到linux)
52 | - [使用到的技术](#使用到的技术)
53 | - [未来展望](#未来展望)
54 | - [分布式服务](#分布式服务)
55 | - [推荐视频展望](#推荐视频展望)
56 | - [如何参与开源项目](#如何参与开源项目)
57 | - [版本控制](#版本控制)
58 | - [贡献者](#贡献者)
59 | - [鸣谢](#鸣谢)
60 |
61 | ### 荣誉展示
62 |
63 |
64 |
65 |
66 |
67 | ### 上手指南
68 |
69 | #### 开发前的配置要求
70 |
71 | 1. go 1.18.1
72 | 2. MySQL(数据库sql文件在config包中)
73 | 3. 搭建Redis、RabbitMQ环境
74 | 4. 配置静态资源服务器:安装Nginx、vsftpd、ffmpeg(相关配置文件在config包中)
75 | 5. [最新版抖音客户端软件](https://pan.baidu.com/s/1kXjvYWH12uhvFBARRMBCGg?pwd=6cos)
76 |
77 |
78 |
79 | #### 安装步骤
80 | 1. 下载源码
81 | 2. 配置SSH、FTP、Redis、静态服务器地址等相关参数
82 | 3. 启动服务
83 | 4. 在客户端配置相关地址服务端地址即可
84 |
85 | ```sh
86 | git clone https://github.com/HammerCloth/tiktok.git
87 | ```
88 | #### 演示界面
89 | **基础功能演示**
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | **拓展功能演示**
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 | **设置服务端地址**
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 | #### 演示视频
116 | [](http://43.138.25.60/tiktok.mp4)
117 |
118 | ### 文件目录说明
119 |
120 | ```
121 | tiktok
122 | ├── /.idea/
123 | ├── /config/ 配置文件包
124 | ├── /controller/ 控制器包
125 | ├── /dao/ 数据库访问
126 | ├── /document/ 敏感词词库
127 | ├── /images/ 图片引用
128 | ├── /middleware/ 中间件
129 | │ ├── ffmpeg/ 视频截图
130 | │ ├── ftp/ 文件服务器
131 | │ ├── jwt/ 鉴权
132 | │ ├── rabbitmq/ 消息队列
133 | │ ├── redis/ 缓存
134 | ├── /service/ 服务层
135 | ├── /util/ 工具
136 | ├── .gitignore
137 | ├── /go.mod/
138 | ├── LICENSE
139 | ├── main.go
140 | ├── README.md
141 | └── router.go
142 | ```
143 |
144 | ### 开发的整体设计
145 | #### 整体的架构图
146 |
147 |
148 |
149 |
150 |
151 | #### 数据库的设计
152 |
153 |
154 |
155 |
156 |
157 |
158 | #### Redis架构的设计
159 |
160 |
161 |
162 |
163 |
164 |
165 | #### RabbitMQ架构的设计
166 |
167 |
168 |
169 |
170 |
171 |
172 | #### 服务模块的设计
173 |
174 | ###### 视频模块的设计
175 | 视频模块包括视频Feed流获取、视频投稿和获取用户投稿列表。
176 | 详情请阅读[视频模块设计说明](https://bytedancecampus1.feishu.cn/docs/doccntmcunjHSMzVUNEhGbxjxJh) 查阅为该模块的详细设计。
177 |
178 | ###### 点赞模块的设计
179 | 点赞模块包括点赞视频、取消赞视频和获取点赞列表。
180 | 详情请阅读[点赞模块设计说明](https://bytedancecampus1.feishu.cn/docs/doccn13iJgTIAebIPpMiRqb0Hwb) 查阅为该模块的详细设计。
181 |
182 | ###### 关注模块的设计
183 | 关注模块包括关注、取关、获取关注列表、获取粉丝列表四个基本功能。
184 | 详情请阅读[关注模块的设计说明](https://bytedancecampus1.feishu.cn/docs/doccnOsdm29SufPJkDfRs7tLHgx) 查阅为该模块的详细设计。
185 |
186 | ###### 用户模块的设计
187 | 用户与安全模块包括用户注册、用户登录和用户信息三个部分
188 | 详情请阅读[用户模块的设计说明](https://bytedancecampus1.feishu.cn/docs/doccn1vusmV9oN1ukTCyLpbJ46f) 查阅为该模块的详细设计。
189 |
190 | ###### 评论模块的设计
191 | 评论模块包括发表评论、删除评论和查看评论。
192 | 详情阅读[评论模块的设计说明](https://bytedancecampus1.feishu.cn/docs/doccnDqfcZJW4tTD409NGlYfvCb) 查阅为该模块的详细设计。
193 |
194 | ### 性能测试
195 | 通过命令 go tool pprof -http=:6060 "http://localhost:8080/debug/pprof/profile?seconds=120" 生成了两个版本的火焰图,左图为v1.0,右图为v1.2版本,通过对比两张详细火焰图,优化后的相同方法调用时间更短(添加了相应的中间件)
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 | 同时包含各个接口的压力测试,详情请阅读[压测报告](https://bytedancecampus1.feishu.cn/docs/doccnoDHHJ84k94G1I2TxHj9Udh) 获得具体的压力数据。
204 | ### 编译项目到linux
205 | ```shell
206 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build ./
207 | ```
208 |
209 | ### 使用到的技术
210 | 框架相关:
211 | - [Gin](https://gin-gonic.com/docs/)
212 | - [Gorm](https://gorm.io/docs/)
213 |
214 | 服务器相关:
215 | - [Nginx](https://www.nginx-cn.net/)
216 | - [vsftpd](https://www.linuxfromscratch.org/blfs/view/svn/server/vsftpd.html)
217 | - [ffmpeg](https://ffmpeg.org/documentation.html)
218 | - [goftp](http://t.zoukankan.com/lvdongjie-p-9554849.html)
219 |
220 | 中间件相关:
221 | - [Redis](https://redis.io/docs/)
222 | - [RabbitMQ](https://www.rabbitmq.com/documentation.html)
223 |
224 | 数据库:
225 | - [MySQL](https://dev.mysql.com/doc/)
226 |
227 | ### 未来展望
228 | #### 分布式服务
229 | 利用dubbogo来完成分布式,貔貅作为网关,Nacos作为注册中心,将五个模块分别布置到不同的服务器上,以rpc调用的方式来调用当前模块依赖其他模块的方法,做到分布式处理与解耦。
230 |
231 |
232 |
233 |
234 |
235 |
236 | #### 推荐视频展望
237 | 队伍创新推荐算法
238 |
239 |
240 |
241 |
242 |
243 |
244 | 详情请阅读[视频推荐展望](https://bytedancecampus1.feishu.cn/docx/doxcnVIK62rWgR0iE49UAI2wB0b)
245 |
246 | ### 如何参与开源项目
247 |
248 | 贡献使开源社区成为一个学习、激励和创造的绝佳场所。你所作的任何贡献都是**非常感谢**的。
249 |
250 | 1. Fork the Project
251 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
252 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
253 | 4. Push to the Branch (`git push origin feature/AmazingFeature`)
254 | 5. Open a Pull Request
255 |
256 | ### 版本控制
257 |
258 | 该项目使用Git进行版本管理。您可以在repository参看当前可用版本。
259 |
260 | ### 贡献者
261 | - 司一雄 邮箱:18552541076@163.com
262 | - 刘宗舟 邮箱:1245314855@qq.com
263 | - 蒋宇栋 邮箱:jiangyudong123@qq.com
264 | - 李思源 邮箱:yuanlaisini_002@qq.com
265 | - 李林森 邮箱:1412837463@qq.com
266 |
267 | *您也可以查阅仓库为该项目做出贡献的开发者。*
268 |
269 | ### 版权说明
270 |
271 | 该项目签署了MIT 授权许可,详情请参阅 [LICENSE.txt](https://github.com/shaojintian/Best_README_template/blob/master/LICENSE.txt)
272 |
273 | ### 鸣谢
274 |
275 | - [字节跳动后端青训营](https://youthcamp.bytedance.com/)
276 |
277 |
278 |
279 | [license-shield]: https://img.shields.io/github/license/mrxuexi/tiktok.svg?style=flat-square
280 |
281 | [license-url]: https://github.com/mrxuexi/tiktok/blob/master/LICENSE.txt
282 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import "time"
4 |
5 | // Secret 密钥
6 | var Secret = "tiktok"
7 |
8 | // OneDayOfHours 时间
9 | var OneDayOfHours = 60 * 60 * 24
10 | var OneMinute = 60 * 1
11 | var OneMonth = 60 * 60 * 24 * 30
12 | var OneYear = 365 * 60 * 60 * 24
13 | var ExpireTime = time.Hour * 48 // 设置Redis数据热度消散时间。
14 |
15 | // VideoCount 每次获取视频流的数量
16 | const VideoCount = 5
17 |
18 | // ConConfig ftp服务器地址
19 | const ConConfig = "43.138.25.60:21"
20 | const FtpUser = "ftpuser"
21 | const FtpPsw = "424193726"
22 | const HeartbeatTime = 2 * 60
23 |
24 | // PlayUrlPrefix 存储的图片和视频的链接
25 | const PlayUrlPrefix = "http://43.138.25.60/"
26 | const CoverUrlPrefix = "http://43.138.25.60/images/"
27 |
28 | // HostSSH SSH配置
29 | const HostSSH = "43.138.25.60"
30 | const UserSSH = "ftpuser"
31 | const PasswordSSH = "424193726"
32 | const TypeSSH = "password"
33 | const PortSSH = 22
34 | const MaxMsgCount = 100
35 | const SSHHeartbeatTime = 10 * 60
36 |
37 | const ValidComment = 0 //评论状态:有效
38 | const InvalidComment = 1 //评论状态:取消
39 | const DateTime = "2006-01-02 15:04:05"
40 |
41 | //const ChanCapacity = 10 //chan管道容量,暂时没定
42 |
43 | const IsLike = 0 //点赞的状态
44 | const Unlike = 1 //取消赞的状态
45 | const LikeAction = 1 //点赞的行为
46 | const Attempts = 3 //操作数据库的最大尝试次数
47 |
48 | const DefaultRedisValue = -1 //redis中key对应的预设值,防脏读
49 |
--------------------------------------------------------------------------------
/config/doc.go:
--------------------------------------------------------------------------------
1 | // Package config
2 | // @Description 用于保存一些全局的变量和一些配置
3 | // @Author 创建人siyixiong;
4 | // @Update 20220513 (创建/修改时间);
5 | package config
6 |
--------------------------------------------------------------------------------
/config/douyin.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Navicat Premium Data Transfer
3 |
4 | Source Server : 抖音
5 | Source Server Type : MySQL
6 | Source Server Version : 50650
7 | Source Host : 43.138.25.60:3306
8 | Source Schema : douyin
9 |
10 | Target Server Type : MySQL
11 | Target Server Version : 50650
12 | File Encoding : 65001
13 |
14 | Date: 04/06/2022 23:43:31
15 | */
16 |
17 | SET NAMES utf8mb4;
18 | SET FOREIGN_KEY_CHECKS = 0;
19 |
20 | -- ----------------------------
21 | -- Table structure for comments
22 | -- ----------------------------
23 | DROP TABLE IF EXISTS `comments`;
24 | CREATE TABLE `comments` (
25 | `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '评论id,自增主键',
26 | `user_id` bigint(20) NOT NULL COMMENT '评论发布用户id',
27 | `video_id` bigint(20) NOT NULL COMMENT '评论视频id',
28 | `comment_text` varchar(255) NOT NULL COMMENT '评论内容',
29 | `create_date` datetime NOT NULL COMMENT '评论发布时间',
30 | `cancel` tinyint(4) NOT NULL DEFAULT '0' COMMENT '默认评论发布为0,取消后为1',
31 | PRIMARY KEY (`id`),
32 | KEY `videoIdIdx` (`video_id`) USING BTREE COMMENT '评论列表使用视频id作为索引-方便查看视频下的评论列表'
33 | ) ENGINE=InnoDB AUTO_INCREMENT=1206 DEFAULT CHARSET=utf8 COMMENT='评论表';
34 |
35 | -- ----------------------------
36 | -- Table structure for follows
37 | -- ----------------------------
38 | DROP TABLE IF EXISTS `follows`;
39 | CREATE TABLE `follows` (
40 | `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
41 | `user_id` bigint(20) NOT NULL COMMENT '用户id',
42 | `follower_id` bigint(20) NOT NULL COMMENT '关注的用户',
43 | `cancel` tinyint(4) NOT NULL DEFAULT '0' COMMENT '默认关注为0,取消关注为1',
44 | PRIMARY KEY (`id`),
45 | UNIQUE KEY `userIdToFollowerIdIdx` (`user_id`,`follower_id`) USING BTREE,
46 | KEY `FollowerIdIdx` (`follower_id`) USING BTREE
47 | ) ENGINE=InnoDB AUTO_INCREMENT=1096 DEFAULT CHARSET=utf8 COMMENT='关注表';
48 |
49 | -- ----------------------------
50 | -- Table structure for likes
51 | -- ----------------------------
52 | DROP TABLE IF EXISTS `likes`;
53 | CREATE TABLE `likes` (
54 | `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
55 | `user_id` bigint(20) NOT NULL COMMENT '点赞用户id',
56 | `video_id` bigint(20) NOT NULL COMMENT '被点赞的视频id',
57 | `cancel` tinyint(4) NOT NULL DEFAULT '0' COMMENT '默认点赞为0,取消赞为1',
58 | PRIMARY KEY (`id`),
59 | UNIQUE KEY `userIdtoVideoIdIdx` (`user_id`,`video_id`) USING BTREE,
60 | KEY `userIdIdx` (`user_id`) USING BTREE,
61 | KEY `videoIdx` (`video_id`) USING BTREE
62 | ) ENGINE=InnoDB AUTO_INCREMENT=1229 DEFAULT CHARSET=utf8 COMMENT='点赞表';
63 |
64 | -- ----------------------------
65 | -- Table structure for users
66 | -- ----------------------------
67 | DROP TABLE IF EXISTS `users`;
68 | CREATE TABLE `users` (
69 | `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户id,自增主键',
70 | `name` varchar(255) NOT NULL COMMENT '用户名',
71 | `password` varchar(255) NOT NULL COMMENT '用户密码',
72 | PRIMARY KEY (`id`),
73 | KEY `name_password_idx` (`name`,`password`) USING BTREE
74 | ) ENGINE=InnoDB AUTO_INCREMENT=20044 DEFAULT CHARSET=utf8 COMMENT='用户表';
75 |
76 | -- ----------------------------
77 | -- Table structure for videos
78 | -- ----------------------------
79 | DROP TABLE IF EXISTS `videos`;
80 | CREATE TABLE `videos` (
81 | `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键,视频唯一id',
82 | `author_id` bigint(20) NOT NULL COMMENT '视频作者id',
83 | `play_url` varchar(255) NOT NULL COMMENT '播放url',
84 | `cover_url` varchar(255) NOT NULL COMMENT '封面url',
85 | `publish_time` datetime NOT NULL COMMENT '发布时间戳',
86 | `title` varchar(255) DEFAULT NULL COMMENT '视频名称',
87 | PRIMARY KEY (`id`),
88 | KEY `time` (`publish_time`) USING BTREE,
89 | KEY `author` (`author_id`) USING BTREE
90 | ) ENGINE=InnoDB AUTO_INCREMENT=115 DEFAULT CHARSET=utf8 COMMENT='\r\n视频表';
91 |
92 | -- ----------------------------
93 | -- Procedure structure for addFollowRelation
94 | -- ----------------------------
95 | DROP PROCEDURE IF EXISTS `addFollowRelation`;
96 | delimiter ;;
97 | CREATE PROCEDURE `addFollowRelation`(IN user_id int,IN follower_id int)
98 | BEGIN
99 | #Routine body goes here...
100 | # 声明记录个数变量。
101 | DECLARE cnt INT DEFAULT 0;
102 | # 获取记录个数变量。
103 | SELECT COUNT(1) FROM follows f where f.user_id = user_id AND f.follower_id = follower_id INTO cnt;
104 | # 判断是否已经存在该记录,并做出相应的插入关系、更新关系动作。
105 | # 插入操作。
106 | IF cnt = 0 THEN
107 | INSERT INTO follows(`user_id`,`follower_id`) VALUES(user_id,follower_id);
108 | END IF;
109 | # 更新操作
110 | IF cnt != 0 THEN
111 | UPDATE follows f SET f.cancel = 0 WHERE f.user_id = user_id AND f.follower_id = follower_id;
112 | END IF;
113 | END
114 | ;;
115 | delimiter ;
116 |
117 | -- ----------------------------
118 | -- Procedure structure for delFollowRelation
119 | -- ----------------------------
120 | DROP PROCEDURE IF EXISTS `delFollowRelation`;
121 | delimiter ;;
122 | CREATE PROCEDURE `delFollowRelation`(IN `user_id` int,IN `follower_id` int)
123 | BEGIN
124 | #Routine body goes here...
125 | # 定义记录个数变量,记录是否存在此关系,默认没有关系。
126 | DECLARE cnt INT DEFAULT 0;
127 | # 查看是否之前有关系。
128 | SELECT COUNT(1) FROM follows f WHERE f.user_id = user_id AND f.follower_id = follower_id INTO cnt;
129 | # 有关系,则需要update cancel = 1,使其关系无效。
130 | IF cnt = 1 THEN
131 | UPDATE follows f SET f.cancel = 1 WHERE f.user_id = user_id AND f.follower_id = follower_id;
132 | END IF;
133 | END
134 | ;;
135 | delimiter ;
136 |
137 | SET FOREIGN_KEY_CHECKS = 1;
138 |
--------------------------------------------------------------------------------
/config/nginx.conf:
--------------------------------------------------------------------------------
1 | # 服务器nginx配置
2 | # 已经配置好图片服务器,视频服务器
3 | #user www www;
4 | worker_processes auto;
5 | #error_log /www/wwwlogs/nginx_error.log crit;
6 | pid /www/server/nginx/logs/nginx.pid;
7 | worker_rlimit_nofile 51200;
8 |
9 | events
10 | {
11 | use epoll;
12 | worker_connections 51200;
13 | multi_accept on;
14 | }
15 |
16 | http
17 | {
18 | include mime.types;
19 | #include luawaf.conf;
20 |
21 | #include proxy.conf;
22 |
23 | default_type application/octet-stream;
24 |
25 | #server_names_hash_bucket_size 512;
26 | #client_header_buffer_size 32k;
27 | #large_client_header_buffers 4 32k;
28 | #client_max_body_size 50m;
29 |
30 | sendfile on;
31 | tcp_nopush on;
32 |
33 | keepalive_timeout 60;
34 |
35 | tcp_nodelay on;
36 | autoindex on; #开启nginx目录浏览功能
37 | autoindex_exact_size off; #文件大小从KB开始显示
38 | autoindex_localtime on; #显示文件修改时间为服务器本地时间
39 |
40 | fastcgi_connect_timeout 300;
41 | fastcgi_send_timeout 300;
42 | fastcgi_read_timeout 300;
43 | fastcgi_buffer_size 64k;
44 | fastcgi_buffers 4 64k;
45 | fastcgi_busy_buffers_size 128k;
46 | fastcgi_temp_file_write_size 256k;
47 | fastcgi_intercept_errors on;
48 | gzip on;
49 | gzip_min_length 1k;
50 | gzip_buffers 4 16k;
51 | gzip_http_version 1.1;
52 | gzip_comp_level 2;
53 | gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml;
54 | gzip_vary on;
55 | gzip_proxied expired no-cache no-store private auth;
56 | gzip_disable "MSIE [1-6]\.";
57 |
58 | limit_conn_zone $binary_remote_addr zone=perip:10m;
59 | limit_conn_zone $server_name zone=perserver:10m;
60 |
61 | server_tokens off;
62 | access_log off;
63 |
64 | server
65 | {
66 | listen 80;
67 | # server_name video;
68 | index index.html;
69 | root /home/ftpuser/video;
70 |
71 | error_page 404 /404.html;
72 | #include enable-php.conf;
73 |
74 | location / {
75 | root html;
76 | index index.html index.htm;
77 | }
78 | #http://43.138.25.60/1.mp4
79 | location ~ .*\.mp4$ {
80 | mp4;
81 | }
82 | #http://43.138.25.60/images/a.jpg
83 | location /images{
84 | root /home/ftpuser;
85 | }
86 | access_log /www/wwwlogs/access.log;
87 | }
88 | # include /www/server/panel/vhost/nginx/*.conf;
89 | }
90 |
--------------------------------------------------------------------------------
/config/vsftpd.conf:
--------------------------------------------------------------------------------
1 | # Example config file /etc/vsftpd/vsftpd.conf
2 | #
3 | # The default compiled in settings are fairly paranoid. This sample file
4 | # loosens things up a bit, to make the ftp daemon more usable.
5 | # Please see vsftpd.conf.5 for all compiled in defaults.
6 | #
7 | # READ THIS: This example file is NOT an exhaustive list of vsftpd options.
8 | # Please read the vsftpd.conf.5 manual page to get a full idea of vsftpd's
9 | # capabilities.
10 | #
11 | # Allow anonymous FTP? (Beware - allowed by default if you comment this out).
12 | anonymous_enable=NO
13 | #
14 | # Uncomment this to allow local users to log in.
15 | # When SELinux is enforcing check for SE bool ftp_home_dir
16 | local_enable=YES
17 | #
18 | # Uncomment this to enable any form of FTP write command.
19 | write_enable=YES
20 | #
21 | # Default umask for local users is 077. You may wish to change this to 022,
22 | # if your users expect that (022 is used by most other ftpd's)
23 | local_umask=022
24 | #
25 | # Uncomment this to allow the anonymous FTP user to upload files. This only
26 | # has an effect if the above global write enable is activated. Also, you will
27 | # obviously need to create a directory writable by the FTP user.
28 | # When SELinux is enforcing check for SE bool allow_ftpd_anon_write, allow_ftpd_full_access
29 | #anon_upload_enable=YES
30 | #
31 | # Uncomment this if you want the anonymous FTP user to be able to create
32 | # new directories.
33 | #anon_mkdir_write_enable=YES
34 | #
35 | # Activate directory messages - messages given to remote users when they
36 | # go into a certain directory.
37 | dirmessage_enable=YES
38 | #
39 | # Activate logging of uploads/downloads.
40 | xferlog_enable=YES
41 | #
42 | # Make sure PORT transfer connections originate from port 20 (ftp-data).
43 | connect_from_port_20=YES
44 | # You may override where the log file goes if you like. The default is shown
45 | # below.
46 | #xferlog_file=/var/log/xferlog
47 | #
48 | # If you want, you can have your log file in standard ftpd xferlog format.
49 | # Note that the default log file location is /var/log/xferlog in this case.
50 | xferlog_std_format=YES
51 | #
52 | # You may change the default value for timing out an idle session.
53 | #idle_session_timeout=600
54 | #
55 | # You may change the default value for timing out a data connection.
56 | #data_connection_timeout=120
57 | #
58 | # It is recommended that you define on your system a unique user which the
59 | # ftp server can use as a totally isolated and unprivileged user.
60 | #nopriv_user=ftpsecure
61 | #
62 | # Enable this and the server will recognise asynchronous ABOR requests. Not
63 | # recommended for security (the code is non-trivial). Not enabling it,
64 | # however, may confuse older FTP clients.
65 | #async_abor_enable=YES
66 | #
67 | # By default the server will pretend to allow ASCII mode but in fact ignore
68 | # the request. Turn on the below options to have the server actually do ASCII
69 | # mangling on files when in ASCII mode. The vsftpd.conf(5) man page explains
70 | # the behaviour when these options are disabled.
71 | # Beware that on some FTP servers, ASCII support allows a denial of service
72 | # attack (DoS) via the command "SIZE /big/file" in ASCII mode. vsftpd
73 | # predicted this attack and has always been safe, reporting the size of the
74 | # raw file.
75 | # ASCII mangling is a horrible feature of the protocol.
76 | #ascii_upload_enable=YES
77 | #ascii_download_enable=YES
78 | #
79 | # You may fully customise the login banner string:
80 | #ftpd_banner=Welcome to blah FTP service.
81 | #
82 | # You may specify a file of disallowed anonymous e-mail addresses. Apparently
83 | # useful for combatting certain DoS attacks.
84 | #deny_email_enable=YES
85 | # (default follows)
86 | #banned_email_file=/etc/vsftpd/banned_emails
87 | #
88 | # You may specify an explicit list of local users to chroot() to their home
89 | # directory. If chroot_local_user is YES, then this list becomes a list of
90 | # users to NOT chroot().
91 | # (Warning! chroot'ing can be very dangerous. If using chroot, make sure that
92 | # the user does not have write access to the top level directory within the
93 | # chroot)
94 | #chroot_local_user=YES
95 | #chroot_list_enable=YES
96 | # (default follows)
97 | #chroot_list_file=/etc/vsftpd/chroot_list
98 | #
99 | # You may activate the "-R" option to the builtin ls. This is disabled by
100 | # default to avoid remote users being able to cause excessive I/O on large
101 | # sites. However, some broken FTP clients such as "ncftp" and "mirror" assume
102 | # the presence of the "-R" option, so there is a strong case for enabling it.
103 | #ls_recurse_enable=YES
104 | #
105 | # When "listen" directive is enabled, vsftpd runs in standalone mode and
106 | # listens on IPv4 sockets. This directive cannot be used in conjunction
107 | # with the listen_ipv6 directive.
108 | listen=YES
109 | #
110 | # This directive enables listening on IPv6 sockets. By default, listening
111 | # on the IPv6 "any" address (::) will accept connections from both IPv6
112 | # and IPv4 clients. It is not necessary to listen on *both* IPv4 and IPv6
113 | # sockets. If you want that (perhaps because you want to listen on specific
114 | # addresses) then you must run two copies of vsftpd with two configuration
115 | # files.
116 | # Make sure, that one of the listen options is commented !!
117 | listen_ipv6=NO
118 |
119 | pam_service_name=vsftpd
120 | userlist_enable=YES
121 | tcp_wrappers=YES
122 | #仅允许user_list文件中的用户访问FTP服务
123 | userlist_deny=NO
124 | #被动模式端口范围
125 | pasv_min_port=30000
126 | pasv_max_port=30999
127 |
128 |
--------------------------------------------------------------------------------
/controller/commentController.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "TikTok/dao"
5 | "TikTok/service"
6 | "TikTok/util"
7 | "github.com/gin-gonic/gin"
8 | "log"
9 | "net/http"
10 | "strconv"
11 | "time"
12 | )
13 |
14 | // CommentListResponse
15 | // 评论列表返回参数
16 | type CommentListResponse struct {
17 | StatusCode int32 `json:"status_code"`
18 | StatusMsg string `json:"status_msg,omitempty"`
19 | CommentList []service.CommentInfo `json:"comment_list,omitempty"`
20 | }
21 |
22 | // CommentActionResponse
23 | // 发表评论返回参数
24 | type CommentActionResponse struct {
25 | StatusCode int32 `json:"status_code"`
26 | StatusMsg string `json:"status_msg,omitempty"`
27 | Comment service.CommentInfo `json:"comment"`
28 | }
29 |
30 | // CommentAction
31 | // 发表 or 删除评论 comment/action/
32 | func CommentAction(c *gin.Context) {
33 | log.Println("CommentController-Comment_Action: running") //函数已运行
34 | //获取userId
35 | id, _ := c.Get("userId")
36 | userid, _ := id.(string)
37 | userId, err := strconv.ParseInt(userid, 10, 64)
38 | log.Printf("err:%v", err)
39 | log.Printf("userId:%v", userId)
40 | //错误处理
41 | if err != nil {
42 | c.JSON(http.StatusOK, CommentActionResponse{
43 | StatusCode: -1,
44 | StatusMsg: "comment userId json invalid",
45 | })
46 | log.Println("CommentController-Comment_Action: return comment userId json invalid") //函数返回userId无效
47 | return
48 | }
49 | //获取videoId
50 | videoId, err := strconv.ParseInt(c.Query("video_id"), 10, 64)
51 | //错误处理
52 | if err != nil {
53 | c.JSON(http.StatusOK, CommentActionResponse{
54 | StatusCode: -1,
55 | StatusMsg: "comment videoId json invalid",
56 | })
57 | log.Println("CommentController-Comment_Action: return comment videoId json invalid") //函数返回视频id无效
58 | return
59 | }
60 | //获取操作类型
61 | actionType, err := strconv.ParseInt(c.Query("action_type"), 10, 32)
62 | //错误处理
63 | if err != nil || actionType < 1 || actionType > 2 {
64 | c.JSON(http.StatusOK, CommentActionResponse{
65 | StatusCode: -1,
66 | StatusMsg: "comment actionType json invalid",
67 | })
68 | log.Println("CommentController-Comment_Action: return actionType json invalid") //评论类型数据无效
69 | return
70 | }
71 | //调用service层评论函数
72 | commentService := new(service.CommentServiceImpl)
73 | if actionType == 1 { //actionType为1,则进行发表评论操作
74 | content := c.Query("comment_text")
75 | // 垃圾评论过滤。
76 | content = util.Filter.Replace(content, '#')
77 | // find, _ := util.Filter.FindIn(content)
78 | /*if find {
79 | log.Println("垃圾评论")
80 | c.JSON(http.StatusOK, CommentActionResponse{
81 | StatusCode: -1,
82 | StatusMsg: "垃圾评论",
83 | })
84 | return
85 | content = "*****"
86 | }
87 | */
88 | //发表评论数据准备
89 | var sendComment dao.Comment
90 | sendComment.UserId = userId
91 | sendComment.VideoId = videoId
92 | sendComment.CommentText = content
93 | timeNow := time.Now()
94 | sendComment.CreateDate = timeNow
95 | //发表评论
96 | commentInfo, err := commentService.Send(sendComment)
97 | //发表评论失败
98 | if err != nil {
99 | c.JSON(http.StatusOK, CommentActionResponse{
100 | StatusCode: -1,
101 | StatusMsg: "send comment failed",
102 | })
103 | log.Println("CommentController-Comment_Action: return send comment failed") //发表失败
104 | return
105 | }
106 |
107 | //发表评论成功:
108 | //返回结果
109 | c.JSON(http.StatusOK, CommentActionResponse{
110 | StatusCode: 0,
111 | StatusMsg: "send comment success",
112 | Comment: commentInfo,
113 | })
114 | log.Println("CommentController-Comment_Action: return Send success") //发表评论成功,返回正确信息
115 | return
116 | } else { //actionType为2,则进行删除评论操作
117 | //获取要删除的评论的id
118 | commentId, err := strconv.ParseInt(c.Query("comment_id"), 10, 64)
119 | if err != nil {
120 | c.JSON(http.StatusOK, CommentActionResponse{
121 | StatusCode: -1,
122 | StatusMsg: "delete commentId invalid",
123 | })
124 | log.Println("CommentController-Comment_Action: return commentId invalid") //评论id格式错误
125 | return
126 | }
127 | //删除评论操作
128 | err = commentService.DelComment(commentId)
129 | if err != nil { //删除评论失败
130 | str := err.Error()
131 | c.JSON(http.StatusOK, CommentActionResponse{
132 | StatusCode: -1,
133 | StatusMsg: str,
134 | })
135 | log.Println("CommentController-Comment_Action: return delete comment failed") //删除失败
136 | return
137 | }
138 | //删除评论成功
139 | c.JSON(http.StatusOK, CommentActionResponse{
140 | StatusCode: 0,
141 | StatusMsg: "delete comment success",
142 | })
143 |
144 | log.Println("CommentController-Comment_Action: return delete success") //函数执行成功,返回正确信息
145 | return
146 | }
147 | }
148 |
149 | // CommentList
150 | // 查看评论列表 comment/list/
151 | func CommentList(c *gin.Context) {
152 | log.Println("CommentController-Comment_List: running") //函数已运行
153 | //获取userId
154 | id, _ := c.Get("userId")
155 | userid, _ := id.(string)
156 | userId, err := strconv.ParseInt(userid, 10, 64)
157 | //log.Printf("err:%v", err)
158 | //log.Printf("userId:%v", userId)
159 |
160 | //获取videoId
161 | videoId, err := strconv.ParseInt(c.Query("video_id"), 10, 64)
162 | //错误处理
163 | if err != nil {
164 | c.JSON(http.StatusOK, Response{
165 | StatusCode: -1,
166 | StatusMsg: "comment videoId json invalid",
167 | })
168 | log.Println("CommentController-Comment_List: return videoId json invalid") //视频id格式有误
169 | return
170 | }
171 | log.Printf("videoId:%v", videoId)
172 |
173 | //调用service层评论函数
174 | commentService := new(service.CommentServiceImpl)
175 | commentList, err := commentService.GetList(videoId, userId)
176 | //commentList, err := commentService.GetListFromRedis(videoId, userId)
177 | if err != nil { //获取评论列表失败
178 | c.JSON(http.StatusOK, CommentListResponse{
179 | StatusCode: -1,
180 | StatusMsg: err.Error(),
181 | })
182 | log.Println("CommentController-Comment_List: return list false") //查询列表失败
183 | return
184 | }
185 |
186 | //获取评论列表成功
187 | c.JSON(http.StatusOK, CommentListResponse{
188 | StatusCode: 0,
189 | StatusMsg: "get comment list success",
190 | CommentList: commentList,
191 | })
192 | log.Println("CommentController-Comment_List: return success") //成功返回列表
193 | return
194 | }
195 |
--------------------------------------------------------------------------------
/controller/doc.go:
--------------------------------------------------------------------------------
1 | // Package controller
2 | // @Description 分层结构的controller包
3 | // @Author 创建人siyixiong;
4 | // @Update 20220513 (创建/修改时间);
5 | package controller
6 |
--------------------------------------------------------------------------------
/controller/followController.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "TikTok/service"
5 | "fmt"
6 | "github.com/gin-gonic/gin"
7 | "log"
8 | "net/http"
9 | "strconv"
10 | )
11 |
12 | // RelationActionResp 关注和取消关注需要返回结构。
13 | type RelationActionResp struct {
14 | Response
15 | }
16 |
17 | // FollowingResp 获取关注列表需要返回的结构。
18 | type FollowingResp struct {
19 | Response
20 | UserList []service.User `json:"user_list,omitempty"`
21 | }
22 |
23 | // FollowersResp 获取粉丝列表需要返回的结构。
24 | type FollowersResp struct {
25 | Response
26 | // 必须大写,才能序列化
27 | UserList []service.User `json:"user_list,omitempty"`
28 | }
29 |
30 | // RelationAction 处理关注和取消关注请求。
31 | func RelationAction(c *gin.Context) {
32 | userId, err1 := strconv.ParseInt(c.GetString("userId"), 10, 64)
33 | toUserId, err2 := strconv.ParseInt(c.Query("to_user_id"), 10, 64)
34 | actionType, err3 := strconv.ParseInt(c.Query("action_type"), 10, 64)
35 | // fmt.Println(userId, toUserId, actionType)
36 | // 传入参数格式有问题。
37 | if nil != err1 || nil != err2 || nil != err3 || actionType < 1 || actionType > 2 {
38 | fmt.Printf("fail")
39 | c.JSON(http.StatusOK, RelationActionResp{
40 | Response{
41 | StatusCode: -1,
42 | StatusMsg: "用户id格式错误",
43 | },
44 | })
45 | return
46 | }
47 | // 正常处理
48 | fsi := service.NewFSIInstance()
49 | switch {
50 | // 关注
51 | case 1 == actionType:
52 | go fsi.AddFollowRelation(userId, toUserId)
53 | // 取关
54 | case 2 == actionType:
55 | go fsi.DeleteFollowRelation(userId, toUserId)
56 | }
57 | log.Println("关注、取关成功。")
58 | c.JSON(http.StatusOK, RelationActionResp{
59 | Response{
60 | StatusCode: 0,
61 | StatusMsg: "OK",
62 | },
63 | })
64 | }
65 |
66 | // GetFollowing 处理获取关注列表请求。
67 | func GetFollowing(c *gin.Context) {
68 | userId, err := strconv.ParseInt(c.Query("user_id"), 10, 64)
69 | // 用户id解析出错。
70 | if nil != err {
71 | c.JSON(http.StatusOK, FollowingResp{
72 | Response: Response{
73 | StatusCode: -1,
74 | StatusMsg: "用户id格式错误。",
75 | },
76 | UserList: nil,
77 | })
78 | return
79 | }
80 | // 正常获取关注列表
81 | fsi := service.NewFSIInstance()
82 | users, err := fsi.GetFollowing(userId)
83 | // 获取关注列表时出错。
84 | if err != nil {
85 | c.JSON(http.StatusOK, FollowingResp{
86 | Response: Response{
87 | StatusCode: -1,
88 | StatusMsg: "获取关注列表时出错。",
89 | },
90 | UserList: nil,
91 | })
92 | return
93 | }
94 | // 成功获取到关注列表。
95 | log.Println("获取关注列表成功。")
96 | c.JSON(http.StatusOK, FollowingResp{
97 | UserList: users,
98 | Response: Response{
99 | StatusCode: 0,
100 | StatusMsg: "OK",
101 | },
102 | })
103 | }
104 |
105 | // GetFollowers 处理获取关注列表请求
106 | func GetFollowers(c *gin.Context) {
107 | userId, err := strconv.ParseInt(c.Query("user_id"), 10, 64)
108 | // 用户id解析出错。
109 | if nil != err {
110 | c.JSON(http.StatusOK, FollowersResp{
111 | Response: Response{
112 | StatusCode: -1,
113 | StatusMsg: "用户id格式错误。",
114 | },
115 | UserList: nil,
116 | })
117 | return
118 | }
119 | // 正常获取粉丝列表
120 | fsi := service.NewFSIInstance()
121 | users, err := fsi.GetFollowers(userId)
122 | // 获取关注列表时出错。
123 | if err != nil {
124 | c.JSON(http.StatusOK, FollowersResp{
125 | Response: Response{
126 | StatusCode: -1,
127 | StatusMsg: "获取粉丝列表时出错。",
128 | },
129 | UserList: nil,
130 | })
131 | return
132 | }
133 | // 成功获取到粉丝列表。
134 | //log.Println("获取粉丝列表成功。")
135 | c.JSON(http.StatusOK, FollowersResp{
136 | Response: Response{
137 | StatusCode: 0,
138 | StatusMsg: "OK",
139 | },
140 | UserList: users,
141 | })
142 | }
143 |
--------------------------------------------------------------------------------
/controller/likeController.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "TikTok/service"
5 | "github.com/gin-gonic/gin"
6 | "log"
7 | "net/http"
8 | "strconv"
9 | )
10 |
11 | type likeResponse struct {
12 | StatusCode int32 `json:"status_code"`
13 | StatusMsg string `json:"status_msg,omitempty"`
14 | }
15 |
16 | type GetFavouriteListResponse struct {
17 | StatusCode int32 `json:"status_code"`
18 | StatusMsg string `json:"status_msg,omitempty"`
19 | VideoList []service.Video `json:"video_list,omitempty"`
20 | }
21 |
22 | // FavoriteAction 点赞或者取消赞操作;
23 | func FavoriteAction(c *gin.Context) {
24 | strUserId := c.GetString("userId")
25 | userId, _ := strconv.ParseInt(strUserId, 10, 64)
26 | strVideoId := c.Query("video_id")
27 | videoId, _ := strconv.ParseInt(strVideoId, 10, 64)
28 | strActionType := c.Query("action_type")
29 | actionType, _ := strconv.ParseInt(strActionType, 10, 64)
30 | like := new(service.LikeServiceImpl)
31 | //获取点赞或者取消赞操作的错误信息
32 | err := like.FavouriteAction(userId, videoId, int32(actionType))
33 | if err == nil {
34 | log.Printf("方法like.FavouriteAction(userid, videoId, int32(actiontype) 成功")
35 | c.JSON(http.StatusOK, likeResponse{
36 | StatusCode: 0,
37 | StatusMsg: "favourite action success",
38 | })
39 | } else {
40 | log.Printf("方法like.FavouriteAction(userid, videoId, int32(actiontype) 失败:%v", err)
41 | c.JSON(http.StatusOK, likeResponse{
42 | StatusCode: 1,
43 | StatusMsg: "favourite action fail",
44 | })
45 | }
46 | }
47 |
48 | // GetFavouriteList 获取点赞列表;
49 | func GetFavouriteList(c *gin.Context) {
50 | strUserId := c.Query("user_id")
51 | strCurId := c.GetString("userId")
52 | userId, _ := strconv.ParseInt(strUserId, 10, 64)
53 | curId, _ := strconv.ParseInt(strCurId, 10, 64)
54 | like := GetVideo()
55 | videos, err := like.GetFavouriteList(userId, curId)
56 | if err == nil {
57 | log.Printf("方法like.GetFavouriteList(userid) 成功")
58 | c.JSON(http.StatusOK, GetFavouriteListResponse{
59 | StatusCode: 0,
60 | StatusMsg: "get favouriteList success",
61 | VideoList: videos,
62 | })
63 | } else {
64 | log.Printf("方法like.GetFavouriteList(userid) 失败:%v", err)
65 | c.JSON(http.StatusOK, GetFavouriteListResponse{
66 | StatusCode: 1,
67 | StatusMsg: "get favouriteList fail ",
68 | })
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/controller/userController.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "TikTok/dao"
5 | "TikTok/service"
6 | "github.com/gin-gonic/gin"
7 | "log"
8 | "net/http"
9 | "strconv"
10 | )
11 |
12 | type Response struct {
13 | StatusCode int32 `json:"status_code"`
14 | StatusMsg string `json:"status_msg,omitempty"`
15 | }
16 |
17 | type UserLoginResponse struct {
18 | Response
19 | UserId int64 `json:"user_id,omitempty"`
20 | Token string `json:"token"`
21 | }
22 |
23 | type UserResponse struct {
24 | Response
25 | User service.User `json:"user"`
26 | }
27 |
28 | // Register POST douyin/user/register/ 用户注册
29 | func Register(c *gin.Context) {
30 | username := c.Query("username")
31 | password := c.Query("password")
32 |
33 | usi := service.UserServiceImpl{}
34 |
35 | u := usi.GetTableUserByUsername(username)
36 | if username == u.Name {
37 | c.JSON(http.StatusOK, UserLoginResponse{
38 | Response: Response{StatusCode: 1, StatusMsg: "User already exist"},
39 | })
40 | } else {
41 | newUser := dao.TableUser{
42 | Name: username,
43 | Password: service.EnCoder(password),
44 | }
45 | if usi.InsertTableUser(&newUser) != true {
46 | println("Insert Data Fail")
47 | }
48 | u := usi.GetTableUserByUsername(username)
49 | token := service.GenerateToken(username)
50 | log.Println("注册返回的id: ", u.Id)
51 | c.JSON(http.StatusOK, UserLoginResponse{
52 | Response: Response{StatusCode: 0},
53 | UserId: u.Id,
54 | Token: token,
55 | })
56 | }
57 | }
58 |
59 | // Login POST douyin/user/login/ 用户登录
60 | func Login(c *gin.Context) {
61 | username := c.Query("username")
62 | password := c.Query("password")
63 | encoderPassword := service.EnCoder(password)
64 | println(encoderPassword)
65 |
66 | usi := service.UserServiceImpl{}
67 |
68 | u := usi.GetTableUserByUsername(username)
69 |
70 | if encoderPassword == u.Password {
71 | token := service.GenerateToken(username)
72 | c.JSON(http.StatusOK, UserLoginResponse{
73 | Response: Response{StatusCode: 0},
74 | UserId: u.Id,
75 | Token: token,
76 | })
77 | } else {
78 | c.JSON(http.StatusOK, UserLoginResponse{
79 | Response: Response{StatusCode: 1, StatusMsg: "Username or Password Error"},
80 | })
81 | }
82 | }
83 |
84 | // UserInfo GET douyin/user/ 用户信息
85 | func UserInfo(c *gin.Context) {
86 | user_id := c.Query("user_id")
87 | id, _ := strconv.ParseInt(user_id, 10, 64)
88 |
89 | usi := service.UserServiceImpl{
90 | FollowService: &service.FollowServiceImp{},
91 | LikeService: &service.LikeServiceImpl{},
92 | }
93 |
94 | if u, err := usi.GetUserById(id); err != nil {
95 | c.JSON(http.StatusOK, UserResponse{
96 | Response: Response{StatusCode: 1, StatusMsg: "User Doesn't Exist"},
97 | })
98 | } else {
99 | c.JSON(http.StatusOK, UserResponse{
100 | Response: Response{StatusCode: 0},
101 | User: u,
102 | })
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/controller/videoController.go:
--------------------------------------------------------------------------------
1 | package controller
2 |
3 | import (
4 | "TikTok/service"
5 | "github.com/gin-gonic/gin"
6 | "log"
7 | "net/http"
8 | "strconv"
9 | "time"
10 | )
11 |
12 | type FeedResponse struct {
13 | Response
14 | VideoList []service.Video `json:"video_list"`
15 | NextTime int64 `json:"next_time"`
16 | }
17 |
18 | type VideoListResponse struct {
19 | Response
20 | VideoList []service.Video `json:"video_list"`
21 | }
22 |
23 | // Feed /feed/
24 | func Feed(c *gin.Context) {
25 | inputTime := c.Query("latest_time")
26 | log.Printf("传入的时间" + inputTime)
27 | var lastTime time.Time
28 | if inputTime != "0" {
29 | me, _ := strconv.ParseInt(inputTime, 10, 64)
30 | lastTime = time.Unix(me, 0)
31 | } else {
32 | lastTime = time.Now()
33 | }
34 | log.Printf("获取到时间戳%v", lastTime)
35 | userId, _ := strconv.ParseInt(c.GetString("userId"), 10, 64)
36 | log.Printf("获取到用户id:%v\n", userId)
37 | videoService := GetVideo()
38 | feed, nextTime, err := videoService.Feed(lastTime, userId)
39 | if err != nil {
40 | log.Printf("方法videoService.Feed(lastTime, userId) 失败:%v", err)
41 | c.JSON(http.StatusOK, FeedResponse{
42 | Response: Response{StatusCode: 1, StatusMsg: "获取视频流失败"},
43 | })
44 | return
45 | }
46 | log.Printf("方法videoService.Feed(lastTime, userId) 成功")
47 | c.JSON(http.StatusOK, FeedResponse{
48 | Response: Response{StatusCode: 0},
49 | VideoList: feed,
50 | NextTime: nextTime.Unix(),
51 | })
52 | }
53 |
54 | // Publish /publish/action/
55 | func Publish(c *gin.Context) {
56 | data, err := c.FormFile("data")
57 | userId, _ := strconv.ParseInt(c.GetString("userId"), 10, 64)
58 | log.Printf("获取到用户id:%v\n", userId)
59 | title := c.PostForm("title")
60 | log.Printf("获取到视频title:%v\n", title)
61 | if err != nil {
62 | log.Printf("获取视频流失败:%v", err)
63 | c.JSON(http.StatusOK, Response{
64 | StatusCode: 1,
65 | StatusMsg: err.Error(),
66 | })
67 | return
68 | }
69 |
70 | videoService := GetVideo()
71 | err = videoService.Publish(data, userId, title)
72 | if err != nil {
73 | log.Printf("方法videoService.Publish(data, userId) 失败:%v", err)
74 | c.JSON(http.StatusOK, Response{
75 | StatusCode: 1,
76 | StatusMsg: err.Error(),
77 | })
78 | return
79 | }
80 | log.Printf("方法videoService.Publish(data, userId) 成功")
81 |
82 | c.JSON(http.StatusOK, Response{
83 | StatusCode: 0,
84 | StatusMsg: "uploaded successfully",
85 | })
86 | }
87 |
88 | // PublishList /publish/list/
89 | func PublishList(c *gin.Context) {
90 | user_Id, _ := c.GetQuery("user_id")
91 | userId, _ := strconv.ParseInt(user_Id, 10, 64)
92 | log.Printf("获取到用户id:%v\n", userId)
93 | curId, _ := strconv.ParseInt(c.GetString("userId"), 10, 64)
94 | log.Printf("获取到当前用户id:%v\n", curId)
95 | videoService := GetVideo()
96 | list, err := videoService.List(userId, curId)
97 | if err != nil {
98 | log.Printf("调用videoService.List(%v)出现错误:%v\n", userId, err)
99 | c.JSON(http.StatusOK, VideoListResponse{
100 | Response: Response{StatusCode: 1, StatusMsg: "获取视频列表失败"},
101 | })
102 | return
103 | }
104 | log.Printf("调用videoService.List(%v)成功", userId)
105 | c.JSON(http.StatusOK, VideoListResponse{
106 | Response: Response{StatusCode: 0},
107 | VideoList: list,
108 | })
109 | }
110 |
111 | // GetVideo 拼装videoService
112 | func GetVideo() service.VideoServiceImpl {
113 | var userService service.UserServiceImpl
114 | var followService service.FollowServiceImp
115 | var videoService service.VideoServiceImpl
116 | var likeService service.LikeServiceImpl
117 | var commentService service.CommentServiceImpl
118 | userService.FollowService = &followService
119 | userService.LikeService = &likeService
120 | followService.UserService = &userService
121 | likeService.VideoService = &videoService
122 | commentService.UserService = &userService
123 | videoService.CommentService = &commentService
124 | videoService.LikeService = &likeService
125 | videoService.UserService = &userService
126 | return videoService
127 | }
128 |
--------------------------------------------------------------------------------
/dao/commentDao.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "TikTok/config"
5 | "errors"
6 | "log"
7 | "time"
8 | )
9 |
10 | // Comment
11 | // 评论信息-数据库中的结构体-dao层使用
12 | type Comment struct {
13 | Id int64 //评论id
14 | UserId int64 //评论用户id
15 | VideoId int64 //视频id
16 | CommentText string //评论内容
17 | CreateDate time.Time //评论发布的日期mm-dd
18 | Cancel int32 //取消评论为1,发布评论为0
19 | }
20 |
21 | // TableName 修改表名映射
22 | func (Comment) TableName() string {
23 | return "comments"
24 | }
25 |
26 | // Count
27 | // 1、使用video id 查询Comment数量
28 | func Count(videoId int64) (int64, error) {
29 | log.Println("CommentDao-Count: running") //函数已运行
30 | //Init()
31 | var count int64
32 | //数据库中查询评论数量
33 | err := Db.Model(Comment{}).Where(map[string]interface{}{"video_id": videoId, "cancel": config.ValidComment}).Count(&count).Error
34 | if err != nil {
35 | log.Println("CommentDao-Count: return count failed") //函数返回提示错误信息
36 | return -1, errors.New("find comments count failed")
37 | }
38 | log.Println("CommentDao-Count: return count success") //函数执行成功,返回正确信息
39 | return count, nil
40 | }
41 |
42 | //CommentIdList 根据视频id获取评论id 列表
43 | func CommentIdList(videoId int64) ([]string, error) {
44 | var commentIdList []string
45 | err := Db.Model(Comment{}).Select("id").Where("video_id = ?", videoId).Find(&commentIdList).Error
46 | if err != nil {
47 | log.Println("CommentIdList:", err)
48 | return nil, err
49 | }
50 | return commentIdList, nil
51 | }
52 |
53 | // InsertComment
54 | // 2、发表评论
55 | func InsertComment(comment Comment) (Comment, error) {
56 | log.Println("CommentDao-InsertComment: running") //函数已运行
57 | //数据库中插入一条评论信息
58 | err := Db.Model(Comment{}).Create(&comment).Error
59 | if err != nil {
60 | log.Println("CommentDao-InsertComment: return create comment failed") //函数返回提示错误信息
61 | return Comment{}, errors.New("create comment failed")
62 | }
63 | log.Println("CommentDao-InsertComment: return success") //函数执行成功,返回正确信息
64 | return comment, nil
65 | }
66 |
67 | // DeleteComment
68 | // 3、删除评论,传入评论id
69 | func DeleteComment(id int64) error {
70 | log.Println("CommentDao-DeleteComment: running") //函数已运行
71 | var commentInfo Comment
72 | //先查询是否有此评论
73 | result := Db.Model(Comment{}).Where(map[string]interface{}{"id": id, "cancel": config.ValidComment}).First(&commentInfo)
74 | if result.RowsAffected == 0 { //查询到此评论数量为0则返回无此评论
75 | log.Println("CommentDao-DeleteComment: return del comment is not exist") //函数返回提示错误信息
76 | return errors.New("del comment is not exist")
77 | }
78 | //数据库中删除评论-更新评论状态为-1
79 | err := Db.Model(Comment{}).Where("id = ?", id).Update("cancel", config.InvalidComment).Error
80 | if err != nil {
81 | log.Println("CommentDao-DeleteComment: return del comment failed") //函数返回提示错误信息
82 | return errors.New("del comment failed")
83 | }
84 | log.Println("CommentDao-DeleteComment: return success") //函数执行成功,返回正确信息
85 | return nil
86 | }
87 |
88 | // GetCommentList
89 | // 4.根据视频id查询所属评论全部列表信息
90 | func GetCommentList(videoId int64) ([]Comment, error) {
91 | log.Println("CommentDao-GetCommentList: running") //函数已运行
92 | //数据库中查询评论信息list
93 | var commentList []Comment
94 | result := Db.Model(Comment{}).Where(map[string]interface{}{"video_id": videoId, "cancel": config.ValidComment}).
95 | Order("create_date desc").Find(&commentList)
96 | //若此视频没有评论信息,返回空列表,不报错
97 | if result.RowsAffected == 0 {
98 | log.Println("CommentDao-GetCommentList: return there are no comments") //函数返回提示无评论
99 | return nil, nil
100 | }
101 | //若获取评论列表出错
102 | if result.Error != nil {
103 | log.Println(result.Error.Error())
104 | log.Println("CommentDao-GetCommentList: return get comment list failed") //函数返回提示获取评论错误
105 | return commentList, errors.New("get comment list failed")
106 | }
107 | log.Println("CommentDao-GetCommentList: return commentList success") //函数执行成功,返回正确信息
108 | return commentList, nil
109 | }
110 |
--------------------------------------------------------------------------------
/dao/commentDao_test.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | "time"
7 | )
8 |
9 | //1、使用video id 查询Comment数量 的测试函数
10 | func TestCountComment(t *testing.T) {
11 | Init()
12 | count, err := Count(14)
13 | fmt.Printf("%v\n", count)
14 | fmt.Printf("[%v]", err)
15 | }
16 |
17 | //2、发表评论 的测试函数
18 | func TestInsertComment(t *testing.T) {
19 | Init()
20 | nowTime := time.Now() //评论时间记录
21 | comment := Comment{
22 | UserId: 20008,
23 | VideoId: 1,
24 | CommentText: "user20008commentVideo1-2",
25 | CreateDate: nowTime,
26 | Cancel: 0,
27 | }
28 | comList, err := InsertComment(comment)
29 | fmt.Printf("[comList:%v][err:%v]", comList, err)
30 | }
31 |
32 | //3、删除评论 的测试函数
33 | func TestDelComment(t *testing.T) {
34 | Init()
35 | err := DeleteComment(int64(8))
36 | fmt.Printf("[%v]", err)
37 | }
38 |
39 | //4.根据视频id查询所属评论全部列表信息 的测试函数
40 | func TestCommentList(t *testing.T) {
41 | Init()
42 | list, err := GetCommentList(int64(1))
43 | fmt.Printf("%v\n", list)
44 | fmt.Printf("[%v]", err)
45 | }
46 |
--------------------------------------------------------------------------------
/dao/doc.go:
--------------------------------------------------------------------------------
1 | // Package dao,
2 | // @Description 分层结构的dao包,本包内用来编写数据库读写的相关代码,
3 | // @Author 创建人siyixiong,
4 | // @Update 20220513 (创建/修改时间),
5 | package dao
6 |
--------------------------------------------------------------------------------
/dao/followDao.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "log"
5 | "sync"
6 | )
7 |
8 | // Follow 用户关系结构,对应用户关系表。
9 | type Follow struct {
10 | Id int64
11 | UserId int64
12 | FollowerId int64
13 | Cancel int8
14 | }
15 |
16 | // TableName 设置Follow结构体对应数据库表名。
17 | func (Follow) TableName() string {
18 | return "follows"
19 | }
20 |
21 | // FollowDao 把dao层看成整体,把dao的curd封装在一个结构体中。
22 | type FollowDao struct {
23 | }
24 |
25 | var (
26 | followDao *FollowDao //操作该dao层crud的结构体变量。
27 | followOnce sync.Once //单例限定,去限定申请一个followDao结构体变量。
28 | )
29 |
30 | // NewFollowDaoInstance 生成并返回followDao的单例对象。
31 | func NewFollowDaoInstance() *FollowDao {
32 | followOnce.Do(
33 | func() {
34 | followDao = &FollowDao{}
35 | })
36 | return followDao
37 | }
38 |
39 | /*
40 | 下面为FollowDao的成员方法,即crud逻辑。
41 | */
42 |
43 | // FindRelation 给定当前用户和目标用户id,查询follow表中相应的记录。
44 | func (*FollowDao) FindRelation(userId int64, targetId int64) (*Follow, error) {
45 | // follow变量用于后续存储数据库查出来的用户关系。
46 | follow := Follow{}
47 | //当查询出现错误时,日志打印err msg,并return err.
48 | if err := Db.
49 | Where("user_id = ?", targetId).
50 | Where("follower_id = ?", userId).
51 | Where("cancel = ?", 0).
52 | Take(&follow).Error; nil != err {
53 | // 当没查到数据时,gorm也会报错。
54 | if "record not found" == err.Error() {
55 | return nil, nil
56 | }
57 | log.Println(err.Error())
58 | return nil, err
59 | }
60 | //正常情况,返回取到的值和空err.
61 | return &follow, nil
62 | }
63 |
64 | // GetFollowerCnt 给定当前用户id,查询follow表中该用户的粉丝数。
65 | func (*FollowDao) GetFollowerCnt(userId int64) (int64, error) {
66 | // 用于存储当前用户粉丝数的变量
67 | var cnt int64
68 | // 当查询出现错误的情况,日志打印err msg,并返回err.
69 | if err := Db.
70 | Model(Follow{}).
71 | Where("user_id = ?", userId).
72 | Where("cancel = ?", 0).
73 | Count(&cnt).Error; nil != err {
74 | log.Println(err.Error())
75 | return 0, err
76 | }
77 | // 正常情况,返回取到的粉丝数。
78 | return cnt, nil
79 | }
80 |
81 | // GetFollowingCnt 给定当前用户id,查询follow表中该用户关注了多少人。
82 | func (*FollowDao) GetFollowingCnt(userId int64) (int64, error) {
83 | // 用于存储当前用户关注了多少人。
84 | var cnt int64
85 | // 查询出错,日志打印err msg,并return err
86 | if err := Db.Model(Follow{}).
87 | Where("follower_id = ?", userId).
88 | Where("cancel = ?", 0).
89 | Count(&cnt).Error; nil != err {
90 | log.Println(err.Error())
91 | return 0, err
92 | }
93 | // 查询成功,返回人数。
94 | return cnt, nil
95 | }
96 |
97 | // InsertFollowRelation 给定用户和目标对象id,插入其关注关系。
98 | func (*FollowDao) InsertFollowRelation(userId int64, targetId int64) (bool, error) {
99 | // 生成需要插入的关系结构体。
100 | follow := Follow{
101 | UserId: userId,
102 | FollowerId: targetId,
103 | Cancel: 0,
104 | }
105 | // 插入失败,返回err.
106 | if err := Db.Select("UserId", "FollowerId", "Cancel").Create(&follow).Error; nil != err {
107 | log.Println(err.Error())
108 | return false, err
109 | }
110 | // 插入成功
111 | return true, nil
112 | }
113 |
114 | // FindEverFollowing 给定当前用户和目标用户id,查看曾经是否有关注关系。
115 | func (*FollowDao) FindEverFollowing(userId int64, targetId int64) (*Follow, error) {
116 | // 用于存储查出来的关注关系。
117 | follow := Follow{}
118 | //当查询出现错误时,日志打印err msg,并return err.
119 | if err := Db.
120 | Where("user_id = ?", userId).
121 | Where("follower_id = ?", targetId).
122 | Where("cancel = ? or cancel = ?", 0, 1).
123 | Take(&follow).Error; nil != err {
124 | // 当没查到记录报错时,不当做错误处理。
125 | if "record not found" == err.Error() {
126 | return nil, nil
127 | }
128 | log.Println(err.Error())
129 | return nil, err
130 | }
131 | //正常情况,返回取到的关系和空err.
132 | return &follow, nil
133 | }
134 |
135 | // UpdateFollowRelation 给定用户和目标用户的id,更新他们的关系为取消关注或再次关注。
136 | func (*FollowDao) UpdateFollowRelation(userId int64, targetId int64, cancel int8) (bool, error) {
137 | // 更新失败,返回错误。
138 | if err := Db.Model(Follow{}).
139 | Where("user_id = ?", userId).
140 | Where("follower_id = ?", targetId).
141 | Update("cancel", cancel).Error; nil != err {
142 | // 更新失败,打印错误日志。
143 | log.Println(err.Error())
144 | return false, err
145 | }
146 | // 更新成功。
147 | return true, nil
148 | }
149 |
150 | // GetFollowingIds 给定用户id,查询他关注了哪些人的id。
151 | func (*FollowDao) GetFollowingIds(userId int64) ([]int64, error) {
152 | var ids []int64
153 | if err := Db.
154 | Model(Follow{}).
155 | Where("follower_id = ?", userId).
156 | Pluck("user_id", &ids).Error; nil != err {
157 | // 没有关注任何人,但是不能算错。
158 | if "record not found" == err.Error() {
159 | return nil, nil
160 | }
161 | // 查询出错。
162 | log.Println(err.Error())
163 | return nil, err
164 | }
165 | // 查询成功。
166 | return ids, nil
167 | }
168 |
169 | // GetFollowersIds 给定用户id,查询他关注了哪些人的id。
170 | func (*FollowDao) GetFollowersIds(userId int64) ([]int64, error) {
171 | var ids []int64
172 | if err := Db.
173 | Model(Follow{}).
174 | Where("user_id = ?", userId).
175 | Where("cancel = ?", 0).
176 | Pluck("follower_id", &ids).Error; nil != err {
177 | // 没有粉丝,但是不能算错。
178 | if "record not found" == err.Error() {
179 | return nil, nil
180 | }
181 | // 查询出错。
182 | log.Println(err.Error())
183 | return nil, err
184 | }
185 | // 查询成功。
186 | return ids, nil
187 | }
188 |
--------------------------------------------------------------------------------
/dao/followDao_test.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
--------------------------------------------------------------------------------
/dao/initDao.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "gorm.io/driver/mysql"
5 | "gorm.io/gorm"
6 | "gorm.io/gorm/logger"
7 | "log"
8 | "os"
9 | "time"
10 | )
11 |
12 | var Db *gorm.DB
13 |
14 | func Init() {
15 | newLogger := logger.New(
16 | log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
17 | logger.Config{
18 | SlowThreshold: time.Second, // 慢 SQL 阈值
19 | LogLevel: logger.Error, // Log level
20 | Colorful: true, // 彩色打印
21 | },
22 | )
23 | var err error
24 | dsn := "douyin:zjqxy@tcp(43.138.25.60:3306)/douyin?charset=utf8mb4&parseTime=True&loc=Local"
25 | //想要正确的处理time.Time,需要带上 parseTime 参数,
26 | //要支持完整的UTF-8编码,需要将 charset=utf8 更改为 charset=utf8mb4
27 | Db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
28 | Logger: newLogger,
29 | })
30 | if err != nil {
31 | log.Panicln("err:", err.Error())
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/dao/initDao_test.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
--------------------------------------------------------------------------------
/dao/likeDao.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "TikTok/config"
5 | "errors"
6 | "log"
7 | )
8 |
9 | // Like 表的结构。
10 | type Like struct {
11 | Id int64 //自增主键
12 | UserId int64 //点赞用户id
13 | VideoId int64 //视频id
14 | Cancel int8 //是否点赞,0为点赞,1为取消赞
15 | }
16 |
17 | // TableName 修改表名映射
18 | func (Like) TableName() string {
19 | return "likes"
20 | }
21 |
22 | // GetLikeUserIdList 根据videoId获取点赞userId
23 | func GetLikeUserIdList(videoId int64) ([]int64, error) {
24 | var likeUserIdList []int64 //存所有该视频点赞用户id;
25 | //查询likes表对应视频id点赞用户,返回查询结果
26 | err := Db.Model(Like{}).Where(map[string]interface{}{"video_id": videoId, "cancel": config.IsLike}).
27 | Pluck("user_id", &likeUserIdList).Error
28 | //查询过程出现错误,返回默认值0,并输出错误信息
29 | if err != nil {
30 | log.Println(err.Error())
31 | return nil, errors.New("get likeUserIdList failed")
32 | } else {
33 | //没查询到或者查询到结果,返回数量以及无报错
34 | return likeUserIdList, nil
35 | }
36 | }
37 |
38 | // UpdateLike 根据userId,videoId,actionType点赞或者取消赞
39 | func UpdateLike(userId int64, videoId int64, actionType int32) error {
40 | //更新当前用户观看视频的点赞状态“cancel”,返回错误结果
41 | err := Db.Model(Like{}).Where(map[string]interface{}{"user_id": userId, "video_id": videoId}).
42 | Update("cancel", actionType).Error
43 | //如果出现错误,返回更新数据库失败
44 | if err != nil {
45 | log.Println(err.Error())
46 | return errors.New("update data fail")
47 | }
48 | //更新操作成功
49 | return nil
50 | }
51 |
52 | // InsertLike 插入点赞数据
53 | func InsertLike(likeData Like) error {
54 | //创建点赞数据,默认为点赞,cancel为0,返回错误结果
55 | err := Db.Model(Like{}).Create(&likeData).Error
56 | //如果有错误结果,返回插入失败
57 | if err != nil {
58 | log.Println(err.Error())
59 | return errors.New("insert data fail")
60 | }
61 | return nil
62 | }
63 |
64 | // GetLikeInfo 根据userId,videoId查询点赞信息
65 | func GetLikeInfo(userId int64, videoId int64) (Like, error) {
66 | //创建一条空like结构体,用来存储查询到的信息
67 | var likeInfo Like
68 | //根据userid,videoId查询是否有该条信息,如果有,存储在likeInfo,返回查询结果
69 | err := Db.Model(Like{}).Where(map[string]interface{}{"user_id": userId, "video_id": videoId}).
70 | First(&likeInfo).Error
71 | if err != nil {
72 | //查询数据为0,打印"can't find data",返回空结构体,这时候就应该要考虑是否插入这条数据了
73 | if "record not found" == err.Error() {
74 | log.Println("can't find data")
75 | return Like{}, nil
76 | } else {
77 | //如果查询数据库失败,返回获取likeInfo信息失败
78 | log.Println(err.Error())
79 | return likeInfo, errors.New("get likeInfo failed")
80 | }
81 | }
82 | return likeInfo, nil
83 | }
84 |
85 | // GetLikeVideoIdList 根据userId查询所属点赞全部videoId
86 | func GetLikeVideoIdList(userId int64) ([]int64, error) {
87 | var likeVideoIdList []int64
88 | err := Db.Model(Like{}).Where(map[string]interface{}{"user_id": userId, "cancel": config.IsLike}).
89 | Pluck("video_id", &likeVideoIdList).Error
90 | if err != nil {
91 | //查询数据为0,返回空likeVideoIdList切片,以及返回无错误
92 | if "record not found" == err.Error() {
93 | log.Println("there are no likeVideoId")
94 | return likeVideoIdList, nil
95 | } else {
96 | //如果查询数据库失败,返回获取likeVideoIdList失败
97 | log.Println(err.Error())
98 | return likeVideoIdList, errors.New("get likeVideoIdList failed")
99 | }
100 | }
101 | return likeVideoIdList, nil
102 | }
103 |
--------------------------------------------------------------------------------
/dao/likeDao_test.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func TestGetLikeUserIdList(t *testing.T) {
9 | Init()
10 | list, err := GetLikeUserIdList(54)
11 | fmt.Printf("%v", list)
12 | fmt.Printf("%v", err)
13 | }
14 |
15 | func TestUpdateLike(t *testing.T) {
16 | Init()
17 | err := UpdateLike(3, 54, 0)
18 | fmt.Printf("%v", err)
19 | }
20 |
21 | func TestInsertLike(t *testing.T) {
22 | Init()
23 | err := InsertLike(Like{
24 | UserId: 20003,
25 | VideoId: 71,
26 | Cancel: 0,
27 | })
28 | fmt.Printf("%v", err)
29 | }
30 |
31 | func TestGetLikeInfo(t *testing.T) {
32 | Init()
33 | likeInfo, err := GetLikeInfo(3, 71)
34 | fmt.Printf("%v", likeInfo)
35 | fmt.Printf("%v", err)
36 | }
37 |
38 | func TestGetLikeVideoIdList(t *testing.T) {
39 | Init()
40 | videoIdList, err := GetLikeVideoIdList(3)
41 | fmt.Printf("%v", videoIdList)
42 | fmt.Printf("%v", err)
43 | }
44 |
--------------------------------------------------------------------------------
/dao/userDao.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "log"
5 | )
6 |
7 | // TableUser 对应数据库User表结构的结构体
8 | type TableUser struct {
9 | Id int64
10 | Name string
11 | Password string
12 | }
13 |
14 | // TableName 修改表名映射
15 | func (tableUser TableUser) TableName() string {
16 | return "users"
17 | }
18 |
19 | // GetTableUserList 获取全部TableUser对象
20 | func GetTableUserList() ([]TableUser, error) {
21 | tableUsers := []TableUser{}
22 | if err := Db.Find(&tableUsers).Error; err != nil {
23 | log.Println(err.Error())
24 | return tableUsers, err
25 | }
26 | return tableUsers, nil
27 | }
28 |
29 | // GetTableUserByUsername 根据username获得TableUser对象
30 | func GetTableUserByUsername(name string) (TableUser, error) {
31 | tableUser := TableUser{}
32 | if err := Db.Where("name = ?", name).First(&tableUser).Error; err != nil {
33 | log.Println(err.Error())
34 | return tableUser, err
35 | }
36 | return tableUser, nil
37 | }
38 |
39 | // GetTableUserById 根据user_id获得TableUser对象
40 | func GetTableUserById(id int64) (TableUser, error) {
41 | tableUser := TableUser{}
42 | if err := Db.Where("id = ?", id).First(&tableUser).Error; err != nil {
43 | log.Println(err.Error())
44 | return tableUser, err
45 | }
46 | return tableUser, nil
47 | }
48 |
49 | // InsertTableUser 将tableUser插入表内
50 | func InsertTableUser(tableUser *TableUser) bool {
51 | if err := Db.Create(&tableUser).Error; err != nil {
52 | log.Println(err.Error())
53 | return false
54 | }
55 | return true
56 | }
57 |
--------------------------------------------------------------------------------
/dao/userDao_test.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func TestGetTableUserList(t *testing.T) {
9 | list, err := GetTableUserList()
10 | fmt.Printf("%v", list)
11 | fmt.Printf("%v", err)
12 | }
13 |
14 | func TestGetTableUserByUsername(t *testing.T) {
15 | list, err := GetTableUserByUsername("test")
16 | fmt.Printf("%v", list)
17 | fmt.Printf("%v", err)
18 | }
19 |
20 | func TestGetTableUserById(t *testing.T) {
21 | list, err := GetTableUserById(int64(4))
22 | fmt.Printf("%v", list)
23 | fmt.Printf("%v", err)
24 | }
25 |
26 | func TestInsertTableUser(t *testing.T) {
27 | tu := &TableUser{
28 | Id: 5,
29 | Name: "a",
30 | Password: "111111",
31 | }
32 | list := InsertTableUser(tu)
33 | fmt.Printf("%v", list)
34 | }
35 |
--------------------------------------------------------------------------------
/dao/videoDao.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "TikTok/config"
5 | "TikTok/middleware/ftp"
6 | "io"
7 | "log"
8 | "time"
9 | )
10 |
11 | type TableVideo struct {
12 | Id int64 `json:"id"`
13 | AuthorId int64
14 | PlayUrl string `json:"play_url"`
15 | CoverUrl string `json:"cover_url"`
16 | PublishTime time.Time
17 | Title string `json:"title"` //视频名,5.23添加
18 | }
19 |
20 | // TableName
21 | // 将TableVideo映射到videos,
22 | // 这样我结构体到名字就不需要是Video了,防止和我Service层到结构体名字冲突
23 | func (TableVideo) TableName() string {
24 | return "videos"
25 | }
26 |
27 | // GetVideosByAuthorId
28 | // 根据作者的id来查询对应数据库数据,并TableVideo返回切片
29 | func GetVideosByAuthorId(authorId int64) ([]TableVideo, error) {
30 | //建立结果集接收
31 | var data []TableVideo
32 | //初始化db
33 | //Init()
34 | result := Db.Where(&TableVideo{AuthorId: authorId}).Find(&data)
35 | //如果出现问题,返回对应到空,并且返回error
36 | if result.Error != nil {
37 | return nil, result.Error
38 | }
39 | return data, nil
40 | }
41 |
42 | // GetVideoByVideoId
43 | // 依据VideoId来获得视频信息
44 | func GetVideoByVideoId(videoId int64) (TableVideo, error) {
45 | var tableVideo TableVideo
46 | tableVideo.Id = videoId
47 | //Init()
48 | result := Db.First(&tableVideo)
49 | if result.Error != nil {
50 | return tableVideo, result.Error
51 | }
52 | return tableVideo, nil
53 |
54 | }
55 |
56 | // GetVideosByLastTime
57 | // 依据一个时间,来获取这个时间之前的一些视频
58 | func GetVideosByLastTime(lastTime time.Time) ([]TableVideo, error) {
59 | videos := make([]TableVideo, config.VideoCount)
60 | result := Db.Where("publish_time", lastTime).Order("publish_time desc").Limit(config.VideoCount).Find(&videos)
61 | if result.Error != nil {
62 | return videos, result.Error
63 | }
64 | return videos, nil
65 | }
66 |
67 | // VideoFTP
68 | // 通过ftp将视频传入服务器
69 | func VideoFTP(file io.Reader, videoName string) error {
70 | //转到video相对路线下
71 | err := ftp.MyFTP.Cwd("video")
72 | if err != nil {
73 | log.Println("转到路径video失败!!!")
74 | } else {
75 | log.Println("转到路径video成功!!!")
76 | }
77 | err = ftp.MyFTP.Stor(videoName+".mp4", file)
78 | if err != nil {
79 | log.Println("上传视频失败!!!!!")
80 | return err
81 | }
82 | log.Println("上传视频成功!!!!!")
83 | return nil
84 | }
85 |
86 | // ImageFTP
87 | // 将图片传入FTP服务器中,但是这里要注意图片的格式随着名字一起给,同时调用时需要自己结束流
88 | func ImageFTP(file io.Reader, imageName string) error {
89 | //转到video相对路线下
90 | err := ftp.MyFTP.Cwd("images")
91 | if err != nil {
92 | log.Println("转到路径images失败!!!")
93 | return err
94 | }
95 | log.Println("转到路径images成功!!!")
96 | if err = ftp.MyFTP.Stor(imageName, file); err != nil {
97 | log.Println("上传图片失败!!!!!")
98 | return err
99 | }
100 | log.Println("上传图片成功!!!!!")
101 | return nil
102 | }
103 |
104 | // Save 保存视频记录
105 | func Save(videoName string, imageName string, authorId int64, title string) error {
106 | var video TableVideo
107 | video.PublishTime = time.Now()
108 | video.PlayUrl = config.PlayUrlPrefix + videoName + ".mp4"
109 | video.CoverUrl = config.CoverUrlPrefix + imageName + ".jpg"
110 | video.AuthorId = authorId
111 | video.Title = title
112 | result := Db.Save(&video)
113 | if result.Error != nil {
114 | return result.Error
115 | }
116 | return nil
117 | }
118 |
119 | // GetVideoIdsByAuthorId
120 | // 通过作者id来查询发布的视频id切片集合
121 | func GetVideoIdsByAuthorId(authorId int64) ([]int64, error) {
122 | var id []int64
123 | //通过pluck来获得单独的切片
124 | result := Db.Model(&TableVideo{}).Where("author_id", authorId).Pluck("id", &id)
125 | //如果出现问题,返回对应到空,并且返回error
126 | if result.Error != nil {
127 | return nil, result.Error
128 | }
129 | return id, nil
130 | }
131 |
--------------------------------------------------------------------------------
/dao/videoDao_test.go:
--------------------------------------------------------------------------------
1 | package dao
2 |
3 | import (
4 | "TikTok/middleware/ftp"
5 | "fmt"
6 | "testing"
7 | "time"
8 | )
9 |
10 | func TestInit(t *testing.T) {
11 | Init()
12 | }
13 |
14 | func TestFind(t *testing.T) {
15 | Init()
16 | var tv TableVideo
17 | result := Db.First(&tv)
18 | fmt.Println(result.RowsAffected)
19 | fmt.Println(tv.Id)
20 | fmt.Println(tv.AuthorId)
21 | fmt.Println(tv.CoverUrl)
22 | fmt.Println(tv.PlayUrl)
23 | fmt.Println(tv.PublishTime)
24 | fmt.Println(tv.Title)
25 | }
26 |
27 | func TestGetVideosByAuthorId(t *testing.T) {
28 | Init()
29 | data, err := GetVideosByAuthorId(2)
30 | if err != nil {
31 | print(err)
32 | }
33 | for _, video := range data {
34 | fmt.Println(video)
35 | }
36 | }
37 |
38 | func TestGetVideoByVideoId(t *testing.T) {
39 | Init()
40 | data, err := GetVideoByVideoId(3)
41 | if err != nil {
42 | print(err)
43 | }
44 | fmt.Println(data)
45 |
46 | }
47 |
48 | func TestGetVideosByLastTime(t *testing.T) {
49 | Init()
50 | data, err := GetVideosByLastTime(time.Now())
51 | if err != nil {
52 | return
53 | }
54 | for _, video := range data {
55 | fmt.Println(video)
56 | }
57 | }
58 | func TestVideoFtp(t *testing.T) {
59 | ftp.InitFTP()
60 | //file, err := os.Open("/Users/siyixiong/Movies/bilibil/bilibil20211219/樱花少女.mp4")
61 | //if err != nil {
62 | // panic(err)
63 | //}
64 | //err = VideoFTP(file, "k2")
65 | //if err != nil {
66 | // return
67 | //}
68 | //defer file.Close()
69 | //ffmpeg.exe -ss 00:00:01 -i spring.mp4 -vframes 1 bb.jpg
70 | //imageName := uuid.NewV4().String() + ".jpg"
71 | //cmdArguments := []string{"-ss", "00:00:01", "-i", "/home/ftpuser/video/" + "1" + ".mp4", "-vframes", "1", "/home/ftpuser/images/" + imageName}
72 | //cmd := exec.Command("ffmpeg", cmdArguments...)
73 | //var out bytes.Buffer
74 | //cmd.Stdout = &out
75 | //err := cmd.Run()
76 | //if err != nil {
77 | // log.Fatal(err)
78 | //}
79 | //fmt.Printf("command output: %q", out.String())
80 | }
81 |
82 | func TestSave(t *testing.T) {
83 | Save("test", "test", 10024, "aaa")
84 | }
85 |
86 | func TestGetVideoIdsByAuthorId(t *testing.T) {
87 | Init()
88 | id, err := GetVideoIdsByAuthorId(20003)
89 | if err != nil {
90 | return
91 | }
92 | fmt.Println(id)
93 | }
94 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module TikTok
2 |
3 | go 1.17
4 |
5 | require (
6 | github.com/dgrijalva/jwt-go v3.2.0+incompatible
7 | github.com/dutchcoders/goftp v0.0.0-20170301105846-ed59a591ce14
8 | github.com/gin-contrib/pprof v1.3.0
9 | github.com/gin-gonic/gin v1.7.7
10 | github.com/go-redis/redis/v8 v8.11.5
11 | github.com/importcjj/sensitive v0.0.0-20200106142752-42d1c505be7b
12 | github.com/satori/go.uuid v1.2.0
13 | github.com/streadway/amqp v1.0.0
14 | golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f
15 | gorm.io/driver/mysql v1.3.3
16 | gorm.io/gorm v1.23.5
17 | )
18 |
19 | require (
20 | github.com/cespare/xxhash/v2 v2.1.2 // indirect
21 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
22 | github.com/gin-contrib/sse v0.1.0 // indirect
23 | github.com/go-playground/locales v0.14.0 // indirect
24 | github.com/go-playground/universal-translator v0.18.0 // indirect
25 | github.com/go-playground/validator/v10 v10.11.0 // indirect
26 | github.com/go-sql-driver/mysql v1.6.0 // indirect
27 | github.com/golang/protobuf v1.5.2 // indirect
28 | github.com/jinzhu/inflection v1.0.0 // indirect
29 | github.com/jinzhu/now v1.1.4 // indirect
30 | github.com/json-iterator/go v1.1.12 // indirect
31 | github.com/leodido/go-urn v1.2.1 // indirect
32 | github.com/mattn/go-isatty v0.0.14 // indirect
33 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
34 | github.com/modern-go/reflect2 v1.0.2 // indirect
35 | github.com/ugorji/go/codec v1.2.7 // indirect
36 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect
37 | golang.org/x/text v0.3.7 // indirect
38 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
39 | google.golang.org/protobuf v1.28.0 // indirect
40 | gopkg.in/yaml.v2 v2.4.0 // indirect
41 | )
42 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
2 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
3 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
4 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
5 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
6 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
11 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
12 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
13 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
14 | github.com/dutchcoders/goftp v0.0.0-20170301105846-ed59a591ce14 h1:EqNCch0Y7FT4tawhoWu1vcaAvfPGm2hRCPHmtKdyZWI=
15 | github.com/dutchcoders/goftp v0.0.0-20170301105846-ed59a591ce14/go.mod h1:0PBGc2Br4QviPu4/IVVcXmCaLcSHcXgzpVqm5JndaB8=
16 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
17 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
18 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
19 | github.com/gin-contrib/pprof v1.3.0 h1:G9eK6HnbkSqDZBYbzG4wrjCsA4e+cvYAHUZw6W+W9K0=
20 | github.com/gin-contrib/pprof v1.3.0/go.mod h1:waMjT1H9b179t3CxuG1cV3DHpga6ybizwfBaM5OXaB0=
21 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
22 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
23 | github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
24 | github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
25 | github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
26 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
27 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
28 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
29 | github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
30 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
31 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
32 | github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
33 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
34 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
35 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
36 | github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw=
37 | github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
38 | github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
39 | github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
40 | github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
41 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
42 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
43 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
44 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
45 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
46 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
47 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
48 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
49 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
50 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
51 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
52 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
53 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
54 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
55 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
56 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
57 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
58 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
59 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
60 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
61 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
62 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
63 | github.com/importcjj/sensitive v0.0.0-20200106142752-42d1c505be7b h1:9hudrgWUhyfR4FRMOfL9KB1uYw48DUdHkkgr9ODOw7Y=
64 | github.com/importcjj/sensitive v0.0.0-20200106142752-42d1c505be7b/go.mod h1:zLVdX6Ed2SvCbEamKmve16U0E03UkdJo4ls1TBfmc8Q=
65 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
66 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
67 | github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
68 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
69 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
70 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
71 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
72 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
73 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
74 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
75 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
76 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
77 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
78 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
79 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
80 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
81 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
82 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
83 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
84 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
85 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
86 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
87 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
88 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
89 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
90 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
91 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
92 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
93 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
94 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
95 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
96 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
97 | github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
98 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
99 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
100 | github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
101 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
102 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
103 | github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
104 | github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
105 | github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
106 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
107 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
108 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
109 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
110 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
111 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
112 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
113 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
114 | github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo=
115 | github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
116 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
117 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
118 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
119 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
120 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
121 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
122 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
123 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
124 | github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
125 | github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
126 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
127 | github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
128 | github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
129 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
130 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
131 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
132 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
133 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
134 | golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc=
135 | golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
136 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
137 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
138 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
139 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
140 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
141 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
142 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
143 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
144 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
145 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
146 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
147 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
148 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
149 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
150 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
151 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
152 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
153 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
154 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
155 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
156 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
157 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
158 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
159 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
160 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
161 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
162 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
163 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
164 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
165 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
166 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
167 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
168 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
169 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
170 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
171 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
172 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
173 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
174 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
175 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
176 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
177 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
178 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
179 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
180 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
181 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
182 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
183 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
184 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
185 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
186 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
187 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
188 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
189 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
190 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
191 | google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
192 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
193 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
194 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
195 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
196 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
197 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
198 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
199 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
200 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
201 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
202 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
203 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
204 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
205 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
206 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
207 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
208 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
209 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
210 | gorm.io/driver/mysql v1.3.3 h1:jXG9ANrwBc4+bMvBcSl8zCfPBaVoPyBEBshA8dA93X8=
211 | gorm.io/driver/mysql v1.3.3/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U=
212 | gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
213 | gorm.io/gorm v1.23.5 h1:TnlF26wScKSvknUC/Rn8t0NLLM22fypYBlvj1+aH6dM=
214 | gorm.io/gorm v1.23.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
215 |
--------------------------------------------------------------------------------
/images/1.0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/1.0.png
--------------------------------------------------------------------------------
/images/1.2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/1.2.png
--------------------------------------------------------------------------------
/images/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/1.png
--------------------------------------------------------------------------------
/images/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/10.png
--------------------------------------------------------------------------------
/images/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/11.png
--------------------------------------------------------------------------------
/images/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/2.png
--------------------------------------------------------------------------------
/images/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/3.png
--------------------------------------------------------------------------------
/images/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/4.png
--------------------------------------------------------------------------------
/images/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/5.png
--------------------------------------------------------------------------------
/images/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/6.png
--------------------------------------------------------------------------------
/images/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/7.png
--------------------------------------------------------------------------------
/images/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/8.png
--------------------------------------------------------------------------------
/images/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/9.png
--------------------------------------------------------------------------------
/images/awards.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/awards.jpg
--------------------------------------------------------------------------------
/images/awards1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/awards1.jpg
--------------------------------------------------------------------------------
/images/framework.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/framework.jpg
--------------------------------------------------------------------------------
/images/future.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/future.png
--------------------------------------------------------------------------------
/images/logo1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/logo1.png
--------------------------------------------------------------------------------
/images/logo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/logo2.png
--------------------------------------------------------------------------------
/images/mysql.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/mysql.jpg
--------------------------------------------------------------------------------
/images/rabbitmq.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/rabbitmq.jpg
--------------------------------------------------------------------------------
/images/recommend.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/recommend.jpg
--------------------------------------------------------------------------------
/images/redis.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/redis.jpg
--------------------------------------------------------------------------------
/images/video.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HammerCloth/tiktok/6f1d9f072b2c0a7c79bf50aca5a45165eca1ae20/images/video.png
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "TikTok/dao"
5 | "TikTok/middleware/ffmpeg"
6 | "TikTok/middleware/ftp"
7 | "TikTok/middleware/rabbitmq"
8 | "TikTok/middleware/redis"
9 | "TikTok/util"
10 | "github.com/gin-contrib/pprof"
11 | "github.com/gin-gonic/gin"
12 | )
13 |
14 | //如果启动有问题,大概是你的IP地址出现变化,需要在项目依赖的服务器中配置安全组
15 | func main() {
16 | //关闭log
17 | //log.SetOutput(ioutil.Discard)
18 | initDeps()
19 | //gin
20 | r := gin.Default()
21 | initRouter(r)
22 | //pprof
23 | pprof.Register(r)
24 | r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
25 | }
26 |
27 | // 加载项目依赖
28 | func initDeps() {
29 | // 初始化数据库
30 | dao.Init()
31 | // 初始化FTP服务器链接
32 | ftp.InitFTP()
33 | // 初始化SSH
34 | ffmpeg.InitSSH()
35 |
36 | // 初始化redis-DB0的连接,follow选择的DB0.
37 | redis.InitRedis()
38 | // 初始化rabbitMQ。
39 | rabbitmq.InitRabbitMQ()
40 | // 初始化Follow的相关消息队列,并开启消费。
41 | rabbitmq.InitFollowRabbitMQ()
42 | // 初始化Like的相关消息队列,并开启消费。
43 | rabbitmq.InitLikeRabbitMQ()
44 | //初始化Comment的消息队列,并开启消费
45 | rabbitmq.InitCommentRabbitMQ()
46 | //初始化敏感词拦截器。
47 | util.InitFilter()
48 | }
49 |
--------------------------------------------------------------------------------
/middleware/ffmpeg/doc.go:
--------------------------------------------------------------------------------
1 | // Package ffmpeg
2 | // @Description 视频截图相关中间件
3 | // @Author 创建人siyixiong;
4 | // @Update 20220513 (创建/修改时间);
5 | package ffmpeg
6 |
--------------------------------------------------------------------------------
/middleware/ffmpeg/ffmpeg.go:
--------------------------------------------------------------------------------
1 | package ffmpeg
2 |
3 | import (
4 | "TikTok/config"
5 | "fmt"
6 | "golang.org/x/crypto/ssh"
7 | "log"
8 | "time"
9 | )
10 |
11 | /*
12 | 将ffmpeg作为一个中间件来调用,
13 | 通过SSH的方式,在远程登录FTP服务器
14 | 调用部署在服务器上的ffmpeg,来完成视频截图,并存储在对应位置
15 | */
16 |
17 | type Ffmsg struct {
18 | VideoName string
19 | ImageName string
20 | }
21 |
22 | var ClientSSH *ssh.Client
23 | var Ffchan chan Ffmsg
24 |
25 | // InitSSH 建立SSH客户端,但是会不会超时导致无法链接,这个需要做一些措施
26 | func InitSSH() {
27 | var err error
28 | //创建sshp登陆配置
29 | SSHconfig := &ssh.ClientConfig{
30 | Timeout: 5 * time.Second, //ssh 连接time out 时间一秒钟, 如果ssh验证错误 会在一秒内返回
31 | User: config.UserSSH,
32 | HostKeyCallback: ssh.InsecureIgnoreHostKey(), //这个可以, 但是不够安全
33 |
34 | //HostKeyCallback: hostKeyCallBackFunc(h.Host),
35 | }
36 | if config.TypeSSH == "password" {
37 | SSHconfig.Auth = []ssh.AuthMethod{ssh.Password(config.PasswordSSH)}
38 | }
39 | //dial 获取ssh client
40 | addr := fmt.Sprintf("%s:%d", config.HostSSH, config.PortSSH)
41 | ClientSSH, err = ssh.Dial("tcp", addr, SSHconfig)
42 | if err != nil {
43 | log.Fatal("创建ssh client 失败", err)
44 | }
45 | log.Printf("获取到客户端:%v", ClientSSH)
46 | //建立通道,作为队列使用,并且确立缓冲区大小
47 | Ffchan = make(chan Ffmsg, config.MaxMsgCount)
48 | //建立携程用于派遣
49 | go dispatcher()
50 | go keepAlive()
51 | }
52 |
53 | //通过增加携程,将获取的信息进行派遣,当信息处理失败之后,还会将处理方式放入通道形成的队列中
54 | func dispatcher() {
55 | for ffmsg := range Ffchan {
56 | go func(f Ffmsg) {
57 | err := Ffmpeg(f.VideoName, f.ImageName)
58 | if err != nil {
59 | Ffchan <- f
60 | log.Fatal("派遣失败:重新派遣")
61 | }
62 | log.Printf("视频%v截图处理成功", f.VideoName)
63 | }(ffmsg)
64 | }
65 | }
66 |
67 | // Ffmpeg 通过远程调用ffmpeg命令来创建视频截图
68 | func Ffmpeg(videoName string, imageName string) error {
69 | session, err := ClientSSH.NewSession()
70 | if err != nil {
71 | log.Fatal("创建ssh session 失败", err)
72 | }
73 | defer session.Close()
74 | //执行远程命令 ffmpeg -ss 00:00:01 -i /home/ftpuser/video/1.mp4 -vframes 1 /home/ftpuser/images/4.jpg
75 | combo, err := session.CombinedOutput("ls;/usr/local/ffmpeg/bin/ffmpeg -ss 00:00:01 -i /home/ftpuser/video/" + videoName + ".mp4 -vframes 1 /home/ftpuser/images/" + imageName + ".jpg")
76 | if err != nil {
77 | //log.Fatal("远程执行cmd 失败", err)
78 | log.Fatal("命令输出:", string(combo))
79 | return err
80 | }
81 | //fmt.Println("命令输出:", string(combo))
82 | return nil
83 | }
84 |
85 | //维持长链接
86 | func keepAlive() {
87 | time.Sleep(time.Duration(config.SSHHeartbeatTime) * time.Second)
88 | session, _ := ClientSSH.NewSession()
89 | session.Close()
90 | }
91 |
--------------------------------------------------------------------------------
/middleware/ftp/doc.go:
--------------------------------------------------------------------------------
1 | // Package ftp
2 | // @Description ftp相关中间件
3 | // @Author 创建人siyixiong;
4 | // @Update 20220513 (创建/修改时间);
5 | package ftp
6 |
--------------------------------------------------------------------------------
/middleware/ftp/ftp.go:
--------------------------------------------------------------------------------
1 | package ftp
2 |
3 | import (
4 | "TikTok/config"
5 | "github.com/dutchcoders/goftp"
6 | "log"
7 | "time"
8 | )
9 |
10 | var MyFTP *goftp.FTP
11 |
12 | func InitFTP() {
13 | //获取到ftp的链接
14 | var err error
15 | MyFTP, err = goftp.Connect(config.ConConfig)
16 | if err != nil {
17 | log.Printf("获取到FTP链接失败!!!")
18 | }
19 | log.Printf("获取到FTP链接成功%v:", MyFTP)
20 | //登录
21 | err = MyFTP.Login(config.FtpUser, config.FtpPsw)
22 | if err != nil {
23 | log.Printf("FTP登录失败!!!")
24 | }
25 | log.Printf("FTP登录成功!!!")
26 | //维持长链接
27 | go keepAlive()
28 | }
29 |
30 | func keepAlive() {
31 | time.Sleep(time.Duration(config.HeartbeatTime) * time.Second)
32 | MyFTP.Noop()
33 | }
34 |
--------------------------------------------------------------------------------
/middleware/jwt/auth.go:
--------------------------------------------------------------------------------
1 | package jwt
2 |
3 | import (
4 | "TikTok/config"
5 | "github.com/dgrijalva/jwt-go"
6 | "github.com/gin-gonic/gin"
7 | "net/http"
8 | "strings"
9 | )
10 |
11 | type Response struct {
12 | StatusCode int32 `json:"status_code"`
13 | StatusMsg string `json:"status_msg,omitempty"`
14 | }
15 |
16 | // Auth 鉴权中间件
17 | // 若用户携带的token正确,解析token,将userId放入上下文context中并放行;否则,返回错误信息
18 | func Auth() gin.HandlerFunc {
19 | return func(context *gin.Context) {
20 | //auth := context.Request.Header.Get("Authorization")
21 | auth := context.Query("token")
22 | if len(auth) == 0 {
23 | context.Abort()
24 | context.JSON(http.StatusUnauthorized, Response{
25 | StatusCode: -1,
26 | StatusMsg: "Unauthorized",
27 | })
28 | }
29 | auth = strings.Fields(auth)[1]
30 | token, err := parseToken(auth)
31 | if err != nil {
32 | context.Abort()
33 | context.JSON(http.StatusUnauthorized, Response{
34 | StatusCode: -1,
35 | StatusMsg: "Token Error",
36 | })
37 | } else {
38 | println("token 正确")
39 | }
40 | context.Set("userId", token.Id)
41 | context.Next()
42 | }
43 | }
44 |
45 | // AuthWithoutLogin 未登录情况下,若携带token,则解析出用户id并放入context;若未携带,则放入用户id默认值0
46 | func AuthWithoutLogin() gin.HandlerFunc {
47 | return func(context *gin.Context) {
48 | auth := context.Query("token")
49 | var userId string
50 | if len(auth) == 0 {
51 | userId = "0"
52 | } else {
53 | auth = strings.Fields(auth)[1]
54 | token, err := parseToken(auth)
55 | if err != nil {
56 | context.Abort()
57 | context.JSON(http.StatusUnauthorized, Response{
58 | StatusCode: -1,
59 | StatusMsg: "Token Error",
60 | })
61 | } else {
62 | userId = token.Id
63 | println("token 正确")
64 | }
65 | }
66 | context.Set("userId", userId)
67 | context.Next()
68 | }
69 | }
70 |
71 | // parseToken 解析token
72 | func parseToken(token string) (*jwt.StandardClaims, error) {
73 | jwtToken, err := jwt.ParseWithClaims(token, &jwt.StandardClaims{}, func(token *jwt.Token) (i interface{}, e error) {
74 | return []byte(config.Secret), nil
75 | })
76 | if err == nil && jwtToken != nil {
77 | if claim, ok := jwtToken.Claims.(*jwt.StandardClaims); ok && jwtToken.Valid {
78 | return claim, nil
79 | }
80 | }
81 | return nil, err
82 | }
83 |
--------------------------------------------------------------------------------
/middleware/jwt/authBody.go:
--------------------------------------------------------------------------------
1 | package jwt
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "net/http"
7 | "strings"
8 | )
9 |
10 | // AuthBody 鉴权中间件
11 | // 若用户携带的token正确,解析token,将userId放入上下文context中并放行;否则,返回错误信息
12 | func AuthBody() gin.HandlerFunc {
13 | return func(context *gin.Context) {
14 | auth := context.Request.PostFormValue("token")
15 | fmt.Printf("%v \n", auth)
16 |
17 | if len(auth) == 0 {
18 | context.Abort()
19 | context.JSON(http.StatusUnauthorized, Response{
20 | StatusCode: -1,
21 | StatusMsg: "Unauthorized",
22 | })
23 | }
24 | auth = strings.Fields(auth)[1]
25 | token, err := parseToken(auth)
26 | if err != nil {
27 | context.Abort()
28 | context.JSON(http.StatusUnauthorized, Response{
29 | StatusCode: -1,
30 | StatusMsg: "Token Error",
31 | })
32 | } else {
33 | println("token 正确")
34 | }
35 | context.Set("userId", token.Id)
36 | context.Next()
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/middleware/jwt/doc.go:
--------------------------------------------------------------------------------
1 | // Package jwt
2 | // @Description 用于存放一些过滤器等
3 | // @Author 创建人siyixiong;
4 | // @Update 20220513 (创建/修改时间);
5 | package jwt
6 |
--------------------------------------------------------------------------------
/middleware/rabbitmq/commentMQ.go:
--------------------------------------------------------------------------------
1 | package rabbitmq
2 |
3 | import (
4 | "TikTok/dao"
5 | "fmt"
6 | "github.com/streadway/amqp"
7 | "log"
8 | "strconv"
9 | )
10 |
11 | type CommentMQ struct {
12 | RabbitMQ
13 | channel *amqp.Channel
14 | queueName string
15 | exchange string
16 | key string
17 | }
18 |
19 | // NewCommentRabbitMQ 获取CommentMQ的对应队列。
20 | func NewCommentRabbitMQ(queueName string) *CommentMQ {
21 | commentMQ := &CommentMQ{
22 | RabbitMQ: *Rmq,
23 | queueName: queueName,
24 | }
25 |
26 | cha, err := commentMQ.conn.Channel()
27 | commentMQ.channel = cha
28 | Rmq.failOnErr(err, "获取通道失败")
29 | return commentMQ
30 | }
31 |
32 | // Publish Comment的发布配置。
33 | func (c *CommentMQ) Publish(message string) {
34 |
35 | _, err := c.channel.QueueDeclare(
36 | c.queueName,
37 | //是否持久化
38 | false,
39 | //是否为自动删除
40 | false,
41 | //是否具有排他性
42 | false,
43 | //是否阻塞
44 | false,
45 | //额外属性
46 | nil,
47 | )
48 | if err != nil {
49 | panic(err)
50 | }
51 |
52 | err1 := c.channel.Publish(
53 | c.exchange,
54 | c.queueName,
55 | false,
56 | false,
57 | amqp.Publishing{
58 | ContentType: "text/plain",
59 | Body: []byte(message),
60 | })
61 | if err1 != nil {
62 | panic(err)
63 | }
64 | }
65 |
66 | // Consumer follow关系的消费逻辑。
67 | func (c *CommentMQ) Consumer() {
68 |
69 | _, err := c.channel.QueueDeclare(c.queueName, false, false, false, false, nil)
70 |
71 | if err != nil {
72 | panic(err)
73 | }
74 |
75 | //2、接收消息
76 | msg, err := c.channel.Consume(
77 | c.queueName,
78 | //用来区分多个消费者
79 | "",
80 | //是否自动应答
81 | true,
82 | //是否具有排他性
83 | false,
84 | //如果设置为true,表示不能将同一个connection中发送的消息传递给这个connection中的消费者
85 | false,
86 | //消息队列是否阻塞
87 | false,
88 | nil,
89 | )
90 | if err != nil {
91 | panic(err)
92 | }
93 |
94 | //只有删除逻辑
95 | forever := make(chan bool)
96 | go c.consumerCommentDel(msg)
97 |
98 | //log.Printf("[*] Waiting for messages,To exit press CTRL+C")
99 |
100 | <-forever
101 |
102 | }
103 |
104 | // 数据库中评论删除的消费方式。
105 | func (c *CommentMQ) consumerCommentDel(msg <-chan amqp.Delivery) {
106 | for d := range msg {
107 | // 参数解析,只有一个评论id
108 | cId := fmt.Sprintf("%s", d.Body)
109 | commentId, _ := strconv.Atoi(cId)
110 | //log.Println("commentId:", commentId)
111 | //删除数据库中评论信息
112 | err := dao.DeleteComment(int64(commentId))
113 | if err != nil {
114 | log.Println(err)
115 | }
116 | }
117 | }
118 |
119 | var RmqCommentDel *CommentMQ
120 |
121 | // InitCommentRabbitMQ 初始化rabbitMQ连接。
122 | func InitCommentRabbitMQ() {
123 | RmqCommentDel = NewCommentRabbitMQ("comment_del")
124 | go RmqCommentDel.Consumer()
125 | }
126 |
--------------------------------------------------------------------------------
/middleware/rabbitmq/doc.go:
--------------------------------------------------------------------------------
1 | // Package rabbitmq
2 | // @Description rabbitmq相关
3 | // @Author 创建人siyixiong;
4 | // @Update 20220513 (创建/修改时间);
5 | package rabbitmq
6 |
--------------------------------------------------------------------------------
/middleware/rabbitmq/followMQ.go:
--------------------------------------------------------------------------------
1 | package rabbitmq
2 |
3 | import (
4 | "TikTok/dao"
5 | "TikTok/middleware/redis"
6 | "fmt"
7 | "github.com/streadway/amqp"
8 | "log"
9 | "strconv"
10 | "strings"
11 | )
12 |
13 | type FollowMQ struct {
14 | RabbitMQ
15 | channel *amqp.Channel
16 | queueName string
17 | exchange string
18 | key string
19 | }
20 |
21 | // NewFollowRabbitMQ 获取followMQ的对应队列。
22 | func NewFollowRabbitMQ(queueName string) *FollowMQ {
23 | followMQ := &FollowMQ{
24 | RabbitMQ: *Rmq,
25 | queueName: queueName,
26 | }
27 |
28 | cha, err := followMQ.conn.Channel()
29 | followMQ.channel = cha
30 | Rmq.failOnErr(err, "获取通道失败")
31 | return followMQ
32 | }
33 |
34 | // 关闭mq通道和mq的连接。
35 | func (f *FollowMQ) destroy() {
36 | f.channel.Close()
37 | }
38 |
39 | // Publish follow关系的发布配置。
40 | func (f *FollowMQ) Publish(message string) {
41 |
42 | _, err := f.channel.QueueDeclare(
43 | f.queueName,
44 | //是否持久化
45 | false,
46 | //是否为自动删除
47 | false,
48 | //是否具有排他性
49 | false,
50 | //是否阻塞
51 | false,
52 | //额外属性
53 | nil,
54 | )
55 | if err != nil {
56 | panic(err)
57 | }
58 |
59 | f.channel.Publish(
60 | f.exchange,
61 | f.queueName,
62 | false,
63 | false,
64 | amqp.Publishing{
65 | ContentType: "text/plain",
66 | Body: []byte(message),
67 | })
68 |
69 | }
70 |
71 | // Consumer follow关系的消费逻辑。
72 | func (f *FollowMQ) Consumer() {
73 |
74 | _, err := f.channel.QueueDeclare(f.queueName, false, false, false, false, nil)
75 |
76 | if err != nil {
77 | panic(err)
78 | }
79 |
80 | //2、接收消息
81 | msgs, err := f.channel.Consume(
82 | f.queueName,
83 | //用来区分多个消费者
84 | "",
85 | //是否自动应答
86 | true,
87 | //是否具有排他性
88 | false,
89 | //如果设置为true,表示不能将同一个connection中发送的消息传递给这个connection中的消费者
90 | false,
91 | //消息队列是否阻塞
92 | false,
93 | nil,
94 | )
95 | if err != nil {
96 | panic(err)
97 | }
98 |
99 | forever := make(chan bool)
100 | switch f.queueName {
101 | case "follow_add":
102 | go f.consumerFollowAdd(msgs)
103 | case "follow_del":
104 | go f.consumerFollowDel(msgs)
105 |
106 | }
107 |
108 | log.Printf("[*] Waiting for messagees,To exit press CTRL+C")
109 |
110 | <-forever
111 |
112 | }
113 |
114 | // 关系添加的消费方式。
115 | func (f *FollowMQ) consumerFollowAdd(msgs <-chan amqp.Delivery) {
116 | for d := range msgs {
117 | // 参数解析。
118 | params := strings.Split(fmt.Sprintf("%s", d.Body), " ")
119 | userId, _ := strconv.Atoi(params[0])
120 | targetId, _ := strconv.Atoi(params[1])
121 | // 日志记录。
122 | sql := fmt.Sprintf("CALL addFollowRelation(%v,%v)", targetId, userId)
123 | log.Printf("消费队列执行添加关系。SQL如下:%s", sql)
124 | // 执行SQL,注必须scan,该SQL才能被执行。
125 | if err := dao.Db.Raw(sql).Scan(nil).Error; nil != err {
126 | // 执行出错,打印日志。
127 | log.Println(err.Error())
128 | }
129 | }
130 | }
131 |
132 | // 关系删除的消费方式。
133 | func (f *FollowMQ) consumerFollowDel(msgs <-chan amqp.Delivery) {
134 | for d := range msgs {
135 | // 参数解析。
136 | params := strings.Split(fmt.Sprintf("%s", d.Body), " ")
137 | userId, _ := strconv.Atoi(params[0])
138 | targetId, _ := strconv.Atoi(params[1])
139 | // 日志记录。
140 | sql := fmt.Sprintf("CALL delFollowRelation(%v,%v)", targetId, userId)
141 | //log.Printf("消费队列执行删除关系。SQL如下:%s", sql)
142 | // 执行SQL,注必须scan,该SQL才能被执行。
143 | if err := dao.Db.Raw(sql).Scan(nil).Error; nil != err {
144 | // 执行出错,打印日志。
145 | log.Println(err.Error())
146 | }
147 | // 再删Redis里的信息,防止脏数据,保证最终一致性。
148 | updateRedisWithDel(userId, targetId)
149 | }
150 | }
151 | func updateRedisWithDel(userId int, targetId int) {
152 | // step1 删除粉丝关系。
153 | targetIdStr := strconv.Itoa(targetId)
154 | if cnt, _ := redis.RdbFollowers.SCard(redis.Ctx, targetIdStr).Result(); 0 != cnt {
155 | redis.RdbFollowers.SRem(redis.Ctx, targetIdStr, userId)
156 | }
157 | // step2 删除关注关系。
158 | followingIdStr := strconv.Itoa(userId)
159 | if cnt, _ := redis.RdbFollowing.SCard(redis.Ctx, followingIdStr).Result(); 0 != cnt {
160 | redis.RdbFollowing.SRem(redis.Ctx, followingIdStr, targetId)
161 | }
162 | // step3 删除部分关注关系。
163 | followingPartUserIdStr := strconv.Itoa(userId)
164 | redis.RdbFollowingPart.SRem(redis.Ctx, followingPartUserIdStr, targetId)
165 | }
166 |
167 | var RmqFollowAdd *FollowMQ
168 | var RmqFollowDel *FollowMQ
169 |
170 | // InitFollowRabbitMQ 初始化rabbitMQ连接。
171 | func InitFollowRabbitMQ() {
172 | RmqFollowAdd = NewFollowRabbitMQ("follow_add")
173 | go RmqFollowAdd.Consumer()
174 |
175 | RmqFollowDel = NewFollowRabbitMQ("follow_del")
176 | go RmqFollowDel.Consumer()
177 | }
178 |
--------------------------------------------------------------------------------
/middleware/rabbitmq/likeMQ.go:
--------------------------------------------------------------------------------
1 | package rabbitmq
2 |
3 | import (
4 | "TikTok/config"
5 | "TikTok/dao"
6 | "errors"
7 | "fmt"
8 | "github.com/streadway/amqp"
9 | "log"
10 | "strconv"
11 | "strings"
12 | )
13 |
14 | type LikeMQ struct {
15 | RabbitMQ
16 | channel *amqp.Channel
17 | queueName string
18 | exchange string
19 | key string
20 | }
21 |
22 | // NewLikeRabbitMQ 获取likeMQ的对应队列。
23 | func NewLikeRabbitMQ(queueName string) *LikeMQ {
24 | likeMQ := &LikeMQ{
25 | RabbitMQ: *Rmq,
26 | queueName: queueName,
27 | }
28 | cha, err := likeMQ.conn.Channel()
29 | likeMQ.channel = cha
30 | Rmq.failOnErr(err, "获取通道失败")
31 | return likeMQ
32 | }
33 |
34 | // Publish like操作的发布配置。
35 | func (l *LikeMQ) Publish(message string) {
36 |
37 | _, err := l.channel.QueueDeclare(
38 | l.queueName,
39 | //是否持久化
40 | false,
41 | //是否为自动删除
42 | false,
43 | //是否具有排他性
44 | false,
45 | //是否阻塞
46 | false,
47 | //额外属性
48 | nil,
49 | )
50 | if err != nil {
51 | panic(err)
52 | }
53 |
54 | err1 := l.channel.Publish(
55 | l.exchange,
56 | l.queueName,
57 | false,
58 | false,
59 | amqp.Publishing{
60 | ContentType: "text/plain",
61 | Body: []byte(message),
62 | })
63 | if err1 != nil {
64 | panic(err)
65 | }
66 |
67 | }
68 |
69 | // Consumer like关系的消费逻辑。
70 | func (l *LikeMQ) Consumer() {
71 |
72 | _, err := l.channel.QueueDeclare(l.queueName, false, false, false, false, nil)
73 |
74 | if err != nil {
75 | panic(err)
76 | }
77 |
78 | //2、接收消息
79 | messages, err1 := l.channel.Consume(
80 | l.queueName,
81 | //用来区分多个消费者
82 | "",
83 | //是否自动应答
84 | true,
85 | //是否具有排他性
86 | false,
87 | //如果设置为true,表示不能将同一个connection中发送的消息传递给这个connection中的消费者
88 | false,
89 | //消息队列是否阻塞
90 | false,
91 | nil,
92 | )
93 | if err1 != nil {
94 | panic(err1)
95 | }
96 |
97 | forever := make(chan bool)
98 | switch l.queueName {
99 | case "like_add":
100 | //点赞消费队列
101 | go l.consumerLikeAdd(messages)
102 | case "like_del":
103 | //取消赞消费队列
104 | go l.consumerLikeDel(messages)
105 |
106 | }
107 |
108 | log.Printf("[*] Waiting for messagees,To exit press CTRL+C")
109 |
110 | <-forever
111 |
112 | }
113 |
114 | //consumerLikeAdd 赞关系添加的消费方式。
115 | func (l *LikeMQ) consumerLikeAdd(messages <-chan amqp.Delivery) {
116 | for d := range messages {
117 | // 参数解析。
118 | params := strings.Split(fmt.Sprintf("%s", d.Body), " ")
119 | userId, _ := strconv.ParseInt(params[0], 10, 64)
120 | videoId, _ := strconv.ParseInt(params[1], 10, 64)
121 | //最多尝试操作数据库的次数
122 | for i := 0; i < config.Attempts; i++ {
123 | flag := false //默认无问题
124 | //如果查询没有数据,用来生成该条点赞信息,存储在likeData中
125 | var likeData dao.Like
126 | //先查询是否有这条数据
127 | likeInfo, err := dao.GetLikeInfo(userId, videoId)
128 | //如果有问题,说明查询数据库失败,打印错误信息err:"get likeInfo failed"
129 | if err != nil {
130 | log.Printf(err.Error())
131 | flag = true //出现问题
132 | } else {
133 | if likeInfo == (dao.Like{}) { //没查到这条数据,则新建这条数据;
134 | likeData.UserId = userId //插入userId
135 | likeData.VideoId = videoId //插入videoId
136 | likeData.Cancel = config.IsLike //插入点赞cancel=0
137 | //如果有问题,说明插入数据库失败,打印错误信息err:"insert data fail"
138 | if err := dao.InsertLike(likeData); err != nil {
139 | log.Printf(err.Error())
140 | flag = true //出现问题
141 | }
142 | } else { //查到这条数据,更新即可;
143 | //如果有问题,说明插入数据库失败,打印错误信息err:"update data fail"
144 | if err := dao.UpdateLike(userId, videoId, config.IsLike); err != nil {
145 | log.Printf(err.Error())
146 | flag = true //出现问题
147 | }
148 | }
149 | //一遍流程下来正常执行了,那就打断结束,不再尝试
150 | if flag == false {
151 | break
152 | }
153 | }
154 | }
155 | }
156 | }
157 |
158 | //consumerLikeDel 赞关系删除的消费方式。
159 | func (l *LikeMQ) consumerLikeDel(messages <-chan amqp.Delivery) {
160 | for d := range messages {
161 | // 参数解析。
162 | params := strings.Split(fmt.Sprintf("%s", d.Body), " ")
163 | userId, _ := strconv.ParseInt(params[0], 10, 64)
164 | videoId, _ := strconv.ParseInt(params[1], 10, 64)
165 | //最多尝试操作数据库的次数
166 | for i := 0; i < config.Attempts; i++ {
167 | flag := false //默认无问题
168 | //取消赞行为,只有当前状态是点赞状态才会发起取消赞行为,所以如果查询到,必然是cancel==0(点赞)
169 | //先查询是否有这条数据
170 | likeInfo, err := dao.GetLikeInfo(userId, videoId)
171 | //如果有问题,说明查询数据库失败,返回错误信息err:"get likeInfo failed"
172 | if err != nil {
173 | log.Printf(err.Error())
174 | flag = true //出现问题
175 | } else {
176 | if likeInfo == (dao.Like{}) { //只有当前是点赞状态才能取消点赞这个行为
177 | // 所以如果查询不到数据则返回错误信息:"can't find data,this action invalid"
178 | log.Printf(errors.New("can't find data,this action invalid").Error())
179 | } else {
180 | //如果查询到数据,则更新为取消赞状态
181 | //如果有问题,说明插入数据库失败,打印错误信息err:"update data fail"
182 | if err := dao.UpdateLike(userId, videoId, config.Unlike); err != nil {
183 | log.Printf(err.Error())
184 | flag = true
185 | }
186 | }
187 | }
188 | //一遍流程下来正常执行了,那就打断结束,不再尝试
189 | if flag == false {
190 | break
191 | }
192 | }
193 | }
194 | }
195 |
196 | var RmqLikeAdd *LikeMQ
197 | var RmqLikeDel *LikeMQ
198 |
199 | // InitLikeRabbitMQ 初始化rabbitMQ连接。
200 | func InitLikeRabbitMQ() {
201 | RmqLikeAdd = NewLikeRabbitMQ("like_add")
202 | go RmqLikeAdd.Consumer()
203 |
204 | RmqLikeDel = NewLikeRabbitMQ("like_del")
205 | go RmqLikeDel.Consumer()
206 | }
207 |
--------------------------------------------------------------------------------
/middleware/rabbitmq/rabbitMQ.go:
--------------------------------------------------------------------------------
1 | package rabbitmq
2 |
3 | import (
4 | "fmt"
5 | "github.com/streadway/amqp"
6 | "log"
7 | )
8 |
9 | const MQURL = "amqp://tiktok:tiktok@106.14.75.229:5672/"
10 |
11 | type RabbitMQ struct {
12 | conn *amqp.Connection
13 | mqurl string
14 | }
15 |
16 | var Rmq *RabbitMQ
17 |
18 | // InitRabbitMQ 初始化RabbitMQ的连接和通道。
19 | func InitRabbitMQ() {
20 |
21 | Rmq = &RabbitMQ{
22 | mqurl: MQURL,
23 | }
24 | dial, err := amqp.Dial(Rmq.mqurl)
25 | Rmq.failOnErr(err, "创建连接失败")
26 | Rmq.conn = dial
27 |
28 | }
29 |
30 | // 连接出错时,输出错误信息。
31 | func (r *RabbitMQ) failOnErr(err error, message string) {
32 | if err != nil {
33 | log.Fatalf("%s:%s\n", err, message)
34 | panic(fmt.Sprintf("%s:%s\n", err, message))
35 | }
36 | }
37 |
38 | // 关闭mq通道和mq的连接。
39 | func (r *RabbitMQ) destroy() {
40 | r.conn.Close()
41 | }
42 |
--------------------------------------------------------------------------------
/middleware/redis/doc.go:
--------------------------------------------------------------------------------
1 | // Package redis
2 | // @Description redis相关
3 | // @Author 创建人siyixiong;
4 | // @Update 20220513 (创建/修改时间);
5 | package redis
6 |
--------------------------------------------------------------------------------
/middleware/redis/redis.go:
--------------------------------------------------------------------------------
1 | package redis
2 |
3 | import (
4 | "context"
5 | "github.com/go-redis/redis/v8"
6 | )
7 |
8 | var Ctx = context.Background()
9 | var RdbFollowers *redis.Client
10 | var RdbFollowing *redis.Client
11 | var RdbFollowingPart *redis.Client
12 |
13 | var RdbLikeUserId *redis.Client //key:userId,value:VideoId
14 | var RdbLikeVideoId *redis.Client //key:VideoId,value:userId
15 |
16 | var RdbVCid *redis.Client //redis db11 -- video_id + comment_id
17 | var RdbCVid *redis.Client //redis db12 -- comment_id + video_id
18 |
19 | // InitRedis 初始化Redis连接。
20 | func InitRedis() {
21 | RdbFollowers = redis.NewClient(&redis.Options{
22 | Addr: "106.14.75.229:6379",
23 | Password: "tiktok",
24 | DB: 0, // 粉丝列表信息存入 DB0.
25 | })
26 | RdbFollowing = redis.NewClient(&redis.Options{
27 | Addr: "106.14.75.229:6379",
28 | Password: "tiktok",
29 | DB: 1, // 关注列表信息信息存入 DB1.
30 | })
31 | RdbFollowingPart = redis.NewClient(&redis.Options{
32 | Addr: "106.14.75.229:6379",
33 | Password: "tiktok",
34 | DB: 3, // 当前用户是否关注了自己粉丝信息存入 DB1.
35 | })
36 |
37 | RdbLikeUserId = redis.NewClient(&redis.Options{
38 | Addr: "106.14.75.229:6379",
39 | Password: "tiktok",
40 | DB: 5, // 选择将点赞视频id信息存入 DB5.
41 | })
42 |
43 | RdbLikeVideoId = redis.NewClient(&redis.Options{
44 | Addr: "106.14.75.229:6379",
45 | Password: "tiktok",
46 | DB: 6, // 选择将点赞用户id信息存入 DB6.
47 | })
48 | RdbVCid = redis.NewClient(&redis.Options{
49 | Addr: "106.14.75.229:6379",
50 | Password: "tiktok",
51 | DB: 11, // lsy 选择将video_id中的评论id s存入 DB11.
52 | })
53 |
54 | RdbCVid = redis.NewClient(&redis.Options{
55 | Addr: "106.14.75.229:6379",
56 | Password: "tiktok",
57 | DB: 12, // lsy 选择将comment_id对应video_id存入 DB12.
58 | })
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/router.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "TikTok/controller"
5 | "TikTok/middleware/jwt"
6 | "github.com/gin-gonic/gin"
7 | )
8 |
9 | func initRouter(r *gin.Engine) {
10 | apiRouter := r.Group("/douyin")
11 | // basic apis
12 | apiRouter.GET("/feed/", jwt.AuthWithoutLogin(), controller.Feed)
13 | apiRouter.POST("/publish/action/", jwt.AuthBody(), controller.Publish)
14 | apiRouter.GET("/publish/list/", jwt.Auth(), controller.PublishList)
15 | apiRouter.GET("/user/", jwt.Auth(), controller.UserInfo)
16 | apiRouter.POST("/user/register/", controller.Register)
17 | apiRouter.POST("/user/login/", controller.Login)
18 | // extra apis - I
19 | apiRouter.POST("/favorite/action/", jwt.Auth(), controller.FavoriteAction)
20 | apiRouter.GET("/favorite/list/", jwt.Auth(), controller.GetFavouriteList)
21 | apiRouter.POST("/comment/action/", jwt.Auth(), controller.CommentAction)
22 | apiRouter.GET("/comment/list/", jwt.AuthWithoutLogin(), controller.CommentList)
23 | // extra apis - II
24 | apiRouter.POST("/relation/action/", jwt.Auth(), controller.RelationAction)
25 | apiRouter.GET("/relation/follow/list/", jwt.Auth(), controller.GetFollowing)
26 | apiRouter.GET("/relation/follower/list", jwt.Auth(), controller.GetFollowers)
27 | }
28 |
--------------------------------------------------------------------------------
/service/commentService.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "TikTok/dao"
5 | "time"
6 | )
7 |
8 | // CommentService 接口定义
9 | // 发表评论-使用的结构体-service层引用dao层↑的Comment。
10 | type CommentService interface {
11 | /*
12 | 一、其他同学(video)需要使用的方法:
13 | */
14 | // CountFromVideoId
15 | // 1.根据videoId获取视频评论数量的接口
16 | CountFromVideoId(id int64) (int64, error)
17 |
18 | /*
19 | 二、评论模块自己request实现的方法:
20 | */
21 | // Send
22 | // 2、发表评论,传进来评论的基本信息,返回保存是否成功的状态描述
23 | Send(comment dao.Comment) (CommentInfo, error)
24 | // DelComment
25 | // 3、删除评论,传入评论id即可,返回错误状态信息
26 | DelComment(commentId int64) error
27 | // GetList
28 | // 4、查看评论列表-返回评论list-在controller层再封装外层的状态信息
29 | GetList(videoId int64, userId int64) ([]CommentInfo, error)
30 | }
31 |
32 | // CommentInfo 查看评论-传出的结构体-service
33 | type CommentInfo struct {
34 | Id int64 `json:"id,omitempty"`
35 | UserInfo User `json:"user,omitempty"`
36 | Content string `json:"content,omitempty"`
37 | CreateDate string `json:"create_date,omitempty"`
38 | }
39 |
40 | type CommentData struct {
41 | Id int64 `json:"id,omitempty"`
42 | UserId int64 `json:"user_id,omitempty"`
43 | Name string `json:"name,omitempty"`
44 | FollowCount int64 `json:"follow_count"`
45 | FollowerCount int64 `json:"follower_count"`
46 | IsFollow bool `json:"is_follow"`
47 | Content string `json:"content,omitempty"`
48 | CreateDate time.Time `json:"create_date,omitempty"`
49 | }
50 |
--------------------------------------------------------------------------------
/service/commentServiceImpl.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "TikTok/config"
5 | "TikTok/dao"
6 | "TikTok/middleware/rabbitmq"
7 | "TikTok/middleware/redis"
8 | "log"
9 | "sort"
10 | "strconv"
11 | "sync"
12 | "time"
13 | )
14 |
15 | type CommentServiceImpl struct {
16 | UserService
17 | }
18 |
19 | // CountFromVideoId
20 | // 1、使用video id 查询Comment数量
21 | func (c CommentServiceImpl) CountFromVideoId(videoId int64) (int64, error) {
22 | //先在缓存中查
23 | cnt, err := redis.RdbVCid.SCard(redis.Ctx, strconv.FormatInt(videoId, 10)).Result()
24 | if err != nil { //若查询缓存出错,则打印log
25 | //return 0, err
26 | log.Println("count from redis error:", err)
27 | }
28 | log.Println("comment count redis :", cnt)
29 | //1.缓存中查到了数量,则返回数量值-1(去除0值)
30 | if cnt != 0 {
31 | return cnt - 1, nil
32 | }
33 | //2.缓存中查不到则去数据库查
34 | cntDao, err1 := dao.Count(videoId)
35 | log.Println("comment count dao :", cntDao)
36 | if err1 != nil {
37 | log.Println("comment count dao err:", err1)
38 | return 0, nil
39 | }
40 | //将评论id切片存入redis-第一次存储 V-C set 值:
41 | go func() {
42 | //查询评论id list
43 | cList, _ := dao.CommentIdList(videoId)
44 | //先在redis中存储一个-1值,防止脏读
45 | _, _err := redis.RdbVCid.SAdd(redis.Ctx, strconv.Itoa(int(videoId)), config.DefaultRedisValue).Result()
46 | if _err != nil { //若存储redis失败,则直接返回
47 | log.Println("redis save one vId - cId 0 failed")
48 | return
49 | }
50 | //设置key值过期时间
51 | _, err := redis.RdbVCid.Expire(redis.Ctx, strconv.Itoa(int(videoId)),
52 | time.Duration(config.OneMonth)*time.Second).Result()
53 | if err != nil {
54 | log.Println("redis save one vId - cId expire failed")
55 | }
56 | //评论id循环存入redis
57 | for _, commentId := range cList {
58 | insertRedisVideoCommentId(strconv.Itoa(int(videoId)), commentId)
59 | }
60 | log.Println("count comment save ids in redis")
61 | }()
62 | //返回结果
63 | return cntDao, nil
64 | }
65 |
66 | // Send
67 | // 2、发表评论
68 | func (c CommentServiceImpl) Send(comment dao.Comment) (CommentInfo, error) {
69 | log.Println("CommentService-Send: running") //函数已运行
70 | //数据准备
71 | var commentInfo dao.Comment
72 | commentInfo.VideoId = comment.VideoId //评论视频id传入
73 | commentInfo.UserId = comment.UserId //评论用户id传入
74 | commentInfo.CommentText = comment.CommentText //评论内容传入
75 | commentInfo.Cancel = config.ValidComment //评论状态,0,有效
76 | commentInfo.CreateDate = comment.CreateDate //评论时间
77 |
78 | //1.评论信息存储:
79 | commentRtn, err := dao.InsertComment(commentInfo)
80 | if err != nil {
81 | return CommentInfo{}, err
82 | }
83 | //2.查询用户信息
84 | impl := UserServiceImpl{
85 | FollowService: &FollowServiceImp{},
86 | }
87 | userData, err2 := impl.GetUserByIdWithCurId(comment.UserId, comment.UserId)
88 | if err2 != nil {
89 | return CommentInfo{}, err2
90 | }
91 | //3.拼接
92 | commentData := CommentInfo{
93 | Id: commentRtn.Id,
94 | UserInfo: userData,
95 | Content: commentRtn.CommentText,
96 | CreateDate: commentRtn.CreateDate.Format(config.DateTime),
97 | }
98 | //将此发表的评论id存入redis
99 | go func() {
100 | insertRedisVideoCommentId(strconv.Itoa(int(comment.VideoId)), strconv.Itoa(int(commentRtn.Id)))
101 | log.Println("send comment save in redis")
102 | }()
103 | //返回结果
104 | return commentData, nil
105 | }
106 |
107 | // DelComment
108 | // 3、删除评论,传入评论id
109 | func (c CommentServiceImpl) DelComment(commentId int64) error {
110 | log.Println("CommentService-DelComment: running") //函数已运行
111 | //1.先查询redis,若有则删除,返回客户端-再go协程删除数据库;无则在数据库中删除,返回客户端。
112 | n, err := redis.RdbCVid.Exists(redis.Ctx, strconv.FormatInt(commentId, 10)).Result()
113 | if err != nil {
114 | log.Println(err)
115 | }
116 | if n > 0 { //在缓存中有此值,则找出来删除,然后返回
117 | vid, err1 := redis.RdbCVid.Get(redis.Ctx, strconv.FormatInt(commentId, 10)).Result()
118 | if err1 != nil { //没找到,返回err
119 | log.Println("redis find CV err:", err1)
120 | }
121 | //删除,两个redis都要删除
122 | del1, err2 := redis.RdbCVid.Del(redis.Ctx, strconv.FormatInt(commentId, 10)).Result()
123 | if err2 != nil {
124 | log.Println(err2)
125 | }
126 | del2, err3 := redis.RdbVCid.SRem(redis.Ctx, vid, strconv.FormatInt(commentId, 10)).Result()
127 | if err3 != nil {
128 | log.Println(err3)
129 | }
130 | log.Println("del comment in Redis success:", del1, del2) //del1、del2代表删除了几条数据
131 |
132 | /*
133 | //由于多协程会造成数据库读取压力大,因此使用chan来减少压力
134 | commentChan := make(chan int64)
135 | //协程删除数据库中的值
136 | go func() {
137 | commentChan <- commentId
138 | close(commentChan)
139 | //err := dao.DeleteComment(commentId)
140 | //if err != nil {
141 | // log.Println(err)
142 | //}
143 | }()
144 | go func() {
145 | for cId := range commentChan {
146 | err := dao.DeleteComment(cId)
147 | if err != nil {
148 | log.Println(err)
149 | }
150 | }
151 | }()*/
152 |
153 | //使用mq进行数据库中评论的删除-评论状态更新
154 | //评论id传入消息队列
155 | rabbitmq.RmqCommentDel.Publish(strconv.FormatInt(commentId, 10))
156 | return nil
157 | }
158 | //不在内存中,则直接走数据库删除
159 | return dao.DeleteComment(commentId)
160 | }
161 |
162 | // GetList
163 | // 4、查看评论列表-返回评论list
164 | func (c CommentServiceImpl) GetList(videoId int64, userId int64) ([]CommentInfo, error) {
165 | log.Println("CommentService-GetList: running") //函数已运行
166 | /*
167 | //法一、使用SQL语句查询评论列表及用户信息,嵌套user信息。且导致提高耦合性。
168 | //1.查找CommentData结构体的信息
169 | commentData := make([]CommentData, 1)
170 | result := dao.Db.Raw("select T.cid id,T.user_id user_id,T.`name`,T.follow_count,T.follower_count,"+
171 | "\nif(f.cancel is null,'false','true') is_follow,"+
172 | "\nT.comment_text content,T.create_date"+
173 | "\nfrom follows f right join\n("+
174 | "\n\tselect cid,vid,id user_id,`name`,comment_text,create_date,"+
175 | "\n\tcount(if(tag = 'follower' and cancel is not null,1,null)) follower_count,"+
176 | "\n\tcount(if(tag = 'follow' and cancel is not null,1,null)) follow_count"+
177 | "\n\tfrom\n\t("+
178 | "\n\t\tselect c.id cid,u.id,c.video_id vid,`name`,f.cancel,comment_text,create_date,'follower' tag"+
179 | "\n\t\tfrom comments c join users u on c.user_id = u.id and c.cancel = 0"+
180 | "\n\t\tleft join follows f on u.id = f.user_id and f.cancel = 0"+
181 | "\n\t\tunion all"+
182 | "\n\t\tselect c.id cid,u.id,c.video_id vid,`name`,f.cancel,comment_text,create_date,'follow' tag"+
183 | "\n\t\tfrom comments c join users u on c.user_id = u.id and c.cancel = 0"+
184 | "\n\t\tleft join follows f on u.id = f.follower_id and f.cancel = 0"+
185 | "\n\t\t) T\n\t\tgroup by cid,vid,id,`name`,comment_text,create_date"+
186 | "\n) T on f.follower_id = T.user_id and f.cancel = 0 and f.user_id = ?"+
187 | "\nwhere vid = ? group by cid order by create_date desc", userId, videoId).Scan(&commentData)
188 |
189 | err := result.Error
190 |
191 | if nil != err {
192 | log.Println("CommentService-GetList: sql error") //sql查询出错
193 | return nil, err
194 | }
195 | //当前有0条评论
196 | if result.RowsAffected == 0 {
197 | return nil, nil
198 | }
199 | //2.拼接
200 | commentInfoList := make([]CommentInfo, 0, len(commentData))
201 | for _, comment := range commentData {
202 | userData := User{
203 | Id: comment.Id,
204 | Name: comment.Name,
205 | FollowCount: comment.FollowCount,
206 | FollowerCount: comment.FollowerCount,
207 | IsFollow: comment.IsFollow,
208 | }
209 | _commentInfo := CommentInfo{
210 | Id: comment.Id,
211 | UserInfo: userData,
212 | Content: comment.Content,
213 | CreateDate: comment.CreateDate.Format(config.DateTime),
214 | }
215 | //3.组装list
216 | commentInfoList = append(commentInfoList, _commentInfo)
217 | }
218 | //-----------------------法一结束--------------------------
219 | */
220 |
221 | //法二:调用dao,先查评论,再循环查用户信息:
222 | //1.先查询评论列表信息
223 | commentList, err := dao.GetCommentList(videoId)
224 | if err != nil {
225 | log.Println("CommentService-GetList: return err: " + err.Error()) //函数返回提示错误信息
226 | return nil, err
227 | }
228 | //当前有0条评论
229 | if commentList == nil {
230 | return nil, nil
231 | }
232 |
233 | //提前定义好切片长度
234 | commentInfoList := make([]CommentInfo, len(commentList))
235 |
236 | wg := &sync.WaitGroup{}
237 | wg.Add(len(commentList))
238 | idx := 0
239 | for _, comment := range commentList {
240 | //2.调用方法组装评论信息,再append
241 | var commentData CommentInfo
242 | //将评论信息进行组装,添加想要的信息,插入从数据库中查到的数据
243 | go func(comment dao.Comment) {
244 | oneComment(&commentData, &comment, userId)
245 | //3.组装list
246 | //commentInfoList = append(commentInfoList, commentData)
247 | commentInfoList[idx] = commentData
248 | idx = idx + 1
249 | wg.Done()
250 | }(comment)
251 | }
252 | wg.Wait()
253 | //评论排序-按照主键排序
254 | sort.Sort(CommentSlice(commentInfoList))
255 | //------------------------法二结束----------------------------
256 |
257 | //协程查询redis中是否有此记录,无则将评论id切片存入redis
258 | go func() {
259 | //1.先在缓存中查此视频是否已有评论列表
260 | cnt, err1 := redis.RdbVCid.SCard(redis.Ctx, strconv.FormatInt(videoId, 10)).Result()
261 | if err1 != nil { //若查询缓存出错,则打印log
262 | //return 0, err
263 | log.Println("count from redis error:", err)
264 | }
265 | //2.缓存中查到了数量大于0,则说明数据正常,不用更新缓存
266 | if cnt > 0 {
267 | return
268 | }
269 | //3.缓存中数据不正确,更新缓存:
270 | //先在redis中存储一个-1 值,防止脏读
271 | _, _err := redis.RdbVCid.SAdd(redis.Ctx, strconv.Itoa(int(videoId)), config.DefaultRedisValue).Result()
272 | if _err != nil { //若存储redis失败,则直接返回
273 | log.Println("redis save one vId - cId 0 failed")
274 | return
275 | }
276 | //设置key值过期时间
277 | _, err2 := redis.RdbVCid.Expire(redis.Ctx, strconv.Itoa(int(videoId)),
278 | time.Duration(config.OneMonth)*time.Second).Result()
279 | if err2 != nil {
280 | log.Println("redis save one vId - cId expire failed")
281 | }
282 | //将评论id循环存入redis
283 | for _, _comment := range commentInfoList {
284 | insertRedisVideoCommentId(strconv.Itoa(int(videoId)), strconv.Itoa(int(_comment.Id)))
285 | }
286 | log.Println("comment list save ids in redis")
287 | }()
288 |
289 | log.Println("CommentService-GetList: return list success") //函数执行成功,返回正确信息
290 | return commentInfoList, nil
291 | }
292 |
293 | //在redis中存储video_id对应的comment_id 、 comment_id对应的video_id
294 | func insertRedisVideoCommentId(videoId string, commentId string) {
295 | //在redis-RdbVCid中存储video_id对应的comment_id
296 | _, err := redis.RdbVCid.SAdd(redis.Ctx, videoId, commentId).Result()
297 | if err != nil { //若存储redis失败-有err,则直接删除key
298 | log.Println("redis save send: vId - cId failed, key deleted")
299 | redis.RdbVCid.Del(redis.Ctx, videoId)
300 | return
301 | }
302 | //在redis-RdbCVid中存储comment_id对应的video_id
303 | _, err = redis.RdbCVid.Set(redis.Ctx, commentId, videoId, 0).Result()
304 | if err != nil {
305 | log.Println("redis save one cId - vId failed")
306 | }
307 | }
308 |
309 | //此函数用于给一个评论赋值:评论信息+用户信息 填充
310 | func oneComment(comment *CommentInfo, com *dao.Comment, userId int64) {
311 | var wg sync.WaitGroup
312 | wg.Add(1)
313 | //根据评论用户id和当前用户id,查询评论用户信息
314 | impl := UserServiceImpl{
315 | FollowService: &FollowServiceImp{},
316 | }
317 | var err error
318 | comment.Id = com.Id
319 | comment.Content = com.CommentText
320 | comment.CreateDate = com.CreateDate.Format(config.DateTime)
321 | comment.UserInfo, err = impl.GetUserByIdWithCurId(com.UserId, userId)
322 | if err != nil {
323 | log.Println("CommentService-GetList: GetUserByIdWithCurId return err: " + err.Error()) //函数返回提示错误信息
324 | }
325 | wg.Done()
326 | wg.Wait()
327 | }
328 |
329 | // CommentSlice 此变量以及以下三个函数都是做排序-准备工作
330 | type CommentSlice []CommentInfo
331 |
332 | func (a CommentSlice) Len() int { //重写Len()方法
333 | return len(a)
334 | }
335 | func (a CommentSlice) Swap(i, j int) { //重写Swap()方法
336 | a[i], a[j] = a[j], a[i]
337 | }
338 | func (a CommentSlice) Less(i, j int) bool { //重写Less()方法
339 | return a[i].Id > a[j].Id
340 | }
341 |
--------------------------------------------------------------------------------
/service/commentServiceImpl_test.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "TikTok/dao"
5 | "fmt"
6 | "testing"
7 | )
8 |
9 | func TestCountComment(t *testing.T) {
10 | dao.Init()
11 | impl := CommentServiceImpl{}
12 | count, err := impl.CountFromVideoId(1)
13 | if err != nil {
14 | fmt.Println("count err:", err)
15 | }
16 | fmt.Println(count)
17 | }
18 |
--------------------------------------------------------------------------------
/service/commentSub.go:
--------------------------------------------------------------------------------
1 | package service
2 |
--------------------------------------------------------------------------------
/service/doc.go:
--------------------------------------------------------------------------------
1 | // Package service
2 | // @Description 本包内分包定义不同模块的service接口和实现
3 | // @Author 创建人siyixiong;
4 | // @Update 20220513 (创建/修改时间);
5 | package service
6 |
--------------------------------------------------------------------------------
/service/followService.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | // FollowService 定义用户关系接口以及用户关系中的各种方法
4 | type FollowService interface {
5 | /*
6 | 一、其他同学需要调用的业务方法。
7 | */
8 | // IsFollowing 根据当前用户id和目标用户id来判断当前用户是否关注了目标用户
9 | IsFollowing(userId int64, targetId int64) (bool, error)
10 | // GetFollowerCnt 根据用户id来查询用户被多少其他用户关注
11 | GetFollowerCnt(userId int64) (int64, error)
12 | // GetFollowingCnt 根据用户id来查询用户关注了多少其它用户
13 | GetFollowingCnt(userId int64) (int64, error)
14 | /*
15 | 二、直接request需要的业务方法
16 | */
17 | // AddFollowRelation 当前用户关注目标用户
18 | AddFollowRelation(userId int64, targetId int64) (bool, error)
19 | // DeleteFollowRelation 当前用户取消对目标用户的关注
20 | DeleteFollowRelation(userId int64, targetId int64) (bool, error)
21 | // GetFollowing 获取当前用户的关注列表
22 | GetFollowing(userId int64) ([]User, error)
23 | // GetFollowers 获取当前用户的粉丝列表
24 | GetFollowers(userId int64) ([]User, error)
25 | }
26 |
--------------------------------------------------------------------------------
/service/followServiceImpl.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "TikTok/config"
5 | "TikTok/dao"
6 | "TikTok/middleware/rabbitmq"
7 | "TikTok/middleware/redis"
8 | "log"
9 | "strconv"
10 | "strings"
11 | "sync"
12 | "time"
13 | )
14 |
15 | // FollowServiceImp 该结构体继承FollowService接口。
16 | type FollowServiceImp struct {
17 | UserService
18 | }
19 |
20 | var (
21 | followServiceImp *FollowServiceImp //controller层通过该实例变量调用service的所有业务方法。
22 | followServiceOnce sync.Once //限定该service对象为单例,节约内存。
23 | )
24 |
25 | // NewFSIInstance 生成并返回FollowServiceImp结构体单例变量。
26 | func NewFSIInstance() *FollowServiceImp {
27 | followServiceOnce.Do(
28 | func() {
29 | followServiceImp = &FollowServiceImp{
30 | UserService: &UserServiceImpl{
31 | // 存在我调userService中,userService又要调我。
32 | FollowService: &FollowServiceImp{},
33 | },
34 | }
35 | })
36 | return followServiceImp
37 | }
38 |
39 | // IsFollowing 给定当前用户和目标用户id,判断是否存在关注关系。
40 | func (*FollowServiceImp) IsFollowing(userId int64, targetId int64) (bool, error) {
41 | // 先查Redis里面是否有此关系。
42 | if flag, err := redis.RdbFollowingPart.SIsMember(redis.Ctx, strconv.Itoa(int(userId)), targetId).Result(); flag {
43 | // 重现设置过期时间。
44 | redis.RdbFollowingPart.Expire(redis.Ctx, strconv.Itoa(int(userId)), config.ExpireTime)
45 | return true, err
46 | }
47 | // SQL 查询。
48 | relation, err := dao.NewFollowDaoInstance().FindRelation(userId, targetId)
49 |
50 | if nil != err {
51 | return false, err
52 | }
53 | if nil == relation {
54 | return false, nil
55 | }
56 | // 存在此关系,将其注入Redis中。
57 | go addRelationToRedis(int(userId), int(targetId))
58 |
59 | return true, nil
60 | }
61 | func addRelationToRedis(userId int, targetId int) {
62 | // 第一次存入时,给该key添加一个-1为key,防止脏数据的写入。当然set可以去重,直接加,便于CPU。
63 | redis.RdbFollowingPart.SAdd(redis.Ctx, strconv.Itoa(int(userId)), -1)
64 | // 将查询到的关注关系注入Redis.
65 | redis.RdbFollowingPart.SAdd(redis.Ctx, strconv.Itoa(int(userId)), targetId)
66 | // 更新过期时间。
67 | redis.RdbFollowingPart.Expire(redis.Ctx, strconv.Itoa(int(userId)), config.ExpireTime)
68 | }
69 |
70 | // GetFollowerCnt 给定当前用户id,查询其粉丝数量。
71 | func (*FollowServiceImp) GetFollowerCnt(userId int64) (int64, error) {
72 | // 查Redis中是否已经存在。
73 | if cnt, err := redis.RdbFollowers.SCard(redis.Ctx, strconv.Itoa(int(userId))).Result(); cnt > 0 {
74 | // 更新过期时间。
75 | redis.RdbFollowers.Expire(redis.Ctx, strconv.Itoa(int(userId)), config.ExpireTime)
76 | return cnt - 1, err
77 | }
78 | // SQL中查询。
79 | ids, err := dao.NewFollowDaoInstance().GetFollowersIds(userId)
80 | if nil != err {
81 | return 0, err
82 | }
83 | // 将数据存入Redis.
84 | // 更新followers 和 followingPart
85 | go addFollowersToRedis(int(userId), ids)
86 |
87 | return int64(len(ids)), err
88 | }
89 | func addFollowersToRedis(userId int, ids []int64) {
90 | redis.RdbFollowers.SAdd(redis.Ctx, strconv.Itoa(userId), -1)
91 | for i, id := range ids {
92 | redis.RdbFollowers.SAdd(redis.Ctx, strconv.Itoa(userId), id)
93 | redis.RdbFollowingPart.SAdd(redis.Ctx, strconv.Itoa(int(id)), userId)
94 | redis.RdbFollowingPart.SAdd(redis.Ctx, strconv.Itoa(int(id)), -1)
95 | // 更新部分关注者的时间
96 | redis.RdbFollowingPart.Expire(redis.Ctx, strconv.Itoa(int(id)),
97 | config.ExpireTime+time.Duration((i%10)<<8))
98 | }
99 | // 更新followers的过期时间。
100 | redis.RdbFollowers.Expire(redis.Ctx, strconv.Itoa(userId), config.ExpireTime)
101 |
102 | }
103 |
104 | // GetFollowingCnt 给定当前用户id,查询其关注者数量。
105 | func (*FollowServiceImp) GetFollowingCnt(userId int64) (int64, error) {
106 | // 查看Redis中是否有关注数。
107 | if cnt, err := redis.RdbFollowing.SCard(redis.Ctx, strconv.Itoa(int(userId))).Result(); cnt > 0 {
108 | // 更新过期时间。
109 | redis.RdbFollowing.Expire(redis.Ctx, strconv.Itoa(int(userId)), config.ExpireTime)
110 | return cnt - 1, err
111 | }
112 | // 用SQL查询。
113 | ids, err := dao.NewFollowDaoInstance().GetFollowingIds(userId)
114 |
115 | if nil != err {
116 | return 0, err
117 | }
118 | // 更新Redis中的followers和followPart
119 | go addFollowingToRedis(int(userId), ids)
120 |
121 | return int64(len(ids)), err
122 | }
123 | func addFollowingToRedis(userId int, ids []int64) {
124 | redis.RdbFollowing.SAdd(redis.Ctx, strconv.Itoa(userId), -1)
125 | for i, id := range ids {
126 | redis.RdbFollowing.SAdd(redis.Ctx, strconv.Itoa(userId), id)
127 | redis.RdbFollowingPart.SAdd(redis.Ctx, strconv.Itoa(userId), id)
128 | redis.RdbFollowingPart.SAdd(redis.Ctx, strconv.Itoa(userId), -1)
129 | // 更新过期时间
130 | redis.RdbFollowingPart.Expire(redis.Ctx, strconv.Itoa(userId),
131 | config.ExpireTime+time.Duration((i%10)<<8))
132 | }
133 | // 更新following的过期时间
134 | redis.RdbFollowing.Expire(redis.Ctx, strconv.Itoa(userId), config.ExpireTime)
135 | }
136 |
137 | // AddFollowRelation 给定当前用户和目标对象id,添加他们之间的关注关系。
138 | func (*FollowServiceImp) AddFollowRelation(userId int64, targetId int64) (bool, error) {
139 | // 加信息打入消息队列。
140 | sb := strings.Builder{}
141 | sb.WriteString(strconv.Itoa(int(userId)))
142 | sb.WriteString(" ")
143 | sb.WriteString(strconv.Itoa(int(targetId)))
144 | rabbitmq.RmqFollowAdd.Publish(sb.String())
145 | // 记录日志
146 | log.Println("消息打入成功。")
147 | // 更新redis信息。
148 | return updateRedisWithAdd(userId, targetId)
149 | /*followDao := dao.NewFollowDaoInstance()
150 | follow, err := followDao.FindEverFollowing(targetId, userId)
151 | // 寻找SQL 出错。
152 | if nil != err {
153 | return false, err
154 | }
155 | // 曾经关注过,只需要update一下cancel即可。
156 | if nil != follow {
157 | _, err := followDao.UpdateFollowRelation(targetId, userId, 0)
158 | // update 出错。
159 | if nil != err {
160 | return false, err
161 | }
162 | // update 成功。
163 | return true, nil
164 | }
165 | // 曾经没有关注过,需要插入一条关注关系。
166 | _, err = followDao.InsertFollowRelation(targetId, userId)
167 | if nil != err {
168 | // insert 出错
169 | return false, err
170 | }
171 | // insert 成功。
172 | return true, nil*/
173 | }
174 |
175 | // 添加关注时,设置Redis
176 | func updateRedisWithAdd(userId int64, targetId int64) (bool, error) {
177 | /*
178 | 1-Redis是否存在followers_targetId.
179 | 2-Redis是否存在following_userId.
180 | 3-Redis是否存在following_part_userId.
181 | */
182 | // step1
183 | targetIdStr := strconv.Itoa(int(targetId))
184 | if cnt, _ := redis.RdbFollowers.SCard(redis.Ctx, targetIdStr).Result(); 0 != cnt {
185 | redis.RdbFollowers.SAdd(redis.Ctx, targetIdStr, userId)
186 | redis.RdbFollowers.Expire(redis.Ctx, targetIdStr, config.ExpireTime)
187 | }
188 | // step2
189 | followingUserIdStr := strconv.Itoa(int(userId))
190 | if cnt, _ := redis.RdbFollowing.SCard(redis.Ctx, followingUserIdStr).Result(); 0 != cnt {
191 | redis.RdbFollowing.SAdd(redis.Ctx, followingUserIdStr, targetId)
192 | redis.RdbFollowing.Expire(redis.Ctx, followingUserIdStr, config.ExpireTime)
193 | }
194 | // step3
195 | followingPartUserIdStr := followingUserIdStr
196 | redis.RdbFollowingPart.SAdd(redis.Ctx, followingPartUserIdStr, targetId)
197 | // 可能是第一次给改用户加followingPart的关注者,需要加上-1防止脏读。
198 | redis.RdbFollowingPart.SAdd(redis.Ctx, followingPartUserIdStr, -1)
199 | redis.RdbFollowingPart.Expire(redis.Ctx, followingPartUserIdStr, config.ExpireTime)
200 | return true, nil
201 | }
202 |
203 | // DeleteFollowRelation 给定当前用户和目标用户id,删除其关注关系。
204 | func (*FollowServiceImp) DeleteFollowRelation(userId int64, targetId int64) (bool, error) {
205 | // 加信息打入消息队列。
206 | sb := strings.Builder{}
207 | sb.WriteString(strconv.Itoa(int(userId)))
208 | sb.WriteString(" ")
209 | sb.WriteString(strconv.Itoa(int(targetId)))
210 | rabbitmq.RmqFollowDel.Publish(sb.String())
211 | // 记录日志
212 | log.Println("消息打入成功。")
213 | // 更新redis信息。
214 | return updateRedisWithDel(userId, targetId)
215 | /*followDao := dao.NewFollowDaoInstance()
216 | follow, err := followDao.FindEverFollowing(targetId, userId)
217 | // 寻找 SQL 出错。
218 | if nil != err {
219 | return false, err
220 | }
221 | // 曾经关注过,只需要update一下cancel即可。
222 | if nil != follow {
223 | _, err := followDao.UpdateFollowRelation(targetId, userId, 1)
224 | // update 出错。
225 | if nil != err {
226 | return false, err
227 | }
228 | // update 成功。
229 | return true, nil
230 | }
231 | // 没有关注关系
232 | return false, nil*/
233 | }
234 |
235 | // 当取关时,更新redis里的信息
236 | func updateRedisWithDel(userId int64, targetId int64) (bool, error) {
237 | /*
238 | 1-Redis是否存在followers_targetId.
239 | 2-Redis是否存在following_userId.
240 | 2-Redis是否存在following_part_userId.
241 | */
242 | // step1
243 | targetIdStr := strconv.Itoa(int(targetId))
244 | if cnt, _ := redis.RdbFollowers.SCard(redis.Ctx, targetIdStr).Result(); 0 != cnt {
245 | redis.RdbFollowers.SRem(redis.Ctx, targetIdStr, userId)
246 | redis.RdbFollowers.Expire(redis.Ctx, targetIdStr, config.ExpireTime)
247 | }
248 | // step2
249 | followingIdStr := strconv.Itoa(int(userId))
250 | if cnt, _ := redis.RdbFollowing.SCard(redis.Ctx, followingIdStr).Result(); 0 != cnt {
251 | redis.RdbFollowing.SRem(redis.Ctx, followingIdStr, targetId)
252 | redis.RdbFollowing.Expire(redis.Ctx, followingIdStr, config.ExpireTime)
253 | }
254 | // step3
255 | followingPartUserIdStr := followingIdStr
256 | if cnt, _ := redis.RdbFollowingPart.Exists(redis.Ctx, followingPartUserIdStr).Result(); 0 != cnt {
257 | redis.RdbFollowingPart.SRem(redis.Ctx, followingPartUserIdStr, targetId)
258 | redis.RdbFollowingPart.Expire(redis.Ctx, followingPartUserIdStr, config.ExpireTime)
259 | }
260 | return true, nil
261 | }
262 |
263 | // GetFollowing 根据当前用户id来查询他的关注者列表。
264 | func (f *FollowServiceImp) getFollowing(userId int64) ([]User, error) {
265 | // 获取关注对象的id数组。
266 | ids, err := dao.NewFollowDaoInstance().GetFollowingIds(userId)
267 | // 查询出错
268 | if nil != err {
269 | return nil, err
270 | }
271 | // 没得关注者
272 | if nil == ids {
273 | return nil, nil
274 | }
275 | // 根据每个id来查询用户信息。
276 | len := len(ids)
277 | if len > 0 {
278 | len -= 1
279 | }
280 | var wg sync.WaitGroup
281 | wg.Add(len)
282 | users := make([]User, len)
283 | i, j := 0, 0
284 | for ; i < len; j++ {
285 | if ids[j] == -1 {
286 | continue
287 | }
288 | go func(i int, idx int64) {
289 | defer wg.Done()
290 | users[i], _ = f.GetUserByIdWithCurId(idx, userId)
291 | }(i, ids[i])
292 | i++
293 | }
294 | wg.Wait()
295 | // 返回关注对象列表。
296 | return users, nil
297 | }
298 |
299 | // GetFollowing 根据当前用户id来查询他的关注者列表。
300 | func (f *FollowServiceImp) GetFollowing(userId int64) ([]User, error) {
301 | return getFollowing(userId)
302 | /*// 先查Redis,看是否有全部关注信息。
303 | followingIdStr := strconv.Itoa(int(userId))
304 | if cnt, _ := middleware.RdbFollowers.SCard(middleware.Ctx, followingIdStr).Result(); 0 == cnt {
305 | users, _ := f.getFollowing(userId)
306 |
307 | go setRedisFollowing(userId, users)
308 |
309 | return users, nil
310 | }
311 | // Redis中有。
312 | UserIdStr := strconv.Itoa(int(userId))
313 | userIds, _ := middleware.RdbFollowing.SMembers(middleware.Ctx, UserIdStr).Result()
314 | len := len(userIds)
315 | if len > 0 {
316 | len -= 1
317 | }
318 | users := make([]User, len)
319 | wg := sync.WaitGroup{}
320 | wg.Add(len)
321 | i, j := 0, 0
322 | for ; i < len; j++ {
323 | idx, _ := strconv.Atoi(userIds[j])
324 | if idx == -1 {
325 | continue
326 | }
327 | go func(i int, idx int) {
328 | defer wg.Done()
329 | users[i], _ = f.GetUserByIdWithCurId(int64(idx), userId)
330 | }(i, idx)
331 |
332 | i++
333 | }
334 | wg.Wait()
335 | log.Println("从Redis中查询到所有关注者。")
336 | return users, nil*/
337 | }
338 |
339 | // 设置Redis关于所有关注的信息。
340 | func setRedisFollowing(userId int64, users []User) {
341 | /*
342 | 1-设置following_userId的所有关注id。
343 | 2-设置following_part_id关注信息。
344 | */
345 | // 加上-1防止脏读
346 | followingIdStr := strconv.Itoa(int(userId))
347 | redis.RdbFollowing.SAdd(redis.Ctx, followingIdStr, -1)
348 | // 设置过期时间
349 | redis.RdbFollowing.Expire(redis.Ctx, followingIdStr, config.ExpireTime)
350 | for i, user := range users {
351 | redis.RdbFollowing.SAdd(redis.Ctx, followingIdStr, user.Id)
352 |
353 | redis.RdbFollowingPart.SAdd(redis.Ctx, followingIdStr, user.Id)
354 | redis.RdbFollowingPart.SAdd(redis.Ctx, followingIdStr, -1)
355 | // 随机设置过期时间
356 | redis.RdbFollowingPart.Expire(redis.Ctx, followingIdStr, config.ExpireTime+
357 | time.Duration((i%10)<<8))
358 | }
359 | }
360 |
361 | // 从数据库查所有关注用户信息。
362 | func getFollowing(userId int64) ([]User, error) {
363 | users := make([]User, 1)
364 | // 查询出错。
365 | if err := dao.Db.Raw("select id,`name`,"+
366 | "\ncount(if(tag = 'follower' and cancel is not null,1,null)) follower_count,"+
367 | "\ncount(if(tag = 'follow' and cancel is not null,1,null)) follow_count,"+
368 | "\n 'true' is_follow\nfrom\n("+
369 | "\nselect f1.follower_id fid,u.id,`name`,f2.cancel,'follower' tag"+
370 | "\nfrom follows f1 join users u on f1.user_id = u.id and f1.cancel = 0"+
371 | "\nleft join follows f2 on u.id = f2.user_id and f2.cancel = 0\n\tunion all"+
372 | "\nselect f1.follower_id fid,u.id,`name`,f2.cancel,'follow' tag"+
373 | "\nfrom follows f1 join users u on f1.user_id = u.id and f1.cancel = 0"+
374 | "\nleft join follows f2 on u.id = f2.follower_id and f2.cancel = 0\n) T"+
375 | "\nwhere fid = ? group by fid,id,`name`", userId).Scan(&users).Error; nil != err {
376 | return nil, err
377 | }
378 | // 返回关注对象列表。
379 | return users, nil
380 | }
381 |
382 | // GetFollowers 根据当前用户id来查询他的粉丝列表。
383 |
384 | func (f *FollowServiceImp) getFollowers(userId int64) ([]User, error) {
385 | // 获取粉丝的id数组。
386 | ids, err := dao.NewFollowDaoInstance().GetFollowersIds(userId)
387 | // 查询出错
388 | if nil != err {
389 | return nil, err
390 | }
391 | // 没得粉丝
392 | if nil == ids {
393 | return nil, nil
394 | }
395 | // 根据每个id来查询用户信息。
396 | return f.getUserById(ids, userId)
397 | }
398 | func (f *FollowServiceImp) getUserById(ids []int64, userId int64) ([]User, error) {
399 | len := len(ids)
400 | if len > 0 {
401 | len -= 1
402 | }
403 | users := make([]User, len)
404 | var wg sync.WaitGroup
405 | wg.Add(len)
406 | i, j := 0, 0
407 | for ; i < len; j++ {
408 | // 越过-1
409 | if ids[j] == -1 {
410 | continue
411 | }
412 | //开启协程来查。
413 | go func(i int, idx int64) {
414 | defer wg.Done()
415 | users[i], _ = f.GetUserByIdWithCurId(idx, userId)
416 | }(i, ids[i])
417 | i++
418 | }
419 | wg.Wait()
420 | // 返回粉丝列表。
421 | return users, nil
422 | }
423 |
424 | // GetFollowers 根据当前用户id来查询他的粉丝列表。
425 | func (f *FollowServiceImp) GetFollowers(userId int64) ([]User, error) {
426 | return getFollowers(userId)
427 | /*// 先查Redis,看是否有全部粉丝信息。
428 | followersIdStr := strconv.Itoa(int(userId))
429 | if cnt, _ := middleware.RdbFollowers.SCard(middleware.Ctx, followersIdStr).Result(); 0 == cnt {
430 | users, _ := f.getFollowers(userId)
431 |
432 | go setRedisFollowers(userId, users)
433 |
434 | return users, nil
435 | }
436 | // Redis中有。
437 | // 先更新有效期。
438 | middleware.RdbFollowers.Expire(middleware.Ctx, followersIdStr, config.ExpireTime)
439 | userIds, _ := middleware.RdbFollowers.SMembers(middleware.Ctx, followersIdStr).Result()
440 | len := len(userIds)
441 | if len > 0 {
442 | len -= 1
443 | }
444 | users := make([]User, len)
445 | var wg sync.WaitGroup
446 | wg.Add(len)
447 | i, j := 0, 0
448 | for ; i < len; j++ {
449 | idx, _ := strconv.Atoi(userIds[j])
450 | if idx == -1 {
451 | continue
452 | }
453 | go func(i int, idx int) {
454 | defer wg.Done()
455 | users[i], _ = f.GetUserByIdWithCurId(int64(idx), userId)
456 | }(i, idx)
457 | i++
458 | }
459 | wg.Wait()
460 | return users, nil*/
461 | }
462 |
463 | // 从数据库查所有粉丝信息。
464 | func getFollowers(userId int64) ([]User, error) {
465 | users := make([]User, 1)
466 |
467 | if err := dao.Db.Raw("select T.id,T.name,T.follow_cnt follow_count,T.follower_cnt follower_count,if(f.cancel is null,'false','true') is_follow"+
468 | "\nfrom follows f right join"+
469 | "\n(select fid,id,`name`,"+
470 | "\ncount(if(tag = 'follower' and cancel is not null,1,null)) follower_cnt,"+
471 | "\ncount(if(tag = 'follow' and cancel is not null,1,null)) follow_cnt"+
472 | "\nfrom("+
473 | "\nselect f1.user_id fid,u.id,`name`,f2.cancel,'follower' tag"+
474 | "\nfrom follows f1 join users u on f1.follower_id = u.id and f1.cancel = 0"+
475 | "\nleft join follows f2 on u.id = f2.user_id and f2.cancel = 0"+
476 | "\nunion all"+
477 | "\nselect f1.user_id fid,u.id,`name`,f2.cancel,'follow' tag"+
478 | "\nfrom follows f1 join users u on f1.follower_id = u.id and f1.cancel = 0"+
479 | "\nleft join follows f2 on u.id = f2.follower_id and f2.cancel = 0"+
480 | "\n) T group by fid,id,`name`"+
481 | "\n) T on f.user_id = T.id and f.follower_id = T.fid and f.cancel = 0 where fid = ?", userId).
482 | Scan(&users).Error; nil != err {
483 | // 查询出错。
484 | return nil, err
485 | }
486 | // 查询成功。
487 | return users, nil
488 | }
489 |
490 | // 设置Redis关于所有粉丝的信息
491 | func setRedisFollowers(userId int64, users []User) {
492 | /*
493 | 1-设置followers_userId的所有粉丝id。
494 | 2-设置following_part_id关注信息。
495 | */
496 | // 加上-1防止脏读。
497 | followersIdStr := strconv.Itoa(int(userId))
498 | redis.RdbFollowers.SAdd(redis.Ctx, followersIdStr, -1)
499 | // 设置过期时间
500 | redis.RdbFollowers.Expire(redis.Ctx, followersIdStr, config.ExpireTime)
501 | for i, user := range users {
502 | redis.RdbFollowers.SAdd(redis.Ctx, followersIdStr, user.Id)
503 |
504 | userUserIdStr := strconv.Itoa(int(user.Id))
505 | redis.RdbFollowingPart.SAdd(redis.Ctx, userUserIdStr, userId)
506 | redis.RdbFollowingPart.SAdd(redis.Ctx, userUserIdStr, -1)
507 | // 随机更新过期时间
508 | redis.RdbFollowingPart.Expire(redis.Ctx, userUserIdStr, config.ExpireTime+
509 | time.Duration((i%10)<<8))
510 |
511 | if user.IsFollow {
512 | redis.RdbFollowingPart.SAdd(redis.Ctx, followersIdStr, user.Id)
513 | redis.RdbFollowingPart.SAdd(redis.Ctx, followersIdStr, -1)
514 | redis.RdbFollowingPart.Expire(redis.Ctx, followersIdStr, config.ExpireTime+
515 | time.Duration((i%10)<<8))
516 | }
517 | }
518 | }
519 |
--------------------------------------------------------------------------------
/service/followServiceImpl_test.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func TestIsFollow(t *testing.T) {
9 | isFollow, err := NewFSIInstance().IsFollowing(1, 2)
10 | if nil != err {
11 | t.Errorf("IsFollow() error = %v", err)
12 | }
13 | if false == isFollow {
14 | fmt.Println("不存在该关系")
15 | }
16 | fmt.Println("存在该关系")
17 | }
18 |
--------------------------------------------------------------------------------
/service/followSub.go:
--------------------------------------------------------------------------------
1 | package service
2 |
--------------------------------------------------------------------------------
/service/likeService.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | // LikeService 定义点赞状态和点赞数量
4 | type LikeService interface {
5 | /*
6 | 1.其他模块(video)需要使用的业务方法。
7 | */
8 | //IsFavorite 根据当前视频id判断是否点赞了该视频。
9 | IsFavourite(videoId int64, userId int64) (bool, error)
10 | //FavouriteCount 根据当前视频id获取当前视频点赞数量。
11 | FavouriteCount(videoId int64) (int64, error)
12 | //TotalFavourite 根据userId获取这个用户总共被点赞数量
13 | TotalFavourite(userId int64) (int64, error)
14 | //FavouriteVideoCount 根据userId获取这个用户点赞视频数量
15 | FavouriteVideoCount(userId int64) (int64, error)
16 | /*
17 | 2.request需要实现的功能
18 | */
19 | //当前用户对视频的点赞操作 ,并把这个行为更新到like表中。
20 | //当前操作行为,1点赞,2取消点赞。
21 | FavouriteAction(userId int64, videoId int64, actionType int32) error
22 | // GetFavouriteList 获取当前用户的所有点赞视频,调用videoService的方法
23 | GetFavouriteList(userId int64, curId int64) ([]Video, error)
24 | }
25 |
--------------------------------------------------------------------------------
/service/likeServiceImpl.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "TikTok/config"
5 | "TikTok/dao"
6 | "TikTok/middleware/rabbitmq"
7 | "TikTok/middleware/redis"
8 | "errors"
9 | "log"
10 | "strconv"
11 | "strings"
12 | "sync"
13 | "time"
14 | )
15 |
16 | type LikeServiceImpl struct {
17 | VideoService
18 | UserService
19 | }
20 |
21 | //IsFavourite 根据userId,videoId查询点赞状态 这边可以快一点,通过查询两个Redis DB;
22 | //step1:查询Redis LikeUserId(key:strUserId)是否已经加载过此信息,通过是否存在value:videoId 判断点赞状态;
23 | //step2:如LikeUserId没有对应信息,查询LikeVideoId(key:strVideoId)是否已经加载过此信息,通过是否存在value:userId 判断点赞状态;
24 | //step3:LikeUserId LikeVideoId中都没有对应key,维护LikeUserId对应key,并通过查询key:strUserId中是否存在value:videoId 判断点赞状态;
25 | func (like *LikeServiceImpl) IsFavourite(videoId int64, userId int64) (bool, error) {
26 | //将int64 userId转换为 string strUserId
27 | strUserId := strconv.FormatInt(userId, 10)
28 | //将int64 videoId转换为 string strVideoId
29 | strVideoId := strconv.FormatInt(videoId, 10)
30 | //step1:查询Redis LikeUserId,key:strUserId中是否存在value:videoId,key中存在value 返回true,不存在返回false
31 | if n, err := redis.RdbLikeUserId.Exists(redis.Ctx, strUserId).Result(); n > 0 {
32 | //如果有问题,说明查询redis失败,返回默认false,返回错误信息
33 | if err != nil {
34 | log.Printf("方法:IsFavourite RedisLikeUserId query key失败:%v", err)
35 | return false, err
36 | }
37 | exist, err1 := redis.RdbLikeUserId.SIsMember(redis.Ctx, strUserId, videoId).Result()
38 | //如果有问题,说明查询redis失败,返回默认false,返回错误信息
39 | if err1 != nil {
40 | log.Printf("方法:IsFavourite RedisLikeUserId query value失败:%v", err1)
41 | return false, err1
42 | }
43 | log.Printf("方法:IsFavourite RedisLikeUserId query value成功")
44 | return exist, nil
45 | } else { //step2:LikeUserId不存在key,查询Redis LikeVideoId,key中存在value 返回true,不存在返回false
46 | if n, err := redis.RdbLikeVideoId.Exists(redis.Ctx, strVideoId).Result(); n > 0 {
47 | //如果有问题,说明查询redis失败,返回默认false,返回错误信息
48 | if err != nil {
49 | log.Printf("方法:IsFavourite RedisLikeVideoId query key失败:%v", err)
50 | return false, err
51 | }
52 | exist, err1 := redis.RdbLikeVideoId.SIsMember(redis.Ctx, strVideoId, userId).Result()
53 | //如果有问题,说明查询redis失败,返回默认false,返回错误信息
54 | if err1 != nil {
55 | log.Printf("方法:IsFavourite RedisLikeVideoId query value失败:%v", err1)
56 | return false, err1
57 | }
58 | log.Printf("方法:IsFavourite RedisLikeVideoId query value成功")
59 | return exist, nil
60 | } else {
61 | //key:strUserId,加入value:DefaultRedisValue,过期才会删,防止删最后一个数据的时候数据库还没更新完出现脏读,或者数据库操作失败造成的脏读
62 | if _, err := redis.RdbLikeUserId.SAdd(redis.Ctx, strUserId, config.DefaultRedisValue).Result(); err != nil {
63 | log.Printf("方法:IsFavourite RedisLikeUserId add value失败")
64 | redis.RdbLikeUserId.Del(redis.Ctx, strUserId)
65 | return false, err
66 | }
67 | //给键值设置有效期,类似于gc机制
68 | _, err := redis.RdbLikeUserId.Expire(redis.Ctx, strUserId,
69 | time.Duration(config.OneMonth)*time.Second).Result()
70 | if err != nil {
71 | log.Printf("方法:IsFavourite RedisLikeUserId 设置有效期失败")
72 | redis.RdbLikeUserId.Del(redis.Ctx, strUserId)
73 | return false, err
74 | }
75 | //step3:LikeUserId LikeVideoId中都没有对应key,通过userId查询likes表,返回所有点赞videoId,并维护到Redis LikeUserId(key:strUserId)
76 | videoIdList, err1 := dao.GetLikeVideoIdList(userId)
77 | //如果有问题,说明查询数据库失败,返回默认false,返回错误信息:"get likeVideoIdList failed"
78 | if err1 != nil {
79 | log.Printf(err1.Error())
80 | return false, err1
81 | }
82 | //维护Redis LikeUserId(key:strUserId),遍历videoIdList加入
83 | for _, likeVideoId := range videoIdList {
84 | redis.RdbLikeUserId.SAdd(redis.Ctx, strUserId, likeVideoId)
85 | }
86 | //查询Redis LikeUserId,key:strUserId中是否存在value:videoId,存在返回true,不存在返回false
87 | exist, err2 := redis.RdbLikeUserId.SIsMember(redis.Ctx, strUserId, videoId).Result()
88 | //如果有问题,说明操作redis失败,返回默认false,返回错误信息
89 | if err2 != nil {
90 | log.Printf("方法:IsFavourite RedisLikeUserId query value失败:%v", err2)
91 | return false, err2
92 | }
93 | log.Printf("方法:IsFavourite RedisLikeUserId query value成功")
94 | return exist, nil
95 | }
96 | }
97 | }
98 |
99 | //FavouriteCount 根据videoId获取对应点赞数量;
100 | //step1:查询Redis LikeVideoId(key:strVideoId)是否已经加载过此信息,通过set集合中userId个数,获取点赞数量;
101 | //step2:LikeVideoId中都没有对应key,维护LikeVideoId对应key,再通过set集合中userId个数,获取点赞数量;
102 | func (like *LikeServiceImpl) FavouriteCount(videoId int64) (int64, error) {
103 | //将int64 videoId转换为 string strVideoId
104 | strVideoId := strconv.FormatInt(videoId, 10)
105 | //step1 如果key:strVideoId存在 则计算集合中userId个数
106 | if n, err := redis.RdbLikeVideoId.Exists(redis.Ctx, strVideoId).Result(); n > 0 {
107 | //如果有问题,说明查询redis失败,返回默认false,返回错误信息
108 | if err != nil {
109 | log.Printf("方法:FavouriteCount RedisLikeVideoId query key失败:%v", err)
110 | return 0, err
111 | }
112 | //获取集合中userId个数
113 | count, err1 := redis.RdbLikeVideoId.SCard(redis.Ctx, strVideoId).Result()
114 | //如果有问题,说明操作redis失败,返回默认0,返回错误信息
115 | if err1 != nil {
116 | log.Printf("方法:FavouriteCount RedisLikeVideoId query count 失败:%v", err1)
117 | return 0, err1
118 | }
119 | log.Printf("方法:FavouriteCount RedisLikeVideoId query count 成功")
120 | return count - 1, nil //去掉DefaultRedisValue
121 | } else {
122 | //key:strVideoId,加入value:DefaultRedisValue,过期才会删,防止删最后一个数据的时候数据库还没更新完出现脏读,或者数据库操作失败造成的脏读
123 | if _, err := redis.RdbLikeVideoId.SAdd(redis.Ctx, strVideoId, config.DefaultRedisValue).Result(); err != nil {
124 | log.Printf("方法:FavouriteCount RedisLikeVideoId add value失败")
125 | redis.RdbLikeVideoId.Del(redis.Ctx, strVideoId)
126 | return 0, err
127 | }
128 | //给键值设置有效期,类似于gc机制
129 | _, err := redis.RdbLikeVideoId.Expire(redis.Ctx, strVideoId,
130 | time.Duration(config.OneMonth)*time.Second).Result()
131 | if err != nil {
132 | log.Printf("方法:FavouriteCount RedisLikeVideoId 设置有效期失败")
133 | redis.RdbLikeVideoId.Del(redis.Ctx, strVideoId)
134 | return 0, err
135 | }
136 | //如果Redis LikeVideoId不存在此key,通过videoId查询likes表,返回所有点赞userId,并维护到Redis LikeVideoId(key:strVideoId)
137 | //再通过set集合中userId个数,获取点赞数量
138 | userIdList, err1 := dao.GetLikeUserIdList(videoId)
139 | //如果有问题,说明查询数据库失败,返回默认0,返回错误信息:"get likeUserIdList failed"
140 | if err1 != nil {
141 | log.Printf(err1.Error())
142 | return 0, err1
143 | }
144 | //维护Redis LikeVideoId(key:strVideoId),遍历userIdList加入
145 | for _, likeUserId := range userIdList {
146 | redis.RdbLikeVideoId.SAdd(redis.Ctx, strVideoId, likeUserId)
147 | }
148 | //再通过set集合中userId个数,获取点赞数量
149 | count, err2 := redis.RdbLikeVideoId.SCard(redis.Ctx, strVideoId).Result()
150 | //如果有问题,说明操作redis失败,返回默认0,返回错误信息
151 | if err2 != nil {
152 | log.Printf("方法:FavouriteCount RedisLikeVideoId query count 失败:%v", err2)
153 | return 0, err2
154 | }
155 | log.Printf("方法:FavouriteCount RedisLikeVideoId query count 成功")
156 | return count - 1, nil //去掉DefaultRedisValue
157 | }
158 | }
159 |
160 | // FavouriteAction 根据userId,videoId,actionType对视频进行点赞或者取消赞操作;
161 | //step1: 维护Redis LikeUserId(key:strUserId),添加或者删除value:videoId,LikeVideoId(key:strVideoId),添加或者删除value:userId;
162 | //step2:更新数据库likes表;
163 | func (like *LikeServiceImpl) FavouriteAction(userId int64, videoId int64, actionType int32) error {
164 | //将int64 videoId转换为 string strVideoId
165 | strUserId := strconv.FormatInt(userId, 10)
166 | //将int64 videoId转换为 string strVideoId
167 | strVideoId := strconv.FormatInt(videoId, 10)
168 | //将要操作数据库likes表的信息打入消息队列RmqLikeAdd或者RmqLikeDel
169 | //拼接打入信息
170 | sb := strings.Builder{}
171 | sb.WriteString(strUserId)
172 | sb.WriteString(" ")
173 | sb.WriteString(strVideoId)
174 |
175 | //step1:维护Redis LikeUserId、LikeVideoId;
176 | //执行点赞操作维护
177 | if actionType == config.LikeAction {
178 | //查询Redis LikeUserId(key:strUserId)是否已经加载过此信息
179 | if n, err := redis.RdbLikeUserId.Exists(redis.Ctx, strUserId).Result(); n > 0 {
180 | //如果有问题,说明查询redis失败,返回错误信息
181 | if err != nil {
182 | log.Printf("方法:FavouriteAction RedisLikeUserId query key失败:%v", err)
183 | return err
184 | } //如果加载过此信息key:strUserId,则加入value:videoId
185 | //如果redis LikeUserId 添加失败,数据库操作成功,会有脏数据,所以只有redis操作成功才执行数据库likes表操作
186 | if _, err1 := redis.RdbLikeUserId.SAdd(redis.Ctx, strUserId, videoId).Result(); err1 != nil {
187 | log.Printf("方法:FavouriteAction RedisLikeUserId add value失败:%v", err1)
188 | return err1
189 | } else {
190 | //如果数据库操作失败了,redis是正确数据,客户端显示的是点赞成功,不会影响后续结果
191 | //只有当该用户取消所有点赞视频的时候redis才会重新加载数据库信息,这时候因为取消赞了必然和数据库信息一致
192 | //同样这条信息消费成功与否也不重要,因为redis是正确信息,理由如上
193 | rabbitmq.RmqLikeAdd.Publish(sb.String())
194 | }
195 | } else { //如果不存在,则维护Redis LikeUserId 新建key:strUserId,设置过期时间,加入DefaultRedisValue,
196 | //key:strUserId,加入value:DefaultRedisValue,过期才会删,防止删最后一个数据的时候数据库还没更新完出现脏读,或者数据库操作失败造成的脏读
197 | //通过userId查询likes表,返回所有点赞videoId,加入key:strUserId集合中,
198 | //再加入当前videoId,再更新likes表此条数据
199 | if _, err := redis.RdbLikeUserId.SAdd(redis.Ctx, strUserId, config.DefaultRedisValue).Result(); err != nil {
200 | log.Printf("方法:FavouriteAction RedisLikeUserId add value失败")
201 | redis.RdbLikeUserId.Del(redis.Ctx, strUserId)
202 | return err
203 | }
204 | //给键值设置有效期,类似于gc机制
205 | _, err := redis.RdbLikeUserId.Expire(redis.Ctx, strUserId,
206 | time.Duration(config.OneMonth)*time.Second).Result()
207 | if err != nil {
208 | log.Printf("方法:FavouriteAction RedisLikeUserId 设置有效期失败")
209 | redis.RdbLikeUserId.Del(redis.Ctx, strUserId)
210 | return err
211 | }
212 | videoIdList, err1 := dao.GetLikeVideoIdList(userId)
213 | //如果有问题,说明查询失败,返回错误信息:"get likeVideoIdList failed"
214 | if err1 != nil {
215 | return err1
216 | }
217 | //遍历videoIdList,添加进key的集合中,若失败,删除key,并返回错误信息,这么做的原因是防止脏读,
218 | //保证redis与mysql数据一致性
219 | for _, likeVideoId := range videoIdList {
220 | if _, err1 := redis.RdbLikeUserId.SAdd(redis.Ctx, strUserId, likeVideoId).Result(); err1 != nil {
221 | log.Printf("方法:FavouriteAction RedisLikeUserId add value失败")
222 | redis.RdbLikeUserId.Del(redis.Ctx, strUserId)
223 | return err1
224 | }
225 | }
226 | //这样操作理由同上
227 | if _, err2 := redis.RdbLikeUserId.SAdd(redis.Ctx, strUserId, videoId).Result(); err2 != nil {
228 | log.Printf("方法:FavouriteAction RedisLikeUserId add value失败:%v", err2)
229 | return err2
230 | } else {
231 | rabbitmq.RmqLikeAdd.Publish(sb.String())
232 | }
233 | }
234 | //查询Redis LikeVideoId(key:strVideoId)是否已经加载过此信息
235 | if n, err := redis.RdbLikeVideoId.Exists(redis.Ctx, strVideoId).Result(); n > 0 {
236 | //如果有问题,说明查询redis失败,返回错误信息
237 | if err != nil {
238 | log.Printf("方法:FavouriteAction RedisLikeVideoId query key失败:%v", err)
239 | return err
240 | } //如果加载过此信息key:strVideoId,则加入value:userId
241 | //如果redis LikeVideoId 添加失败,返回错误信息
242 | if _, err1 := redis.RdbLikeVideoId.SAdd(redis.Ctx, strVideoId, userId).Result(); err1 != nil {
243 | log.Printf("方法:FavouriteAction RedisLikeVideoId add value失败:%v", err1)
244 | return err1
245 | }
246 | } else { //如果不存在,则维护Redis LikeVideoId 新建key:strVideoId,设置有效期,加入DefaultRedisValue
247 | //通过videoId查询likes表,返回所有点赞userId,加入key:strVideoId集合中,
248 | //再加入当前userId,再更新likes表此条数据
249 | //key:strVideoId,加入value:DefaultRedisValue,过期才会删,防止删最后一个数据的时候数据库还没更新完出现脏读,或者数据库操作失败造成的脏读
250 | if _, err := redis.RdbLikeVideoId.SAdd(redis.Ctx, strVideoId, config.DefaultRedisValue).Result(); err != nil {
251 | log.Printf("方法:FavouriteAction RedisLikeVideoId add value失败")
252 | redis.RdbLikeVideoId.Del(redis.Ctx, strVideoId)
253 | return err
254 | }
255 | //给键值设置有效期,类似于gc机制
256 | _, err := redis.RdbLikeVideoId.Expire(redis.Ctx, strVideoId,
257 | time.Duration(config.OneMonth)*time.Second).Result()
258 | if err != nil {
259 | log.Printf("方法:FavouriteAction RedisLikeVideoId 设置有效期失败")
260 | redis.RdbLikeVideoId.Del(redis.Ctx, strVideoId)
261 | return err
262 | }
263 | userIdList, err1 := dao.GetLikeUserIdList(videoId)
264 | //如果有问题,说明查询失败,返回错误信息:"get likeUserIdList failed"
265 | if err1 != nil {
266 | return err1
267 | }
268 | //遍历userIdList,添加进key的集合中,若失败,删除key,并返回错误信息,这么做的原因是防止脏读,
269 | //保证redis与mysql数据一致性
270 | for _, likeUserId := range userIdList {
271 | if _, err1 := redis.RdbLikeVideoId.SAdd(redis.Ctx, strVideoId, likeUserId).Result(); err1 != nil {
272 | log.Printf("方法:FavouriteAction RedisLikeVideoId add value失败")
273 | redis.RdbLikeVideoId.Del(redis.Ctx, strVideoId)
274 | return err1
275 | }
276 | }
277 | //这样操作理由同上
278 | if _, err2 := redis.RdbLikeVideoId.SAdd(redis.Ctx, strVideoId, userId).Result(); err2 != nil {
279 | log.Printf("方法:FavouriteAction RedisLikeVideoId add value失败:%v", err2)
280 | return err2
281 | }
282 | }
283 | } else { //执行取消赞操作维护
284 | //查询Redis LikeUserId(key:strUserId)是否已经加载过此信息
285 | if n, err := redis.RdbLikeUserId.Exists(redis.Ctx, strUserId).Result(); n > 0 {
286 | //如果有问题,说明查询redis失败,返回错误信息
287 | if err != nil {
288 | log.Printf("方法:FavouriteAction RedisLikeUserId query key失败:%v", err)
289 | return err
290 | } //防止出现redis数据不一致情况,当redis删除操作成功,才执行数据库更新操作
291 | if _, err1 := redis.RdbLikeUserId.SRem(redis.Ctx, strUserId, videoId).Result(); err1 != nil {
292 | log.Printf("方法:FavouriteAction RedisLikeUserId del value失败:%v", err1)
293 | return err1
294 | } else {
295 | //后续数据库的操作,可以在mq里设置若执行数据库更新操作失败,重新消费该信息
296 | rabbitmq.RmqLikeDel.Publish(sb.String())
297 | }
298 | } else { //如果不存在,则维护Redis LikeUserId 新建key:strUserId,加入value:DefaultRedisValue,过期才会删,防止删最后一个数据的时候数据库
299 | // 还没更新完出现脏读,或者数据库操作失败造成的脏读
300 | //通过userId查询likes表,返回所有点赞videoId,加入key:strUserId集合中,
301 | //再删除当前videoId,再更新likes表此条数据
302 | //key:strUserId,加入value:DefaultRedisValue,过期才会删,防止删最后一个数据的时候数据库还没更新完出现脏读,或者数据库操作失败造成的脏读
303 | if _, err := redis.RdbLikeUserId.SAdd(redis.Ctx, strUserId, config.DefaultRedisValue).Result(); err != nil {
304 | log.Printf("方法:FavouriteAction RedisLikeUserId add value失败")
305 | redis.RdbLikeUserId.Del(redis.Ctx, strUserId)
306 | return err
307 | }
308 | //给键值设置有效期,类似于gc机制
309 | _, err := redis.RdbLikeUserId.Expire(redis.Ctx, strUserId,
310 | time.Duration(config.OneMonth)*time.Second).Result()
311 | if err != nil {
312 | log.Printf("方法:FavouriteAction RedisLikeUserId 设置有效期失败")
313 | redis.RdbLikeUserId.Del(redis.Ctx, strUserId)
314 | return err
315 | }
316 | videoIdList, err1 := dao.GetLikeVideoIdList(userId)
317 | //如果有问题,说明查询失败,返回错误信息:"get likeVideoIdList failed"
318 | if err1 != nil {
319 | return err1
320 | }
321 | //遍历videoIdList,添加进key的集合中,若失败,删除key,并返回错误信息,这么做的原因是防止脏读,
322 | //保证redis与mysql 数据原子性
323 | for _, likeVideoId := range videoIdList {
324 | if _, err1 := redis.RdbLikeUserId.SAdd(redis.Ctx, strUserId, likeVideoId).Result(); err1 != nil {
325 | log.Printf("方法:FavouriteAction RedisLikeUserId add value失败")
326 | redis.RdbLikeUserId.Del(redis.Ctx, strUserId)
327 | return err1
328 | }
329 | }
330 | //这样操作理由同上
331 | if _, err2 := redis.RdbLikeUserId.SRem(redis.Ctx, strUserId, videoId).Result(); err2 != nil {
332 | log.Printf("方法:FavouriteAction RedisLikeUserId del value失败:%v", err2)
333 | return err2
334 | } else {
335 | rabbitmq.RmqLikeDel.Publish(sb.String())
336 | }
337 | }
338 |
339 | //查询Redis LikeVideoId(key:strVideoId)是否已经加载过此信息
340 | if n, err := redis.RdbLikeVideoId.Exists(redis.Ctx, strVideoId).Result(); n > 0 {
341 | //如果有问题,说明查询redis失败,返回错误信息
342 | if err != nil {
343 | log.Printf("方法:FavouriteAction RedisLikeVideoId query key失败:%v", err)
344 | return err
345 | } //如果加载过此信息key:strVideoId,则删除value:userId
346 | //如果redis LikeVideoId 删除失败,返回错误信息
347 | if _, err1 := redis.RdbLikeVideoId.SRem(redis.Ctx, strVideoId, userId).Result(); err1 != nil {
348 | log.Printf("方法:FavouriteAction RedisLikeVideoId del value失败:%v", err1)
349 | return err1
350 | }
351 | } else { //如果不存在,则维护Redis LikeVideoId 新建key:strVideoId,加入value:DefaultRedisValue,过期才会删,防止删最后一个数据的时候数据库
352 | // 还没更新完出现脏读,或者数据库操作失败造成的脏读
353 | //通过videoId查询likes表,返回所有点赞userId,加入key:strVideoId集合中,
354 | //再删除当前userId,再更新likes表此条数据
355 | //key:strVideoId,加入value:DefaultRedisValue,过期才会删,防止删最后一个数据的时候数据库还没更新完出现脏读,或者数据库操作失败造成的脏读
356 | if _, err := redis.RdbLikeVideoId.SAdd(redis.Ctx, strVideoId, config.DefaultRedisValue).Result(); err != nil {
357 | log.Printf("方法:FavouriteAction RedisLikeVideoId add value失败")
358 | redis.RdbLikeVideoId.Del(redis.Ctx, strVideoId)
359 | return err
360 | }
361 | //给键值设置有效期,类似于gc机制
362 | _, err := redis.RdbLikeVideoId.Expire(redis.Ctx, strVideoId,
363 | time.Duration(config.OneMonth)*time.Second).Result()
364 | if err != nil {
365 | log.Printf("方法:FavouriteAction RedisLikeVideoId 设置有效期失败")
366 | redis.RdbLikeVideoId.Del(redis.Ctx, strVideoId)
367 | return err
368 | }
369 |
370 | userIdList, err1 := dao.GetLikeUserIdList(videoId)
371 | //如果有问题,说明查询失败,返回错误信息:"get likeUserIdList failed"
372 | if err1 != nil {
373 | redis.RdbLikeVideoId.Del(redis.Ctx, strVideoId)
374 | return err1
375 | }
376 | //遍历userIdList,添加进key的集合中,若失败,删除key,并返回错误信息,这么做的原因是防止脏读,
377 | //保证redis与mysql数据一致性
378 | for _, likeUserId := range userIdList {
379 | if _, err1 := redis.RdbLikeVideoId.SAdd(redis.Ctx, strVideoId, likeUserId).Result(); err1 != nil {
380 | log.Printf("方法:FavouriteAction RedisLikeVideoId add value失败")
381 | redis.RdbLikeVideoId.Del(redis.Ctx, strVideoId)
382 | return err1
383 | }
384 | }
385 | //这样操作理由同上
386 | if _, err2 := redis.RdbLikeVideoId.SRem(redis.Ctx, strVideoId, userId).Result(); err2 != nil {
387 | log.Printf("方法:FavouriteAction RedisLikeVideoId del value失败:%v", err2)
388 | return err2
389 | }
390 | }
391 | }
392 | return nil
393 | }
394 |
395 | //GetFavouriteList 根据userId,curId(当前用户Id),返回userId的点赞列表;
396 | //step1:查询Redis LikeUserId(key:strUserId)是否已经加载过此信息,获取集合中全部videoId,并添加到点赞列表集合中;
397 | //step2:LikeUserId中都没有对应key,维护LikeUserId对应key,同时添加到点赞列表集合中;
398 | func (like *LikeServiceImpl) GetFavouriteList(userId int64, curId int64) ([]Video, error) {
399 | //将int64 userId转换为 string strUserId
400 | strUserId := strconv.FormatInt(userId, 10)
401 | //step1:查询Redis LikeUserId,如果key:strUserId存在,则获取集合中全部videoId
402 | if n, err := redis.RdbLikeUserId.Exists(redis.Ctx, strUserId).Result(); n > 0 {
403 | //如果有问题,说明查询redis失败,返回默认nil,返回错误信息
404 | if err != nil {
405 | log.Printf("方法:GetFavouriteList RedisLikeVideoId query key失败:%v", err)
406 | return nil, err
407 | }
408 | //获取集合中全部videoId
409 | videoIdList, err1 := redis.RdbLikeUserId.SMembers(redis.Ctx, strUserId).Result()
410 | //如果有问题,说明查询redis失败,返回默认nil,返回错误信息
411 | if err1 != nil {
412 | log.Printf("方法:GetFavouriteList RedisLikeVideoId get values失败:%v", err1)
413 | return nil, err1
414 | }
415 | //提前开辟点赞列表空间
416 | favoriteVideoList := new([]Video)
417 | //采用协程并发将Video类型对象添加到集合中去
418 | i := len(videoIdList) - 1 //去掉DefaultRedisValue
419 | if i == 0 {
420 | return *favoriteVideoList, nil
421 | }
422 | var wg sync.WaitGroup
423 | wg.Add(i)
424 | for j := 0; j <= i; j++ {
425 | //将string videoId转换为 int64 VideoId
426 | videoId, _ := strconv.ParseInt(videoIdList[j], 10, 64)
427 | if videoId == config.DefaultRedisValue {
428 | continue
429 | }
430 | go like.addFavouriteVideoList(videoId, curId, favoriteVideoList, &wg)
431 | }
432 | wg.Wait()
433 | return *favoriteVideoList, nil
434 | } else { //如果Redis LikeUserId不存在此key,通过userId查询likes表,返回所有点赞videoId,并维护到Redis LikeUserId(key:strUserId)
435 | //key:strUserId,加入value:DefaultRedisValue,过期才会删,防止删最后一个数据的时候数据库还没更新完出现脏读,或者数据库操作失败造成的脏读
436 | if _, err := redis.RdbLikeUserId.SAdd(redis.Ctx, strUserId, config.DefaultRedisValue).Result(); err != nil {
437 | log.Printf("方法:GetFavouriteList RedisLikeUserId add value失败")
438 | redis.RdbLikeUserId.Del(redis.Ctx, strUserId)
439 | return nil, err
440 | }
441 | //给键值设置有效期,类似于gc机制
442 | _, err := redis.RdbLikeUserId.Expire(redis.Ctx, strUserId,
443 | time.Duration(config.OneMonth)*time.Second).Result()
444 | if err != nil {
445 | log.Printf("方法:GetFavouriteList RedisLikeUserId 设置有效期失败")
446 | redis.RdbLikeUserId.Del(redis.Ctx, strUserId)
447 | return nil, err
448 | }
449 | videoIdList, err1 := dao.GetLikeVideoIdList(userId)
450 | //如果有问题,说明查询数据库失败,返回nil和错误信息:"get likeVideoIdList failed"
451 | if err1 != nil {
452 | log.Println(err1.Error())
453 | redis.RdbLikeUserId.Del(redis.Ctx, strUserId)
454 | return nil, err1
455 | }
456 | //遍历videoIdList,添加进key的集合中,若失败,删除key,并返回错误信息,这么做的原因是防止脏读,
457 | //保证redis与mysql数据一致性
458 | for _, likeVideoId := range videoIdList {
459 | if _, err2 := redis.RdbLikeUserId.SAdd(redis.Ctx, strUserId, likeVideoId).Result(); err2 != nil {
460 | log.Printf("方法:GetFavouriteList RedisLikeUserId add value失败")
461 | redis.RdbLikeUserId.Del(redis.Ctx, strUserId)
462 | return nil, err2
463 | }
464 | }
465 | //提前开辟点赞列表空间
466 | favoriteVideoList := new([]Video)
467 | //采用协程并发将Video类型对象添加到集合中去
468 | i := len(videoIdList) - 1 //去掉DefaultRedisValue
469 | if i == 0 {
470 | return *favoriteVideoList, nil
471 | }
472 | var wg sync.WaitGroup
473 | wg.Add(i)
474 | for j := 0; j <= i; j++ {
475 | if videoIdList[j] == config.DefaultRedisValue {
476 | continue
477 | }
478 | go like.addFavouriteVideoList(videoIdList[j], curId, favoriteVideoList, &wg)
479 | }
480 | wg.Wait()
481 | return *favoriteVideoList, nil
482 | }
483 | }
484 |
485 | //addFavouriteVideoList 根据videoId,登录用户curId,添加视频对象到点赞列表空间
486 | func (like *LikeServiceImpl) addFavouriteVideoList(videoId int64, curId int64, favoriteVideoList *[]Video, wg *sync.WaitGroup) {
487 | defer wg.Done()
488 | //调用videoService接口,GetVideo:根据videoId,当前用户id:curId,返回Video类型对象
489 | video, err := like.GetVideo(videoId, curId)
490 | //如果没有获取这个video_id的视频,视频可能被删除了,打印异常,并且不加入此视频
491 | if err != nil {
492 | log.Println(errors.New("this favourite video is miss"))
493 | return
494 | }
495 | //将Video类型对象添加到集合中去
496 | *favoriteVideoList = append(*favoriteVideoList, video)
497 | }
498 |
499 | //TotalFavourite 根据userId获取这个用户总共被点赞数量
500 | func (like *LikeServiceImpl) TotalFavourite(userId int64) (int64, error) {
501 | //根据userId获取这个用户的发布视频列表信息
502 | videoIdList, err := like.GetVideoIdList(userId)
503 | if err != nil {
504 | log.Printf(err.Error())
505 | return 0, err
506 | }
507 | var sum int64 //该用户的总被点赞数
508 | //提前开辟空间,存取每个视频的点赞数
509 | videoLikeCountList := new([]int64)
510 | //采用协程并发将对应videoId的点赞数添加到集合中去
511 | i := len(videoIdList)
512 | var wg sync.WaitGroup
513 | wg.Add(i)
514 | for j := 0; j < i; j++ {
515 | go like.addVideoLikeCount(videoIdList[j], videoLikeCountList, &wg)
516 | }
517 | wg.Wait()
518 | //遍历累加,求总被点赞数
519 | for _, count := range *videoLikeCountList {
520 | sum += count
521 | }
522 | return sum, nil
523 | }
524 |
525 | //FavouriteVideoCount 根据userId获取这个用户点赞视频数量
526 | func (like *LikeServiceImpl) FavouriteVideoCount(userId int64) (int64, error) {
527 | //将int64 userId转换为 string strUserId
528 | strUserId := strconv.FormatInt(userId, 10)
529 | //step1:查询Redis LikeUserId,如果key:strUserId存在,则获取集合中元素个数
530 | if n, err := redis.RdbLikeUserId.Exists(redis.Ctx, strUserId).Result(); n > 0 {
531 | //如果有问题,说明查询redis失败,返回默认0,返回错误信息
532 | if err != nil {
533 | log.Printf("方法:FavouriteVideoCount RdbLikeUserId query key失败:%v", err)
534 | return 0, err
535 | } else {
536 | count, err1 := redis.RdbLikeUserId.SCard(redis.Ctx, strUserId).Result()
537 | //如果有问题,说明操作redis失败,返回默认0,返回错误信息
538 | if err1 != nil {
539 | log.Printf("方法:FavouriteVideoCount RdbLikeUserId query count 失败:%v", err1)
540 | return 0, err1
541 | }
542 | log.Printf("方法:FavouriteVideoCount RdbLikeUserId query count 成功")
543 | return count - 1, nil //去掉DefaultRedisValue
544 |
545 | }
546 | } else { //如果Redis LikeUserId不存在此key,通过userId查询likes表,返回所有点赞videoId,并维护到Redis LikeUserId(key:strUserId)
547 | //再通过set集合中userId个数,获取点赞数量
548 | //key:strUserId,加入value:DefaultRedisValue,过期才会删,防止删最后一个数据的时候数据库还没更新完出现脏读,或者数据库操作失败造成的脏读
549 | if _, err := redis.RdbLikeUserId.SAdd(redis.Ctx, strUserId, config.DefaultRedisValue).Result(); err != nil {
550 | log.Printf("方法:FavouriteVideoCount RedisLikeUserId add value失败")
551 | redis.RdbLikeUserId.Del(redis.Ctx, strUserId)
552 | return 0, err
553 | }
554 | //给键值设置有效期,类似于gc机制
555 | _, err := redis.RdbLikeUserId.Expire(redis.Ctx, strUserId,
556 | time.Duration(config.OneMonth)*time.Second).Result()
557 | if err != nil {
558 | log.Printf("方法:FavouriteVideoCount RedisLikeUserId 设置有效期失败")
559 | redis.RdbLikeUserId.Del(redis.Ctx, strUserId)
560 | return 0, err
561 | }
562 | videoIdList, err1 := dao.GetLikeVideoIdList(userId)
563 | //如果有问题,说明查询数据库失败,返回默认0,返回错误信息:"get likeVideoIdList failed"
564 | if err1 != nil {
565 | log.Printf(err1.Error())
566 | return 0, err1
567 | }
568 | //维护Redis LikeUserId(key:strUserId),遍历videoIdList加入
569 | for _, likeVideoId := range videoIdList {
570 | if _, err1 := redis.RdbLikeUserId.SAdd(redis.Ctx, strUserId, likeVideoId).Result(); err1 != nil {
571 | log.Printf("方法:FavouriteVideoCount RedisLikeUserId add value失败")
572 | redis.RdbLikeUserId.Del(redis.Ctx, strUserId)
573 | return 0, err1
574 | }
575 | }
576 | //再通过set集合中videoId个数,获取点赞数量
577 | count, err2 := redis.RdbLikeUserId.SCard(redis.Ctx, strUserId).Result()
578 | //如果有问题,说明操作redis失败,返回默认0,返回错误信息
579 | if err2 != nil {
580 | log.Printf("方法:FavouriteVideoCount RdbLikeUserId query count 失败:%v", err2)
581 | return 0, err2
582 | }
583 | log.Printf("方法:FavouriteVideoCount RdbLikeUserId query count 成功")
584 | return count - 1, nil //去掉DefaultRedisValue
585 | }
586 | }
587 |
588 | //addVideoLikeCount 根据videoId,将该视频点赞数加入对应提前开辟好的空间内
589 | func (like *LikeServiceImpl) addVideoLikeCount(videoId int64, videoLikeCountList *[]int64, wg *sync.WaitGroup) {
590 | defer wg.Done()
591 | //调用FavouriteCount:根据videoId,获取点赞数
592 | count, err := like.FavouriteCount(videoId)
593 | if err != nil {
594 | //如果有错误,输出错误信息,并不加入该视频点赞数
595 | log.Printf(err.Error())
596 | return
597 | }
598 | *videoLikeCountList = append(*videoLikeCountList, count)
599 | }
600 |
601 | //GetLikeService 解决likeService调videoService,videoService调userService,useService调likeService循环依赖的问题
602 | func GetLikeService() LikeServiceImpl {
603 | var userService UserServiceImpl
604 | var videoService VideoServiceImpl
605 | var likeService LikeServiceImpl
606 | userService.LikeService = &likeService
607 | likeService.VideoService = &videoService
608 | videoService.UserService = &userService
609 | return likeService
610 | }
611 |
--------------------------------------------------------------------------------
/service/likeServiceImpl_test.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "TikTok/dao"
5 | "TikTok/middleware/ffmpeg"
6 | "TikTok/middleware/rabbitmq"
7 | "TikTok/middleware/redis"
8 | "fmt"
9 | "testing"
10 | )
11 |
12 | func TestIsFavourite(t *testing.T) {
13 | // 初始化数据库
14 | dao.Init()
15 | // 初始化FTP服务器链接
16 | dao.InitFTP()
17 | // 初始化SSH
18 | ffmpeg.InitSSH()
19 |
20 | // 初始化redis-DB0的连接,follow选择的DB0.
21 | redis.InitRedis()
22 | // 初始化rabbitMQ。
23 | rabbitmq.InitRabbitMQ()
24 | // 初始化Follow的相关消息队列,并开启消费。
25 | rabbitmq.InitFollowRabbitMQ()
26 | // 初始化Like的相关消息队列,并开启消费。
27 | impl := LikeServiceImpl{}
28 | isFavourite, _ := impl.IsFavourite(666, 3)
29 | fmt.Printf("%v", isFavourite)
30 | }
31 |
32 | func TestFavouriteCount(t *testing.T) {
33 | // 初始化数据库
34 | dao.Init()
35 | // 初始化FTP服务器链接
36 | dao.InitFTP()
37 | // 初始化SSH
38 | ffmpeg.InitSSH()
39 |
40 | // 初始化redis-DB0的连接,follow选择的DB0.
41 | redis.InitRedis()
42 | // 初始化rabbitMQ。
43 | rabbitmq.InitRabbitMQ()
44 | // 初始化Follow的相关消息队列,并开启消费。
45 | rabbitmq.InitFollowRabbitMQ()
46 | // 初始化Like的相关消息队列,并开启消费。
47 | impl := LikeServiceImpl{}
48 | count, _ := impl.FavouriteCount(666)
49 | fmt.Printf("%v", count)
50 | }
51 |
52 | func TestTotalFavourite(t *testing.T) {
53 | // 初始化数据库
54 | dao.Init()
55 | // 初始化FTP服务器链接
56 | dao.InitFTP()
57 | // 初始化SSH
58 | ffmpeg.InitSSH()
59 |
60 | // 初始化redis-DB0的连接,follow选择的DB0.
61 | redis.InitRedis()
62 | // 初始化rabbitMQ。
63 | rabbitmq.InitRabbitMQ()
64 | // 初始化Follow的相关消息队列,并开启消费。
65 | rabbitmq.InitFollowRabbitMQ()
66 | // 初始化Like的相关消息队列,并开启消费。
67 | rabbitmq.InitLikeRabbitMQ()
68 | impl := LikeServiceImpl{}
69 | count, _ := impl.TotalFavourite(3)
70 | fmt.Printf("%v", count)
71 | }
72 |
--------------------------------------------------------------------------------
/service/likeSub.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import "TikTok/dao"
4 |
5 | type LikeSub struct {
6 | }
7 |
8 | func (like *LikeSub) GetVideo(videoId int64, userId int64) (Video, error) {
9 | if videoId%2 == 1 {
10 | return Video{
11 | TableVideo: dao.TableVideo{
12 | Id: 1,
13 | AuthorId: 1,
14 | PlayUrl: "www.baidu.com",
15 | CoverUrl: "www.baidu.com",
16 | },
17 | Author: User{
18 | Id: 1,
19 | Name: "lzz",
20 | FollowCount: 12,
21 | FollowerCount: 13,
22 | IsFollow: true,
23 | },
24 | FavoriteCount: 2,
25 | CommentCount: 3,
26 | IsFavorite: true,
27 | }, nil
28 | } else {
29 | return Video{
30 | TableVideo: dao.TableVideo{
31 | Id: 2,
32 | AuthorId: 3,
33 | PlayUrl: "www.baidu.com",
34 | CoverUrl: "www.baidu11.com",
35 | },
36 | Author: User{
37 | Id: 2,
38 | Name: "lzz",
39 | FollowCount: 12,
40 | FollowerCount: 13,
41 | IsFollow: true,
42 | },
43 | FavoriteCount: 2,
44 | CommentCount: 3,
45 | IsFavorite: true,
46 | }, nil
47 | }
48 | }
49 |
50 | func (like *LikeSub) GetVideoIdList(userId int64) ([]int64, error) {
51 | videoList := []int64{51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65,
52 | 66, 67, 68, 69, 70, 71, 72, 73, 74, 75}
53 | return videoList, nil
54 | }
55 |
--------------------------------------------------------------------------------
/service/userService.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import "TikTok/dao"
4 |
5 | type UserService interface {
6 | /*
7 | 个人使用
8 | */
9 | // GetTableUserList 获得全部TableUser对象
10 | GetTableUserList() []dao.TableUser
11 |
12 | // GetTableUserByUsername 根据username获得TableUser对象
13 | GetTableUserByUsername(name string) dao.TableUser
14 |
15 | // GetTableUserById 根据user_id获得TableUser对象
16 | GetTableUserById(id int64) dao.TableUser
17 |
18 | // InsertTableUser 将tableUser插入表内
19 | InsertTableUser(tableUser *dao.TableUser) bool
20 | /*
21 | 他人使用
22 | */
23 | // GetUserById 未登录情况下,根据user_id获得User对象
24 | GetUserById(id int64) (User, error)
25 |
26 | // GetUserByIdWithCurId 已登录(curID)情况下,根据user_id获得User对象
27 | GetUserByIdWithCurId(id int64, curId int64) (User, error)
28 |
29 | // 根据token返回id
30 | // 接口:auth中间件,解析完token,将userid放入context
31 | //(调用方法:直接在context内拿参数"userId"的值) fmt.Printf("userInfo: %v\n", c.GetString("userId"))
32 | }
33 |
34 | // User 最终封装后,controller返回的User结构体
35 | type User struct {
36 | Id int64 `json:"id,omitempty"`
37 | Name string `json:"name,omitempty"`
38 | FollowCount int64 `json:"follow_count"`
39 | FollowerCount int64 `json:"follower_count"`
40 | IsFollow bool `json:"is_follow"`
41 | TotalFavorited int64 `json:"total_favorited,omitempty"`
42 | FavoriteCount int64 `json:"favorite_count,omitempty"`
43 | }
44 |
--------------------------------------------------------------------------------
/service/userServiceImpl.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "TikTok/config"
5 | "TikTok/dao"
6 | "crypto/hmac"
7 | "crypto/sha256"
8 | "encoding/hex"
9 | "fmt"
10 | "github.com/dgrijalva/jwt-go"
11 | "log"
12 | "strconv"
13 | "time"
14 | )
15 |
16 | type UserServiceImpl struct {
17 | FollowService
18 | LikeService
19 | }
20 |
21 | // GetTableUserList 获得全部TableUser对象
22 | func (usi *UserServiceImpl) GetTableUserList() []dao.TableUser {
23 | tableUsers, err := dao.GetTableUserList()
24 | if err != nil {
25 | log.Println("Err:", err.Error())
26 | return tableUsers
27 | }
28 | return tableUsers
29 | }
30 |
31 | // GetTableUserByUsername 根据username获得TableUser对象
32 | func (usi *UserServiceImpl) GetTableUserByUsername(name string) dao.TableUser {
33 | tableUser, err := dao.GetTableUserByUsername(name)
34 | if err != nil {
35 | log.Println("Err:", err.Error())
36 | log.Println("User Not Found")
37 | return tableUser
38 | }
39 | log.Println("Query User Success")
40 | return tableUser
41 | }
42 |
43 | // GetTableUserById 根据user_id获得TableUser对象
44 | func (usi *UserServiceImpl) GetTableUserById(id int64) dao.TableUser {
45 | tableUser, err := dao.GetTableUserById(id)
46 | if err != nil {
47 | log.Println("Err:", err.Error())
48 | log.Println("User Not Found")
49 | return tableUser
50 | }
51 | log.Println("Query User Success")
52 | return tableUser
53 | }
54 |
55 | // InsertTableUser 将tableUser插入表内
56 | func (usi *UserServiceImpl) InsertTableUser(tableUser *dao.TableUser) bool {
57 | flag := dao.InsertTableUser(tableUser)
58 | if flag == false {
59 | log.Println("插入失败")
60 | return false
61 | }
62 | return true
63 | }
64 |
65 | // GetUserById 未登录情况下,根据user_id获得User对象
66 | func (usi *UserServiceImpl) GetUserById(id int64) (User, error) {
67 | user := User{
68 | Id: 0,
69 | Name: "",
70 | FollowCount: 0,
71 | FollowerCount: 0,
72 | IsFollow: false,
73 | TotalFavorited: 0,
74 | FavoriteCount: 0,
75 | }
76 | tableUser, err := dao.GetTableUserById(id)
77 | if err != nil {
78 | log.Println("Err:", err.Error())
79 | log.Println("User Not Found")
80 | return user, err
81 | }
82 | log.Println("Query User Success")
83 | followCount, _ := usi.GetFollowingCnt(id)
84 | if err != nil {
85 | log.Println("Err:", err.Error())
86 | }
87 | followerCount, _ := usi.GetFollowerCnt(id)
88 | if err != nil {
89 | log.Println("Err:", err.Error())
90 | }
91 | u := GetLikeService() //解决循环依赖
92 | totalFavorited, _ := u.TotalFavourite(id)
93 | favoritedCount, _ := u.FavouriteVideoCount(id)
94 | user = User{
95 | Id: id,
96 | Name: tableUser.Name,
97 | FollowCount: followCount,
98 | FollowerCount: followerCount,
99 | IsFollow: false,
100 | TotalFavorited: totalFavorited,
101 | FavoriteCount: favoritedCount,
102 | }
103 | return user, nil
104 | }
105 |
106 | // GetUserByIdWithCurId 已登录(curID)情况下,根据user_id获得User对象
107 | func (usi *UserServiceImpl) GetUserByIdWithCurId(id int64, curId int64) (User, error) {
108 | user := User{
109 | Id: 0,
110 | Name: "",
111 | FollowCount: 0,
112 | FollowerCount: 0,
113 | IsFollow: false,
114 | TotalFavorited: 0,
115 | FavoriteCount: 0,
116 | }
117 | tableUser, err := dao.GetTableUserById(id)
118 | if err != nil {
119 | log.Println("Err:", err.Error())
120 | log.Println("User Not Found")
121 | return user, err
122 | }
123 | log.Println("Query User Success")
124 | followCount, err := usi.GetFollowingCnt(id)
125 | if err != nil {
126 | log.Println("Err:", err.Error())
127 | }
128 | followerCount, err := usi.GetFollowerCnt(id)
129 | if err != nil {
130 | log.Println("Err:", err.Error())
131 | }
132 | isfollow, err := usi.IsFollowing(curId, id)
133 | if err != nil {
134 | log.Println("Err:", err.Error())
135 | }
136 | u := GetLikeService() //解决循环依赖
137 | totalFavorited, _ := u.TotalFavourite(id)
138 | favoritedCount, _ := u.FavouriteVideoCount(id)
139 | user = User{
140 | Id: id,
141 | Name: tableUser.Name,
142 | FollowCount: followCount,
143 | FollowerCount: followerCount,
144 | IsFollow: isfollow,
145 | TotalFavorited: totalFavorited,
146 | FavoriteCount: favoritedCount,
147 | }
148 | return user, nil
149 | }
150 |
151 | // GenerateToken 根据username生成一个token
152 | func GenerateToken(username string) string {
153 | u := UserService.GetTableUserByUsername(new(UserServiceImpl), username)
154 | fmt.Printf("generatetoken: %v\n", u)
155 | token := NewToken(u)
156 | println(token)
157 | return token
158 | }
159 |
160 | // NewToken 根据信息创建token
161 | func NewToken(u dao.TableUser) string {
162 | expiresTime := time.Now().Unix() + int64(config.OneDayOfHours)
163 | fmt.Printf("expiresTime: %v\n", expiresTime)
164 | id64 := u.Id
165 | fmt.Printf("id: %v\n", strconv.FormatInt(id64, 10))
166 | claims := jwt.StandardClaims{
167 | Audience: u.Name,
168 | ExpiresAt: expiresTime,
169 | Id: strconv.FormatInt(id64, 10),
170 | IssuedAt: time.Now().Unix(),
171 | Issuer: "tiktok",
172 | NotBefore: time.Now().Unix(),
173 | Subject: "token",
174 | }
175 | var jwtSecret = []byte(config.Secret)
176 | tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
177 | if token, err := tokenClaims.SignedString(jwtSecret); err == nil {
178 | token = "Bearer " + token
179 | println("generate token success!\n")
180 | return token
181 | } else {
182 | println("generate token fail\n")
183 | return "fail"
184 | }
185 | }
186 |
187 | // EnCoder 密码加密
188 | func EnCoder(password string) string {
189 | h := hmac.New(sha256.New, []byte(password))
190 | sha := hex.EncodeToString(h.Sum(nil))
191 | fmt.Println("Result: " + sha)
192 | return sha
193 | }
194 |
--------------------------------------------------------------------------------
/service/userServiceImpl_test.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "TikTok/dao"
5 | "fmt"
6 | "testing"
7 | )
8 |
9 | func TestGetTableUserList(t *testing.T) {
10 | impl := UserServiceImpl{}
11 | list := impl.GetTableUserList()
12 | fmt.Printf("%v", list)
13 | }
14 |
15 | func TestGetTableUserByUsername(t *testing.T) {
16 | impl := UserServiceImpl{}
17 | list := impl.GetTableUserByUsername("test")
18 | fmt.Printf("%v", list)
19 | }
20 |
21 | func TestGetTableUserById(t *testing.T) {
22 | impl := UserServiceImpl{}
23 | list := impl.GetTableUserById(int64(4))
24 | fmt.Printf("%v", list)
25 | }
26 |
27 | func TestInsertTableUser(t *testing.T) {
28 | impl := UserServiceImpl{}
29 | user := &dao.TableUser{
30 | Id: 20000,
31 | Name: "qaq",
32 | Password: "111111",
33 | }
34 | list := impl.InsertTableUser(user)
35 | fmt.Printf("%v", list)
36 | }
37 |
38 | func TestGetUserById(t *testing.T) {
39 | impl := UserServiceImpl{
40 | FollowService: &FollowServiceImp{},
41 | LikeService: &LikeServiceImpl{},
42 | }
43 | list, _ := impl.GetUserById(int64(4))
44 | fmt.Printf("%v", list)
45 | }
46 |
47 | func TestGetUserByIdWithCurId(t *testing.T) {
48 | impl := UserServiceImpl{
49 | FollowService: &FollowServiceImp{},
50 | }
51 | list, _ := impl.GetUserByIdWithCurId(int64(482), int64(130))
52 | fmt.Printf("%v", list)
53 | }
54 |
--------------------------------------------------------------------------------
/service/userSub.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | // 如果要实现用户登录所有功能,将下方代码恢复
4 | /*type FollowServiceImpl struct {
5 | }
6 |
7 | func (fsi *FollowServiceImpl) IsFollowing(userId int64, targetId int64) (bool, error) {
8 | return true, nil
9 | }
10 |
11 | func (fsi *FollowServiceImpl) GetFollowingCnt(userId int64) (int64, error) {
12 | return int64(1), nil
13 | }
14 |
15 | func (fsi *FollowServiceImpl) GetFollowerCnt(userId int64) (int64, error) {
16 | return int64(1), nil
17 | }
18 |
19 | func (fsi *FollowServiceImpl) AddFollowRelation(userId int64, targetId int64) (bool, error) {
20 | return true, nil
21 | }
22 |
23 | func (fsi *FollowServiceImpl) DeleteFollowRelation(userId int64, targetId int64) (bool, error) {
24 | return true, nil
25 | }
26 |
27 | func (fsi *FollowServiceImpl) GetFollowing(userId int64) ([]User, error) {
28 | return nil, nil
29 | }
30 |
31 | func (fsi *FollowServiceImpl) GetFollowers(userId int64) ([]User, error) {
32 | return nil, nil
33 | }*/
34 |
--------------------------------------------------------------------------------
/service/videoService.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "TikTok/dao"
5 | "mime/multipart"
6 | "time"
7 | )
8 |
9 | type Video struct {
10 | dao.TableVideo
11 | Author User `json:"author"`
12 | FavoriteCount int64 `json:"favorite_count"`
13 | CommentCount int64 `json:"comment_count"`
14 | IsFavorite bool `json:"is_favorite"`
15 | }
16 |
17 | type VideoService interface {
18 | // Feed
19 | // 通过传入时间戳,当前用户的id,返回对应的视频切片数组,以及视频数组中最早的发布时间
20 | Feed(lastTime time.Time, userId int64) ([]Video, time.Time, error)
21 |
22 | // GetVideo
23 | // 传入视频id获得对应的视频对象
24 | GetVideo(videoId int64, userId int64) (Video, error)
25 |
26 | // Publish
27 | // 将传入的视频流保存在文件服务器中,并存储在mysql表中
28 | // 5.23 加入title
29 | Publish(data *multipart.FileHeader, userId int64, title string) error
30 |
31 | // List
32 | // 通过userId来查询对应用户发布的视频,并返回对应的视频切片数组
33 | List(userId int64, curId int64) ([]Video, error)
34 |
35 | // GetVideoIdList
36 | // 通过一个作者id,返回该用户发布的视频id切片数组
37 | GetVideoIdList(userId int64) ([]int64, error)
38 | }
39 |
--------------------------------------------------------------------------------
/service/videoServiceImpl.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "TikTok/config"
5 | "TikTok/dao"
6 | "TikTok/middleware/ffmpeg"
7 | "github.com/satori/go.uuid"
8 | "log"
9 | "mime/multipart"
10 | "sync"
11 | "time"
12 | )
13 |
14 | type VideoServiceImpl struct {
15 | UserService
16 | LikeService
17 | CommentService
18 | }
19 |
20 | // Feed
21 | // 通过传入时间戳,当前用户的id,返回对应的视频数组,以及视频数组中最早的发布时间
22 | // 获取视频数组大小是可以控制的,在config中的videoCount变量
23 | func (videoService VideoServiceImpl) Feed(lastTime time.Time, userId int64) ([]Video, time.Time, error) {
24 | //创建对应返回视频的切片数组,提前将切片的容量设置好,可以减少切片扩容的性能
25 | videos := make([]Video, 0, config.VideoCount)
26 | //根据传入的时间,获得传入时间前n个视频,可以通过config.videoCount来控制
27 | tableVideos, err := dao.GetVideosByLastTime(lastTime)
28 | if err != nil {
29 | log.Printf("方法dao.GetVideosByLastTime(lastTime) 失败:%v", err)
30 | return nil, time.Time{}, err
31 | }
32 | log.Printf("方法dao.GetVideosByLastTime(lastTime) 成功:%v", tableVideos)
33 | //将数据通过copyVideos进行处理,在拷贝的过程中对数据进行组装
34 | err = videoService.copyVideos(&videos, &tableVideos, userId)
35 | if err != nil {
36 | log.Printf("方法videoService.copyVideos(&videos, &tableVideos, userId) 失败:%v", err)
37 | return nil, time.Time{}, err
38 | }
39 | log.Printf("方法videoService.copyVideos(&videos, &tableVideos, userId) 成功")
40 | //返回数据,同时获得视频中最早的时间返回
41 | return videos, tableVideos[len(tableVideos)-1].PublishTime, nil
42 | }
43 |
44 | // GetVideo
45 | // 传入视频id获得对应的视频对象,注意还需要传入当前登录用户id
46 | func (videoService *VideoServiceImpl) GetVideo(videoId int64, userId int64) (Video, error) {
47 | //初始化video对象
48 | var video Video
49 |
50 | //从数据库中查询数据,如果查询不到数据,就直接失败返回,后续流程就不需要执行了
51 | data, err := dao.GetVideoByVideoId(videoId)
52 | if err != nil {
53 | log.Printf("方法dao.GetVideoByVideoId(videoId) 失败:%v", err)
54 | return video, err
55 | } else {
56 | log.Printf("方法dao.GetVideoByVideoId(videoId) 成功")
57 | }
58 |
59 | //插入从数据库中查到的数据
60 | videoService.creatVideo(&video, &data, userId)
61 | return video, nil
62 | }
63 |
64 | // Publish
65 | // 将传入的视频流保存在文件服务器中,并存储在mysql表中
66 | func (videoService *VideoServiceImpl) Publish(data *multipart.FileHeader, userId int64, title string) error {
67 | //将视频流上传到视频服务器,保存视频链接
68 | file, err := data.Open()
69 | if err != nil {
70 | log.Printf("方法data.Open() 失败%v", err)
71 | return err
72 | }
73 | log.Printf("方法data.Open() 成功")
74 | //生成一个uuid作为视频的名字
75 | videoName := uuid.NewV4().String()
76 | log.Printf("生成视频名称%v", videoName)
77 | err = dao.VideoFTP(file, videoName)
78 | if err != nil {
79 | log.Printf("方法dao.VideoFTP(file, videoName) 失败%v", err)
80 | return err
81 | }
82 | log.Printf("方法dao.VideoFTP(file, videoName) 成功")
83 | defer file.Close()
84 | //在服务器上执行ffmpeg 从视频流中获取第一帧截图,并上传图片服务器,保存图片链接
85 | imageName := uuid.NewV4().String()
86 | //向队列中添加消息
87 | ffmpeg.Ffchan <- ffmpeg.Ffmsg{
88 | videoName,
89 | imageName,
90 | }
91 | //组装并持久化
92 | err = dao.Save(videoName, imageName, userId, title)
93 | if err != nil {
94 | log.Printf("方法dao.Save(videoName, imageName, userId) 失败%v", err)
95 | return err
96 | }
97 | log.Printf("方法dao.Save(videoName, imageName, userId) 成功")
98 | return nil
99 | }
100 |
101 | // List
102 | // 通过userId来查询对应用户发布的视频,并返回对应的视频数组
103 | func (videoService *VideoServiceImpl) List(userId int64, curId int64) ([]Video, error) {
104 | //依据用户id查询所有的视频,获取视频列表
105 | data, err := dao.GetVideosByAuthorId(userId)
106 | if err != nil {
107 | log.Printf("方法dao.GetVideosByAuthorId(%v)失败:%v", userId, err)
108 | return nil, err
109 | }
110 | log.Printf("方法dao.GetVideosByAuthorId(%v)成功:%v", userId, data)
111 | //提前定义好切片长度
112 | result := make([]Video, 0, len(data))
113 | //调用拷贝方法,将数据进行转换
114 | err = videoService.copyVideos(&result, &data, curId)
115 | if err != nil {
116 | log.Printf("方法videoService.copyVideos(&result, &data, %v)失败:%v", userId, err)
117 | return nil, err
118 | }
119 | //如果数据没有问题,则直接返回
120 | return result, nil
121 | }
122 |
123 | // 该方法可以将数据进行拷贝和转换,并从其他方法获取对应的数据
124 | func (videoService *VideoServiceImpl) copyVideos(result *[]Video, data *[]dao.TableVideo, userId int64) error {
125 | for _, temp := range *data {
126 | var video Video
127 | //将video进行组装,添加想要的信息,插入从数据库中查到的数据
128 | videoService.creatVideo(&video, &temp, userId)
129 | *result = append(*result, video)
130 | }
131 | return nil
132 | }
133 |
134 | //将video进行组装,添加想要的信息,插入从数据库中查到的数据
135 | func (videoService *VideoServiceImpl) creatVideo(video *Video, data *dao.TableVideo, userId int64) {
136 | //建立协程组,当这一组的携程全部完成后,才会结束本方法
137 | var wg sync.WaitGroup
138 | wg.Add(4)
139 | var err error
140 | video.TableVideo = *data
141 | //插入Author,这里需要将视频的发布者和当前登录的用户传入,才能正确获得isFollow,
142 | //如果出现错误,不能直接返回失败,将默认值返回,保证稳定
143 | go func() {
144 | video.Author, err = videoService.GetUserByIdWithCurId(data.AuthorId, userId)
145 | if err != nil {
146 | log.Printf("方法videoService.GetUserByIdWithCurId(data.AuthorId, userId) 失败:%v", err)
147 | } else {
148 | log.Printf("方法videoService.GetUserByIdWithCurId(data.AuthorId, userId) 成功")
149 | }
150 | wg.Done()
151 | }()
152 |
153 | //插入点赞数量,同上所示,不将nil直接向上返回,数据没有就算了,给一个默认就行了
154 | go func() {
155 | video.FavoriteCount, err = videoService.FavouriteCount(data.Id)
156 | if err != nil {
157 | log.Printf("方法videoService.FavouriteCount(data.ID) 失败:%v", err)
158 | } else {
159 | log.Printf("方法videoService.FavouriteCount(data.ID) 成功")
160 | }
161 | wg.Done()
162 | }()
163 |
164 | //获取该视屏的评论数字
165 | go func() {
166 | video.CommentCount, err = videoService.CountFromVideoId(data.Id)
167 | if err != nil {
168 | log.Printf("方法videoService.CountFromVideoId(data.ID) 失败:%v", err)
169 | } else {
170 | log.Printf("方法videoService.CountFromVideoId(data.ID) 成功")
171 | }
172 | wg.Done()
173 | }()
174 |
175 | //获取当前用户是否点赞了该视频
176 | go func() {
177 | video.IsFavorite, err = videoService.IsFavourite(video.Id, userId)
178 | if err != nil {
179 | log.Printf("方法videoService.IsFavourit(video.Id, userId) 失败:%v", err)
180 | } else {
181 | log.Printf("方法videoService.IsFavourit(video.Id, userId) 成功")
182 | }
183 | wg.Done()
184 | }()
185 |
186 | wg.Wait()
187 | }
188 |
189 | // GetVideoIdList
190 | // 通过一个作者id,返回该用户发布的视频id切片数组
191 | func (videoService *VideoServiceImpl) GetVideoIdList(authorId int64) ([]int64, error) {
192 | //直接调用dao层方法获取id即可
193 | id, err := dao.GetVideoIdsByAuthorId(authorId)
194 | if err != nil {
195 | log.Printf("方法dao.GetVideoIdsByAuthorId(%v) 失败:%v", authorId, err)
196 | return nil, err
197 | } else {
198 | log.Printf("方法dao.GetVideoIdsByAuthorId(%v) 成功", authorId)
199 | }
200 | return id, nil
201 | }
202 |
--------------------------------------------------------------------------------
/service/videoServiceImpl_test.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "TikTok/dao"
5 | "fmt"
6 | "testing"
7 | "time"
8 | )
9 |
10 | func getVideoService2() VideoService {
11 | dao.Init()
12 | var userService UserServiceImpl
13 | var followService FollowServiceImp
14 | var videoService VideoServiceImpl
15 | var likeService LikeServiceImpl
16 | var commentService CommentServiceImpl
17 | userService.FollowService = &followService
18 | followService.UserService = &userService
19 | likeService.VideoService = &videoService
20 | commentService.UserService = &userService
21 | videoService.CommentService = &commentService
22 | videoService.LikeService = &likeService
23 | videoService.UserService = &userService
24 | return &videoService
25 | }
26 |
27 | func TestList(t *testing.T) {
28 | videoService := getVideoService2()
29 | list, err := videoService.List(3, 2)
30 | if err != nil {
31 | return
32 | }
33 | for _, video := range list {
34 | fmt.Println(video)
35 | }
36 |
37 | }
38 |
39 | func TestGetVideo(t *testing.T) {
40 | videoService := getVideoService2()
41 | video, err := videoService.GetVideo(1, 2)
42 | if err != nil {
43 | return
44 | }
45 | fmt.Println(video)
46 | }
47 |
48 | func TestFeed(t *testing.T) {
49 | videoService := getVideoService2()
50 | feed, t2, err := videoService.Feed(time.Now(), 2)
51 | if err != nil {
52 | return
53 | }
54 | for _, video := range feed {
55 | fmt.Println(video)
56 | }
57 | fmt.Println(t2)
58 | }
59 |
--------------------------------------------------------------------------------
/service/videoSub.go:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import (
4 | "TikTok/dao"
5 | )
6 |
7 | type VideoSub struct {
8 | }
9 |
10 | func (vs VideoSub) Send(comment dao.Comment) error {
11 | //TODO implement me
12 | panic("implement me")
13 | }
14 |
15 | func (vs VideoSub) CountFromVideoId(id int64) (int64, error) {
16 | return 4, nil
17 | }
18 |
19 | func (vs VideoSub) DelComment(id int64) error {
20 | //TODO implement me
21 | panic("implement me")
22 | }
23 |
24 | func (vs VideoSub) GetList(vedioId int64, userId int64) ([]CommentInfo, error) {
25 | //TODO implement me
26 | panic("implement me")
27 | }
28 |
29 | func (vs VideoSub) IsFollowing(userId int64, targetId int64) (bool, error) {
30 | //TODO implement me
31 | panic("implement me")
32 | }
33 |
34 | func (vs VideoSub) GetFollowerCnt(userId int64) (int64, error) {
35 | //TODO implement me
36 | panic("implement me")
37 | }
38 |
39 | func (vs VideoSub) GetFollowingCnt(userId int64) (int64, error) {
40 | //TODO implement me
41 | panic("implement me")
42 | }
43 |
44 | func (vs VideoSub) AddFollowRelation(userId int64, targetId int64) (bool, error) {
45 | //TODO implement me
46 | panic("implement me")
47 | }
48 |
49 | func (vs VideoSub) DeleteFollowRelation(userId int64, targetId int64) (bool, error) {
50 | //TODO implement me
51 | panic("implement me")
52 | }
53 |
54 | func (vs VideoSub) GetFollowing(userId int64) ([]User, error) {
55 | //TODO implement me
56 | panic("implement me")
57 | }
58 |
59 | func (vs VideoSub) GetFollowers(userId int64) ([]User, error) {
60 | //TODO implement me
61 | panic("implement me")
62 | }
63 |
64 | func (vs VideoSub) CheckCommentString() string {
65 | //TODO implement me
66 | panic("implement me")
67 | }
68 |
69 | func (vs VideoSub) IsFavourit(videoId int64, userId int64) (bool, error) {
70 | return true, nil
71 | }
72 |
73 | func (vs VideoSub) FavouriteCount(videoId int64) (int64, error) {
74 | return 3, nil
75 | }
76 |
77 | func (vs VideoSub) FavouriteAction(userId int64, videoId int64, action_type int32) error {
78 | //TODO implement me
79 | panic("implement me")
80 | }
81 |
82 | func (vs VideoSub) GetFavouriteList(userId int64) ([]Video, error) {
83 | //TODO implement me
84 | panic("implement me")
85 | }
86 |
87 | func (vs VideoSub) GetTableUserList() []dao.TableUser {
88 | //TODO implement me
89 | panic("implement me")
90 | }
91 |
92 | func (vs VideoSub) GetTableUserByUsername(name string) dao.TableUser {
93 | //TODO implement me
94 | panic("implement me")
95 | }
96 |
97 | func (vs VideoSub) GetTableUserById(id int64) dao.TableUser {
98 | //TODO implement me
99 | panic("implement me")
100 | }
101 |
102 | func (vs VideoSub) InsertTableUser(tableUser *dao.TableUser) bool {
103 | //TODO implement me
104 | panic("implement me")
105 | }
106 |
107 | func (vs VideoSub) GetUserById(id int64) (User, error) {
108 | //TODO implement me
109 | panic("implement me")
110 | }
111 |
112 | func (vs VideoSub) GetUserByIdWithCurId(id int64, curId int64) (User, error) {
113 | var user User
114 | return user, nil
115 | }
116 |
--------------------------------------------------------------------------------
/util/sensitiveFilter.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "github.com/importcjj/sensitive"
5 | "log"
6 | )
7 |
8 | var Filter *sensitive.Filter
9 |
10 | const WordDictPath = "./document/sensitiveDict.txt"
11 |
12 | func InitFilter() {
13 | Filter = sensitive.New()
14 | err := Filter.LoadWordDict(WordDictPath)
15 | if err != nil {
16 | log.Println("InitFilter Fail,Err=" + err.Error())
17 | }
18 | }
19 |
--------------------------------------------------------------------------------