# 🚩 写在前面
> 如有不足的地方,欢迎补充、填坑。
# 😎 知识点概览
- [x] 本章节将对【学成在线】项目的知识点进行回顾、总结、梳理。
# 项目代码
后端代码:[https://gitee.com/codeyee/xuecheng-project-services](https://gitee.com/codeyee/xuecheng-project-services)
前端代码:[https://gitee.com/codeyee/xuecheng-project-ui](https://gitee.com/codeyee/xuecheng-project-ui)
完整的数据库文件以及 `nginx` 配置已上传至后端代码工程内
# 目录
内容会比较多,小伙伴们可以根据目录进行按需查阅。
[TOC]
# 一、学成在线是一个什么样的项目?
## 0x01 功能模块
学成在线是一个在线教育平台,提供IT职业课程在线学习,平台包括:门户、学习中心、教学管理中心、系统管理中心、社交系统等子系统。
项目的功能架构如下图:

门户是整个平台的入口,功能包括:
- [x] 门户首页
- [x] 注册/登录
- [ ] 课程搜索
- [ ] 职业规划
- [ ] 客服等
学习中心为用户提供在线学习服务,包括:
- [x] 我的课程
- [x] 视频点播
- [ ] 视频直播
- [ ] 在线考试
- [ ] 在线答疑
- [ ] 学习统计等功能
教学管理中心为教育机构或个人讲师提供教学管理功能,包括:
- [x] 课程管理
- [x] 媒资管理
- [ ] 考试管理
- [ ] 问答管理等功能
系统管理中心提供系统参数配置
- [x] CMS
- [x] 数据字典
- [ ] 分类管理等功能
## 0x02 技术架构
项目采用前后端分离的技术架构,前端采用vue.js构建,服务端采用 `Spring Cloud Netflix` 微服务架构,系统分为用户层、`CDN`、负载均衡、前端UI、微服务层、数据层、接口层 及 `DevOps` 等部分组成,下图是完整的技术架构图:

业务流程举例:
1、用户可以通过pc、手机等客户端访问系统进行在线学习。
2、 系统应用 `CDN` 技术,对一些 `图片`、`CSS样式文件`、`视频` 等资源从 `CDN` 调度访问。
3、所有的请求全部经过负载均衡器。
4、对于PC、H5等客户端请求,首先请求UI层,渲染用户界面。
5、客户端UI请求服务层获取进行具体的业务操作。
6、服务层将数据持久化到数据库
下图是技术架构简图:

1、用户层,用户层描述了本系统所支持的客户端用户有哪些,本项目目前为各用户提供服务,包括H5、PC、Android和IOS等。
2、`CDN` 全称 `Content Delivery Network`,即内容分发网络,本系统所有静态资源全部通过 `CDN` 加速来提高访问速度。系统静态资源包括:html页面、js文件、css文件、image图片、pdf和ppt及doc教学文档、video视频等
3、负载均衡 系统的CDN层、UI层、服务层及数据层均设置了负载均衡服务,系统采用LVS+Nginx实现负载均衡均
衡。
4、`UI` 层 UI层描述了系统向pc用户、app用户、h5用户提供的产品界面。本项目在 `PC` 和 `H5` 端采用`vue.js+elementUI` 实现。
5、微服务层将系统服务分类三类:前端服务、后端服务及系统服务。 前端服务:主要为学习用户提供学习服务。
后端服务:主要为管理用户提供教学管理服务。 系统服务:公共服务,为系统的所有微服务提供公共服务功能
6、外部系统接口 包括如下接口:
- 第三方登录接口,如QQ、微博、微信等。
- 支付宝、微信支付接口
- 短信接口 (阿里大于)
- 邮件接口,通过smpt邮件服务器对外发送电子邮件。
- 微信公众号
- 点播、直播。
- `OSS` 存储
- CDN,使用最阿里云CDN服务加速视频访问速度。
7、`DevOps` 提供了本系统开发、运营、维护支撑的系统,包括如下内容:
- Eureka 服务治理中心:提供服务治理服务,包括:服务注册、服务获取等。
- Docker 容器化部署服务:将本系统所有服务采用容器化部署方式。
- Maven 项目管理工具:提供管理项目所有的 `Java` 包依赖、项目工程打包服务。
- Git/GitLab 代码管理服务:提供 `git` 代码管理服务。
- Spring Boot Admin 服务健康监控:监控微服务的健康状态、会话数量、并发数等
# 二、项目采用什么技术架构?
## 0x01 微服务技术栈
所有微服务基于 `Spring Boot`、`Spring Cloud Netflix`构建
- **控制层**
`Spring MVC`、`Spring Security Oauth2` 、`Swagger`
- **业务层**
事务控制:`Spring`
任务处理:`Spring Task`
数据缓存:`Spring Data Redis`
消息队列:`Spring Rabbit Template`
搜索: `Elasticsearch`
- **持久层**
操作 MySQL:`MyBatis`、`Druid` 连接池、`Spring Data JPA`
操作MongoDB:`Spring Data Mongodb`
- **数据层**
采用 `MySQL` 和 `MongoDb` 存储数据,`MySQL` 存储用户、课程等系统核心信息,`MongoDB` 存储 `cms`、配置信息。等认证模块使用 `redis` 储存用户的令牌信息
## 0x02 接口定义规范
项目架构设立接口层,接口层使用 `swagger` 注解描述接口的内容,接口定义规范如下:
**请求**
- `GET` 请求时,前端请求 key/value 串,SpringMVC采用基本数据类型(String、Integer等)或自定义类型接收。
- `POST` 请求时,前端请 `Form` 表单数据(application/x-www-form-urlencoded)和 `Json` 数据(ContentType=application/json)、多部件类型数据(multipart/form-data),对于Json数据SpringMVC使用
`@RequestBody` 注解解析请求的 `json` 数据。
**响应**
- 响应结果统一信息为:是否成功、操作代码、提示信息及自定义数据。
- 响应结果统一格式为 `json`。
## 0x03 微服务注册中心
两台 `Eureka Server` 互相注册,组成高可用。
微服务向 `Eureka Server` 注册自己,并在远程调用时从微服务发现目标服务地址。

微服务远程调用采用客户端负载均衡技术,使用 `Feign Client`。

## 0x04 微服务网关
网关的作用是负载均衡、路由转发、请求过虑等。
项目中网关与 `Nginx` 配合使用。

## 0x05 项目使用Spring了吗?用了它的哪些东西?
该项目是基于 `Spring` 进行构建的
1、所有的微服务开发采用 `Spring Boot` 开发
3、数据层使用Spring Data JPA、Spring Data MongoDB、Spring Data redis。
4、业务层使用Spring来控制本地事务,还使用了 `Spring Task` 任务调度框架、`Spring AMQP` 组件等。
5、控制层使用 SpringMVC、`Sprnig Security Oauth2`。
6、微服务管理使用Spring Cloud的Eureka注册中心,微服务之间调用使用 `Ribbon` 和 `Feign Client` 完成。
7、使用 `Zuul`网关完成微服务安全验证
## 0x06 项目中Spring Cloud是怎么使用的?
> 此问题通常是在回答了项目的技术架构后被问及,根据具体的使用Spring Cloud完成微服务开发的步骤来回答即可。
1、每个微服务使用 `Spring Boot` 开发,每个微服务工程包括了web、service、dao三层,这和开发一般的项目没有区别:
- web 层使用Spring MVC实现,对外暴露API接口给前端调用。
- service 层就是根据业务逻辑编写 JavaBean,并使用Spring的声明式事务控制方式来控制事务。
- dao 层就是数据访问接口,来访问MySQL和Mongodb,访问MySQL使用 `Spring Data JPA` 和 `Mybatis`,访问 `mongodb`使用 `Spring data mongodb`。
2、微服务开发完成要向 `Eureka` 注册中心注册,以便被其它微服务查找和访问。
3、微服务与微服务之间使用 `feign` 来调用,`feign Client`具有负载均衡的作用。只需要在接口上声明`@FeignClient` 注,Spring 底层会产生动态代理对象,使用 `ribbon` 客户端完成调用。
4、前端访问微服务需要通过网关,网关使用`Nginx` 和 `Zuul` 来实现,Nginx 是最前边的负载均衡,通过 Nginx 之后便到达了 Zuul,项目中 Zuul 的功能是过虑用户请求,判断用户身份,对于一些对外公开的微服务则需要经过 Zuul,直接通过 `Nginx` 负载均衡即可访问
## 0x07 Spring Data JPA 和 MyBatis 为什么两个都用?具体怎么用的?
> 此问题是考察对数据访问接口的使用程度。
项目中使用 `Spring Data JPA` 和 `MyBatis` 都是用来访问 `MySQL`,但是它们的分工不同:
Spring Data JPA 是 `Spring` 提供的一套JPA接口,使用 Spring Data JPA 主要完成一些简单的增、删、改、查功能。
对于复杂的查询功能会使用 `MyBatis` 编写SQL语言来实现,因为使用 `Spring Data JPA` 来做一些复杂的查询是没有 `MyBatis` 方便的,Spring Data JPA 是面向的对象,而 `MyBatis` 直接面向 `SQL` 语句,并且复杂的 `sql` 操作使用原生的 sql 实现的话也便于后续的优化。
## 0x08 什么雪崩?如何解决?
容错保护是指微服务在执行过程中出现错误并从错误中恢复的能力。微服务容错性不好很容易导致雪崩效应,什么
是雪崩效应

微服务的雪崩效应表现在服务与服务之间调用,当其中一个服务无法提供服务可能导致其它服务也死掉,比如:单
点登录服务调用用户信息服务查询用户信息,由于用户信息服务无法提供服务导致单点登录服务一直等待,从而导
致用户登录、用户退出功能无法使用,像这样由一个服务所引起的一连串的多个服务无法提供服务即是微服务的雪
崩效应。

Spring Cloud Hystrix 是基于 `Netflix` 的开源框架 `Hystrix` 的整合,它实现了断路保护、线程隔离、信号隔离等容错功能。
什么是断路保护?
断路保护就类似家庭电路中的保险丝,当电路过载时保险丝会自动切断,保护整个电路的安全。微服务的断路保护
的工作原理是当请求微服务失败的数量达到一定比例时会切换为开路状态,当请求微服务时就直接返回结果不再请
求微服务,当保持开路状态一段时间后判断微服务是否可以正常请求,如果正常则切换到半开路状态,最后切换到
哪闭路状态。

具体的操作方法可以采用 Fallback,会每个FeignClient方法调用Fallback,当出现开路则调用Fallback方法返回错
误结果。
什么是线程隔离?
调用微服务使用不同的线程池,线程池之间互不影响,即使某个服务不可用也不影响其它服务的调用,比如:对商
品服务的调用使用一个线程池,对用户服务的调用使用另一个线程池,即使用户服务不可用也不影响商品服务的调
用。
## 0x09 视图层用什么技术实现?
此问题问的较模糊,没有问是客户端的视图还是服务端的视图,所以此问题不光是视图技术还是考察我们对前后端分离的理解。
1、视图层在前端和服务端都存在。
2、前端视图采用 `vue.js` + `elementUI` 产品界面。
3、服务端都是暴露的 `rest` 接口,统一用 `json` 展示数据。
## 0x0A 接口是怎么定义的?采用什么数据格式?
本问题考察前后端分离开发中接口定义技能。
**1、接口定义**
使用 `SpringMVC` 编写`Controller` 方法,对外暴露 `Http` 接口,在 `Controller` 方法上使用`RequestMapping`、`PostMapping`、`GetMapping` 等注解定义 `Http` 接口。
**2、采用什么数据格式?**
- 请求:
`GET` 请求时:前端请求 `key/value` 串,`SpringMVC` 采用基本数据类型(String、Integer等)或自定义类型接收。
`POST` 请求时:前端请 `Form` 表单数据(application/x-www-form-urlencoded)和Json数据(ContentType=application/json)、多部件类型数据(multipart/form-data),对于 `Json` 数据SpringMVC使用 `@RequestBody` 注解解析请求的json数据。
- 响应:
统一响应 `json` 格式,`json` 格式数据 `SpringMVC` 采用 `FastJson` 解析为对象。
非 `json` 格式数据 `SpringMVC` 提供参数绑定的方法,将 `key/value` 或 `Form-Data` 数据转换为对象或基本数据类型的变量。
# 三、前端开发时具体流程是什么?
前后端分离开发模式在互联网公司最常见,特别是一些大型的互联网公司,但是一些传统的软件开发企业仍然是采用传统开发模式,此问题被问及是考察你有没有真正体会前端开发的好处。
1、前端与后端开发人员讨论确定接口。
接口讨论通过,形成接口文档 。
本项目专门设立一个 `api` 工程,在此工程定义接口,`Spring Boot` 集成 `Swagger`,生成 `Swagger` 接口,前后端开发人员通过 `html` 查看接口文档的内容。
2、前端与后端开发人员按照接口文档进行开发。
开发过程中各自进行单元测试。
**前端人员怎么进行单元测试?**
前端人员可以通过一些工具生成一些模拟数据,比如:`EasyMock`。
3、双方功能开发完成进行前后端联调。
阅读:https://github.com/phodal/fe/blob/master/chapters/chapter-13.md
## 前端采用什么技术栈?
前端工程大多为单页面应用(SPA),采用 `vue.js` 框架开发,搜索功能前端采用 `nuxt.js` 服务端渲染(SSR)框架开发。
技术栈包括:
| 名称 | 说明 |
| ---------- | ------------------------------------------------------------ |
| node.js | Node.js是一个事件驱动I/O服务端 `JavaScript` 环境,基于Google的 `V8` 引擎,V8引擎执行 Javascript 的速度非常快,性能非常好。 |
| vue.js | 一套构建用户界面的渐进式框架。`Vue` 的目标是通过尽可能简单的 `API` 实现响应的数据绑定和组合的视图组件。 |
| npm/cnpm | NPM是随同 `NodeJS` 一起安装的包管理工具,能解决NodeJS代码部署上的很多问题 |
| webpack | `Webpack` 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。 |
| axios | Axios 是一个基于 `Promise` 的 HTTP 库,可以用在浏览器和 `node.js` 中。 |
| nuxt.js | `Nuxt.js` 是一个通过 Vue 用于服务端渲染的简单框架,灵感来自 `Next.js` |
| element-ui | 一套为开发者、设计师和产品经理准备的基于 `Vue 2.0` 的桌面端组件库 |
# 四、该项目当前完成了哪些功能
## 0x01 CMS页面管理
`CMS`(Content Management System)即内容管理系统,本项目对 `CMS` 系统的定位是对各各网站(子站点)页面的管理,本项目的CMS系统不去管理每个子网站的全部资源,比如:图片、`CSS`、`html` 页面等,主要管理由于运营需要而经常变动的页面,从而满足根据运营需要快速开发、上线的需求。
功能包括:
- [x] 站点管理,站点就是本项目各各子网站,站点信息包括:站点名称、站点域名、端口、服务器物理路径等
- [x] 模板管理,由于要对页面进行静态化,使用 `freemarker` 引擎技术,所以需要定义模板。
- [x] 页面管理,包括:页面添加、页面修改、页面删除等操作。
- [x] 页面预览,对页面静态化,在浏览器预览页面静态化内容。
- [x] 页面发布,将页面静态化后发布到所属站点服务器。

### GirdFS是什么?工作原理是什么?如何使用?
> 是什么?为什么?怎么用?
`GridFS` 是 `MongoDB` 提供的用于持久化存储文件的模块,它可以作为分布式文件系统使用,`CMS` 子系统将页面文件、模板文件存储到 `GridFS` 中,由于本项目使用 `MongoDB`,选用 `GridFS` 可以快速集成开发。
它的工作原理是:
在 `GridFS` 存储文件是将文件分块存储,文件会按照 `256KB` 的大小分割成多个块进行存储,`GridFS` 使用两个集合(collection)存储文件,一个集合是 `chunks`, 用于存储文件的二进制数据;一个集合是 `files`,用于存储文件的元数据信息(文件名称、块大小、上传时间等信息)。
从 `GridFS` 中读取文件要对文件的各个块进行组装、合并。

使用方法是:
使用 `Spring data mongodb` 包下提供的 `GridFsTemplate` 访问 `GirdFS`。
```
gridFsTemplate.findone() 查询文件
gridFsTemplate.delete() 删除文件
gridFsTemplate.store()存储文件
```
### MQ在本项目中是如何使用的?

1、平台包括多个站点,页面归属不同的站点,需求是发布一个页面应将该页面发布到所属站点的服务器上。
2、每个站点服务部署 `CMS Client` 程序,并与交换机绑定,绑定时指定站点 `Id` 为 `routingKey`。指定站点`id` 为`routingKey` 就可以实现 `cms client` 只能接收到所属站点的页面发布消息。
3、页面发布程序向 `MQ` 发布消息时指定页面所属站点 `Id` 为 `routingKey`,根据 `routingKey` 将消息发给指定的 `CMS Client`。
### 页面发布的结果如何收集?
每次发布会在数据库记录发布日志,每个 `CMS Client` 完成页面发布会上报发布结果。
1、在站点管理中配置了每个站点的服务器信息
2、在每次发布页面时会记录发布日志(服务器ID、页面ID、页面名称、发布结果)
3、`CMS Client` 完成页面发布后会向数据库记录发布结果。
4、用户通过查询发布日志表的信息就可以知道每一次的发布结果(哪些服务器页面发布成功,哪些发布失败)
## 0x02 课程管理

### 为什么用多张表存储课程信息?
1、课程信息比较复杂,为了方便教学机构按步骤管理课程信息,并且也可以划分权限管理课程信息,将课程信息
管理功能分为多个表,如下
- 课程基本信息表
- 课程图片表
- 课程营销信息表
- 课程计划表等
2、将课程信息分开也是为了系统扩展需要,如果将课程所有信息存储在一张表中将不利于系统扩展。
## 0x03 媒资管理
每个教学机构都可以在媒资系统管理自己的教学资源,包括:视频、教案等文件。
媒资管理的主要管理对象是课程录播视频,包括:媒资文件的查询、视频上传、视频删除、视频处理等。
- 媒资查询:教学机构查询自己所拥有的媒体文件。
- 视频上传:将用户线下录制的教学视频上传到媒资系统。
- 视频处理:视频上传成功,系统自动对视频进行编码处理。
- 视频删除:如果该视频已不再使用,可以从媒资系统删除。
### 如何上传大文件?
前端使用 `WebUploader` 将文件分块,调用服务端分块上传接口来上传分块文件,分块上传完毕前端请求服务端进行合并,当上传过程中断再次进行上传时服务端判断分块是否已经上传,已经上传的分块不再重新上传。

### 如何进行视频处理?
如上图所示,`Java` 程序调用 `ffmpeg` 及流媒体程序员提供的视频处理类库(C程序)完成 `avi`、`mp4` 视频转成 `m3u8` 格式的视频。
`Java` 程序使用Jdk提供的 `Process Builder` 调用 `ffmpeg` 及 `C` 程序进行视频处理。
`Process Builder` 可以调用第三方程序,在 `java` 程序运行时启动第三方程序进程。
视频处理完成,`Java` 程序捕获第三方程序的输出日志,解析出视频处理完成标记,更新视频处理状态为已完成。
### CDN 内容分发是什么?
视频处理完成会在中心媒体服务器保存一份,另外通过 `CDN` 程序将视频发布到边缘媒体服务器,用户点播视频通
过 `CDN` 请求边缘媒体服务器中的视频,提高了视频播放速度。
具体使用的是第三方公司的 `CDN` 服务。
> 本项目中未实现CDN内容分发
## 0x04 搜索
项目中课程搜索采用 `ElasticSearch` 来完成。
实现方法是:
1、使用 `Logstash`(logstash是ES下的一款开源软件,它能够同时 从多个来源采集数据、转换数据)将MySQL中的课程信息读取到 `ES` 中创建索引,使用IK分词器进行分词。
2、使用 `Java High Level REST Client` 完成搜索。
3、生产环境使用 `ES` 部署为集群。

## 0x05 图片服务器
本项目采用 `FastDFS` 分布式系统作为图片服务器。
`FastDFS` 是用 `c` 语言编写的一款开源的分布式文件系统,适合小文件的存储。
FastDFS 包括 `Tracker server` 和 `Storage server` 。客户端请求 `Tracker server` 进行文件上传、下载,通过 `Tracker server` 调度向 `Storage server` 完成文件上传和下载。
使用 `FastDFS` 官方提供的 `Java API` 实现。
图片服务使用 `Nginx` 作为代理服务器,对 `Storage`上部署的 `Nginx` 完成负载均衡请求。

### 使用FastDFS的好处是什么?
`FastDFS` 相比其它的分布式文件系统它适用小文件存储,它不对文件进行分块存储,也不用对文件进行合并处理,所以性能比 `GFS`、`HDFS` 等通用文件系统的性能要高。
### 图片上传流程是怎么样的?
时序图如下

执行流程如下:
1、管理员进入教学管理前端,点击上传图片
2、图片上传至文件系统服务,文件系统请求 `fastDFS` 上传文件
3、文件系统将文件信息入库,将文件信息存储到文件系统服务数据库中。
4、文件系统服务向前端返回文件上传结果,如果成功则包括文件的 `Url` 路径。
5、课程管理前端请求课程管理,进行保存课程图片信息到课程数据库。
6、课程管理服务将课程图片信息保存在课程数据库。
### FastDFS支持断点续传吗?
`FastDFS` 支付断点续传,在 `Api` 中有 `append_file1` 方法就是用来实现断点续传的,本项目没有使用 `FastDFS` 的断点续传功能。
## 0x06 在线视频点播
本项目采用 `HLS` 技术实现视频点播。
1、使用 `FFmpeg` 对视频进行编码处理,生成 `m3u8` 文件及 `ts` 文件。
2、使用 `Nginx` 作为媒体服务器。
3、客户端使用 `video.js` 播放视频。
# 五、项目一些常见的问题
## 0x01 认证授权如何实现?
本项目采用 `Spring security` + `Oauth2` 完成用户认证及用户授权。认证授权流程如下:
1、用户请求认证服务完成身份认证。
2、认证服务下发用户 `JTI` (身份令牌)和 `JWT` 令牌,拥有身份令牌表示身份合法,`Jwt` 令牌用于完成授权。
3、用户携带 `jwt` 令牌请求资源服务。
4、网关校验用户身份令牌的合法,不合法表示用户没有登录,如果合法则放行继续访问。
5、资源服务获取 `jwt` 令牌,根据 `jwt` 令牌完成授权,并放行用户访问指定的资源。

## 0x02 事务是怎么控制的?分布式项目如何进行事务控制?
此问题考察对事务的理解和应用程度。
1、在微服务中使用 `Spring` 声明式事务控制方式进行控制,在 `Service` 方法上添加 `@Transctional` 注解即可实现事务控制,它控制的是 `MySQL` 的本地事务。
2、项目中大量存在分布式事务控制,比如下单支付、课程发布等地址都用到了分布式事务。本项目实现分布式事务控制实现最终数据一致性,做法是:
a、将分布式事务拆分为多个本地事务。
b、提交事务前每个参与者要通过数据校验,和资源预留。
c、由消息队列去通知多个事务参与者完成本地事务的提交。
d、提交失败的本地事务会重试。

## 0x03 一个接口出现bug你是怎么进行调试的?
1、接口的开发需要前端和服务端共同调试,要仔细阅读测试人员反映的 `bug` 信息,判断这个 `bug` 是服务端的`bug` 还是前端的 `bug`。通常服务接口开发完成会使用 `postman` 工具进行测试,测试没有问题再提交到 `Git`或`SVN` 。
2、找到 `bug` 的出错点就可以根据 `bug` 信息进行修改。
3、修改完成需要前后端再次连调测试,按照测试人员提交的测试流程重新进行测试,测试通过将此 `bug` 置为已解决。
## 0x04 本项目中在线支付是如何实现的?遇到哪些问题
### 实现流程
1、系统中收费的课程需要用户在线支付,支付接口采用微信的扫码支付。
2、拿到需求后,确定使用微信支付,首先去阅读微信的接口文档,这里重点阅读统一下单、支付结果通知、支付
结果查询三个接口。
3、下载官方提供的 `sdk` 编写单元测试用例测试每个接口。测试时没有使用微信的沙箱测试,直接使用正式接口,我们将金额改的小一些进行测试。
4、单元测试通过后开发整个支付功能,最终集成测试通过。
### 一些问题
接口参数的签名问题,当时是因为自己没有仔细看接口文档导致少写一个必填参数一直报签名失败,随后将所有必填参数填写完成,最终解决问题。
> 本项目中还未实现在线支付功能,这里仅是提供一些实现的思路,需要后续自己完善
## 0x05 系统的异常是怎么处理的?
系统对异常的处理使用统一的异常处理流程。
1、自定义异常类型。
2、自定义错误代码及错误信息。
3、对于可预知的异常由程序员在代码中主动抛出自定义异常类型的异常,抛出异常时需要指定错误代码。
4、对于不可预知的异常(运行时异常)由 `SpringMVC` 统一捕获 `Exception` 类型的异常,由统一的异常捕获类来解析处理,并转换为与自定义异常类型一致的信息格式(错误代码+错误信息)。
5、可预知的异常及不可预知的运行时异常最终会采用统一的信息格式(错误代码+错误信息)来表示,最终也会随请求响应给客户端。

## 0x06 使用消息队列了吗?如何使用的?有哪些应用场景
项目使用 `RabbitMQ` 消息队列。
`RabbitMQ` 提供很多的工作模式,如下:
- Work queues
- Publish/Subscribe
- Routing
- Topics
- Header
- RPC
项目主要使用了 `Routing` 模式。
`Routing` 模式即路由模式,使用方法是:
1、每个消费者监听自己的队列,并且设置 `routingkey`。
2、生产者将消息发给交换机,由交换机根据 `routingkey` 来转发消息到指定的队列。
**有哪些应用场景?**
1、任务异步处理。
将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间。
2、应用程序解耦合
`MQ` 相当于一个中介,生产方通过 `MQ` 与消费方交互,它将应用程序进行解耦合。
## 0x07 你在开发中遇到什么问题?是怎么解决的
> 此问题考察开发人员的问题描述及问题解决能力,可列举开发中实际的技术问题。
回答此问题要从两个方面来回答:
1、问题的描述
2、问题的解决方案
**例子:**
在处理订单时要用到定时任务,当时采用的是 `Spring Task` 来完成,由于一个订单服务会部署多个,多个订单服务同时去处理任务会造成任务被重复处理的情况,如何解决任务的重复处理。
**解决:**
采用乐观锁解决,在任务表中设置一个 `version` 字段记录版本号,取出任务记录同时拿到任务的版本号,执行前对任务进行锁定,具体的做法是执行 `update` 根据当前版本号将版本号加 `1`,`update` 成功表示锁定任务成功,即可开始执行任务。
# 六、项目功能的整体的测试
## 0x01 准备工作
### 1)启动基础设施
- [x] Mysql(本项目中用于储存课程管理相关数据)
- [x] Redis(本项目中用于储存JWT令牌信息)
- [x] MongoDB(本项目中用于储存页面信息、数据等)
- [x] RabbitMQ(消息队列)
- [x] ElasticSearch (提供数据索引的API)
- [x] Logstash (将数据并且进行分词处理后发布到ES)
- [x] Nginx(学成在线静态门户、各个服务的反向代理)
- [x] FastDFS Tracker、FastDFS Storage (提供小文件储存服务,本项目中用于储存图片)
`ElasticSearch` 和 `Logstash` 使用`windows` 批处理启动(开发环境)其他服务均使用 `windows` 服务启动,启动效果如下

ES启动脚本
```bat
@echo off
setlocal enabledelayedexpansion
setlocal enableextensions
SET params='%*'
:loop
FOR /F "usebackq tokens=1* delims= " %%A IN (!params!) DO (
SET current=%%A
SET params='%%B'
SET silent=N
IF "!current!" == "-s" (
SET silent=Y
)
IF "!current!" == "--silent" (
SET silent=Y
)
IF "!silent!" == "Y" (
SET nopauseonerror=Y
) ELSE (
IF "x!newparams!" NEQ "x" (
SET newparams=!newparams! !current!
) ELSE (
SET newparams=!current!
)
)
IF "x!params!" NEQ "x" (
GOTO loop
)
)
CALL "%~dp0elasticsearch-env.bat" || exit /b 1
IF ERRORLEVEL 1 (
IF NOT DEFINED nopauseonerror (
PAUSE
)
EXIT /B %ERRORLEVEL%
)
set ES_JVM_OPTIONS=%ES_PATH_CONF%\jvm.options
@setlocal
for /F "usebackq delims=" %%a in (`CALL %JAVA% -cp "!ES_CLASSPATH!" "org.elasticsearch.tools.launchers.JvmOptionsParser" "!ES_JVM_OPTIONS!" ^|^| echo jvm_options_parser_failed`) do set JVM_OPTIONS=%%a
@endlocal & set "MAYBE_JVM_OPTIONS_PARSER_FAILED=%JVM_OPTIONS%" & set ES_JAVA_OPTS=%JVM_OPTIONS:${ES_TMPDIR}=!ES_TMPDIR!% %ES_JAVA_OPTS%
if "%MAYBE_JVM_OPTIONS_PARSER_FAILED%" == "jvm_options_parser_failed" (
exit /b 1
)
cd /d "%ES_HOME%"
%JAVA% %ES_JAVA_OPTS% -Delasticsearch -Des.path.home="%ES_HOME%" -Des.path.conf="%ES_PATH_CONF%" -Des.distribution.flavor="%ES_DISTRIBUTION_FLAVOR%" -Des.distribution.type="%ES_DISTRIBUTION_TYPE%" -cp "%ES_CLASSPATH%" "org.elasticsearch.bootstrap.Elasticsearch" !newparams!
endlocal
endlocal
exit /b %ERRORLEVEL%
```
Logstash 启动脚本
```bat
@title logstash i n teachplan_media_pub
logstash.bat -f ../config/mysql_course_media.conf --path.data=../data/teachplan_media/
```
### 2)启动所有服务,并且检查是否注册到的eureka中
| Application | 服务描述 |
| :-------------------------------- | :----------------------------------------------------------- |
| XC-GOVERN-CENTER | Eureka服务注册中心,本项目中启动两个实例作为一主一从 |
| XC-GOVERN-GATEWAY | Zuul网关 |
| XC-SERVICE-BASE-FILESYSTEM | 文件系统服务,本项目中主要提供图片服务上传下载功能 |
| XC-SERVICE-LEARNING | 学习中心服务,提供中心相关的API接口 |
| XC-SERVICE-MANAGE-CMS | 站点CMS,提供网站页面静态化、制作、发布等相关API接口 |
| XC-SERVICE-MANAGE-CMS-CLIENT | 站点CMS客户端,通过MQ接收页面发布的通知, |
| XC-SERVICE-MANAGE-COURSE | 课程管理服务,提供课程管理相关的API |
| XC-SERVICE-MANAGE-MEDIA | 课程媒资管理服务,提供课程媒体文件相关的API |
| XC-SERVICE-MANAGE-MEDIA-PROCESSOR | 媒资处理服务,通过MQ接收视频处理通知,再调用第三方API来对媒资文件进行转码、分块等。 |
| XC-SERVICE-MANAGE-ORDER | 订单管理服务,提供订单处理相关的API |
| XC-SERVICE-SEARCH | 搜索服务,提供搜索相关的API |
| XC-SERVICE-UCENTER | 用户中心服务,提供用户相关的API |
| XC-SERVICE-UCENTER-AUTH | 统一认证中心服务,提供认证、授权相关操作的API |
在IDEA中启动服务,启动效果如下

所有服务成功的注册到 `eureka` 中

### 3)启动前端工程
- [x] xc-ui-pc-portal (搜索门户前端)
- [x] xc-ui-pc-sysmanage(站点CMS前端)
- [x] xc-ui-pc-teach(课程管理前端)
- [x] xc-ui-pc-leanring(用户学习中心前端)
启动效果如下

## 0x02 功能测试
### 1)页面静态化测试
访问cms前端 http://cms.xuecheng.com/#/cms/page/list
> cms.xuecheng.com在hosts文件中指向本地,并且配置nginx虚拟主机

找到一个以往的页面,点击预览,效果如下

新增页面,填写页面数据,填写数据模型url,用于结合模板渲染页面

添加成功

预览效果如下

编辑页面信息测试

删除测试

删除成功

#### 测试过程中出现的一些问题
1. CMS接口无权限问题
问题描述:由于在之前的章节中没有对CMS前端做登录授权的相关配置,导致访问CMS页面时无法正常获取数据
解决方案:参考day18的 “四、前端集成认证授权” 章节进行配置。
2. 课程预览无权限
问题描述:由于CMS在预览课程时候使用的是 `window.open()` 来访问预览页面,无法向 `header` 传递认证信息
解决方案:在 `cms` 服务和 `course` 服务的 `ResourceServerConfig` 分别放心 `/cms/preview/*`,`/cms/config/getmodel/*` 和 `/course/preview/model/*` 的鉴权
### 2)课程管理
访问学成主站 http://www.xuecheng.com/
点击右上角登录,进入到登录页面

登录成功,主站右上角显示用户信息

登录成功后,用户的认证令牌信息储存到 `redis` 中

点击右上方的 “教学提供方” 进入到课程管理前端,点击我的课程,页面初始化前会访问 `/course/list` 接口获取该用户所属的所有课程信息,并且渲染到页面当中,效果如下

点击管理课程,测试更新课程信息

测试更换课程图片,删除原有图片并且上传

营销信息修改

添加课程计划测试

预览课程
点击课程预览,生成预览链接

访问预览链接,效果如下
从效果图中可以看到,成功将我们的课程数据与课程模板相结合进行静态化渲染,得到最终的课程详情页面效果

课程发布
点击课程发布按钮

点击查看课程详情页面,会自动跳转到该课程的正式发布页面,跳转到的链接如下
http://www.xuecheng.com/course/detail/4028e58161bcf7f40161bcf8b77c0000.html
页面效果如下

课程重新发布后,课程发布信息的时间戳会被更新,logstash也会重新采集我们发布的课程数据并且添加到 `ElasticSearch` 的索引库当中

更新的 `ES` 索引库数据如下

### 3)媒资管理
上传视频文件测试,效果如下图

访问我的媒资,可以看到我们刚才上传的视频文件的相关信息,如下图

### 4)课程信息搜索
访问搜索门户页面 http://www.xuecheng.com/course/search 为了显示分页的效果,我们设置为每页显示两个结果

输入 `cloud` 关键字进行搜索,并且实现关键字高亮,效果如下

> 课程图片随意上传的,别较真哈哈
点击搜索结果进入该课程的课程详情页面

### 5)在线点播
在课程的课程计划管理中,为某个课程计划关联我们刚才上传的视频,操作示例如下

关联成功后,重新发布该课程信息

课程信息重新发布后,会更新该课程的时间戳,`logstash` 检索到时间戳的变化后会自动将更新后的课程信息到 `ElasticSearch` 的索引库中,如下图所示

自动更新课程计划信息

来到该课程的课程详情页面,点击 `马上学习`

进入到在线学习页面,点击目录中的课程计划,将会自动切换到课程计划节点对应的媒资内容

播放测试,推动进度条

# 七、该项目待完善的地方
已分散到每个章节的最后总结中,待整理汇总至此
> 欢迎各位小伙伴进行补充
# 八、项目学习周期
平均每天投入3小时左右进行知识点的学习,练习,笔记总结等,坚持下来大概需要花费2个月左右可以将该项目完整的学习下来,该项目还留下了一些需求需要自己去补充完善。
# 😁 认识作者
作者:👦 LCyee ,一个向往体面生活的代码🐕
自建博客:[https://www.codeyee.com](https://www.codeyee.com)
> 记录学习以及项目开发过程中的笔记与心得,记录认知迭代的过程,分享想法与观点。
CSDN 博客:[https://blog.csdn.net/codeyee](https://blog.csdn.net/codeyee)
> 记录和分享一些开发过程中遇到的问题以及解决的思路。
欢迎加入微服务练习生的队伍,一起交流项目学习过程中的一些问题、分享学习心得等,不定期组织一起刷题、刷项目,共同见证成长。

![微服务[学成在线] day20:项目总结](https://qnoss.codeyee.com/20200704_5b6u5pyN5YqhW+WtpuaIkOWcqOe6v10gZGF5MjAtMu+8mumhueebruaAu+e7kw==/image4.png)
微服务[学成在线] day20:项目总结