在线教育平台项目总结

1.项目自述

针对职业技能教育的网络课堂系统,提供相关课程。项目基于B2B2C的业务模式,培训机构可以在平台入驻、发布课程,运营方人员对发布的课程进行审核,课程包括免费和收费两种形式,对于免费课程普通用户可以直接选课学习,对于收费课程在选课后需要支付成功才可以继续学习。

采用前后端分离架构,后端采用SpringBoot、SpringCloud技术栈开发,数据库使用了MySQL,还使用的Redis、rabbitMQ、minIO、Elasticsearch等中间件系统。

2.核心模块

base模块:定义分页,全局异常处理

课程管理模块:主要负责课程、教师、机构相关增删改查,课程审核、发布

系统模块:数据字典

媒资管理模块:上传照片、上传视频、处理视频、审核媒资、绑定媒资,minIO分布式文件系统

网关:知道每个微服务实例的地址,nacos作为注册中心和配置中心

订单:负责订单管理

学习:选课,支付等

3.数据库

(1)xcplus_course

course_base(课程基础信息表):

课程信息表。id,机构公司id,大分类小分类,审核状态(未审核,已审核,审核成功),课程发布状态(未发布,已发布,下线),课程类型(是否收费)

course_category(课程分类表):

课程分类表。父结点id(第一级的父节点是0,自关联字段id,0,1,1-11,1-11,1-12,1-12,1-12),是否显示,是否叶子,排序字段。

course_market(课程营销表):

收费规则(对应数据字典),价格,有效天数,qq,微信

course_chapter(课程章节表):具体课程内容

id,父id,层级,类型(视频or文档),章节介绍,时长,

course_teacher(课程师资表):

id,课程id,教师id,职位,照片(存储的照片地址)

course_message(消息记录表,用于索引同步):

id,消息类型代码,失恋业务信息,通知次数,处理状态,回复失败内容,回复成功时间,最近通知时间,id,课程发布id,isRedis,isES,isMinIO

(2)xcplus_media

media_files(媒资信息表):

文件id(md5值),机构id,name,type(图片,文档,视频),bucket,路径,审核状态

media_upload(视频上传表):

id,文件id,name,bucket,url(路径),上传状态(未处理,上传中,成功,失败)

media_process(视频处理表):

id,文件id,name,bucket,url(路径),处理状态(未处理,处理中,成功,失败)

meida_content(媒资绑定表):

课程章节id,视频id

(3)xcplus_base

dictionary(字典表):

id,name,code,描述

(4)xcplus_users

user_company(机构表):

id,name,简介,营业执照,logo

user(用户表):

id,name,用户类型(学生,老师,管理员),phone,email

user_role(用户角色表,记录用户-角色):

id,用户id,角色id(一个用户会有多个角色)

user_permission(权限表,记录角色-权限):

id,角色id,权限代码(不是按用户类型,是数字)

user_menu(菜单表,记录权限-菜单):

id,name,pid,权限id(有什么权限,可以访问什么菜单)

(5)xcplus_learning

learn_choose(选课记录表):

id,课程id,用户id,选课状态(选课成功、待支付、选课删除),选课类型(免费课程、收费课程)

learn_courseTable(我的课表):

id,用户id,课程id,机构id

(6)xcplus_order

order(订单表):

id,总价,交易状态,用户id,订单类型,name,订单描述

order_goods(订单-物品表):

id,订单id,物品id,物品类型,物品价格

order_pay(订单支付记录表):

id,本系统支付交易id,第三方交易id,订单id,总价,币种,支付状态

1.base

(1)全局异常处理

1-@ControllerAdvice

1)系统自定义业务异常

抛给框架处理。运用AOP思想,@ControllerAdvice控制增强,通常和@ExceptionHandler结合使用。增强类处理。新建本项目自定义异常类型(继承runtimeexception),新建类写异常处理(可以区别捕获的异常类型)。使用该类的cast函数在业务代码中throw异常

2)jsr303异常

异常处理器中要解析jsr303的异常,定义枚举类

否则会报unknow的统一异常信息,因为他不属于xuecheng的程序员自定义类型异常。

(2)分页

1-mybatisplus

mybatisplus框架,定义pageParam和pageResult

(3)返回

1-高可用VO

定义返回的类型RestResponse

(4)工具类

一些自定义的utils

(5)拿到字典值

所有的魔法值都在这里拿

2.course(课程管理)

(1)课程查询

1-分页

每个服务的model去引入base依赖。使用时在微服务的service层配置@bean,mybatisPlusInterceptor拦截器指明数据库类型,进行分页。

Page<CourseBase> page = new Page<>(pageParams.getPageNo(), pageParams.getPageSize());
Page<CourseBase> pageResult = courseBaseMapper.selectPage(page, queryWrapper);
List<CourseBase> items = pageResult.getRecords();

2-测试

postman,junit,httpclient

(2)课程新增

1-JSR303分组校验

JSR303分组校验。在Model的dto中分组校验,Controller中接收参数时在@RequestBody后插入

@Validated(ValidationGroups.Inster.class)

前端后端都要校验,为了防止使用postman等访问后端

2-数据库事务控制

关于文件上传过程中的事务控制,抽取出只有数据库操作的方法来加。在类内curProxy通过代理对象调用。

(3)课程分类查询

1-递归查询分类目录

with recursive t1 as (
select * from  course_category p where  id= '1'
union all
 select t.* from course_category t inner join t1 on t1.id = t.parentid
)
select *  from t1 order by t1.id, t1.orderby

用mapper.select(),拿到数据表所有行,建立map(根结点排除,前端要查的是该id的childrenTree),key=id,value=一行。

从头遍历 List<CourseCategoryTreeDto> ,一边遍历一边找子节点放在父节点childrenTreeNodes

(4)课程营销增删改查

(5)课程计划增删改查

(6)课程审核

审核时不允许修改课程内容,以免发生冲突。

审核成功或失败后可以继续进行修改,重新进入到审核阶段

(7)课程发布

发布后公开展示在网站上供学生查看、选课和学习

1-CAP理论

要么cp,要么ap

C:一致性,查哪个结果都是最新的

A:可用性,都能查到结果,不保证最新

P:分区容忍性,网络通信异常情况下仍可对外服务

2-索引同步(AP)

如何快速搜索课程?

本地消息表+任务调度的机制来完成分布式事务的最终数据一致性的控制。建的本地消息表和课程发布表在同一个数据库,使用数据库事务控制,保证发布了就一定会在消息表中,然后任务调度定时调度消息表存储给redis,es,minIO。若当中有失败的,则下次定时会继续发,已经成功的根据幂等性,会跳过。

不是财务类似系统,采用AP服务

满足CP的话有一个写失败就要回滚

采用xxl-job,只要在一定时间内最终向redis、elasticsearch、MinIO写数据成功即可。

3-分布式事务(CP)

两阶段提交协议(2PC - Two-Phase Commit)

两阶段提交是最常见的分布式事务协议,分为两个阶段:准备阶段和提交阶段。

  • 准备阶段(Prepare Phase):事务管理器向所有数据库发送“准备提交”请求,各参与者执行事务操作但不提交,并将状态返回给协调者。
  • 提交阶段(Commit Phase):如果所有参与者都表示准备就绪,协调者发送“提交”指令,所有参与者正式提交事务;如果有一个参与者返回失败,协调者发送“回滚”指令,所有参与者撤销操作。

三阶段提交,要多一个预提交阶段,锁颗粒度更小,时间短。

4-openFeign

微服务之间的声明式调用。在nacos注册,请求来了之后到gateway负载均衡和请求转发,然后使用openfeign进行声明式调用。

5-Hystrix

Hystrix:设置超时时间。在FeignClient中指定fallbackFactory

熔断:保证C服务不会影响到B

降级:本地的一个方法,C出现问题时,给B返回说C出现问题了

返回一个null对象,上游服务请求接口得到一个null说明执行了降级处理。

3.media(媒资管理)

(1)上传照片

1-minIO

使用minIO来存储。分布式文件系统,损坏磁盘在1/2以下都可以恢复

bucket(桶)相当于存储的目录

有两个桶:mediafiles: 普通文件。video:视频文件

连接需要三个参数Endpoint(minio的http:端口)、Access Key(用户id)、Secret Key(密码)

增加:先使mimeType=通用的字节流,然后通过扩展名(.mp4)得到mimeType

通过路径去增删改查

根据bucket名和路径上传,看md5值判定是否已经在minio里。

@Transactional开启事务,因此需要impl内currentProxy

(2)上传视频

1-断点续传

视频比较大,所以需要断点续传

对文件分块,然后上传时检查,最后合并分块,校验是否完整

文件上传到一半不上传了,定时任务去表里查看视频处理表,删了

(3)处理视频

主要将视频格式统一保存,转mp4。(兼容性好,文件小)

1-xxl-job分布式任务调度(多线程)

高效处理任务,使用分布式+多线程,主要有调度中心、执行器、任务

调度中心:自身不承担业务代码,主要职责为执行器管理、任务管理、监控运维、日志管理等

执行器:负责接收调度请求并执行任务逻辑

任务:负责执行具体的业务处理

可以任务分片,为了保证多个执行器不重复执行

配置调度过期策略阻塞处理策略,避免同一个执行器多次重复执行同一个任务

调度过期策略:调度过期后,忽略过期的任务,从当前时间开始重新计算下次触发时间;
阻塞处理策略:丢弃后续调度。

和@Scheduled优缺点

优点:
不需要依赖外部框架。
简单快速实现任务。@EnableScheduling、@Scheduled 注解

缺点:
无法管理任务。要停止某个任务,必须重新发布。
不支持动态调整。修改任务参数需要重启项目。
不支持集群方式部署。集群模式下会出现任务多次被调度执行的情况,因为集群的节点之间是不会共享任务信息的,每个节点上的任务都会按时执行。

2-幂等性(分布式锁)

上传视频,如果正在处理中或处理完则不再处理

1.数据库锁(乐观)

谁update成功谁拿到锁

2.redis(setnx)

使用SETNX命令在Redis中设置一个键值对,如果该键不存在,则设置成功,表示获取到了锁;如果该键已经存在,则设置失败,表示锁已经被其他客户端持有。

为了避免锁的过期导致死锁,可以为锁设置一个过期时间

释放锁时,可以使用DEL命令删除对应的键。

3.另起线程看门狗

看线程是否干完活,可以续过期时间。

(4)绑定媒资

将媒资和课程内容章节绑定起来,写入到meida_content中

(5)课程预览

1-模板引擎

Jsp、Freemarker、Thymeleaf

一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。

要在naocs的公用配置组添加freemarker-config-dev.yaml

在使用freemarker渲染生成视图时需要数据模型,此数据模型包括了基本信息、营销信息、课程内容、师资、课程视频等信息。

所以定义一个数据模型类,拿到这些信息,addobject给freemarker

静态化开发,在freemarker中替换课程名称、预览图片等

2-nginx

作为web服务器,访问静态资源,css、js、图片等。

对http://file.51xuecheng.cn/mediafiles/网址进行转化,并且使本地html可以通过网址访问到

负载均衡,隐藏后端服务器地址,静态资源缓存可以频繁在本地访问,减少访问后端服务器次数

通过nginx访问网关,是最前方的代理。原来前端直接指向后台网关地址,现在要更改为Nginx地址

4.gateway

连接nacos。网关的yml中配置routes,uri使用loadbalance:uri: lb://content-api。

predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/content/** # 这个是按照路径匹配,只要以/content/开头就符合,转给content-api

可以配置白名单:http://gatewayserver/content/open/

然后让nginx转到这个地址

1-nacos

服务注册中心和配置中心

1)配置文件名组成

namespace、group、dataid。

namespace(开发环境,测试环境,上线环境)和group(学成在线,瑞吉外卖)。

Dataid:  包括三部分:(content-service)-(dev). (yaml)三部分组成

content-service=application.yaml中配置的应用名,nacos 客户端要根据此确定配置文件名称,所以spring.application.name不在nacos中配置,而是要在工程的bootstrap.yml进行配置。

dev=spring.profiles.active

2)extend和shared

在项目的yml中,extension-configs是特有的,shared-configs是共有的。

因为api接口工程依赖了service工程 的jar,所以这里使用extension-configs扩展配置文件 的方式引用service工程所用到的配置文件

3)执行顺序及配置优先级

执行顺序:bootstrap.yml->nacos->application.yml

各配置文件的优先级:项目应用名配置文件(nacos自己根据spring.application.name找的) > 扩展配置文件  > 共享配置文件 > 本地配置文件(application.yml)。名字name在bootstrap.yml

2-跨域

浏览器同源策略:协议、ip、端口。服务器间没有

三种方案:

JSONP,通过script标签的src属性

添加响应头, Access-Control-Allow-Origin:(在system模块添加)

nginx代理跨域,因为服务器间没有

5.search

(1)课程搜索

1-elasticsearch

1)es结构

index是表,document是行,field是字段,mapping是结构

创建索引,并指定Mapping

通过GET /course-publish/_mapping 查询course-publish的索引结构

删除索引命令:DELETE /course-publish

2)倒排索引

倒排索引,对课程发布信息进行索引和搜索。

倒排索引将文档的内容作为索引的键,将文档的编号或者标识作为索引的值,从而可以根据关键词快速定位到包含该关键词的文档。倒排索引由词项(Terms)和倒排列表(Posting List)组成。

假设有以下文档:

  • 文档1: "apple orange banana"
  • 文档2: "apple banana"
  • 文档3: "orange banana"

构建的倒排索引可能如下所示:

Term       Posting List
apple      1, 2
orange     1, 3
banana     1, 2, 3

当用户输入一个关键词进行查询时,系统会根据关键词在倒排索引中查找对应的倒排列表,从而得到包含该关键词的文档编号或者标识,进而检索出相应的文档。

3)MultiMatchQuery(聚合搜索,根据关键字搜索)

根据关键字搜索name、description字段。

{
  "query": {
    "multi_match": {
      "query": "搜索关键词",
      "fields": ["字段1", "字段2", "字段3"],
      "type": "best_fields"  // 可选项,默认为best_fields
    }
  }
}

还可以为每一个fields加权重,配置模糊查询

4)过滤器(根据确定的学科分类搜索)

根据分类、课程等级搜索采用过滤器实现精确查找,不用计算相关度得分,只是判断文档是否符合过滤条件效率更高。

5)分页

6)高亮

2-kibana

通过可视化界面访问elasticsearch的索引库,进行测试的时候使用用DSL测试成功之后

{
  "query": {
    "bool": {
      "must": {
        "match": {
          "content": "Elasticsearch"
        }
      },
      "filter": {
        "term": {
          "status": "published"
        }
      }
    }
  }
}

正式使用java。

6.auth

(1)统一认证

认证通过由认证服务向给用户颁发令牌,相当于访问系统的通行证,用户拿着令牌去访问系统的资源

(2)单点登录

用户只需要认证一次,便可以在多个拥有访问权限的系统中访问。

1-JWT

普通令牌校验每次要去auth验证,而且是存在session,加大服务器压力

而JWT会给每个为微服务一个密钥,自己可以校验令牌。而且基于json,是无状态认证。

由三部分组成,header(头部),内容(用户的信息),签名(前两部分想加再加密),

一个接口有很多个实现类,在service中增加名字(password,wx,phone)

(3)权限控制(RBAC)

1-基于角色or资源访问控制

1)基于角色的访问控制

比如:主体的角色为总经理可以查询企业运营报表,查询员工工资信息等

if(主体.hasRole("总经理角色id")){
查询工资
}

2)基于资源的访问控制

比如:用户必须具有查询工资权限才可以查询员工工资信息等,访问控制流程如下:

if(主体.hasPermission("查询工资权限标识")){
    查询工资
}

if里面写的是角色还是是否有这个权限(一个属性字段)。基于资源的访问控制健壮性更好。使用@PreAuthorize("hasAuthority('权限标识符')")进行控制,标识符会在数据库中定义。在方法前面加该注解,拥有此权限才可以访问该方法。

划分更加细致,可以有多种组合,不用定义非常多的用户类型,让用户去组合角色类型即可形成大量的权限等级。

细粒度授权(数据权限),对某一资源有访问权限,但是可以访问到的数据不一样,比如两个培训机构。course_base中有培训机构id。在service接口根据参数去做,security框架无法实现

(4)白名单(无需鉴权)

1-redis

redis存放白名单接口(无需认证即可访问)所需要的数据,缓存了普通用户所要查询的数据(我的订单、我的选课),缓存热点数据(最新发布的课程信息、推荐课程信息等)。

每类信息有不同的缓存过期时间,为了避免缓存雪崩缓存时间加了随机数。

1)如何限流:

  1. 令牌桶算法:通过在令牌桶中存放一定数量的令牌,每个令牌代表一个可用的请求。当有请求到来时,需要先从令牌桶中获取一个令牌,如果令牌桶中没有足够的令牌,则拒绝请求或者将请求放入队列中等待。

  2. 基于计数器的限流:通过Redis的计数器功能,可以对请求的数量进行计数,并根据设定的阈值来限制请求的流量。

  3. 漏桶算法:漏桶算法是另一种常见的流量控制算法,它通过固定速率从漏桶中漏出请求,当有请求到来时,将请求放入漏桶中,然后以固定速率处理漏桶中的请求。如果漏桶已满,则拒绝新的请求。

2)为什么快?

redis是单线程但速度依旧很快,基于内存的数据存储,使用了IO多路复用模型。

3)数据类型

string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。

查询所有的key用:

KEYS *

SCAN 0 MATCH *

keys会阻塞一段时间,推荐用scan,因为是非阻塞的。

4)布隆过滤器

可以用来判断一个元素一定不在集合中,但不保证一定在集合中。

核心是一个位数组(Bit Array)和多个哈希函数

  1. 初始化:创建一个长度为m的位数组,所有位都初始化为0。

  2. 插入操作:将待插入的元素通过多个哈希函数计算出多个哈希值,然后将位数组中对应的位置置为1。

  3. 查询操作:将待查询的元素通过多个哈希函数计算出多个哈希值,然后检查位数组中对应的位置是否都为1。如果所有位置都为1,则说明该元素可能在集合中;如果存在任意一个位置不为1,则说明该元素一定不在集合中。

5)Zset

与普通集合相比,在存储元素的同时,每个元素都关联了一个分数(score),这样就可以根据分数对元素进行排序。

Zset的底层数据结构是跳跃表(Skip List)和哈希表(Hash Table)的结合。

跳表:负责元素的排序和范围查询,用于实现按分数的有序访问。

哈希表:负责快速查找元素的分数和判断元素是否存在,用于支持高效的增删改查操作。key是元素,value是元素分数

6)持久化

RDB:快照的持久化方式,在指定的时间间隔内将数据集快照写入磁盘,可能会丢失

AOF:基于日志的持久化方式,将写命令追加到文件末尾,文件大,恢复慢

2-缓存三兄弟

缓存穿透,高并发请求发现缓存中没有,会去多次查数据库。

解决方案:第一次请求时数据库中不存在数据给缓存到redis,但是值为null,加过期时间,防止数据库中什么时候真的有了该key。

缓存雪崩,缓存的大量key失效,因为他们设置了相同的缓存失效时间。

解决方案:对同一类型的key设置不同的过期时间。

缓存击穿,大量并发访问同一个热点数据,热点数据失效后同时访问数据库。

解决方案:热点数据不过期,有写操作时更新redis。

7.learning

(1)选课

收费课程,免费课程,选课成功后有课程资料权限

(2)支付

涉及订单数据库

1-RabbitMQ支付通知

将支付结果发通过给fanout类型的交换机,由交换机将消息广播发送至每个接收支付结果的微服务。

1、订单服务创建支付结果通知交换机。

2、学习中心服务绑定队列到交换机。   

为什么用:异步处理,用户体验更好,流量削峰,消息不会丢失。

1)丢失

消息持久化:在发送消息时,设置消息的持久化属性,确保消息被持久化到磁盘中,

确认机制:生产者消费者确认机制

2)重复

消息唯一标识:给每个消息分配一个唯一的ID,进行去重,避免重复处理相同的消息。

3)顺序

默认不有序。有序的话在消息的内容中添加序列号或者时间戳等标识。

4)堆积

定期监控队列深度:超过一定阈值时,触发报警或者自动进行处理。

消息 TTL 设置:为队列设置消息的 TTL(Time To Live),即消息的生存时间。当消息在指定的时间内没有被消费者处理,则消息会被丢弃或者进入死信队列,避免消息在队列中长时间堆积。

(3)学习

学生付费成功后可在线学习

8.难点

1-视频上传过程中断了怎么办?

2-多个微服务抢着去处理同一个视频

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值