学成在线笔记十一:媒资管理系统集成

学习页面查询课程计划

后端开发

修改xc-service-searcj服务中的代码

API定义

    @ApiOperation("根据id查询课程信息")
    Map<String, EsCoursePub> getAll(String id);

EsCourseController

    @Override
    @GetMapping("getall/{id}")
    public Map<String, EsCoursePub> getAll(@PathVariable String id) {
        return esCourseService.getAll(id);
    }

EsCourseService

    /**
     * 查询课程信息
     *
     * @param id 课程id
     * @return Map<String, EsCoursePub>
     */
    public Map<String, EsCoursePub> getAll(String id) {
        Map<String, EsCoursePub> result = new HashMap<>();
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

        // 查询条件
        nativeSearchQueryBuilder.withQuery(QueryBuilders.termQuery("id", id));

        AggregatedPage<EsCoursePub> queryForPage = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), EsCoursePub.class);
        queryForPage.getContent().forEach(coursePub -> result.put(coursePub.getId(), coursePub));

        return result;
    }

测试

前端开发

learning_video.vue

主要修改learning_video.vue文件中的create()

created(){
    //当前请求的url
    this.url = window.location
        //课程id
        this.courseId = this.$route.params.courseId
        //章节id
        this.chapter = this.$route.params.chapter
        //取出课程Id
        systemApi.course_view(this.courseId).then((view_course)=>{
        console.log(view_course)
            if(!view_course || !view_course[this.courseId]){
                this.$message.error("获取课程信息失败,请重新进入此页面!")
                    return ;
            }

        let courseInfo = view_course[this.courseId]
            console.log(courseInfo)
            this.coursename = courseInfo.name
            if(courseInfo.teachplan){
                let teachplan = JSON.parse(courseInfo.teachplan);
                this.teachplanList = teachplan.children;

            }
    })

}

Nginx配置文件

截至目前为止,nginx配置文件如下

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;
 
  
    # cms页面预览
    upstream cms_server_pool{
        server 127.0.0.1:31001 weight=10;
    } 

    # 静态资源服务
    upstream static_server_pool{
        server 127.0.0.1:91 weight=10;
    } 

    # 前端动态门户
    upstream dynamic_portal_server_pool{
        server 127.0.0.1:10000 weight=10;
    }

    # 搜索接口
    upstream search_server_pool{
        server 127.0.0.1:40100 weight=10;
    }


    #媒体服务
    upstream video_server_pool{
        server 127.0.0.1:90 weight=10;
    }   


    server{
    	listen       80;
    	server_name  www.xuecheng.com;
    	ssi on;
    	ssi_silent_errors on;
    	location / {
    		alias   F:/xcEdu/xcEdu_ui/xc-ui-pc-static-portal/;
    		index  index.html;
    	}

        # 静态资源,包括系统所需要的图片,js、css等静态资源
        location /static/img/ {  
            alias   F:/xcEdu/xcEdu_ui/xc-ui-pc-static-portal/img/;
        } 
        location /static/css/ {  
            alias   F:/xcEdu/xcEdu_ui/xc-ui-pc-static-portal/css/;
        } 
        location /static/js/ {  
            alias   F:/xcEdu/xcEdu_ui/xc-ui-pc-static-portal/js/;
        } 
        location /static/plugins/ {  
            alias   F:/xcEdu/xcEdu_ui/xc-ui-pc-static-portal/plugins/;
            add_header Access-Control-Allow-Origin *;  
            add_header Access-Control-Allow-Credentials true;  
            add_header Access-Control-Allow-Methods GET;
        } 
        location /plugins/ {  
            alias   F:/xcEdu/xcEdu_ui/xc-ui-pc-static-portal/plugins/;
            add_header Access-Control-Allow-Origin *;  
            add_header Access-Control-Allow-Credentials true;  
            add_header Access-Control-Allow-Methods GET;
        } 


        # 页面预览
        location /cms/preview/ {
            proxy_pass http://cms_server_pool/cms/preview/;
        }
        location /static/company/ {  
            proxy_pass http://static_server_pool;
        } 
        location /static/teacher/ {  
            proxy_pass http://static_server_pool;
        } 
        location /static/stat/ {  
            proxy_pass http://static_server_pool;
        } 
        location /course/detail/ {   
            proxy_pass http://static_server_pool;
        }

        # 前端门户课程搜索
        location ^~ /course/search {
            proxy_pass http://dynamic_portal_server_pool;
        }
        # 后端搜索服务
        location /openapi/search/ {  
            proxy_pass http://search_server_pool/search/;
        }
        # 分类信息
        location /static/category/ {  
            proxy_pass http://static_server_pool;
        }
        # 开发环境Webpack定时加载文件
        location ^~ /__webpack_hmr {
            proxy_pass http://dynamic_portal_server_pool/__webpack_hmr;
        }
        # 开发环境nuxt访问
        location ^~ /_nuxt/ {
            proxy_pass http://dynamic_portal_server_pool/_nuxt/;
        }
        
    }

    #学成网媒体服务代理
    map $http_origin $origin_list{
        default http://www.xuecheng.com;
        "~http://www.xuecheng.com" http://www.xuecheng.com;
        "~http://ucenter.xuecheng.com" http://ucenter.xuecheng.com;
    }

    #学成网媒体服务代理
    server {
        listen       80;
        server_name video.xuecheng.com;
        
        location /video {  
            proxy_pass http://video_server_pool;  
            add_header Access-Control-Allow-Origin $origin_list;
            #add_header Access-Control-Allow-Origin *;
            add_header Access-Control-Allow-Credentials true;  
            add_header Access-Control-Allow-Methods GET;
        } 
        
    }

    #学成网用户中心
    server {
        listen       80;
        server_name ucenter.xuecheng.com;
        
        #个人中心
        location / {  
            proxy_pass http://ucenter_server_pool;  
        } 

        # 后端搜索服务
        location /openapi/search/ {  
            proxy_pass http://search_server_pool/search/;
        }
    }

    #前端ucenter
    upstream ucenter_server_pool{
      server 127.0.0.1:13000 weight=10;
    }


     
    # 学成网静态资源
    server {
        listen       91;
        server_name localhost;
        
        # 公司信息
        location /static/company/ {  
            alias   F:/xcEdu/xcEdu_ui/static/company/;
        } 
        # 老师信息
        location /static/teacher/ {  
            alias   F:/xcEdu/xcEdu_ui/static/teacher/;
        } 
        # 统计信息
        location /static/stat/ {  
            alias   F:/xcEdu/xcEdu_ui/static/stat/;
        } 
        # 课程静态页
        location /course/detail/ {  
            alias  F:/xcEdu/xcEdu_ui/static/course/detail/;
        } 
     	# 分类信息
     	location /static/category/ {
     		alias  F:/xcEdu/xcEdu_ui/static/category/;
     	}
    }


    #学成网媒体服务
    server {
        listen       90;
        server_name  localhost;
     
        #视频目录
        location /video/ {
            alias   E:/nginx/xcEdu/video/;
        }
    }



    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}

}

测试

课程计划展示成功。

学习页面获取视频播放地址

需求分析

在线学习视频播放流程图如下:

  1. 用户进入在线学习页面,页面请求搜索服务获取课程信息(包括课程计划信息)并且在页面展示。
  2. 在线学习请求学习服务获取视频播放地址。
  3. 学习服务校验当前用户是否有权限学习,如果没有权限学习则提示用户。
  4. 学习服务校验通过,请求搜索服务获取课程媒资信息。
  5. 搜索服务请求ElasticSearch获取课程媒资信息。

保存课程媒资数据

实体类

package com.xuecheng.framework.domain.course;

import lombok.Data;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

@Data
@ToString
@Entity
@Table(name = "teachplan_media_pub")
@GenericGenerator(name = "jpa‐assigned", strategy = "assigned")
public class TeachplanMediaPub implements Serializable {

    private static final long serialVersionUID = -916357110051689485L;

    @Id
    @GeneratedValue(generator = "jpa‐assigned")
    @Column(name = "teachplan_id")
    private String teachplanId;

    @Column(name = "media_id")
    private String mediaId;

    @Column(name = "media_fileoriginalname")
    private String mediaFileOriginalName;

    @Column(name = "media_url")
    private String mediaUrl;

    @Column(name = "courseid")
    private String courseId;

    @Column(name = "timestamp")
    private Date timestamp;//时间戳

}

Dao修改

  • TeachplanMediaPubRepository

    package com.xuecheng.manage_course.dao;
    
    import com.xuecheng.framework.domain.course.TeachplanMediaPub;
    import org.springframework.data.jpa.repository.JpaRepository;
    
    public interface TeachplanMediaPubRepository extends JpaRepository<TeachplanMediaPub, String> {
        //根据课程id删除课程计划媒资信息
        long deleteByCourseId(String courseId);
    }
    
  • TeachplanMediaRepository新增方法

    List<TeachplanMedia> findByCourseId(String courseId);
    

CourseService

新增保存课程媒资方法并在发布课程时调用

    @Autowired
    private TeachplanMediaPubRepository teachplanMediaPubRepository;

	/**
     * 保存指定课程的课程计划媒资信息到索引表中
     *
     * @param id 课程ID
     */
    private void saveTeachplanMediaPub(String id) {
        // 查询课程媒资信息
        List<TeachplanMedia> teachplanMediaList = teachplanMediaRepository.findByCourseId(id);

        // 删除原有数据
        teachplanMediaPubRepository.deleteByCourseId(id);

        // 将课程计划媒资信息存储待索引表
        List<TeachplanMediaPub> teachplanMediaPubList = new ArrayList<>();
        teachplanMediaList.forEach(teachplanMedia -> {
            TeachplanMediaPub teachplanMediaPub = new TeachplanMediaPub();
            BeanUtils.copyProperties(teachplanMedia, teachplanMediaPub);
            teachplanMediaPubList.add(teachplanMediaPub);
        });

        teachplanMediaPubRepository.saveAll(teachplanMediaPubList);
    }

Logstash导入数据到索引库

创建索引

PUT xc_course_media/doc/_mapping
{
  "settings": {
    "number_of_shards": 1, 
    "number_of_replicas": 0
  }
}

创建映射

POST xc_course_media/doc/_mapping
{
	"properties": {
		"courseid": {
			"type": "keyword"
		},
		"teachplan_id": {
			"type": "keyword"
		},
		"media_id": {
			"type": "keyword"
		},
		"media_url": {
			"index": false,
			"type": "text"
		},
		"media_fileoriginalname": {
			"index": false,
			"type": "text"
		}
	}
}

创建模板文件

创建xc_course_media_template.json文件

{
	"mappings": {
		"doc": {
			"properties": {
				"courseid": {
					"type": "keyword"
				},
				"teachplan_id": {
					"type": "keyword"
				},
				"media_id": {
					"type": "keyword"
				},
				"media_url": {
					"index": false,
					"type": "text"
				},
				"media_fileoriginalname": {
					"index": false,
					"type": "text"
				}
			}
		},
		"template": "xc_course_media"
	}
}

Logstash数据导入脚本

与课程发布信息导入类似,只是执行的sql脚本不同和导入的映射不同

input {
  stdin {
  }
  jdbc {
  jdbc_connection_string => "jdbc:mysql://192.168.136.110: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 => "123456"
  # the path to our downloaded jdbc driver
  jdbc_driver_library => "/usr/share/logstash/config/mysql-connector-java-8.0.13.jar"
  # the name of the driver class for mysql
  jdbc_driver_class => "com.mysql.cj.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 => "/usr/share/logstash/config/logstash_metadata"
  }
}

filter{
    json{
        source => "message"
        remove_field => ["message"]
    }
}


output {
  elasticsearch {
  #ES的ip地址和端口
  hosts => "192.168.136.110:9200"
  #hosts => ["localhost:9200","localhost:9202","localhost:9203"]
  #ES索引库名称
  index => "xc_course_media"
  document_id => "%{courseid}"
  document_type => "doc"
  template => "/usr/share/logstash/config/xc_course_media_template.json"
  template_name => "xc_course_media"
  template_overwrite => "true"
  }
  stdout {
 #日志输出
  codec => json_lines
  }
}

查看导入数据

课程媒资接口

xc-service-search中添加相关代码

ES实体类定义

package com.xuecheng.framework.domain.search;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "xc_course_media", type = "doc", shards = 1)
public class EsTeachplanMediaPub {

    @Id
    private String courseid;

    @Field
    private String media_fileoriginalname;
    @Field
    private String media_id;
    @Field
    private String media_url;
    @Field
    private String teachplan_id;

}

appliction.yml

新增配置

elasticsearch:
  es_course_source_field: id,name,grade,mt,st,charge,valid,pic,qq,price,price_old,status,studymodel,teachmode,expires,pub_time,start_time,end_time
  es_course_media_source_field: courseid,media_id,media_url,teachplan_id,media_fileoriginalname

配置类

package com.xuecheng.search.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "elasticsearch")
public class ElasticsearchConfig {

    private String esCourseSourceField;

    private String esCourseMediaSourceField;
}

API定义

    @ApiOperation("根据课程计划查询媒资信息")
    EsTeachplanMediaPub getMedia(String teachplanId);

EsCourseController

    @Override
    @GetMapping("getmedia/{teachplanId}")
    public EsTeachplanMediaPub getMedia(@PathVariable String teachplanId) {
        //将课程计划id放在数组中,为调用service作准备
        String[] teachplanIds = new String[]{teachplanId};
        //通过service查询ES获取课程媒资信息
        List<EsTeachplanMediaPub> esTeachplanMediaPubList = esCourseService.getMedia(teachplanIds);

        return esTeachplanMediaPubList.isEmpty() ? null : esTeachplanMediaPubList.get(0);
    }

EsCourseService

    /**
     * 查询课程媒资信息
     *
     * @param teachplanIds 课程计划ID
     */
    public List<EsTeachplanMediaPub> getMedia(String[] teachplanIds) {
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

        // 结果过滤
        nativeSearchQueryBuilder.withSourceFilter(
                new FetchSourceFilter(elasticsearchConfig.getEsCourseMediaSourceField().split(","), null));

        // 查询条件
        nativeSearchQueryBuilder.withQuery(QueryBuilders.termQuery("teachplan_id", Arrays.stream(teachplanIds).reduce((a, b) -> a + "," +b).get()));

        return elasticsearchTemplate.queryForList(nativeSearchQueryBuilder.build(), EsTeachplanMediaPub.class);
    }

在线学习

微服务项目导入(省略)

OPEN API

开放搜索微服务的API,修改搜索微服务相关代码

引入eureka依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

application.yml配置

新增eureka配置

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 #请求处理的超时时间

启动类

在启动类上添加@EnableDiscoveryClient

编写Api Client

在学习微服务中编写Feign Client

package com.xuecheng.learning.client;

import com.xuecheng.framework.client.XcServiceList;
import com.xuecheng.framework.domain.search.EsTeachplanMediaPub;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(value = XcServiceList.XC_SERVICE_SEARCH)
public interface CourseSearchClient {

    @GetMapping(value = "search/course/getmedia/{teachplanId}")
    EsTeachplanMediaPub getMedia(@PathVariable("teachplanId") String teachplanId);

}

在线学习接口后端

返回结果实体类

package com.xuecheng.framework.domain.learning.response;

import com.xuecheng.framework.model.response.ResponseResult;
import com.xuecheng.framework.model.response.ResultCode;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@ToString
@NoArgsConstructor
public class GetMediaResult extends ResponseResult {

    public GetMediaResult(ResultCode resultCode, String fileUrl) {
        super(resultCode);
        this.fileUrl = fileUrl;
    }

    //媒资文件播放地址
    private String fileUrl;
}

API定义

package com.xuecheng.api.learning;

import com.xuecheng.framework.domain.learning.response.GetMediaResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

@Api(value = "录播课程学习管理", description = "录播课程学习管理")
public interface CourseLearningControllerApi {

    @ApiOperation("获取课程学习地址")
    GetMediaResult getMedia(String courseId, String teachplanId);

}

CourseLearningController

package com.xuecheng.learning.controller;

import com.xuecheng.api.learning.CourseLearningControllerApi;
import com.xuecheng.framework.domain.learning.response.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;

@RestController
@RequestMapping("learning")
public class CourseLearningController implements CourseLearningControllerApi {
    @Autowired
    LearningService learningService;

    @Override
    @GetMapping("getmedia/{courseId}/{teachplanId}")
    public GetMediaResult getMedia(@PathVariable String courseId, @PathVariable String teachplanId) {
        //获取课程学习地址 
        return learningService.getMedia(courseId, teachplanId);
    }
}

CourseLearningService

package com.xuecheng.learning.service;

import com.xuecheng.framework.domain.learning.response.GetMediaResult;
import com.xuecheng.framework.domain.learning.response.LearningCode;
import com.xuecheng.framework.domain.search.EsTeachplanMediaPub;
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;

@Service
public class LearningService {

    @Autowired
    CourseSearchClient courseSearchClient;

    /**
     * 获取课程视频信息
     *
     * @param courseId    课程ID
     * @param teachplanId 课程计划ID
     * @return GetMediaResult
     */
    public GetMediaResult getMedia(String courseId, String teachplanId) {

        // TODO 校验学生的学习权限, 是否资费

        // 调用搜索服务查询
        EsTeachplanMediaPub teachplanMediaPub = courseSearchClient.getMedia(teachplanId);
        if (teachplanMediaPub == null || StringUtils.isEmpty(teachplanMediaPub.getMedia_url())) {
            //获取视频播放地址出错
            ExceptionCast.cast(LearningCode.LEARNING_GETMEDIA_ERROR);
        }
        return new GetMediaResult(CommonCode.SUCCESS, teachplanMediaPub.getMedia_url());
    }
}

在线学习接口前端

learning_video.vue

  • methods中新增方法

    getFirstTeachplan(){ 
            for(var i=0;i<this.teachplanList.length;i++) {
              let firstTeachplan = this.teachplanList[i]; 
              if(firstTeachplan.children && firstTeachplan.children.length>0){ 
                let secondTeachplan = firstTeachplan.children[0]
                return secondTeachplan.id
                } 
              } 
              return ; 
          },  
          //开始学习
          study(chapter){
            // 获取播放地址
            courseApi.get_media(this.courseId,chapter).then((res)=>{
              if(res.success){
                let fileUrl = sysConfig.videoUrl + res.fileUrl
                  //播放视频
                  this.playvideo(fileUrl)
                } else if(res.message) {
                  this.$message.error(res.message)
                } else {
                  this.$message.error("播放视频失败,请刷新页面重试")
                }
              }).catch(res => {
                this.$message.error("播放视频失败,请刷新页面重试")
              });
          }
    
  • 修改created中拿到课程ID后回调逻辑

    if(!this.chapter || this.chapter == '0'){ 
        //取出第一个教学计划 
        this.chapter = this.getFirstTeachplan() 
        console.log(this.chapter)  
    }
    //开始学习 
    this.study(this.chapter)
    

测试

成功点播视频。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值