首页 💎字节跳动夏令营项目 Tiktok
文章
取消

💎字节跳动夏令营项目 Tiktok

💎字节跳动夏令营项目-TikTok

⚙️项目背景

⚫此项目是字节跳动夏令营结营大项目 通过讲师所讲的内容 实现一个极简版的抖音 来切实感受 实践课程中学到的知识点 如 常用框架 网络 数据库 操作系统 工业数据结构算法 分布式对象存储 消息队列 以及性能优化 同时 能提升对未来的实际开发工作有更多的深入了解与认识 以及 个人技术成长或视野

⚙️项目架构

⚙️项目模块

⭐️后端采用主流的MVC架构
1⃣️用户业务模块
⚫登录注册模块(Redis+腾讯云短信+JWT+拦截器)

​ 用户通过输入手机号+验证码的方式 实现登录、未注册的用户会创建一个新的账号 ​ 6位验证码由后端通过(Math.random() * 9 + 1) * 100000 生成 通过腾讯云的短信服务 发送到对应手机的用户 ​ 为了防止用户暴力登录和非法攻击 使用redis对于同一个IP地址的同一个手机号实现每隔60s才能发送的限制🚫—— redis.setnx60s(MOBILE_SMSCODE+":"+ip,ip); ​ 然后通过写好的 PassportInterceptor 登录拦截器 配合InterceptorConfig(拦截器都要注册进来) 来校验当前ip地址和redis的是否一致 不一致的话抛出异常 一致的话就继续 ​ 生产的验证码存入redis中——redis.set(MOBILE_SMSCODE+":"+mobile(手机号),code(验证码),30*60); ​ 用户登录成功后会通过JWT生成==Token== 然后用redis将其缓存——redis.set(REDIS_USER_TOKEN+":"+users.getId(),JWTtoken); ​ 最后数据存入MySQL并且把对应的验证码缓存删除 最后返回 对应对象给app ​ 如果用户退出登录则删除对应redis缓存 redis.del(REDIS_USER_TOKEN+":"+userId); ​ ⚠️==Tips==: ​ ·对BO的字段的样子 通过validation类去验证 同时在接口层架@valid注解开始验证 ​ ·如果捕获到字段不正确 通过自定义的ExceptionHandler和自定义的Excepition去抛出异常

⚫用户信息模块(Redis+MinIO+拦截器)

​ ⚫查询个人信息接口 ​ 通过传入的用户电话去数据库找 返回一个user对象 然后将其转换为 userVO对象 同时用beanUtils拷贝类的信息 然后对userVO中要展示的粉丝 关注 点赞 属性进行赋值 (通过去Redis 中去取对应的用户的粉丝 关注 点赞 值) redis.get(REDIS_MY_XXX_COUNTS + ":" + userId)「XXX为FANS、FOLLOW、LIKE」;最后返回UserVO对象给前端做渲染

​ ⚫修改个人信息接口: ​ 通过传入的updateUserBO对象 去数据库覆盖对应的信息 通过前端传入的每一项type判断要修改哪一个选项(tiktok号、姓名、性别、星座、地址···) 最后返回前端修改完成的User对象

​ ⚫修改个人头像和背景接口 ​ 但点击图片或者背景 通过MinIO的去上传对应图片 指定 MinIO的桶名、文件的名字、file文件流 通过提前写好的MinIO配置类上传文件或者图片到MinIO中 ​ 上传完成后获取对应的信息拼接成一个 外部能访问的url地址String url = minIOConfig.getFileHost()+"/"+bucketName+"/"+filename; ​ 然后构建updateUserBO对象 为其url属性赋值 这里通过前端给的参数判断对象是修改设置头像还是背景的值 然后修改数据同步到Mysql 最后返回对象 ​ 其中为这个接口设计的拦截器 UserTokenInterceptor用于去验证 当前本地的用户和token令牌 通过id从redis中拿到了 token值 然后对这个token值进行解压 得到用户id ​ 如果得到的 用户id和当前的一致的话说明 验证通过拦截器放行 否则抛出异常 此外此拦截器还要注册到对应的InterceptorConfig中去 要去绑定验证的请求路径 ​ String redisToken = redis.get(REDIS_USER_TOKEN + ":" + userId);

2⃣️粉丝业务模块
⚫关注接口

​ ⚫前端传入 用户id和博主id 通过查询用户方法 查看这两个人是否存在数据库 如果存在 则 通过 粉丝service层创建 fans对象 存入数据库中 ​ 创建 Fans类 设置主键值(一个工具随机生成)、vlogerId、fansId、互相关注字段(通过一个方法查看当前用户的粉丝列表是否有博主 如果有那么两者的互粉字段设置为1) ​ 数据插入MySQL

​ ⚫ 再用Redis存入 对应的关注数量 粉丝数量 以及粉丝和博主的关系 加快查询速度 ​ 我关注了一个人 我的关注➕1 —— redis.increment (REDIS_MY_FOLLOWS_COUNTS+":"+myId,1); ​ 博主被关注 博主的粉丝➕1 —— redis.increment (REDIS_MY_FANS_COUNTS+":"+vlogerId,1); ​ 设置一个键表示 粉丝和博主的关系⚛️ —— redis.set(REDIS_FANS_AND_VLOGGER_RELATIONSHIP+":"+myId+":"+vlogerId,"1");

​ ⚫当关注一个人成功后 使用MongoDB创建一个msgMO对象(包括发送者的id、发送者的昵称、发送者的头像、消息的内容、消息类型、接受者的id、消息的创建时间)发送到MongoDB中 ​

⚫取消关注接口

​ ⚫前端传入粉丝id和博主id 去查询 Fans表 先查看博主的粉丝列表 查看博主是否关注了我 如果关注了我 并且互粉状态为1 那么 就要 再去查看我是的粉丝是否有博主 如果有 把我的粉丝(博主)的互粉字段 设置为0 把我的粉丝(博主)更新到数据库 然后再从 博主的粉丝列表 删除我 让我不是他的粉丝

​ ⚫接着在Redis中把我的 关注数量 博主的粉丝数 以及 粉丝和博主的关系 设置设置为自减1

⚫我的关注接口

​ ⚫通过myID去数据库 进行查询 -通过fans表和user表 去查询对应的信息 然后分页表示出来 ​ 对应的SQL语句

1
2
3
4
5
6
7
8
9
10
SELECT
	u.id AS vlogerId,
	u.face AS face,
	u.nickname AS nickname 
FROM fans f
LEFT JOIN users u 
ON f.vloger_id = u.id 
WHERE f.fan_id = 1 
ORDER BY u.nickname
ASC 
⚫我的粉丝列表接口

​ ⚫通过传入的myId去数据库 进行查询 -通过fans表和user表 去查询对应的信息 然后分页表示出来

SQL语句
SELECT
	u.id AS vlogerId,
	u.face AS face,
	u.nickname AS nickname 
FROM fans f
LEFT JOIN users u 
ON f.fans_id = u.id 
WHERE f.vlogerId = xxx 
ORDER BY u.nickname
ASC
3⃣️视频业务模块:
⚫上传视频接口

​ ⚫由前端上传到uni_cloudOSS中 然后返回 一个vlogBo对象 再把vlogBo 通过beanUtil工具类进行值的拷贝 最后存入MySQL数据库

⚫推荐列表接口

​ ⚫前端传入 用户id和搜索的关键词 ​ ⚫通过数据库 用户表和视频表的关联查询 vlog.vloger.id = u.id 并且 is_private是0 也就是公开的视频 并且 视频的titile = 传入的keyword 按照 创建时间的倒叙排列 ​ ⚠️ 此处的使用了 like = "%xxxx%"方式去模糊查询 但是设置的索引会失效 可以使用ES去优化查询速度 ​ ⚫最后返回查询的视频列表

⚫获取视频详情接口

​ ⚫前端传入 视频的id 以及 用户id 通过视频id 作为条件 让用户表和视频表的关联查询 vlog.vloger.id = user.id 条件是 vlog id = 传入视频id 返回对应的一条视频详情 返回前端

⚫设为私密视频接口

​ ⚫前端传入 视频的id 以及 用户id 通过视频id 作为条件 再把vlog的is_private设置为1(表示私密) 构成一个新的vlog对象 去数据库中更新对于对象

⚫设为公开视频接口

​ ⚫前端传入 视频的id 以及 用户id 通过视频id 作为条件 再把vlog的is_private设置为0(表示公开) 构成一个新的vlog对象 去数据库中更新对于对象

⚫我的私密视频

​ ⚫前端传入 用户id 和 视频vlog的is_private为1(表示私密) 作为条件 构成一个新的vlog对象 去vlog表中数据库中查找vlog集合 并且 分页返回

⚫我的公开视频

​ ⚫前端传入 用户id 和 视频vlog的is_private为0(表示公开) 作为条件 构成一个新的vlog对象 去vlog表中数据库中查找vlog集合 并且 分页返回

⚫点赞视频操作(对点赞表操作)

​ ⚫通过 用户id和视频id 为条件 以及生成的主键ID 构建一个LikeVlog类 用于对应数据库中的表 然后将其插入MySQL 即可完成点赞 ​ ⚫点赞完成后 还有构建一个msgMO对象(包含发送信息用户、接受信息用户以及视频的相关信息) 存入MongoDB完成消息提醒功能 ​ ⚫之后 因为视频点赞数 以及 用户是否喜欢 某视频的关系 可以通过Redis去记录这些数字信息 ​ ·点赞了一个视频就 让点赞数 自增1redis.increment(REDIS_VLOG_BE_LIKED_COUNTS+":"+vlogId,1); ​ ·点赞了表示用户喜欢某个视频 redis.set(REDIS_USER_LIKE_VLOG+":"+userId+":"+vlogId,"1");

⚫取消视频操作

​ ⚫通过 用户id和视频id 为条件 以及生成的主键ID 构建一个LikeVlog类 用于对应数据库中的表 然后将其删除MySQL 即可完成取消点赞点赞 ​ ​ ⚫之后 因为视频点赞数 也存在于Redis中 故redis也要做同步的删除 ​ ·取消点赞了一个视频就 让点赞数 自减1redis.increment(REDIS_VLOG_BE_LIKED_COUNTS+":"+vlogId,1); ​ ·取消 点赞了 表示用户不喜欢某个视频 把代表其关系的key做一个删除 redis.del(REDIS_USER_LIKE_VLOG+":"+userId+":"+vlogId);

⚫我的点赞视频列表

​ ⚫通过用户id去数据库中查询 myLike和vlog表 做关联v.id = mlv.vlog_id 并且视频是公开的 获取对应视频列表 然后返回队友点赞List

⚫关注的人视频列表

​ ⚫通过 用户id 去表vlog 和 fans 表 中做关联查询 v.通过用户id去找到对应关注的博主 然后 通过博主 id去vlog表中找 其发表的视频列表信息 并且视频必须是公开的

⚫我的朋友视频列表

​ ⚫通过 用户id 去表vlog 和 fans 表 中做关联查询 v.通过用户id去找到对应关注的博主 双方要是互关的状态 然后 通过博主的id去vlog表中找 其发表的视频列表信息 并且视频必须是公开的

4⃣️评论业务模块:
⚫创建评论

​ ⚫前端传入CommentBO对象 然后对将其设置id 时间 将其转化为 Comment对象 再将其插入MySQL评论表中 ​ redis中添加一个字段 表示某个视频被点赞的次数 用于前端的快速获取redis.increment(REDIS_VLOG_COMMENT_COUNTS+":"+commentBO.getVlogId(),1); ​ ⚫添加完评论后要通知 被评论的视频博主 使用MongoDB先构建msgMO对象(设置发送人信息、接受者信息、发送的具体消息、消息类型[评论消息、还是回复评论消息]) 入库

⚫获取评论

​ ⚫从redis中获取某视频的评论数量 再返回 不用去数据库去统计 缓存了评论的数量 ​ ⚫通过 视频id 去 评论表中 找出对应视频的列表commentList 然后 把映射成commentVoList 对每一个 commentVo都要设置 视频被点赞的数量 以及视频被评论的总数去redis获取 作为 ​ 视图层的Comment对象

⚫删除评论

​ ⚫前端传入 commentId、评论者id、评论的视频id ​ ⚫通过commentId和评论者id作为一个对象 删除对应的评论 ​ ⚫然后还要去redis中减少一条评论的数量 redis.decrement(REDIS_VLOG_COMMENT_COUNTS+":"+vlogId,1);

⚫点赞评论

​ ⚫纯redis操作 不需要用到 mysql 只需要知道是那条评论和哪个人就行了 ​ ⚫如果点赞了一条评论 首先要会构建一条消息发送给MongoDB ​ ⚫通过评论id可以拿到 评论 以及 博主id 从而得到视频的信息 ​ ⚫然后构建一个msgMO对象 发送到 mongoDB 完成的对视频评论的点赞消息 入库

​ ⚫之后还要对redis中 某条评论的评论数量自增1 ​ redis.increment(REDIS_VLOG_COMMENT_LIKED_COUNTS+":"+commentId,1); ​ ⚫以及某人点赞了某个视频的关系 设置一个key ​ redis.set(REDIS_USER_LIKE_COMMENT+":"+userId+":"+commentId,"1");

⚫取消点赞评论

​ ⚫对redis中某条评论的评论数量数自减1 ​ redis.dcrement(REDIS_VLOG_COMMENT_LIKED_COUNTS+":"+commentId,1); ​ ⚫以及某人点赞了某个视频的关系 删除代表关系的这个key ​ redis.del(REDIS_USER_LIKE_COMMENT+":"+userId+":"+commentId);

5⃣️消息业务模块
⚫消息的创建

​ ⚫通过构建一个msgMO的对象 ​ 消息的发送者、接受者、消息类型、消息的内容、发送者昵称、发送者的头像和消息的发送时间 最后存入MongoDB

⚫消息的获取

​ ⚫前端传过来的用户id作为 toUserID 去查询所有消息

⚙️技术选型

⭐️前后端分离架构
1⃣️前端:Uni_App、Uni_Cloud、Vue、Html、Css
2⃣️后端:Java
3⃣️框架: SpringMVC、SpringBoot、MyBatis
4⃣️数据库: MySQL
5⃣️中间件: Redis、MongoDB、RabbitMQ、MinIO、Nginx、ELK
6⃣️DevOps: Git、Maven、Linux

⚙️项目优化

1⃣️网络
⚫CDN加速⏩:

​ ⚫起因:用户加载视频的时间长 app的整体体验感不好
​ ⚫解决:使用阿里云的CDN加速 将app视频分发至全球各地最接近用户的节点 缩短用户到节点的物理距离,使用户可就近取得所需内容,降低延迟

⚫Nginx反向代理⏩:

​ ⚫起因:当用户访问量高的时候 造成单结点的服务器的压力很大
​ ⚫解决:使用Nginx的反向代理技术和负载均衡策略 app部署在不同的云服务器上 通过统一的域名进入 Nginx则对请求进行分发 减轻了服务器的压力 提高了系统的吞吐量 ​ ⚫效果:使用Jmeter对评论接口进行了测试 在服务器测试 开启Nginx和不开启Nginx的效果 在5k流量下 ==性能提升效果提升显著 平均响应时间降低64% 吞吐量提升5倍==

2⃣️后端
⚫RabbitMQ异步解耦

​ ⚫起因: 由于每次进行点赞视频、关注用户、评论视频操作后都需要 把对应的信息发送到MongoDB 即在处理核心数据的后面同步的处理非核心的数据 如果此操作出现问题的话 那么会影响核心数据的处理 让整条调用链瘫痪 模块间会互相影响 从而增加了调用链长度 导致用户操作的时候得到的反馈和速度都不佳 ​ ⚫解决:使用消息中间件把同步调用改为异步调用 同时降低消息模块和其他模块之间的耦合度 具体采用RabbitMQ消息队列 ​ ⚫效果:使用Jmeter对接口进行了测试 在服务器测试 开启和不开启RabbitMQ的效果 在1w流量下 ==性能提升效果为 平均响应时间降低18% 吞吐量提升约2倍==

⚫MinIO分布式存储

​ ⚫起因:传统的单机架构下 视频等静态资源都是放在服务器本地 当用户数量很多的时候 视频的存储是一个很大问题 造成单个的服务器的存储压力很大 所以需要找到一个 海量存储 简单易用便宜的存储系统 适合计算中间结果的服务器
​ ⚫解决:使用MinIO进行分布式等对象存储 对象存储具有EB级别的海量存储 对象的数量不受限制 基于Restful 的http接口 简单易用 使用普通的x86服务器 租用便宜 具体流程是把用户上传的视频上传至MinIO服务器 后续访问静态资源的时候 只需要访问MinIO共享share出来的url就行了 减轻了服务器的压力 提高了系统性能

3⃣️数据:
⚫MySQL索引查询优化

​ ⚫起因:为了提高查询的响应速度 提升查询的效率 提升用户体验 ​ ⚫解决:在表的连接处或者where条件中 建立MySQL的索引 配合 Explain关键字 查看是否该查询走了索引 ​ ⚫效果:使用进行了对比测试 查看是否走了索引

⚫Redis缓存热点数据

​ ⚫起因:为了加快数据的响应 对数字的操作和存储 以及常用的数据 可以使用Redis进行缓存 ​ 1.登录接口: ​ 缓存ip地址 设置失效时间60s ​ 缓存验证码 失效时间 3*60s ​ 缓存Token 解决分布式Session问题 ​ 2.个人信息接口 ​ 缓存 用户的关注数 用户的粉丝数 以及获赞数 ​ 3.粉丝接口 ​ 用户关注博主 缓存 用户关注数+1、博主粉丝数+1、表示用户关注博主的键设置为1 ​ 用户取关博主 缓存 用户关注数-1、博主粉丝数-1、删除 表示用户关注博主的关系键 ​ 4.视频接口 ​ 视频被某人点赞 缓存 视频的被点赞数量 还有 表示拥护点赞了某条视频的键表示 两者有关系 ​ 视频被某人取消点赞 删除缓存 减少视频的被点赞数量 还有 删除 表示拥护点赞了某条视频的键 ​ 5.评论接口 ​ 视频被评论 缓存 视频被评论的数量+1 ​ 视频评论被删除 缓存 视频被评论的数量-1 ​ 6.评论点赞 ​ 视频的评论被点赞 缓存 评论被点赞的数量+1 以及 添加 代表视频评论被某人点赞的关系的键 ​ 视频的评论取消点赞 缓存 评论被点赞的数量-1 以及 删除代表视频评论被某人点赞的关系的键

​ ⚫解决:使用Redis进行缓存

⚫ELK数据查询优化

​ ⚫起因:为了加快响应全文搜索等速度 由于全文搜索是对视频的titile和content做模糊查询like %xxx% 不能用索引去优化 因为会导致索引失效 ​ ⚫解决:使用ElasticSeach+Logstash+Kibana+IK分词器 先用logstash 把数据库数据同步到es中 再对es执行搜索 采用ik分词技术 利用es的倒排索引 提高了视频的查询速度 ​ ⚫效果:使用Jmeter对接口进行对比测试

⚙️项目部署

1⃣️采用宝塔可视化UI➕阿里云项目部署上线
2⃣️项目GitHub地址:https://github.com/Zhangz1w3nBeatbox/TikTok
3⃣️项目安卓端演示地址:http://106.14.35.137:9000/tiktok/TikTok.apk
本文由作者按照 CC BY 4.0 进行授权

💎并发编程_volatile

Os Inputandoutput