在线教育网站项目
一、项目简介:
在线教育项目采用的是B2C的商业模块,主要分为前台网站系统和后台运营平台。
角色分为管理员和(普通会员)用户,其中系统后台由管理员进行使用,系统前台由用户进行使用。
系统后台包含的模块主要有:
1、讲师管理模块
2、课程分类管理功能
3、课程管理功能
(1)视频
4、统计分析模块
5、订单管理
6、banner管理、等等
系统前台包含的模块主要有:
1、首页数据的显示
2、讲师列表和详情
3、课程列表和课程详情
(1)视频在线播放
4、登录和注册功能、等等
二、开发环境
(一)、开发环境
Windows+JDK1.8+Idea+vscode+linux+mysql+maven+spring boot
(二)、在线教育项目技术栈
后端技术架构:SpringBoot + SpringCloud + MyBatis-Plus + MySQL + Maven+EasyExcel+ nginx
前端的架构是:Vue+element-ui+Node.js + ECharts
其他涉及到的技术:包括Redis、阿里云OSS、阿里云视频点播
业务中使用了ECharts做图表展示,使用EasyExcel完成分类批量添加、注册分布式单点登录使用了JWT
(三)、开发时间
2020.6
三、项目模块实现
项目结构:
创建父工程 其类型为pom类型,用于管理依赖版本和放公共依赖
下面为子模块和子子模块,其中子子模块为功能实现模块
配置文件: application.properties
# 服务端口
server.port=8001
# 服务名
spring.application.name=service-edu
# 环境设置:dev、test、prod
spring.profiles.active=dev
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456
#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
#注册中心nacos
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
#开启熔断机制配置
feign.hystrix.enabled=true
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#配置mapper xml文件的路径
mybatis-plus.mapper-locations=classpath:com/gt/eduservice/mapper/xml/*.xml
springboot启动类
package com.gt.eduservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = {"com.gt"})
@EnableDiscoveryClient //注册中心
@EnableFeignClients //调用服务
/*@MapperScan("com.gt.eduservice.mapper")*/
public class eduApplication {
public static void main(String[] args) {
SpringApplication.run(eduApplication.class,args);
}
}
配置swagger2
*前后端分离开发模式中,api文档是最好的沟通方式。
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。*
1、在父工程下创建子模块common,再commom的pom文件中引入,lombok,swagger,
mybatis-plus等依赖
具体以来如下:
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<scope>provided </scope>
</dependency>
<!--lombok用来简化实体类:需要安装lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided </scope>
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<scope>provided </scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<scope>provided </scope>
</dependency>
2、在子模块common下创建子子模块service_base,并在子子模块service_base中创建swagger的配置类。
代码如下:
package com.gt.servicebase;
import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@EnableSwagger2
@Configuration
public class SwaggerConfig {
@Bean
public Docket webApiConfig(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("webApi")
.apiInfo(webApiInfo())
.select()
.paths(Predicates.not(PathSelectors.regex("/admin/.*")))
.paths(Predicates.not(PathSelectors.regex("/error.*")))
.build();
}
private ApiInfo webApiInfo(){
return new ApiInfoBuilder()
.title("网站-课程中心API文档")
.description("本文档描述了课程中心微服务接口定义")
.version("1.0")
.contact(new Contact("Helen", "http://atguigu.com", "55317332@qq.com"))
.build();
}
}
3、在子模块service中引入service_base
定义统一返回数据格式
将响应封装成json返回,一般我们会将所有接口的数据格式统一, 使前端(iOS Android, Web)对数据的操作更一致、轻松。
在子模块common下创建子子模块common-utils
1、创建接口定义返回码
package com.gt.commonutils;
public interface ResultCode {
public static Integer SUCCESS=20000;
public static Integer ERROR = 20001;
}
2、创建结果类
package com.gt.commonutils;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
@Data
public class Msg {
private Boolean success;
private Integer code;
private String message;
private Map<String,Object> data = new HashMap<String,Object>();
private Msg(){}
public static Msg ok(){
Msg msg = new Msg();
msg.setSuccess(true);
msg.setCode(ResultCode.SUCCESS);
msg.setMessage("成功");
return msg;
}
public static Msg error(){
Msg msg = new Msg();
msg.setSuccess(false);
msg.setCode(ResultCode.ERROR);
msg.setMessage("失败");
return msg;
}
public Msg success(boolean success){
this.setSuccess(success);
return this;
}
public Msg message(String message){
this.setMessage(message);
return this;
}
public Msg code(Integer code){
this.setCode(code);
return this;
}
public Msg data(String key, Object value){
this.data.put(key, value);
return this;
}
public Msg data(Map<String, Object> map){
this.setData(map);
return this;
}
}
3、在子模块service中引入子子模块common_utils,因为在子子模块service_base中已经引入了子子模块common_utils,所以直接在子模块service中引入子子模块service_base即可。
自动填充封装:
在子子service-base模块中创建自动填充类MymetaobjectHandler
package com.gt.servicebase.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
//自动添加创建时间,更新时间
@Component
public class MymetaobjectHandler implements MetaObjectHandler{
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("gmtCreate",new Date(),metaObject);
this.setFieldValByName("gmtModified",new Date(),metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("gmtModified",new Date(),metaObject);
}
}
安装vscode,
下载vue-element-admin,
vue-element-admin是基于element-ui 的一套后台管理系统集成方案。
下载vue.js,vue是一套用于构建用户界面的渐进式框架
下载node.js, Node.js 就是运行在服务端的 JavaScript。
Node.js是一个事件驱动I/O服务端JavaScript环境
===============以上为前期准备工作 ===============
(1)讲师管理模块:其中 课程分类管理功能、课程管理功能都在讲师管理模块中
讲师管理:
a、创建数据库,创建讲师数据库表
b、创建MP代码生成器:
生成comtroller 、service、mapper 和实体类等
讲师的条件查询带分页 以及 增加、修改、删除功能实现
//条件查询带分页
@PostMapping("pagelistbycondition/{current}/{limit}")
public Msg pagelistbycondition(@PathVariable long current,
@PathVariable long limit,
@RequestBody(required = false) TeacherQuery teacherQuery){
//创建page对象
Page<EduTeacher>pagelist = new Page<>(current,limit);
//创建条件对象
QueryWrapper<EduTeacher>Wrapper=new QueryWrapper<>();
String name = teacherQuery.getName();
Integer level = teacherQuery.getLevel();
String begin = teacherQuery.getBegin();
String end = teacherQuery.getEnd();
//判断条件是否为空,不为空就拼接条件
if (!StringUtils.isEmpty(name)) {
Wrapper.like("name",name);
}
if (!StringUtils.isEmpty(level)){
Wrapper.eq("level",level);
}
if (!StringUtils.isEmpty(begin)) {
Wrapper.ge("gmt_create",begin);
}
if (!StringUtils.isEmpty(end)) {
Wrapper.le("gmt_create",end);
}
//排序
Wrapper.orderByDesc("gmt_create");
//底层封装,把分页数据封装到pagelist对象中
eduTeacherService.page(pagelist, Wrapper);
long total = pagelist.getTotal();//总记录数
List<EduTeacher> records = pagelist.getRecords();//每页数据的list集合
return Msg.ok().data("total",total).data("records",records);
}
//添加讲师接口
@PostMapping("postteacher")
public Msg postteacher(@RequestBody EduTeacher eduTeacher){
boolean save = eduTeacherService.save(eduTeacher);
if (save) {
return Msg.ok();
}else{
return Msg.error();
}
}
@PostMapping("teacher")
public Msg updateteacher(@RequestBody EduTeacher eduTeacher){
boolean b = eduTeacherService.updateById(eduTeacher);
if (b) {
return Msg.ok();
}else {
return Msg.error();
}
}
@DeleteMapping("{id}")
public Msg removebyid(@PathVariable String id){
boolean b = eduTeacherService.removeById(id);
return Msg.ok();
}
前台调用:
在js文件中进行代码的编写:
vue界面进行调用:
这里只做一部分展示
特别注意的是,要使用前后端整合时可能会出现跨域问题
跨域问题:通过一个地址去访问另外一个地址,这个过程中有三个地方不一样都会出现跨域问题。
访问协议,IP地址,端口号
解决方案在controller层中加上@CrossOrigin解决
问题:修改讲师和添加讲师都在同一个页面中显示,当修改完成后,再次添加时,清空表单的代码没有执行,这是因为vue页面中多次跳转到同一个页面中时 created只会执行一次。解决方法,使用vue中的监听方法,当有路由发生变化时,就会执行清空表单的方法。
阿里云oss上传讲师头像:
1创建子子模块service_oss,创建配置文件
到阿里云上,申请管理控制台得到 accessKeyId和accessKeySecret
创建子模块service_oss
创建配置文件:
#服务端口
server.port=8002
#服务名
spring.application.name=service-oss
#环境设置:dev、test、prod
spring.profiles.active=dev
#注册中心nacos
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
#阿里云 OSS
#不同的服务器,地址不同
aliyun.oss.file.endpoint=oss-cn-beijing.aliyuncs.com
aliyun.oss.file.keyid=LTAI4GFU1kvnA8ctTzaWpnka
aliyun.oss.file.keysecret=05Zj6xHrgH32hxNxxjEr9TlfzyQnM8
#bucket可以在控制台创建,也可以使用java代码创建
aliyun.oss.file.bucketname=edu-guli1103
2创建启动类
3创建常量类读取配置文件内容
package com.gt.vod.utils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
//交给spring容器管理
@Component
//实现一个接口用来spring加载后实现其中的一个方法
public class constantPropertiesUtils implements InitializingBean {
//读取配置文件
@Value("${aliyun.oss.file.endpoint}")
private String endpoint;
@Value("${aliyun.oss.file.keyid}")
private String keyid;
@Value("${aliyun.oss.file.keysecret}")
private String keysecret;
@Value("${aliyun.oss.file.bucketname}")
private String bucketname;
//定义公开静态常量
public static String END_POIND;
public static String ACCESS_KEY_ID;
public static String ACCESS_KEY_SECRET;
public static String BUCKET_NAME;
@Override
public void afterPropertiesSet() throws Exception {
END_POIND = endpoint;
ACCESS_KEY_ID= keyid;
ACCESS_KEY_SECRET = keysecret;
BUCKET_NAME = bucketname;
}
}
4编写controller 实现上传文件的代码
在service实现上传文件到oss的过程
controller:
service:
前端进行调用:
新添加模块后,路径中有多个路由时,使用nginx进行请求转发操作
nginx:反向代理服务器。
主要有三个功能:1,请求转发 2,负载均衡 3,动静分离
客户端发起请求,nginx通过得到客户端的请求后会进行路径匹配,数据会到不同的服务器上。
(2)课程分类管理
创建数据库表edu_subject
表的存储关系:一级分类的id是二级分类的parentId
在添加课程分类时,我们通过EasyExcel来读取excel表格来添加课程分类到数据库中
EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
实现过程:
第一步:引入依赖:
<dependencies>
<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
第二步:
创建和Excel对应的实体类
@Data
public class excelData {
@ExcelProperty(index = 0)
private String oneSubjectName;
@ExcelProperty(index = 1)
private String twoSubjectName;
}
编写controller:
//获取上传过来得文件 把文件内容读取出来
@PostMapping(“addSubject”)
public Msg addSubject(MultipartFile file){
eduSubjectService.saveSubject(file,eduSubjectService);
return Msg.ok();
}
获取上传的excel文件 MultipartFile file
业务层在service中实现
@Service
public class EduSubjectServiceImpl extends ServiceImpl<EduSubjectMapper, EduSubject> implements EduSubjectService {
//添加课程分类
@Override
public void saveSubject(MultipartFile file,EduSubjectService eduSubjectService) {
try {
//文件输入流
InputStream inputStream = file.getInputStream();
EasyExcel.read(inputStream,excelData.class,new SubjectExcelListener(eduSubjectService)).sheet().doRead();
} catch (IOException e) {
e.printStackTrace();
}
}
创建读取Excel监听器:
前台是通过vue的表单提交到接口中实现,把课程分类到数据库中
课程列表显示:显示一二级分类
编写controller:
封装两个实体类
一级分类,二级分类,其中一级分类里面包含二级分类
@Data//一级分类
public class OneSubject {
private String id;
private String title;
//一个一级分类有多个二级分类
private List<TwoSubject> children = new ArrayList<>();
}
@Data//二级分类
public class TwoSubject {
private String id;
private String title;
}
service层中
先得到一级分类和二级分类
然后封装一级分类和二级分类到两个list集合中,最后整合到最终的list集合用于返回
前端js代码:
vue界面:vue界面进行调用js中的代码,然后进行遍历
(3)课程管理功能:
课程添加:课程添加的同时,向简介表中添加简介信息
controller层:
service层:
前端页面显示:
js代码
vue页面显示:
讲师下拉列表的显示就是查询出所有的讲师,然后传到前端进行遍历
课程查询:即点击一级分类时显示对应的二级分类
实现过程为:
显示一级分类:还是先调用EduSubjectController中的查询分类的方法
然后进行表单遍历
一二级联动效果 实在vue中的代码进行实现的,先得出所有的一级分类和二级分类的集合,再通过比较点击得一级分类id和遍历出来的一级分类id作比较,相吻合的情况下,就把这个一级分类得children给二级分类的集合,然后遍历出二级分类。
回显修改课程信息:根据课程id查询课程基本信息用于数据的回显。
修改课程信息:
需要注意:修改和添加课程在前端中是同一个页面,此时要进行辨别,如果页面中有课程id就是修改,没有课程id就是添加!
课程列表的显示:
对章节和小节进行查询,和一级分类二级分类一样,首先先封装两个实体类
其中章节里面有小节。
前端遍历之后得到的结果:
删除章节和小节:
先判断有没有小节,有小节 不能删除,没有小节才可以删除
最终发布课程:
根据id查询课程信息,mp不能满足我们的需求,需要使用多表查询
调用mapper
前端页面与前面相似,不再赘述,最终得到结果为:
课程最终发布:
如何判断课程是否发布:根据数据库表中的状态
如果为normal就时发布,为D开头的就是没有发布
发布课程之后查询所有课程列表
删除课程:
这里考虑一般情况,删除课程时把里面的章节,小节,描述信息,课程信息,一起删掉
注意,删除小节时,顺便把小节视频也进行删除
小节上传视频:
上传到阿里云视频点播控制台:返回视频id
一、服务端渲染技术NUXT
1、什么是服务端渲染
服务端渲染又称SSR (Server Side Render)是在服务端完成页面的内容,而不是在客户端通过AJAX获取数据。
服务器端渲染(SSR)的优势主要在于:更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。
如果你的应用程序初始展示 loading 菊花图,然后通过 Ajax 获取内容,抓取工具并不会等待异步完成后再进行页面内容的抓取。也就是说,如果 SEO 对你的站点至关重要,而你的页面又是异步获取内容,则你可能需要服务器端渲染(SSR)解决此问题。
另外,使用服务器端渲染,我们可以获得更快的内容到达时间(time-to-content),无需等待所有的 JavaScript 都完成下载并执行,产生更好的用户体验,对于那些「内容到达时间(time-to-content)与转化率直接相关」的应用程序而言,服务器端渲染(SSR)至关重要。
2、什么是NUXT
Nuxt.js 是一个基于 Vue.js 的轻量级应用框架,可用来创建服务端渲染 (SSR) 应用,也可充当静态站点引擎生成静态站点应用,具有优雅的代码结构分层和热加载等特性
banner管理:分为两部分
1、是对后端banner ,crud管理
2、另一部分是调用banner接口进行显示
1、这里只做一部分展示
2、另一部分是调用banner接口进行显示
降序排列只取前两条记录
发送短信验证码,使用阿里云的短信验证服务:其中短信验证码的时间实现用的是redis 设置的
需要网站签名名称 模板code 手机号
效果图:
用户登录与注册
登录通过手机号和密码与数据库中的信息作比较,登录功能返回的是token字符串
注册,带有发送验证码的,验证码已经在短信模块中被存储到redis中了,先判断redis中有没有验证码,没有就不能注册,然后有的话再把用户信息放入数据库表中
名师列表
后端接口
名师列表前端显示:
在api中创建js文件,定义接口地址,
vue页面中引入js文件,调用方法进行显示
名师详情
后端接口,根据讲师id查询讲师基本信息和所讲课程
在api中创建js文件,定义接口地址,
vue页面中引入js文件,调用方法进行显示
课程列表,条件查询带分页
前端实现,在api中创建js文件,定义接口地址,
vue页面中引入js文件,调用方法进行显示,显示所有的一级分类和二级分类,点击对应的一级分类,显示二级分类
课程详情:
后端接口,根据id进行课程信息查询时要进行联合查询。根据课程id获得章节要进行调用章节接口。
根据视频id进行视频播放。根据播放凭证
前端整合视频播放器进行整合:
视频播放:
===================================分割线=
项目已上传到码云:https://gitee.com/octandnov/onlinestudy.git