# imooc-bilibili **Repository Path**: wake-up-g/imooc-bilibili ## Basic Information - **Project Name**: imooc-bilibili - **Description**: 仿B站 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2023-02-14 - **Last Updated**: 2023-02-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # imooc_bilibili imooc_bilibili 是仿 B 站项目。主要实现功能:用户注册,用户登录,用户关注,动态提醒,AOP 权限控制,视频上传、存储和在线观看,ElasticSearch 全文搜索,以及基于用户的推荐。 **相关技术:** SpringBoot,Devtools,FastJson,MyBatis,MySQL,Maven,Redis,RocketMQ,FastDFS,Docker。 ## Restfule 接口 REST(Representational State Transfer)是一组架构约束,表述资源的状态性转移,在 web 中资源就是 URI。 约束和原则是为了让开发更加规范,不能成为束缚开发的枷锁。 | 方法 | 语义 | | ------ | ------------------------------------------------------------ | | GET | 获取指定的资源 | | DELETE | 删除指定的资源 | | POST | 发送数据给服务器 | | PUT | 使用请求的负载创建或者替换目标资源。PUT 和 POST 的区别在于:PUT 是幂等的,而 POST 不是。幂等可以理解为调用一次与连续调用多次是等价的(没有副作用或副作用不变) | **Restfule 接口 URL 命名原则:** 1. HTTP 方法后跟的 URL 必须是名词且是复数形式 2. URL 采用全小写单词,如果有多个单词,使用 `-` 连接 **Restfule 接口 URL 分级原则** 1. 一级定位资源分类,如 */users* 表示需要定位到用户相关资源 2. 二级定位具体资源,如 */users/20* 表示 id=20 的用户 ## 用户模块 ### 用户验证 项目使用基于 JWT 的用户 token 验证,其中加密算法为 RSA 非对称加密。浏览器首先请求获得 RSA 公钥,然后将用户名和密码加密后发出。服务器对密码进行解密,然后再进行 MD5 单向加密,将结果与数据库的密码进行比较。如果密码正确,服务器将创建一个设有过期时间的 token 返回。 #### 基于 session 的用户验证 服务器验证浏览器携带的密码,验证通过后将用户信息保存在服务端 session。相同用户再次访问时,服务器查询匹配的 session,实现登录状态保持。 缺点:随着登录用户的增多,服务器要保存的 session 也越来越多,内存压力增大;若请求 cookie 被攻击者拦截,容易受到跨站请求伪造攻击;session 不能共享,分布式系统下扩展性不强。 #### 基于 JWT 的用户 token 验证 服务器验证浏览器携带的用户名和密码,验证通过后生成用户令牌(token)并返回给浏览器,浏览器再次访问时携带 token,服务器校验 token 并返回验证错误或请求数据。 优点:token 不储存在服务器,不消耗服务器内存;token 可以存储在非 cookie 中(比如请求头),安全性高;分布式系统下扩展性强。 #### 什么是 JWT JWT(JSON Web Token)是一种规范,用于在空间受限的环境下安全地传递声明。JWT 分成三部分,第一部分是头部(header),第二部分是载荷(payload),第三部分是签名(signature)。 头部存放声明的类型、声明的加密算法。 载荷存放有效信息,一般包含签发者、所面向的用户、接受方、过期时间、签发时间以及唯一身份标识。 签名主要由头部、载荷以及秘钥组合加密而成,就是 token。 ### 关注,粉丝,动态 #### 查询当前用户的关注列表 1. 从 *UserFollowing* 表查询用户关注的所有信息。 2. 根据关注用户的 *UserId* 查询 *UserInfo*,再将其注入关联的 *UserFollowing*。 3. 获取用户的所有关注分组,然后对 *UserFollowing* 进行分组,最后返回。 #### 动态提醒 订阅发布模式:用户发布动态 -> 将动态存入数据库和 RocketMQ -> 消费者获取消息,将动态追加到 redis 中粉丝用户对应的 key-value,如果没有则创建这样的键值对 -> 粉丝直接从 redis 获取关注的用户动态信息。 1. 用户新增动态,先往数据库添加信息。 2. 使用 *fastjson* 将动态对象转换为 byte 数组,然后构造消息队列的 Message,最后发送消息到队列。 3. 消费者获取到动态更新消息,使用 *fastjson* 将消息转换为动态对象。然后获取动态发布者的粉丝列表,根据这些粉丝的 id,构造特定的 key,将动态更新消息存入 redis。 4. 如果某个用户要获取自己关注的用户的动态信息,服务端直接从 redis 查询返回。当然,其中需要将字符串 value 转换为动态对象数组。 RocketMQ:开源消息中间件,高性能、低延迟、分布式事务。 Redis:高性能缓存工具,数据存储在内存中,读写速度非常快。 > 订阅发布模式:发布者和订阅者完全解耦,彼此不知道对方,完全通过代理人来执行事项。 > > 观察者模式:观察者和主题之间是松耦合关系,它们之间没有代理人。 ### 用户权限控制 #### RBAC 权限控制模型 权限控制就是控制用户对系统资源的操作,前端是页面和元素的控制,服务端是接口和数据的控制。 **RBAC 基于角色的权限控制模型** 权限具体表现为可以访问某些 API,获取某些数据。不同的角色拥有不同的权限,最后给用户分配一个或多个角色。这样,用户就拥有指定的权限。 项目中,角色有 Lv0~Lv6,权限有视频投稿、动态发布、弹幕功能等。 #### AOP 实现 这里只说明使用 AOP 根据用户等级控制 API 的访问。 使用注解标注切点,定义注解 `ApiLimitedRole`,它含有一个 `limitedRoleCodeList` 的字符串数组属性,用于表示限制访问的用户等级。 编写切面类,切点方法: ``` @Pointcut("@annotation(xxx.ApiLimitedRole)") public void check() {} ``` 使用前置通知: 获取注解的 `limitedRoleCodeList`,以了解哪些等级的用户不能访问。 然后通过 `UserSupport` 工具类获取 userId,再根据 *userId* 查询用户的角色,只要该用户有一个角色是被限制的,那么此用户就不可访问当前的 API,抛出异常。 这里并没有什么难理解与难实现的,但使用注解标注切点是之前从未使用过的,感觉非常自由。但这种方式可能不方便对整个包集进行切入。 ## 视频与弹幕模块 ### FastDFS 文件服务器 在云服务器搭建 FastDFS,同时配置 nginx,实现文件资源的 HTTP 访问。 这里主要是使用 *fastdfs* 提供的工具包,理解思想即可,不深入学习。 **FastDFS** FastDFS 是开源的轻量级分布式文件系统,用于解决大数据量存储和负载均衡等问题,类比 HDFS。它有两种服务结点:跟踪服务器(Tracker)、存储服务器(Storage),类似 HDFS 的 NameNode 和 ServerNode。 Tracker 主要做调度工作,起到负载均衡的作用。它是客户端和存储服务器交互的枢纽。Tracker 主要提供容量和备份服务,存储服务器是以组(Group)为单位,每个组内可以有多台存储服务器,数据互为备份。文件及属性(Meta Data)都保存在该服务器。 **Nginx** Nginx 是反向代理服务器。客户端通过代理发送请求到互联网上的服务器,从而获取想要的资源。它具有反向代理、负载均衡的功能。 > 什么是反向代理? > > 正向代理是服务端不知道客户端、客户端知道代理端。也就是客户端与服务端完全解耦,类似订阅发布模式。 > > 而反向代理是服务端知道客户端,客户端不知道代理端。客户端其实是直接向服务端发送请求,但这个请求中间会被 nginx 截取处理,然后分发到服务端,服务端收到请求时无法指定这个请求是否被处理过。 ### 秒传 视频在上传时,首先要根据视频内容得到 MD5 加密,这是该视频的唯一标识符。即使视频的名称、日期等信息改变,但它的 MD5 加密不会变。 视频资源的 URL 等信息会和 MD5 一同保存在数据库。当用户上传文件时,会在数据库查询这个视频的 MD5 是否存在,如果存在则说明这个视频已经上传过,直接返回上传成功。使得用户感觉瞬间上传成功了文件。 ### 视频在线观看 直接使用 FastDFS 的 HTTP 路径获取视频不安全,这会暴露文件服务器的地址,而且无法控制权限。所以项目通过 API 访问视频资源。客户端请求视频时会携带视频在 FastDFS 中的位置,服务端再将此 *url* 与配置信息拼接得到视频的 HTTP 路径,然后通过 *URL* 类获取文件输入流,再将此内容写到 *response* 以传给客户端视频。 ### 弹幕功能 场景:客户端对某一视频创建一条弹幕,发送后端进行处理,后端需要对所有正在观看此视频的用户推送该弹幕。 实现方式有短连接通信和长连接通信: 1. 短链接就是所有观看视频的客户端不断询问后端,若有新的弹幕则拉取显示。此方式效率低,浪费资源,客户端要不断的请求服务端。 2. 长连接是采用 WebSocket 进行前后端通信,服务端主动将弹幕信息发送给客户端。 HTTP 协议的通信只能由客户端发起,做不到服务器主动向客户端推送信息。所以要采用 WebSocket 通信协议。 > WebSocket 是基于 TCP 的一种网络协议,它实现浏览器与服务器全双工(Full-Duplex)通信。客户端可以主动发送信息给服务端,服务端也可以主动发送信息给客户端 **弹幕系统架构设计** 客户端与服务端建立 WebSocket 通信。 客户端发送弹幕,服务端接收到此请求会进行一下操作: 1. 将弹幕存入 MQ,消费者监听到后会将弹幕推送到所有的 WebSocket 客户端。 2. 将弹幕持久化:`@Async` 异步保存数据库,同步保存 redis。 后面获取弹幕信息,优先查询 redis 缓存,如果没有缓存,则查询数据库,然后将数据写入缓存。 ## 全局模块 ### ElasticSearch 全文搜索 ElasticSearch 是一个分布式、可扩展、实时的搜索与数据分析引擎。不深入讨论,实现即可。 ### 用户内容推荐 使用协同过滤算法来实现内容推荐,项目选用 Apache Mahout 工具实现基于用户的推荐,核心就是推荐和此用户相似的用户喜欢的内容。不深入讨论,实现即可。 ### 弹幕遮罩 原理:截取视频帧截图,将截图内容进行人像分割,最后将人体轮廓图转换为黑白剪影图,前端通过黑白剪影图覆盖原视频形成遮挡弹幕的效果。 未实现。