学习页面查询课程计划
需求分析
到目前位置,我们已经可以编辑课程计划信息并且上传课程视频,下一步我们要是心啊在线学习页面动态获取章节对应的视频并且进行播放,在线学习页面所需要的信息有两类,一类是课程计划信息,一类是课程学习信息(视频地址,学习进度等),如下图
在线学习继承每集管理的需求如下:
1.在学习页面显示课程计划
2.点击课程计划播放该课程计划对应的视频
本章节实现学习页面动态显示课程计划,进入不同课程的学习也买你右侧显示当前课程的课程计划.
API接口
课程计划信息从哪里获取?
目前课程计划信息在课程管理数据库和ES索引库中存在,考虑性能需求,课程发布后对课程查询同意从ES索引库中查询.
前端通过请求搜索服务获取课程信息,需要单独在搜索服务中定义客户菜呢个信息查询接口.
本接口接收课程id,查询课程所有信息,返回给前端.
在search中定义接口api
@ApiOperation("根据课程id查询课程信息")
public Map<String,CoursePub> getall (String id);
服务端开发
启动es,
打开es文件,启动es和eshead
启动es
打开eslasticsearch.bat
启动eshead
npm run start
Service
// 使用ES客户端向ES请求查询索引信息
public Map<String, CoursePub> getAll(String id) {
// 定义一个搜索的请求对象 xc_andrew
SearchRequest searchRequest = new SearchRequest(index);
// 指定type doc
searchRequest.types(type);
// 定义一个searchSourceBuilder
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 使用termQuery
searchSourceBuilder.query(QueryBuilders.termQuery("id", id));
// 过滤原字段,去除所有字段,不用设置过滤原字段
searchRequest.source(searchSourceBuilder);
Map<String, CoursePub> map = new HashMap<>();
try {
SearchResponse search = restHighLevelClient.search(searchRequest);
SearchHits hits = search.getHits();
SearchHit[] hits1 = hits.getHits();
for (SearchHit hit : hits1) {
// 最终要返回的课程信息
CoursePub coursePub = new CoursePub();
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
// 课程id
String courseId = (String) sourceAsMap.get("id");
String name = (String) sourceAsMap.get("name");
String grade = (String) sourceAsMap.get("grade");
String charge = (String) sourceAsMap.get("charge");
String pic = (String) sourceAsMap.get("pic");
String description = (String) sourceAsMap.get("description");
String teachplan = (String) sourceAsMap.get("teachplan");
coursePub.setId(courseId);
coursePub.setGrade(grade);
coursePub.setCharge(charge);
coursePub.setPic(pic);
coursePub.setDescription(description);
coursePub.setTeachplan(teachplan);
map.put(courseId, coursePub);
}
} catch (IOException e) {
e.printStackTrace();
}
return map;
}
Controller
@Override
@GetMapping("/getall/{id}")
public Map<String, CoursePub> getall(String id) {
return esCourseService.getAll(id);
}
测试
前端开发
配置虚拟主机
学习中心的二级域名为ucenter.xuecheng.com,我们在nginx中配置ucenter虚拟主机.
对应的方法,返回一个map<String,CoursePub>然后返回给前端调用
这里配置完nginx后需要重新启动
判断nginx是否启动,如果没有,那么直接启动,如果已经启动,那么
进行重启
nginx -s reload
API方法
API调试
测试
学习页面获取视频播放地址
需求分析
用户进入在线学习页面,点击课程计划,将播放该课程计划对应的教学视频.
业务流程如下:
业务流程说明:
1.用户进入在线学习页面,页面请求搜索获取课程信息(包括课程计划信息)并且在页面展示.
2.在线学习请求学习服务获取视频播放地址.
3.学习服务校验当前用户是否有全新啊学习,如果没有全新啊学习,则提示用户.
4.学习服务校验通过,请求搜索服务获取课程媒资信息.
2.搜索服务请求ElasticSearch获取课程媒资信息.
为甚要请求ElasticSearch查询课程媒资信息?
处于性能的考虑,公开查询课程信息从搜索服务查询.
什么时候将课程媒资信息储存到ElasticSearch中?
课程媒资信息是在课程发布的时候存入ElasticSearch,因为课程发布后课程信息将基本不再修改…
课程发布存储媒资信息
需求分析
课程媒资信息实在课程发布的时候存入ElasticSearch索引库,因为课程发布后课程信息将基本不在修改,具体的业务流程如下.
业务流程如下:
1.课程发布,像课程媒资i西南西表写入数据.
2.根据课程id删除teachplanMediaPub中的数据
3. 根据课程id查询teachplanMedia数据
4.将查询到的teachplanMedia数据插入到teachplanMediaPub中
5.Logstash定时扫描课程妹子信息表,将课程媒资信息写入索引库.
数据模型
Dao
package com.xuecheng.manage_course.dao;
import com.xuecheng.framework.domain.course.TeachplanMedia;
import com.xuecheng.framework.domain.course.TeachplanMediaPub;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
/**
* Created by Administrator.
*/
public interface TeachplanMediaPubRepository extends JpaRepository<TeachplanMediaPub,String> {
// 根据课程id删除记录
long deleteByCourseId(String courseId);
}
package com.xuecheng.manage_course.dao;
import com.xuecheng.framework.domain.course.CourseBase;
import com.xuecheng.framework.domain.course.TeachplanMedia;
import org.apache.ibatis.annotations.Param;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
/**
* Created by Administrator.
*/
public interface TeachplanMediaRepository extends JpaRepository<TeachplanMedia,String> {
// 根据课程id来查询列表
List<TeachplanMedia> findByCourseId( String courseId);
}
Service
//得到页面的url
String pageUrl = cmsPostPageResult.getPageUrl();
// 向teachplanMediaPub中保存课程的信息
saveTeachplanMediaPub(id);
return new CoursePublishResult(CommonCode.SUCCESS, pageUrl);
}
// 向teachplanMediaPub中保存课程的信息
private void saveTeachplanMediaPub(String courseId){
// 先删除teachplanMediaPub中的数据
teachplanMediaPubRepository.deleteByCourseId(courseId);
// 从teachplanMedia中查询
List<TeachplanMedia> teachplanMediaList = teachplanMediaRepository.findByCourseId(courseId);
// 将teachpanMediaList插入到teachplanMediaPub表中
List<TeachplanMediaPub> teachplanMediaPubs = new ArrayList<>();
// 将teachplanMediaList中的数据放到TeachplanMediaPubs中
for (TeachplanMediaPub teachplanMediaPub : teachplanMediaPubs) {
// 类型不对,看看如何将一个不同类型的对象融入到另一个对象中
TeachplanMediaPub teachplanMediaPub1 = new TeachplanMediaPub();
// 将0中的数据拷贝到1中
BeanUtils.copyProperties(teachplanMediaPub,teachplanMediaPub1);
// 最后在pub中添加一个时间戳
teachplanMediaPub1.setTimestamp(new Date());
teachplanMediaPubs. add(teachplanMediaPub1);
}
// 将课程媒资信息插入到Pub中
teachplanMediaPubRepository.saveAll(teachplanMediaPubs);
}
测试
测试课程发布后是否成功将课程媒资信息存储到teachplan_media_pub中,测试流程如下:
1.指点一个课程
2.为课程计划添加课程媒资
3.执行课程发布
4.观察课程计划媒资信息是否存储值teachplan_meida_pub中
注意:由于此测试仅用于测试发布课程计划媒资信息的功能,可暂时将cms页面发布功能暂时屏蔽,提高测试效率.
Logstash扫描课程计划媒资
Logstash定时扫描课程媒资信息表,并将课程媒资信息写入索引库.
创建索引
1.创建xc_course_media索引
点击此处的新建,创建media索引库
2.并向此索引创建如下映射
Post http://localhost:9200/xc_course_media/doc/_mapping
//启动es,打开es文件点击bin目录下的elasticsearch.bat文件启动
//打开head文件的cmd,使用npm run start 启动
打开postman 发送
http://localhost:9200/xc_andrew_media/doc/_mapping
{
"properties":{
"courseid":{
"type": "keyword"
},
"media_id":{
"type":"keyword"
},
"media_url":{
"index":false,
"type":"text"
},
"media_fileoriginalname":{
"index":false,
"type":"text"
}
}
}
创建成功:
{
"acknowledged": true
}
创建Logstash模板文件
启动logstash
在logstach的config目录创建xc_course_media_template.json,内容格式如下:
本教程的xc_course_media_template.json目录是:
配置mysql.conf
input {
stdin {
}
jdbc {
jdbc_connection_string => "jdbc:mysql://localhost:3306/xc_course?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC"
# the user we wish to excute our statement as
jdbc_user => "root"
jdbc_password => root
# the path to our downloaded jdbc driver
jdbc_driver_library => "D:/BaiduNetdiskDownload/maven_repository/mysql/mysql-connector-java/5.1.47/mysql-connector-java-5.1.47.jar"
# the name of the driver class for mysql
jdbc_driver_class => "com.mysql.jdbc.Driver"
jdbc_paging_enabled => "true"
jdbc_page_size => "50000"
#要执行的sql文件
#statement_filepath => "/conf/course.sql"
statement => "select * from teachplan_media_pub where timestamp > date_add(:sql_last_value,INTERVAL 8 HOUR)"
#定时配置
schedule => "* * * * *"
record_last_run => true
last_run_metadata_path => "D:/BaiduNetdiskDownload/es/logstash-6.2.1/config/logstash_metadata"
}
}
output {
elasticsearch {
#ES的ip地址和端口
hosts => "localhost:9200"
#hosts => ["localhost:9200","localhost:9202","localhost:9203"]
#ES索引库名称
index => "xc_andrew_media"
document_id => "%{teachplan_id}"
document_type => "doc"
template =>"D:/BaiduNetdiskDownload/es/logstash-6.2.1/config/xc_course_media_template.json"
template_name =>"xc_course_media"
template_overwrite =>"true"
}
stdout {
#日志输出
codec => json_lines
}
}
启动mysql.conf
启动logstash.bat
配置完成后,进入bin目录
logstash.bat -f ../config/mysql_course_media.conf
启动
自动导入conf中配置的具体mysql库中的值
搜索服务查询课程媒资接口
需求分析
搜索服务提供查询课程媒资接口,此接口供学习服务调用.
API
在课程搜索包下定义Api
//
@ApiOperation("根据课程计划id查询课程媒资信息")
public TeachplanMediaPub getmedia (String id);
Service
//通过多个课程计划查询课程媒资信息
public QueryResponseResult<TeachplanMediaPub> getmeida(String[] teachplanIds) {
//定义一个搜索请求对象
SearchRequest searchRequest = new SearchRequest(media_index);
// 指定Type
searchRequest.types(media_type);
// 定义SearchSourceBuilder
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 过滤字段
String[] mediaSourceField = media_source_field.split(",");
// 前一个参数表示包括那些字段,后一个表示排除 那些字段
searchSourceBuilder.fetchSource(mediaSourceField,new String[]{});
// 设置使用TermQuery,根据多个id来查询
searchSourceBuilder.query(QueryBuilders.termsQuery("teachplan_id",teachplanIds));
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = null;
// 使用es的客户端搜索请求es
long total = 0;
List<TeachplanMediaPub> teachplanMediaPubList = new ArrayList<>();
try {
searchResponse = restHighLevelClient.search(searchRequest);
// 得到我们要的匹配到的结果
SearchHits hits = searchResponse.getHits();
total = hits.totalHits;
// 从当前对象中得到匹配的记录
SearchHit[] searchHits = hits.getHits();
for (SearchHit searchHit : searchHits) {
// 得到记录的原文档
TeachplanMediaPub teachplanMediaPub = new TeachplanMediaPub() ;
Map<String, Object> sourceAsMap = searchHit.getSourceAsMap();
// 取出课程计划的媒资信息
String codurseid = (String) sourceAsMap.get("courseid");
String media_id = (String) sourceAsMap.get("media_id");
String media_url = (String) sourceAsMap.get("media_url");
String teachplan_id = (String) sourceAsMap.get("teachplan_id");
String media_fileoriginalname = (String) sourceAsMap.get("media_fileoriginalname");
teachplanMediaPub.setCourseId(codurseid);
teachplanMediaPub.setMediaId(media_id);
teachplanMediaPub.setMediaUrl(media_url);
teachplanMediaPub.setTeachplanId(teachplan_id);
teachplanMediaPub.setMediaFileOriginalName(media_fileoriginalname);
teachplanMediaPubList.add(teachplanMediaPub);
}
} catch (IOException e) {
e.printStackTrace();
}
// 在定义之前,有一个数据集和
QueryResult<TeachplanMediaPub> queryResult = new QueryResult<>();
queryResult.setList(teachplanMediaPubList);
queryResult.setTotal(total);
QueryResponseResult<TeachplanMediaPub> queryResponseResult = new QueryResponseResult<>(CommonCode.SUCCESS_SEARCH_MEDIA_PUBLIST,queryResult);
return queryResponseResult;
}
Controller
@Override
@GetMapping("/getmedia/{id}")
public TeachplanMediaPub getmedia(@PathVariable("id") String teachplanid) {
String[] teachplanIds = new String[]{teachplanid};
QueryResponseResult<TeachplanMediaPub> queryResponseResult = esCourseService.getmeida(teachplanIds);
QueryResult<TeachplanMediaPub> queryResult = queryResponseResult.getQueryResult();
if (queryResult!=null){
List<TeachplanMediaPub> list = queryResult.getList();
int size = list.size();
if (list != null&& size>0){
// 根据一个id只是取出一条记录
return list.get(0);
}
}
return new TeachplanMediaPub();
}
测试
在线学习接口
需求分析
根据下边的业务流程,本章节完成前端需恶习页面请求学习服务获取视频课程的地址,并且自动播放视频.
*
搭建开发环境
创建xc_learning数据库,学习数据库将记录学生的选课信息,学习信息.
导入:资料/xc_learning.sql
创建数据库
/*
SQLyog v10.2
MySQL - 5.7.21-log : Database - xc_learning
*********************************************************************
*/
/*!40101 SET NAMES utf8 */;
/*!40101 SET SQL_MODE=''*/;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`xc_learning` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `xc_learning`;
/*Table structure for table `xc_learning_course` */
DROP TABLE IF EXISTS `xc_learning_course`;
CREATE TABLE `xc_learning_course` (
`id` varchar(32) NOT NULL,
`course_id` varchar(32) NOT NULL COMMENT '课程id',
`user_id` varchar(32) NOT NULL COMMENT '用户id',
`valid` varchar(32) DEFAULT NULL COMMENT '有效性',
`start_time` datetime DEFAULT NULL,
`end_time` datetime DEFAULT NULL,
`status` varchar(32) DEFAULT NULL COMMENT '选课状态',
PRIMARY KEY (`id`),
UNIQUE KEY `xc_learning_list_unique` (`course_id`,`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Data for the table `xc_learning_course` */
insert into `xc_learning_course`(`id`,`course_id`,`user_id`,`valid`,`start_time`,`end_time`,`status`) values ('402885816243d2dd016243f24c030002','402885816243d2dd016243f24c030002','49',NULL,NULL,NULL,'501001'),('8a7e82b564b5e53f0164b5ee61e50002','4028e581617f945f01617f9dabc40000','49',NULL,NULL,NULL,'501001'),('8a7e82b564b5e53f0164b5ee6b780003','4028e581617f945f01617f9dabc40001','49',NULL,NULL,NULL,'501001');
/*Table structure for table `xc_task_his` */
DROP TABLE IF EXISTS `xc_task_his`;
CREATE TABLE `xc_task_his` (
`id` varchar(32) NOT NULL COMMENT '任务id',
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
`delete_time` datetime DEFAULT NULL,
`task_type` varchar(32) DEFAULT NULL COMMENT '任务类型',
`mq_exchange` varchar(64) DEFAULT NULL COMMENT '交换机名称',
`mq_routingkey` varchar(64) DEFAULT NULL COMMENT 'routingkey',
`request_body` varchar(512) DEFAULT NULL COMMENT '任务请求的内容',
`version` int(10) DEFAULT '0' COMMENT '乐观锁版本号',
`status` varchar(32) DEFAULT NULL COMMENT '任务状态',
`errormsg` varchar(512) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Data for the table `xc_task_his` */
insert into `xc_task_his`(`id`,`create_time`,`update_time`,`delete_time`,`task_type`,`mq_exchange`,`mq_routingkey`,`request_body`,`version`,`status`,`errormsg`) values ('10','2018-04-04 22:58:20','2018-07-13 22:58:54','2018-07-16 12:24:36',NULL,'ex_learning_addchoosecourse','addchoosecourse','{\"userId\":\"49\",\"courseId\":\"4028e581617f945f01617f9dabc40000\"}',NULL,'10201',NULL),('11','2018-07-16 12:28:03','2018-07-15 12:28:04','2018-07-16 12:29:11',NULL,'ex_learning_addchoosecourse','addchoosecourse','{\"userId\":\"49\",\"courseId\":\"4028e581617f945f01617f9dabc40001\"}',NULL,NULL,NULL);
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
创建学习服务工程
application
server:
port: ${PORT:40600}
spring:
application:
name: xc-service-learning
datasource:
druid:
url: ${MYSQL_URL:jdbc:mysql://localhost:3306/xc_learning?characterEncoding=utf-8}
username: root
password: root
driverClassName: com.mysql.jdbc.Driver
initialSize: 5 #初始建立连接数量
minIdle: 5 #最小连接数量
maxActive: 20 #最大连接数量
maxWait: 10000 #获取连接最大等待时间,毫秒
testOnBorrow: true #申请连接时检测连接是否有效
testOnReturn: false #归还连接时检测连接是否有效
timeBetweenEvictionRunsMillis: 60000 #配置间隔检测连接是否有效的时间(单位是毫秒)
minEvictableIdleTimeMillis: 300000 #连接在连接池的最小生存时间(毫秒)
#rabbitmq配置
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
publisher-confirms: true
virtual-host: /
eureka:
client:
registerWithEureka: true #服务注册开关
fetchRegistry: true #服务发现开关
serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址,多个中间用逗号分隔
defaultZone: ${EUREKA_SERVER:http://localhost:50101/eureka/,http://localhost:50102/eureka/}
instance:
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
ip-address: ${IP_ADDRESS:127.0.0.1}
instance-id: ${spring.application.name}:${server.port} #指定实例id
ribbon:
MaxAutoRetries: 2 #最大重试次数,当Eureka中可以找到服务,但是服务连不上时将会重试,如果eureka中找不到服务则直接走断路器
MaxAutoRetriesNextServer: 3 #切换实例的重试次数
OkToRetryOnAllOperations: false #对所有操作请求都进行重试,如果是get则可以,如果是post,put等操作没有实现幂等的情况下是很危险的,所以设置为false
ConnectTimeout: 5000 #请求连接的超时时间
ReadTimeout: 6000 #请求处理的超时时间
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>xc-framework-parent</artifactId>
<groupId>com.xuecheng</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath>../xc-framework-parent/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>xc-service-learning</artifactId>
<dependencies>
<!-- 导入Eureka客户端的依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.xuecheng</groupId>
<artifactId>xc-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.xuecheng</groupId>
<artifactId>xc-framework-model</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.xuecheng</groupId>
<artifactId>xc-framework-utils</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.xuecheng</groupId>
<artifactId>xc-framework-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
</project>
API接口
package com.xuecheng.api.learning;
import com.xuecheng.framework.domain.learning.respones.GetMediaResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
/**
* @author Andrewer
* @version 1.0
* @project xcEduService01
* @description
* @date 2023/1/12 14:59:06
*/
@Api(value = "录播课程学习管理",description = "录播课程学习管理")
public interface CourseLearningControllerApi {
@ApiOperation("获取课程学习地址")
// 第一个参数校验学习的资格,第二个参数从es获取媒资地址信息
public GetMediaResult getMedia(String courseId,String teachPlanId);
}
package com.xuecheng.framework.domain.learning.respones;
import com.xuecheng.framework.model.response.ResponseResult;
import com.xuecheng.framework.model.response.ResultCode;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* @author Andrewer
* @version 1.0
* @project xcEduService01
* @description
* @date 2023/1/12 15:02:07
*/
@Data
@ToString
//远程调用的时候需要加一个无参的构造方法
@NoArgsConstructor
public class GetMediaResult extends ResponseResult {
// 视频播放地址
String fileUrl;
public GetMediaResult(ResultCode resultCode,String fileUrl){
super(resultCode);
this.fileUrl = fileUrl ;
}
}
服务端开发
需求分析
学习服务根据传入课程ID,章节id(课程计划ID)请求搜索服务,获取学习地址.
搜索服务注册Eureka
学习服务要嗲用搜索服务串课程媒资信息,所以需要将搜索服务注册到eureka中
1.查看服务名称是否为xc-service-search
2.配置搜索服务的配置文件applicaiton.yml,加入Eureka,配置如下:
server:
port: ${port:40100}
spring:
application:
name: xc-service-search
xuecheng:
elasticsearch:
hostlist: ${eshostlist:127.0.0.1:9200} # 多个节点中间用','分割
course:
index: xc_andrew
type: doc
source_field: id,name,grade,mt,st,charge,valid,pic,qq,price,price_old,status,studymodel,teachmode,expires,pub_time,start_time,end_time
media:
index: xc_andrew_media
type: doc
source_field: courseid,media_id,media_url,teachplan_id,media_fileoriginalname
eureka:
client:
registerWithEureka: true #服务注册开关
fetchRegistry: true #服务发现开关
serviceUrl: #Eureka客户端 与Eureka服务端进行交互的地址,多个中间用逗号分割
defaultZone: ${EUREKA_SERVER:http://localhost:50101/eureka/,http://localhost:50102/eureka/}
instance:
prefer-ip-address: true #将自己的ip注册到Eureka里面
ip-address: ${IP_ADDRESS:127.0.0.1}
instance-id: ${spring.application.name}:${server.port} #指定实例ip
ribbon:
MaxAutoRetries: 2 #最大重试的此时,当Eureka中可以找到服务,但是服务连接不上的时候会重试,如果eureka中找不到服务,则直接走断路器
MaxAutoRetriesNextserver: 3 # 切换实例的重试此时
OkToRetryOnAllOperations: false #对所有操作请求都进行重试,如歌是get则可以,如果是post,put等操作没有实现幂等的情况下是很危险的,所以设置false
ConnectTimeOut: 5000 #请求连接的超时时间
ReadTimeOut: 6000 # 请求处理的超时时间
package com.xuecheng.search;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.ComponentScan;
/**
* @author Administrator
* @version 1.0
**/
//使Eureka发现服务的地址
@EnableDiscoveryClient
//如果使用搜索服务,需要加FeignClient,如果搜索服务不需要对外远程调用其他的服务,则不需要加
//@EnableFeignClients
@SpringBootApplication
@EntityScan("com.xuecheng.framework.domain.search")//扫描实体类
@ComponentScan(basePackages={"com.xuecheng.api"})//扫描接口
@ComponentScan(basePackages={"com.xuecheng.search"})//扫描本项目下的所有类
@ComponentScan(basePackages={"com.xuecheng.framework"})//扫描common下的所有类
public class SearchApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(SearchApplication.class, args);
}
}
搜索服务客服端
Service
package com.xuecheng.learning.service;
import com.xuecheng.framework.domain.course.TeachplanMediaPub;
import com.xuecheng.framework.domain.learning.respones.GetMediaResult;
import com.xuecheng.framework.domain.learning.respones.LearningCode;
import com.xuecheng.framework.exception.ExceptionCast;
import com.xuecheng.framework.model.response.CommonCode;
import com.xuecheng.learning.client.CourseSearchClient;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author Andrewer
* @version 1.0
* @project xcEduService01
* @description
* @date 2023/1/12 16:05:07
*/
@Service
public class LearningService {
@Autowired
CourseSearchClient searchClient;
//获取课程的学习地址(视频的播放地址)
public GetMediaResult getMedia(String courseId, String teachPlanId) {
// 校验学生学习的权限
// 远程调用搜索服务查询课程计划所对应的媒资信息
// 就像调用本地方法一样进行远程调用
TeachplanMediaPub teachplanMediaPub = searchClient.getMedia(teachPlanId);
// 这里需要进行判断一下
if (teachplanMediaPub==null|| StringUtils.isEmpty(teachplanMediaPub.getMediaUrl())){
// 获取学习地址错误
ExceptionCast.cast(LearningCode.LEARNING_GETMEDIA_ERROR);
}
return new GetMediaResult(CommonCode.SUCCESS,teachplanMediaPub.getMediaUrl());
}
}
Controller
package com.xuecheng.learning.controller;
import com.netflix.discovery.converters.Auto;
import com.xuecheng.api.learning.CourseLearningControllerApi;
import com.xuecheng.framework.domain.learning.respones.GetMediaResult;
import com.xuecheng.learning.service.LearningService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Andrewer
* @version 1.0
* @project xcEduService01
* @description
* @date 2023/1/12 16:00:16
*/
@RestController
@RequestMapping("/learning/course")
public class CourseLearningController implements CourseLearningControllerApi {
@Autowired
LearningService learningService;
@Override
@GetMapping("/getmeida/{courseId}/{teachplanId}")
public GetMediaResult getMedia(@PathVariable("courseId") String courseId,
@PathVariable("teachplanId") String teachPlanId) {
return learningService.getMedia(courseId,teachPlanId);
}
}
测试
前端开发
需求分析
需要在学习中心前端页面需要完成如下功能:
1.进入课程学习页面,需要带上课程Id参数集课程计划Id的参数,其中课程Id参数笔袋,课程计划Id可以为空.
2.进入页面更具课程Id取出该课程的课程计划显示在右侧.
4.进入页面后判断如果请求参数中有课程计划Id的则播放该章节的视频
4.进入页面后判断你如果是课程计划id为0,则需要取出本课程的第一个课程计划的Id,并播放第一个课程计划的视频.
Api方法
配置代理
#学习服务
upstreamlearning_server_pool{server127.0.0.1:40600weight=10;
}
#学习服务
location^~/api/learning/{
proxy_passhttp://learning_server_pool/learning/;}
视频播放页面
1、如果传入的课程计划id为0则取出第一个课程计划id在created钩子方法中完成
created(){
//当前请求的url
this.url=window.location//课程id
this.courseId=this.$route.params.courseId//课程计划id
this.chapter=this.$route.params.chapter//查询课程信息
systemApi.course_view(this.courseId).then((view_course)=>{
if(!view_course||!view_course[this.courseId]){this.$message.error("获取课程信息失败,请重新进入此页面!")return;
}
letcourseInfo=view_course[this.courseId]console.log(courseInfo)
this.coursename=courseInfo.nameif(courseInfo.teachplan){
//将从服务端获取的课程计划json转成对象letteachplan=JSON.parse(courseInfo.teachplan);
//将课程计划赋值给数据模型this.teachplanList=teachplan.children;console.log(this.teachplanList)
if(!this.chapter||this.chapter=='0'){//取出第一个教学计划
this.chapter=this.getFirstTeachplan()console.log(this.chapter)
//开始学习this.study(this.chapter)
}}
})},
取出第一个章节id:
//取出第一个章节getFirstTeachplan(){
for(vari=0;i<this.teachplanList.length;i++){letfirstTeachplan=this.teachplanList[i];
if(firstTeachplan.children&&firstTeachplan.children.length>0){letsecondTeachplan=firstTeachplan.children[0];
returnsecondTeachplan.id;}
}return;
},
开始学习:
//开始学习study(chapter){
//获取播放地址
courseApi.get_media(this.courseId,chapter).then((res)=>{if(res.success){
letfileUrl=sysConfig.videoUrl+res.fileUrl
//播放视频this.playvideo(fileUrl)
}elseif(res.message){this.$message.error(res.message)
}else{this.$message.error("播放视频失败,请刷新页面重试")
}
}).catch(res=>{this.$message.error("播放视频失败,请刷新页面重试")
});},
2、点击右侧课程章节切换播放
在原有代码基础上添加click事件,点击调用开始学习方法(study)。
<liv‐if="teachplan_first.children!=null"v‐for="(teachplan_second,index)inteachplan_first.children"><iclass="glyphiconglyphicon‐check"></i>
<a:href="url"@click="study(teachplan_second.id)">{{teachplan_second.pname}}
</a></li>
测试
访问在线学习页面:http://ucenter.xuecheng.com/#/learning/课程id/课程计划id
通过url传入两个参数:课程id和课程计划id如果没有课程计划则传入0
测试项目如下:1、传入正确的课程id、课程计划id,自动播放本章节的视频
2、传入正确的课程id、课程计划id传入0,自动播放第一个视频
3、传入错误的课程id或课程计划id,提示错误信息。
4、通过右侧章节目录切换章节及播放视频。