新用户注册入口 老用户登录入口

[转载]微服务[学成在线] day15:媒资管理系统集成

文章作者:转载 更新时间:2023-12-16 12:41:01 阅读数量:72
文章标签:在线学习课程计划媒资信息查询接口搜索服务m3u8地址
本文摘要:该文章详述了【学成在线】项目中查询课程计划和获取视频播放地址的关键技术实现。首先,针对在线学习场景,通过搜索服务提供的查询接口,从前端请求并从Elasticsearch索引库获取课程计划信息。在课程发布阶段,将媒资信息(包括m3u8地址)存储至teachplan_media_publish表,并使用Logstash采集至Elasticsearch供后续检索。此外,文章还介绍了在线学习接口开发过程中如何搭建环境、设计API以及前端调用接口实现视频播放功能的具体步骤,其中涉及NginX配置以支持前后端服务间的通信与数据交换。
转载文章

本篇文章为转载内容。原文链接:https://blog.csdn.net/codeyee/article/details/107558901。

该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。

作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。

如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。

😎 知识点概览

为了方便后续回顾该项目时能够清晰的知道本章节讲了哪些内容,并且能够从该章节的笔记中得到一些帮助,所以在完成本章节的学习后在此对本章节所涉及到的知识点进行总结概述。

本章节为【学成在线】项目的 day15 的内容

  • 根据 课程ID 搜索该课程已发布的课程信息,并返回该课程的所有课程计划信息。
  • 将指定课程 发布时 所的课程计划的媒资信息保存到 teachplan_media_publish 表中,
  • 根据 课程计划id 搜索该课程计划所对应的媒资信息,需要用到的是该课程计划对应的 m3u8 地址,用于在线播放视频,该接口在课程管理服务中开发,供学习服务进行远程调用。
  • 在学习服务中远程调用 课程计划媒资信息查询接口,获取该课程计划的视频播放的 m3u8 url地址,并返回给前端,前端使用该 url 进行视频的在线播放。
  • 在线学习完整的测试流程:媒资信息的上传、选择、发布到前端门户、搜索门户测试,在线学习的播放视频。

目录

内容会比较多,小伙伴门可以根据目录进行按需查阅。

文章目录

  • 😎 知识点概览
  • 目录
  • 一、学习页面:查询课程计划
    • 0x01 需求分析
    • 0x02 Api接口
    • 0x03 服务端开发
      • Controller
      • Service
      • 测试
    • 0x04 前端开发
      • 配置NGINX虚拟主机
      • 前端 API 方法
      • 前端 API 方法调用
      • 测试
  • 二、学习页面:获取视频播放地址
    • 0x01 需求分析
    • 0x02 课程发布:储存媒资信息
      • 需求分析
      • 数据模型
      • Dao
      • Service
      • 测试
    • 0x03 Logstash:扫描课程计划媒资
      • 创建索引
      • 创建模板文件
      • 配置 mysql.conf
      • 启动 logstash.bat
      • Logstash多实例运行
    • 0x04 搜素服务:查询课程媒资接口
      • 需求分析
      • Api接口定义
      • Service
      • Controller
      • 测试
  • 三、在线学习:接口开发
    • 0x01 需求分析
    • 0x02 搭建开发环境
    • 0x03 Api接口
    • 0x04 服务端开发
      • 需求分析
      • 搜索服务注册Eureka
      • 搜索服务客户端
      • 自定义错误代码
      • Service
      • Controller
      • 测试
    • 0x05 前端开发
      • 需求分析
      • api方法
      • 配置代理
      • 视频播放页面
      • 简单的测试
      • 完整的测试
        • 1、上传文件
          • 一些问题
          • ~~方案1:删除本地分块文件重新尝试上传~~
          • 方案2:检查前端提交的MD5值是否正确
        • 2、为课程计划选择媒资信息
        • 3、前端门户测试
  • 四、待完善的一些功能
  • 😁 认识作者

一、学习页面:查询课程计划

0x01 需求分析

到目前为止,我们已可以编辑课程计划信息并上传课程视频,下一步我们要实现在线学习页面动态读取章节对应的视频并进行播放。在线学习页面所需要的信息有两类:

  • 课程计划信息
  • 课程学习信息(视频地址、学习进度等)

如下图:

在线学习集成媒资管理的需求如下:

1、在线学习页面显示课程计划

2、点击课程计划播放该课程计划对应的视频

本章节实现学习页面动态显示课程计划,进入不同课程的学习页面右侧动态显示当前课程的课程计划。

0x02 Api接口

课程计划信息从哪里获取?

在课程发布完成后会自动发布到一个 course_pub 的表中,logstash 会自动将课程发布后的信息自动采集到 ES 索引库中,这些信息也包含课程计划信息。

所以考虑性能要求,课程发布后对课程的查询统一从 ES 索引库中查询。

前端通过请求 搜索服务 获取课程信息,需要单独在 搜索服务 中定义课程信息查询接口。
本接口接收课程id,查询课程所有信息返回给前端。

我们在搜素服务 API 下添加以下方法

@ApiOperation("根据id搜索课程发布信息")
public Map<String,CoursePub> getdetail(String id);

返回的课程信息为 json 结构:key 为课程id,value 为课程内容。

0x03 服务端开发

在搜索服务中开发查询课程信息接口。

Controller

在搜素服务下添加以下方法

/*** 根据id搜索课程发布信息* @param id 课程id* @return JSON数据*/
@Override
@GetMapping("/getdetail/{id}")
public Map<String, CoursePub> getdetail(@PathVariable("id")String id) {return esCourseService.getdetail(id);
}

Service

/*** 根据id搜索课程发布信息* @param id 课程id* @return JSON数据*/
public Map<String, CoursePub> getdetail(String id) {//设置索引SearchRequest searchRequest = new SearchRequest(es_index);//设置类型searchRequest.types(es_type);//创建搜索源对象SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();//设置查询条件,根据id进行查询searchSourceBuilder.query(QueryBuilders.termQuery("id",id));//这里不使用source的原字段过滤,查询所有字段// searchSourceBuilder.fetchSource(new String[]{"name", "grade", "charge","pic"}, newString[]{});//设置搜索源对象searchRequest.source(searchSourceBuilder);//执行搜索SearchResponse searchResponse = null;try {searchResponse = restHighLevelClient.search(searchRequest);} catch (IOException e) {e.printStackTrace();}//获取搜索结果SearchHits hits = searchResponse.getHits();SearchHit[] searchHits = hits.getHits(); //获取最优结果Map<String,CoursePub> map = new HashMap<>();for (SearchHit hit: searchHits) {//从搜索结果中取值并添加到coursePub对象Map<String, Object> sourceAsMap = hit.getSourceAsMap();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 coursePub = new CoursePub();coursePub.setId(courseId);coursePub.setName(name);coursePub.setPic(pic);coursePub.setGrade(grade);coursePub.setTeachplan(teachplan);coursePub.setDescription(description);//设置map对象map.put(courseId,coursePub);}return map;
}

测试

使用 swagger-uipostman 测试查询课程信息接口。

0x04 前端开发

配置NGINX虚拟主机

学习中心的二级域名为 ucenter.xuecheng.com ,我们在 nginx 中配置 ucenter 虚拟主机。

#学成网用户中心
server {listen 80;server_name ucenter.xuecheng.com;#个人中心location / {proxy_pass http://ucenter_server_pool;}
} 
#前端ucenter
upstream ucenter_server_pool{#server 127.0.0.1:7081 weight=10;server 127.0.0.1:13000 weight=10;
}

在学习中心要调用搜索的 API,使用 Nginx 解决代理,如下图:

ucenter 虚拟主机下配置搜索 Api 代理路径

#后台搜索(公开api)
upstream search_server_pool{server 127.0.0.1:40100 weight=10;
} 
#学成网用户中心
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/;}
} 

前端 API 方法

在学习中心 xc-ui-pc-leanring 对课程信息的查询属于基础常用功能,所以我们将课程查询的 api 方法定义在base 模块下,如下图:

system.js 中定义课程查询方法:

import http from './public'
export const course_view = id => {return http.requestGet('/openapi/search/course/getdetail/'+id);
}

前端 API 方法调用

learning_video.vue 页面中调用课程信息查询接口得到课程计划,将课程计划json 串转成对象。

xc-ui-pc-leanring/src/module/course/page/learning_video.vue

1、定义视图

课程计划

<!--课程计划部分代码-->
<div class="navCont"><div class="course-weeklist"><div class="nav nav-stacked" v-for="(teachplan_first, index) in teachplanList"><div class="tit nav-justified text-center"><i class="pull-left glyphicon glyphicon-th-list"></i>{{teachplan_first.pname} }<i class="pull-right"></i></div><li   v-if="teachplan_first.children!=null" v-for="(teachplan_second, index) in teachplan_first.children"><i class="glyphicon glyphicon-check"></i><a :href="url" @click="study(teachplan_second.id)">{{teachplan_second.pname} }</a></li><!-- <div class="tit nav-justified text-center"><i class="pull-left glyphicon glyphicon-th-list"></i>第一章<i class="pull-right"></i></div>
<li  ><i class="glyphicon glyphicon-check"></i>
<a :href="url" >
第一节
</a>
</li>--><!--<li><i class="glyphicon glyphicon-unchecked"></i>为什么分为A、B、C部分</li>--></div></div>
</div>

课程名称

<div class="top text-center">
{{coursename} }
</div>

定义数据对象

data() {return {url:'',//当前urlcourseId:'',//课程idchapter:'',//章节Idcoursename:'',//课程名称coursepic:'',//课程图片teachplanList:[],//课程计划playerOptions: {//播放参数autoplay: false,controls: true,sources: [{type: "application/x-mpegURL",src: ''}]},}
}

created 钩子方法中获取课程信息

created(){//当前请求的urlthis.url = window.location//课程idthis.courseId = this.$route.params.courseId//章节idthis.chapter = this.$route.params.chapter//查询课程信息systemApi.course_view(this.courseId).then((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.nameif(courseInfo.teachplan){let teachplan = JSON.parse(courseInfo.teachplan);this.teachplanList = teachplan.children;}})
},

测试

在浏览器请求:http://ucenter.xuecheng.com/#/learning/4028e581617f945f01617f9dabc40000/0

  • 4028e581617f945f01617f9dabc40000:第一个参数为课程 id,测试时从 ES索引库找一个课程 id
  • 0:第二个参数为课程计划 id,此参数用于点击课程计划播放视频。

如果出现跨域问题,但是确定已经配置了跨域,请尝试结束所以 nginx.exe 的进程 和 清空浏览器缓存。

如果还没有解决?重启电脑试试。

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

0x01 需求分析

用户进入在线学习页面,点击课程计划将播放该课程计划对应的教学视频。

业务流程如下:

业务流程说明:

1、用户进入在线学习页面,页面请求搜索服务获取课程信息(包括课程计划信息)并且在页面展示。

2、在线学习请求学习服务获取视频播放地址。

3、学习服务校验当前用户是否有权限学习,如果没有权限学习则提示用户。

4、学习服务校验通过,请求搜索服务获取课程媒资信息。

5、搜索服务请求ElasticSearch获取课程媒资信息。

为什么要请求 ElasticSearch 查询课程媒资信息?

出于性能的考虑,公开查询课程信息从搜索服务查询,分摊 mysql 数据库的访问压力。

什么时候将课程媒资信息存储到 ElasticSearch 中?

课程媒资信息是在课程发布的时候存入 ElasticSearch,因为课程发布后课程信息将基本不再修改。

0x02 课程发布:储存媒资信息

需求分析

课程媒资信息是在课程发布的时候存入 ElasticSearch 索引库,因为课程发布后课程信息将基本不再修改,具体的业务流程如下。

1、课程发布,向课程媒资信息表写入数据。

1)根据课程 id 删除 teachplanMediaPub 中的数据

2)根据课程 id 查询 teachplanMedia 数据

3)将查询到的 teachplanMedia 数据插入到 teachplanMediaPub

2、Logstash 定时扫描课程媒资信息表,并将课程媒资信息写入索引库。

数据模型

xc_course 数据库创建课程计划媒资发布表:

CREATE TABLE `teachplan_media_pub` (`teachplan_id` varchar(32) NOT NULL COMMENT '课程计划id',`media_id` varchar(32) NOT NULL COMMENT '媒资文件id',`media_fileoriginalname` varchar(128) NOT NULL COMMENT '媒资文件的原始名称',`media_url` varchar(256) NOT NULL COMMENT '媒资文件访问地址',`courseid` varchar(32) NOT NULL COMMENT '课程Id',`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT'logstash使用',PRIMARY KEY (`teachplan_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

数据模型类如下:

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

创建 TeachplanMediaPub 表的 Dao,向 TeachplanMediaPub 存储信息采用先删除该课程的媒资信息,再添加该课程的媒资信息,所以这里定义根据课程 id 删除课程计划媒资方法:

public interface TeachplanMediaPubRepository extends JpaRepository<TeachplanMediaPub, String> {//根据课程id删除课程计划媒资信息long deleteByCourseId(String courseId);
} 

从TeachplanMedia查询课程计划媒资信息

//从TeachplanMedia查询课程计划媒资信息
public interface TeachplanMediaRepository extends JpaRepository<TeachplanMedia, String> {List<TeachplanMedia> findByCourseId(String courseId);
}

Service

编写保存课程计划媒资信息方法,并在课程发布时调用此方法。

1、保存课程计划媒资信息方法

本方法采用先删除该课程的媒资信息,再添加该课程的媒资信息,在 CourseService 下定义该方法

//保存课程计划媒资信息
private void saveTeachplanMediaPub(String courseId){//查询课程媒资信息List<TeachplanMedia> byCourseId = teachplanMediaRepository.findByCourseId(courseId);if(byCourseId == null) return;  //没有查询到媒资数据则直接结束该方法//将课程计划媒资信息储存到待索引表//删除原有的索引信息teachplanMediaPubRepository.deleteByCourseId(courseId);//一个课程可能会有多个媒资信息,遍历并使用list进行储存List<TeachplanMediaPub> teachplanMediaPubList = new ArrayList<>();for (TeachplanMedia teachplanMedia: byCourseId) {TeachplanMediaPub teachplanMediaPub = new TeachplanMediaPub();BeanUtils.copyProperties(teachplanMedia, teachplanMediaPub);teachplanMediaPubList.add(teachplanMediaPub);}//保存所有信息teachplanMediaPubRepository.saveAll(teachplanMediaPubList);
}

2、课程发布时调用此方法

修改课程发布的 coursePublish 方法:

....
//保存课程计划媒资信息到待索引表
saveTeachplanMediaPub(courseId);
//页面url
String pageUrl = cmsPostPageResult.getPageUrl();
return new CoursePublishResult(CommonCode.SUCCESS,pageUrl);
.....

测试

测试课程发布后是否成功将课程媒资信息存储到 teachplan_media_pub 中,测试流程如下:

1、指定一个课程

2、为课程计划添加课程媒资

3、执行课程发布

4、观察课程计划媒资信息是否存储至 teachplan_media_pub

注意:由于此测试仅用于测试发布课程计划媒资信息的功能,可暂时将 cms页面发布的功能暂时屏蔽,提高测试效率。

测试结果如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vrzs5589-1595567273126)(https://qnoss.codeyee.com/20200704_15/image7)]

0x03 Logstash:扫描课程计划媒资

Logstash 定时扫描课程媒资信息表,并将课程媒资信息写入索引库。

创建索引

1、创建 xc_course_media 索引

2、并向此索引创建如下映射

POST: http://localhost:9200/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"}}
}

索引创建成功

创建模板文件

logstachconfig 目录文件 xc_course_media_template.json

文件路径为 %ES_ROOT_DIR%/logstash6.8.8/config/xc_course_media_template.json

%ES_ROOT_DIR% 为 ElasticSearch 和 logstash 的安装目录

内容如下:

{"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"}
}

配置 mysql.conf

在logstash的 config 目录下配置 mysql_course_media.conf 文件供 logstash 使用,logstash 会根据
mysql_course_media.conf 文件的配置的地址从 MySQL 中读取数据向 ES 中写入索引。

参考https://www.elastic.co/guide/en/logstash/current/plugins-inputs-jdbc.html

配置输入数据源和输出数据源。

input {stdin {} jdbc {jdbc_connection_string => "jdbc:mysql://localhost:3306/xc_course?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC"# 数据库信息jdbc_user => "root"jdbc_password => "123123"# MYSQL 驱动地址,修改为maven仓库对应的位置jdbc_driver_library => "D:/soft/apache-maven-3.5.4/repository/mysql/mysql-connector-java/5.1.40/mysql-connector-java-5.1.40.jar"# the name of the driver class for mysqljdbc_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 => truelast_run_metadata_path => "D:/soft/elasticsearch/logstash-6.8.8/config/xc_course_media_metadata"}
} 
output {elasticsearch {#ES的ip地址和端口hosts => "localhost:9200"#hosts => ["localhost:9200","localhost:9202","localhost:9203"]#ES索引库名称index => "xc_course_media"document_id => "%{teachplan_id}"document_type => "doc"template => "D:/soft/elasticsearch/logstash-6.8.8/config/xc_course_media_template.json"template_name =>"xc_course_media"template_overwrite =>"true"} stdout {#日志输出codec => json_lines}
}

启动 logstash.bat

启动 logstash.bat 采集 teachplan_media_pub 中的数据,向 ES 写入索引。

logstash.bat -f ../config/mysql_course_media.conf

课程发布成功后,Logstash 会自动参加 teachplan_media_pub 表中新增的数据,效果如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ILPBxfXi-1595567273134)(https://qnoss.codeyee.com/20200704_15/image10)]

Logstash多实例运行

由于之前我们还启动了一个 Logstash 对课程的发布信息进行采集,所以如果想两个 logstash 实例同时运行,因为每个实例都有一个.lock文件,所以不能使用同一个目录来存放数据,所以我们需要使用 --path.data= 为每个实例指定单独的数据目录,具体的代码如下:

该配置是在windows下进行的

课程发布实例

logstash_start_course_pub.bat

@title logstash in course_pub
logstash.bat -f ..\config\mysql.conf --path.data=../data/course_pub

课程计划媒体发布实例

logstash_start_teachplan_media.bat

@title logstash i n teachplan_media_pub
logstash.bat -f ../config/mysql_course_media.conf --path.data=../data/teachplan_media/

同时运行效果如下

0x04 搜素服务:查询课程媒资接口

需求分析

搜索服务 提供查询课程媒资接口,此接口供学习服务调用。

Api接口定义

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

Service

1、配置课程计划媒资索引库等信息

application.yml 中配置

xuecheng:elasticsearch:hostlist: ${eshostlist:127.0.0.1:9200} #多个结点中间用逗号分隔course:index: xc_coursetype: docsource_field: id,name,grade,mt,st,charge,valid,pic,qq,price,price_old,status,studymodel,teachmode,expires,pub_time,start_time,end_timemedia:index: xc_course_mediatype: docsource_field: courseid,media_id,media_url,teachplan_id,media_fileoriginalname

2、service 方法开发

课程搜索服务 中定义课程媒资查询接口,为了适应后续需求,service 参数定义为数组,可一次查询多个课程计划的媒资信息。

 /*** 根据一个或者多个课程计划id查询媒资信息* @param teachplanIds 课程id* @return QueryResponseResult*/public QueryResponseResult<TeachplanMediaPub> getmedia(String [] teachplanIds){//设置索引SearchRequest searchRequest = new SearchRequest(media_index);//设置类型searchRequest.types(media_type);//创建搜索源对象SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();//源字段过滤String[] media_index_arr = media_field.split(",");searchSourceBuilder.fetchSource(media_index_arr, new String[]{});//查询条件,根据课程计划id查询(可以传入多个课程计划id)searchSourceBuilder.query(QueryBuilders.termsQuery("teachplan_id", teachplanIds));searchRequest.source(searchSourceBuilder);SearchResponse searchResponse = null;try {searchResponse = restHighLevelClient.search(searchRequest);} catch (IOException e) {e.printStackTrace();}//获取结果SearchHits hits = searchResponse.getHits();long totalHits = hits.getTotalHits();SearchHit[] searchHits = hits.getHits();//数据列表List<TeachplanMediaPub> teachplanMediaPubList = new ArrayList<>();for(SearchHit hit:searchHits){TeachplanMediaPub teachplanMediaPub =new TeachplanMediaPub();Map<String, Object> sourceAsMap = hit.getSourceAsMap();//取出课程计划媒资信息String courseid = (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(courseid);teachplanMediaPub.setMediaUrl(media_url);teachplanMediaPub.setMediaFileOriginalName(media_fileoriginalname);teachplanMediaPub.setMediaId(media_id);teachplanMediaPub.setTeachplanId(teachplan_id);//将对象加入到列表中teachplanMediaPubList.add(teachplanMediaPub);}//构建返回课程媒资信息对象QueryResult<TeachplanMediaPub> queryResult = new QueryResult<>();queryResult.setList(teachplanMediaPubList);queryResult.setTotal(totalHits);return new QueryResponseResult<TeachplanMediaPub>(CommonCode.SUCCESS,queryResult);}

Controller

/*** 根据课程计划id搜索发布后的媒资信息* @param teachplanId* @return*/
@GetMapping(value="/getmedia/{teachplanId}")
@Override
public TeachplanMediaPub getmedia(@PathVariable("teachplanId") String teachplanId) {//为了service的拓展性,所以我们service接收的是数组作为参数,以便后续开发查询多个ID的接口String[] teachplanIds = new String[]{teachplanId};//通过service查询ES获取课程媒资信息QueryResponseResult<TeachplanMediaPub> mediaPubQueryResponseResult = esCourseService.getmedia(teachplanIds);QueryResult<TeachplanMediaPub> queryResult = mediaPubQueryResponseResult.getQueryResult();if(queryResult!=null&& queryResult.getList()!=null&& queryResult.getList().size()>0){//返回课程计划对应课程媒资return queryResult.getList().get(0);} return new TeachplanMediaPub();
}

测试

使用 swagger-uipostman 测试课程媒资查询接口。

三、在线学习:接口开发

0x01 需求分析

根据下边的业务流程,本章节完成前端学习页面请求学习服务获取课程视频地址,并自动播放视频。

0x02 搭建开发环境

1、创建数据库

创建 xc_learning 数据库,学习数据库将记录学生的选课信息、学习信息。

导入:资料/xc_learning.sql

2、创建学习服务工程

参考课程管理服务工程结构,创建学习服务工程:

导入:资料/xc-service-learning.zip

项目工程结构如下

0x03 Api接口

api 接口是课程学习页面请求学习服务获取课程学习地址。

定义返回值类型:

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;
}

定义接口,学习服务根据传入课程 ID、章节 Id(课程计划 ID)来取学习地址。

@Api(value = "录播课程学习管理",description = "录播课程学习管理")
public interface CourseLearningControllerApi {@ApiOperation("获取课程学习地址")public GetMediaResult getMediaPlayUrl(String courseId,String teachplanId);
}

0x04 服务端开发

需求分析

学习服务根据传入课程ID、章节Id(课程计划ID)请求搜索服务获取学习地址。

搜索服务注册Eureka

学习服务要调用搜索服务查询课程媒资信息,所以需要将搜索服务注册到 eureka 中。

1、查看服务名称是否为 xc-service-search

# 注意修改application.xml中的服务名称:
spring:application:name: xc‐service‐search

2、配置搜索服务的配置文件 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等操作没有实现幂等的情况下是很危险的,所以设置为falseConnectTimeout: 5000  #请求连接的超时时间ReadTimeout: 6000 #请求处理的超时时间

3、添加 eureka 依赖

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

4、修改启动类,在class上添加如下注解:

@EnableDiscoveryClient

搜索服务客户端

学习服务 创建搜索服务的客户端接口,此接口会生成代理对象,调用搜索服务:

package com.xuecheng.learning.client;
import com.xuecheng.framework.domain.course.TeachplanMediaPub;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;@FeignClient(value = "xc‐service‐search")
public interface CourseSearchClient {@GetMapping(value="/getmedia/{teachplanId}")public TeachplanMediaPub getmedia(@PathVariable("teachplanId") String teachplanId);
}

自定义错误代码

我们在 com.xuecheng.framework.domain.learning.response 包下自定义一个错误消息模型

package com.xuecheng.framework.domain.learning.response;import com.xuecheng.framework.model.response.ResultCode;
import lombok.ToString;@ToString
public enum LearningCode implements ResultCode {LEARNING_GET_MEDIA_ERROR(false,23001,"学习中心获取媒资信息错误!");//操作代码boolean success;//操作代码int code;//提示信息String message;private LearningCode(boolean success, int code, String message){this.success = success;this.code = code;this.message = message;}@Overridepublic boolean success() {return success;}@Overridepublic int code() {return code;}@Overridepublic String message() {return message;}
}

该消息模型基于 ResultCode 来实现,代码如下

package com.xuecheng.framework.model.response;/*** Created by mrt on 2018/3/5.* 10000-- 通用错误代码* 22000-- 媒资错误代码* 23000-- 用户中心错误代码* 24000-- cms错误代码* 25000-- 文件系统*/
public interface ResultCode {//操作是否成功,true为成功,false操作失败boolean success();//操作代码int code();//提示信息String message();

ResultCode 中我们可以看出,我们约定了用户中心的错误代码使用 23000,所以我们定义的一些错误信息的代码就从 23000 开始计数。

Service

在学习服务中定义 service 方法,此方法远程请求课程管理服务、媒资管理服务获取课程学习地址。

package com.xuecheng.learning.service.impl;import com.netflix.discovery.converters.Auto;
import com.xuecheng.framework.domain.course.TeachplanMediaPub;
import com.xuecheng.framework.domain.learning.response.GetMediaResult;
import com.xuecheng.framework.exception.ExceptionCast;
import com.xuecheng.framework.model.response.CommonCode;
import com.xuecheng.learning.client.CourseSearchClient;
import com.xuecheng.learning.service.LearningService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class LearningServiceImpl implements LearningService {@AutowiredCourseSearchClient courseSearchClient;/*** 远程调用搜索服务获取已发布媒体信息中的url* @param courseId 课程id* @param teachplanId  媒体信息id* @return*/@Overridepublic GetMediaResult getMediaPlayUrl(String courseId, String teachplanId) {//校验学生权限,是否已付费等//远程调用搜索服务进行查询媒体信息TeachplanMediaPub mediaPub = courseSearchClient.getmedia(teachplanId);if(mediaPub == null) ExceptionCast.cast(CommonCode.FAIL);return new GetMediaResult(CommonCode.SUCCESS, mediaPub.getMediaUrl());}
}

Controller

调用 service 根据课程计划 id 查询视频播放地址:

@RestController
@RequestMapping("/learning/course")
public class CourseLearningController implements CourseLearningControllerApi {@AutowiredLearningService learningService;@Override@GetMapping("/getmedia/{courseId}/{teachplanId}")public GetMediaResult getMediaPlayUrl(@PathVariable String courseId, @PathVariable String teachplanId) {//获取课程学习地址return learningService.getMedia(courseId, teachplanId);}
}

测试

使用 swagger-uipostman 测试学习服务查询课程视频地址接口。

0x05 前端开发

需求分析

需要在学习中心前端页面需要完成如下功能:

1、进入课程学习页面需要带上 课程 Id参数及课程计划Id的参数,其中 课程 Id 参数必带,课程计划 Id 可以为空。

2、进入页面根据 课程 Id 取出该课程的课程计划显示在右侧。

3、进入页面后判断如果请求参数中有课程计划 Id 则播放该章节的视频。

4、进入页面后判断如果 课程计划id 为0则需要取出本课程第一个 课程计划的Id,并播放第一个课程计划的视频。

进入到模块 xc-ui-pc-leanring/src/module/course

api方法

let sysConfig = require('@/../config/sysConfig')
let apiUrl = sysConfig.xcApiUrlPre;
/*获取播放地址*/
export const get_media = (courseId,chapter) => {return http.requestGet(apiUrl+'/api/learning/course/getmedia/'+courseId+'/'+chapter);
}

配置代理

Nginx 中的 ucenter.xuecheng.com 虚拟主机中配置 /api/learning/ 的路径转发,此url 请转发到学习服务。

#学习服务
upstream learning_server_pool{server 127.0.0.1:40600 weight=10;
}#学成网用户中心
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/; }#学习服务location ^~ /api/learning/ {proxy_pass http://learning_server_pool/learning/;}
} 

视频播放页面

1、如果传入的课程计划id为0则取出第一个课程计划id

created 钩子方法中完成

created(){//当前请求的urlthis.url = window.location//课程idthis.courseId = this.$route.params.courseId//章节idthis.chapter = this.$route.params.chapter//查询课程信息systemApi.course_view(this.courseId).then((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.nameif(courseInfo.teachplan){console.log("准备开始播放视频")let teachplan = JSON.parse(courseInfo.teachplan);this.teachplanList = teachplan.children;//开始学习if(this.chapter == "0" || !this.chapter){//取出第一个教学计划this.chapter = this.getFirstTeachplan();console.log("第一个教学计划id为 ",this.chapter);this.study(this.chapter);}else{this.study(this.chapter);}}})
},

取出第一个章节 id,用户未输入课程计划 id 或者输入为 0 时,播放第一个。

//取出第一个章节
getFirstTeachplan(){for(var i=0;i<this.teachplanList.length;i++){let firstTeachplan = this.teachplanList[i];//如果当前children存在,则取出第一个返回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("播放视频失败,请刷新页面重试")});
},

2、点击右侧课程章节切换播放

在原有代码基础上添加 click 事件,点击调用开始学习方法(study)。

<li v‐if="teachplan_first.children!=null" v‐for="(teachplan_second, index) in
teachplan_first.children"><i class="glyphicon glyphicon‐check"></i>
<a :href="url" @click="study(teachplan_second.id)">
{{teachplan_second.pname} }
</a>
</li>

3、地址栏路由url变更

这里需要注意一个问题,在用户点击课程章节切换播放时,地址栏的 url 也应该同步改变为当前所选择的课程计划 id


4、在线学习按钮

learnstatus 默认更改为 1,这样就能显示出马上学习的按钮,方便我们后续的集成测试。

文件路径为 xc-ui-pc-static-portal/include/course_detail_dynamic.html 部分代码块如下

<script>var body= new Vue({   //创建一个Vue的实例el: "#body", //挂载点是id="app"的地方data: {editLoading: false,title:'测试',courseId:'',charge:'',//203001免费,203002收费learnstatus: 1 ,//课程状态,1:马上学习,2:立即报名、3:立即购买course:{},companyId:'template',company_stat:[],course_stat:{"s601001":"","s601002":"","s601003":""}},

简单的测试

访问在线学习页面:http://ucenter.xuecheng.com/#/learning/课程id/课程计划id

通过 url 传入两个参数:课程id课程计划id

如果没有课程计划则传入0

测试项目如下:

1、传入正确的课程id、课程计划id,自动播放本章节的视频

2、传入正确的课程id、课程计划id传入0,自动播放第一个视频

3、传入错误的课程id 或 课程计划id,提示错误信息。

4、通过右侧章节目录切换章节及播放视频。

访问: http://ucenter.xuecheng.com/#/learning/4028e58161bcf7f40161bcf8b77c0000/4028e58161bd18ea0161bd1f73190008

传入正确的课程id、课程计划id,自动播放本章节的视频

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ef0xxym7-1595567273153)(https://qnoss.codeyee.com/20200704_15/image17)]

传入正确的课程id、课程计划id传入0,自动播放第一个视频

访问 http://ucenter.xuecheng.com/#/learning/4028e58161bcf7f40161bcf8b77c0000/0

识别出第一个课程计划的 id

需要注意的是这里的 chapter 参数是我自己在 study 函数里加上去的,可以忽略。

传入错误的课程id或课程计划id,提示错误信息。

通过右侧章节目录切换章节及播放视频。

点击章节即可播放,但是点击制定章节后 url 没有发生改变,这个问题暂时还没有解决,关注笔记后面的内容。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TOGdxwb4-1595567273158)(https://qnoss.codeyee.com/20200704_15/image20)]

完整的测试

准备工作

  • 启动 RabbitMQ,启动 LogstashElasticSearch

  • 建议把所有后端服务都开起来

  • 启动 前端静态门户、启动 nginx 、启动课程管理前端

我们整理一下测试的流程

  1. 上传两个媒资视频文件,用于测试
  2. 进入到课程管理,为课程计划选择媒资信息
  3. 发布课程,等待 logstash 将数据采集到 ElasticSearch 的索引库中
  4. 进入学成网主页,点击课程,进入到搜索门户页面
  5. 搜索课程,进入到课程详情页面
  6. 点击开始学习,进入到课程学习页面,选择课程计划中的一个章节进行学习。

1、上传文件

首先我们使用之前开发的媒资管理模块,上传两个视频文件用于测试。

第一个文件上传成功

一些问题

在上传第二个文件时,发生了错误,我们来检查一下问题出在了哪里

在媒体服务的控制台中可以看到,在 mergeChunks 方法在校验文件 md5 时候抛出了异常

我们在 MD5 校验这里打个断点,重新上传文件,分析一下问题所在。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OpEMZGI8-1595567273166)(https://qnoss.codeyee.com/20200704_15/image23)]

单步调试后发现,合并文件后的MD5值与用户上传的源文件值不相等

方案1:删除本地分块文件重新尝试上传

考虑到可能是在用户上传完 视频的分块文件时发生了一些问题,导致合并文件后与源文件的大小不等,导致MD5也不相同,这里我们把这个视频上传到本地的文件全部删除,在媒资上传页面重新上传文件。

对比所有分块文件的字节大小和本地源文件的大小,完全是相等的

删除所有文件后重新上传,md5值还是不等,考虑从调试一下文件合并的代码。

方案2:检查前端提交的MD5值是否正确

在查阅是否有其他的MD5值获取方案时,发现了一个使用 windows 本地命令获取文件MD5值的方法

certutil -hashfile .\19-在线学习接口-集成测试.avi md5

惊奇的发现,TM的原来是前端那边转换的MD5值不正确,后端这边是没有问题的。

从前面的图可以看出,本地和后端转换的都是以一个 f6f0 开头的MD5值

那么问题就出现在前端了,还需要花一些时间去分析一下,这里暂时就先告一段落,因为上传了几个文件测试中只有这一个文件出现了问题。

2、为课程计划选择媒资信息

进入到一个课程的管理页面

http://localhost:12000/#/course/manage/baseinfo/4028e58161bcf7f40161bcf8b77c0000

将刚才我们上传的媒资文件的信息和课程计划绑定

选择效果如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-epKaqzCD-1595567273178)(https://qnoss.codeyee.com/20200704_15/image29)]

2、发布课程,等待 logstashcourse_pub 以及 teachplan_media_pub 表中采集数据到 ElasticSearch 当中

发布成功后,我们可以从 teachplan_media_pub 表中看到刚才我们发布的媒资信息

再观察 Logstash 的控制台,发现两个 Logstash 的实例都对更新的课程发布信息进行了采集

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hTUve2ik-1595567273183)(https://qnoss.codeyee.com/20200704_15/image32)]

3、前端门户测试

打开我们的门户主站 http://www.xuecheng.com/

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4wZe9R84-1595567273185)(https://qnoss.codeyee.com/20200704_15/image33)]

点击导航栏的课程,进入到我们的搜索门户页面

如果无法进入到搜索门户,请检查你的 xc-ui-pc-portal 前端工程是否已经启动

进入到搜索门户后,可以看到一些初始化时搜索的课程数据,默认是搜索第一页的数据,每页2个课程。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BJ1AKoJb-1595567273187)(https://qnoss.codeyee.com/20200704_15/image34)]

我们可以测试搜索一下前面我们选择媒资信息时所用的课程

点击课程,进入到课程详情页面,然后再点击开始学习。

点击马上学习后,会进入到该课程的在线学习页面,默认自动播放我们第一个课程计划中的视频。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tcuLWnf2-1595567273193)(https://qnoss.codeyee.com/20200704_15/image37)]

我们可以在右侧的目录中选择第二个课程计划,会自动播放所选的课程计划所对应的媒资视频播放地址,该 播放地址正是我们刚才通过 Logstash 自动采集到 ElasticSearch 的索引信息,效果图如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cvi9Dr0Y-1595567273195)(https://qnoss.codeyee.com/20200704_15/image38)]

四、待完善的一些功能

  • 课程发布前,校验课程计划里面是否包含二级课程计划
  • 课程发布前,校验课程计划信息里面是否全部包含媒资信息
  • 删除媒资信息,并且同步删除ES中的索引
  • 在获取该课程的播放地址时校验用户的合法、
  • 在线学习页面,点击右侧目录中的课程计划同时改变url中的课程计划地址
  • 视频文件 19-在线学习接口-集成测试.avi 前端上传时提交的MD5值不正确

😁 认识作者

作者:👦 LCyee ,全干型代码🐕

自建博客:https://www.codeyee.com

记录学习以及项目开发过程中的笔记与心得,记录认知迭代的过程,分享想法与观点。

CSDN 博客:https://blog.csdn.net/codeyee

记录和分享一些开发过程中遇到的问题以及解决的思路。

欢迎加入微服务练习生的队伍,一起交流项目学习过程中的一些问题、分享学习心得等,不定期组织一起刷题、刷项目,共同见证成长。

本篇文章为转载内容。原文链接:https://blog.csdn.net/codeyee/article/details/107558901。

该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。

作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。

如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。

相关阅读
文章标题:[转载][洛谷P1082]同余方程

更新时间:2023-02-18
[转载][洛谷P1082]同余方程
文章标题:[转载]webpack优化之HappyPack实战

更新时间:2023-08-07
[转载]webpack优化之HappyPack实战
文章标题:[转载]oracle 同时更新多表,在Oracle数据库中同时更新两张表的简单方法

更新时间:2023-09-10
[转载]oracle 同时更新多表,在Oracle数据库中同时更新两张表的简单方法
文章标题:[转载][Unity] 包括场景互动与射击要素的俯视角闯关游戏Demo

更新时间:2024-03-11
[转载][Unity] 包括场景互动与射击要素的俯视角闯关游戏Demo
文章标题:[转载]程序员也分三六九等?等级差异,一个看不起一个!

更新时间:2024-05-10
[转载]程序员也分三六九等?等级差异,一个看不起一个!
文章标题:[转载]海贼王 动漫 全集目录 分章节 精彩打斗剧集

更新时间:2024-01-12
[转载]海贼王 动漫 全集目录 分章节 精彩打斗剧集
名词解释
作为当前文章的名词解释,仅对当前文章有效。
Elasticsearch (ES)Elasticsearch 是一个开源、分布式的搜索和分析引擎,能够对大规模数据快速地进行全文检索、结构化检索以及分析操作。在本文的上下文中,【学成在线】项目利用 Elasticsearch 构建了一个课程信息发布与检索系统,将课程计划等信息存储在 Elasticsearch 的索引库中,以便通过查询接口高效地从海量数据中获取指定课程的详细信息。
LogstashLogstash 是一款开源的数据收集引擎,常用于日志管理和转发,支持从多种来源采集数据,并将其转换为适合下游系统消费的格式后输出到多个目标存储。在【学成在线】项目的实施过程中,Logstash 被用来实时扫描并收集课程发布后的媒资信息,自动保存至 Elasticsearch 索引库,确保课程资源的及时更新与同步。
m3u8 地址m3u8 是一种 HLS(HTTP Live Streaming)协议采用的播放列表文件格式,主要用于流媒体内容分发。m3u8 文件包含一系列 TS(Transport Stream)视频片段的 URL 列表,允许客户端根据网络条件动态选择不同码率的视频流进行播放。在本文所述的在线教育平台中,每个课程计划对应的媒资信息包含了用于在线播放视频的 m3u8 地址,前端通过调用接口获取这个地址来实现视频的流畅播放。
延伸阅读
作为当前文章的延伸阅读,仅对当前文章有效。
在深入理解【学成在线】项目中关于课程计划查询与视频播放地址获取的技术实现后,我们发现线上教育平台的媒资管理、数据检索以及API设计的重要性不言而喻。随着互联网技术的发展和在线教育市场的持续火爆,越来越多的教育机构开始关注如何提升用户体验、优化教育资源管理和分发效率。
近日,《中国远程教育》杂志发布的一篇深度分析文章探讨了当前在线教育平台在内容分发网络(CDN)选择、大数据存储与检索策略方面的最佳实践。文中指出,在线教育平台应充分利用Elasticsearch等高效索引工具,结合Logstash的数据收集能力,实时同步并处理大量课程媒资信息,以确保用户能够快速、准确地获取所需的学习资料。
此外,为了保障视频流媒体服务的质量与稳定性,许多教育平台正积极采用更先进的HTTP Live Streaming(HLS)协议,并通过m3u8地址格式进行视频片段分发。例如,某知名在线教育企业近期升级其视频播放系统,实现了基于用户网络环境动态调整视频码率的功能,极大提升了用户的观看体验。
同时,在架构设计层面,使用Nginx作为反向代理服务器已成为业界标准配置,它不仅能够解决跨域调用问题,还能通过对请求的负载均衡分配,提高系统的稳定性和响应速度。正如《高性能Nginx服务器详解》一书中所述,合理配置Nginx对于构建高性能、高可用的在线教育服务平台至关重要。
综上所述,不论是紧跟技术潮流,采用高效的检索技术和流媒体解决方案,还是从架构设计角度优化服务性能,都是现代在线教育平台保持竞争力的关键所在。未来,在线教育领域的技术创新将更加注重个性化、智能化和互动化,为用户提供更加优质、便捷的学习体验。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
uniq file.txt - 移除连续重复行。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
纯js实用T恤衫花纹图案预览特效 01-26 基于Bootstrap仿Github样式下拉列表框插件 08-08 jQuery电子邮件地址填写自动完成插件 04-30 Superset 数据源连接配置:精细化自定义SQLAlchemy URI实现数据分析与可视化,含SSL加密连接实例 03-19 jquery可任意拖动排序的导航图片效果 02-23 侧边栏个人图文简历HTML模板 12-09 Beego框架升级中的Bee工具版本兼容性问题与迁移策略:结构变更、功能接口变动及社区解决方案 12-07 Kibana无法启动:针对服务器内部错误的Elasticsearch连接、配置文件、端口冲突与资源排查解决(注:由于字数限制,未能完全包含所有关键词,但包含了核心问题描述及几个关键排查点) 11-01 ClickHouse外部表使用中文件权限与不存在问题的解决方案:错误提示、查询操作与文件路径管理实务 09-29 本次刷新还10个文章未展示,点击 更多查看。
Apache Atlas UI无法正常加载与样式丢失问题排查及解决方案:关注网络连接、浏览器缓存与开发者工具应用 09-25 Greenplum数据库中数据插入操作详解:单行多行插入与gpfdist实现大批量导入 08-02 [转载]html5 footer header,html-5 --html5教程article、footer、header、nav、section使用 07-16 [转载][GCC for C]编译选项---IDE掩盖下的天空 06-29 简洁大方珠宝钻石收藏网站模板下载 06-20 黑色高端精致汽车4s店美容html5模板下载 06-01 蓝色互联网项目融资管理平台网站模板 05-16 响应式游戏开发类企业前端cms模板下载 05-02 Beego框架动态路由实现:重定向与命令行参数驱动的路由设计实践 04-05 .NET 中字典操作避免 KeyNotFoundException:TryGetValue、ContainsKey 与 GetOrAdd 实践详解 04-04 [转载]2021/4/23爬虫第五次课(爬虫网络请求模块下下) 03-01
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"