zoukankan      html  css  js  c++  java
  • 学成在线(第10天)课程发布及ElasticSearch

     课程发布

     需求分析

    课程发布后将生成正式的课程详情页面,课程发布后用户即可浏览课程详情页面,并开始课程的学习。
    课程发布生成课程详情页面的流程与课程预览业务流程相同,如下:
    1、用户进入教学管理中心,进入某个课程的管理界面
    2、点击课程发布,前端请求到课程管理服务
    3、课程管理服务远程调用CMS生成课程发布页面,CMS将课程详情页面发布到服务器
    4、课程管理服务修改课程发布状态为 “已发布”,并向前端返回发布成功
    5、用户在教学管理中心点击“课程详情页面”链接,查看课程详情页面内容

     CMS 一键发布接口

    根据需求分析内容,需要在 cms服务增加页面发布接口供课程管理服务调用,此接口的功能如下:
    1、接收课程管理服务发布的页面信息
    2、将页面信息添加到 数据库(mongodb)
    3、对页面信息进行静态化
    4、将页面信息发布到服务器

    接口定义

    1、创建响应结果类型
    页面发布成功cms返回页面的url
    页面Url= cmsSite.siteDomain+cmsSite.siteWebPath+ cmsPage.pageWebPath + cmsPage.pageName

    @Data
    @NoArgsConstructor//无参构造器注解
    public class CmsPostPageResult extends ResponseResult  {
        String pageUrl;
        public CmsPostPageResult(ResultCode resultCode,String pageUrl) {
            super(resultCode);
            this.pageUrl = pageUrl;
        }
    }

    2、在api工程定义页面发布接口

    @ApiOperation("一键发布页面")
    public CmsPostPageResult postPageQuick(CmsPage cmsPage);

    Dao

    1、站点dao
    接口中需要获取站点的信息(站点域名、站点访问路径等

    public interface CmsSiteRepository extends MongoRepository<CmsSite,String> {
    }

    Service

    1、添加页面,如果已存在则更新页面

    //添加页面,如果已存在则更新页面
    public CmsPageResult save(CmsPage cmsPage){
        //校验页面是否存在,根据页面名称、站点Id、页面webpath查询
        CmsPage cmsPage1 =
    cmsPageRepository.findByPageNameAndSiteIdAndPageWebPath(cmsPage.getPageName(),
    cmsPage.getSiteId(), cmsPage.getPageWebPath());
        if(cmsPage1 !=null){
            //更新
            return this.update(cmsPage1.getPageId(),cmsPage);
        }else{
            //添加
            return this.add(cmsPage);
        }
    }

    2、页面发布方法

    //一键发布页面
        public CmsPostPageResult postPageQuick(CmsPage cmsPage){
            //添加页面
            CmsPageResult save = this.save(cmsPage);
            if(!save.isSuccess()){
                return new CmsPostPageResult(CommonCode.FAIL,null);
            }
            CmsPage cmsPage1 = save.getCmsPage();
            //要布的页面id
            String pageId = cmsPage1.getPageId();
            //发布页面
            ResponseResult responseResult = this.postPage(pageId);
            if(!responseResult.isSuccess()){
                return new CmsPostPageResult(CommonCode.FAIL,null);
            }
            //得到页面的url
            //页面url=站点域名+站点webpath+页面webpath+页面名称
            //站点id
            String siteId = cmsPage1.getSiteId();
            //查询站点信息
            CmsSite cmsSite = findCmsSiteById(siteId);
            //站点域名
            String siteDomain = cmsSite.getSiteDomain();
            //站点web路径
            String siteWebPath = cmsSite.getSiteWebPath();
            //页面web路径
            String pageWebPath = cmsPage1.getPageWebPath();
            //页面名称
            String pageName = cmsPage1.getPageName();
            //页面的web访问地址
            String pageUrl = siteDomain+siteWebPath+pageWebPath+pageName;
            return new CmsPostPageResult(CommonCode.SUCCESS,pageUrl);
        }
    //根据id查询站点信息
        public CmsSite findCmsSiteById(String siteId){
            Optional<CmsSite> optional = cmsSiteRepository.findById(siteId);
            if(optional.isPresent()){
                return optional.get();
            }
            return null;
        }
    View Code

    Controller

    @Override
    @PostMapping("/postPageQuick")
    public CmsPostPageResult postPageQuick(@RequestBody CmsPage cmsPage) {
        return pageService.postPageQuick(cmsPage);
    }

    课程发布接口

    Api接口

    此Api接口由课程管理提供,由课程管理前端调用此Api接口,实现课程发布。
    在api工程下课程管理包下定义接口:

    @ApiOperation("发布课程")
    public CoursePublishResult publish(@PathVariable String id);

    创建Feign Client

    在课程管理工程创建CMS服务页面发布的Feign Client

    @FeignClient(value = XcServiceList.XC_SERVICE_MANAGE_CMS)
    public interface CmsPageClient {
        //一键发布页面
        @PostMapping("/cms/page/postPageQuick")
        public CmsPostPageResult postPageQuick(CmsPage cmsPage);
    }

    Service

    1、配置课程发布页面参数
    在application.yml中配置

    course‐publish:
      siteId: 5b30cba5f58b4411fc6cb1e5
      templateId: 5ad9a24d68db5239b8fef199
      previewUrl: http://www.xuecheng.com/cms/preview/
      pageWebPath: /course/detail/
      pagePhysicalPath: /course/detail/
      dataUrlPre: http://localhost:31200/course/courseview/

    siteId:站点id
    templateId:模板id
    dataurlPre:数据url的前缀
    pageWebPath: 页面的web访问路径
    pagePhysicalPath:页面的物理存储路径。

    2、Service方法如下

    @Value("${course‐publish.dataUrlPre}")
    private String publish_dataUrlPre;
    @Value("${course‐publish.pagePhysicalPath}")
    private String publish_page_physicalpath;
    @Value("${course‐publish.pageWebPath}")
    private String publish_page_webpath;
    @Value("${course‐publish.siteId}")
    private String publish_siteId;
    @Value("${course‐publish.templateId}")
    private String publish_templateId;
    @Value("${course‐publish.previewUrl}")
    private String previewUrl;
    @Autowired
    CmsPageClient cmsPageClient;
     //课程发布
        @Transactional
        public CoursePublishResult publish(String courseId){
            //课程信息
            CourseBase one = this.findCourseBaseById(courseId);
            //发布课程详情页面
            CmsPostPageResult cmsPostPageResult = publish_page(courseId);
            if(!cmsPostPageResult.isSuccess()){
                ExceptionCast.cast(CommonCode.FAIL);
    }
            //更新课程状态
            CourseBase courseBase = saveCoursePubState(courseId);
            //课程索引...
            //课程缓存...
            //页面url
            String pageUrl = cmsPostPageResult.getPageUrl();
            return new CoursePublishResult(CommonCode.SUCCESS,pageUrl);
        }
        //更新课程发布状态
        private CourseBase saveCoursePubState(String courseId){
            CourseBase courseBase this.findCourseBaseById(courseId);
            //更新发布状态
            courseBase.setStatus("202002");
            CourseBase save = courseBaseRepository.save(courseBase);
            return save;
        }
        //发布课程正式页面
        public CmsPostPageResult publish_page(String courseId){
            CourseBase one this.findCourseBaseById(courseId);
            //发布课程预览页面
            CmsPage cmsPage = new CmsPage();
            //站点
            cmsPage.setSiteId(publish_siteId);//课程预览站点
            //模板
            cmsPage.setTemplateId(publish_templateId);
            //页面名称
            cmsPage.setPageName(courseId+".html");
            //页面别名
            cmsPage.setPageAliase(one.getName());
            //页面访问路径
            cmsPage.setPageWebPath(publish_page_webpath);
            //页面存储路径
            cmsPage.setPagePhysicalPath(publish_page_physicalpath);
            //数据url
            cmsPage.setDataUrl(publish_dataUrlPre+courseId);
            //发布页面
            CmsPostPageResult cmsPostPageResult = cmsPageClient.postPageQuick(cmsPage);
            return cmsPostPageResult;
        }
    View Code

    Controller

    @Override
        @PostMapping("/publish/{id}")
        public CoursePublishResult publish(@PathVariable String id) {
            return courseService.publish(id);
        }

    新增站点和模板

    1、新增课程详情页面的站点信息
    如果已增加课程详情页面的站点则忽略此步骤。
    向cms_site中新增如下信息

    {
        "_id" : ObjectId("5b30b052f58b4411fc6cb1cf"),
        "_class" : "com.xuecheng.framework.domain.cms.CmsSite",
        "siteName" : "课程详情站点",
        "siteDomain" : "http://www.xuecheng.com",
        "sitePort" : "80",
        "siteWebPath" : "",
        "siteCreateTime" : ISODate("2018‐02‐03T02:34:19.113+0000")
    }

    2、新增课程详情模板信息
    可直接使用前边章节制作的课程详情信息模板。
    可以GridFS的测试代码添加模板,如果已添加则不用重复添加。
    使用测试GridFS Api将模板文件存储到mongodb:

    //文件存储2
    @Test
    public void testStore2() throws FileNotFoundException {
        File file new File("C:\Users\admin\Desktop\coursedetail_t.html");
        FileInputStream inputStream new FileInputStream(file);
        //保存模版文件内容
        GridFSFile gridFSFile = gridFsTemplate.store(inputStream, "测试文件","");
        String fileId = gridFSFile.getId().toString();
        System.out.println(fileId);
    }

    单元测试

    1、启动RabbitMQ服务
    2、启动cms服务
    3、启动cms_client,注意配置routingKey和队列名称

    xuecheng:
      mq:
      #cms客户端监控的队列名称(不同的客户端监控的队列不能重复)
        queue: queue_cms_postpage_03
        routingKey: 5b30b052f58b4411fc6cb1cf  #此routingKey为门户站点ID

     

     全文检索 Elasticearch 研究

    ElasticSearch 介绍

    1、elasticsearch是一个基于Lucene的高扩展的分布式搜索服务器,支持开箱即用。
    2、elasticsearch隐藏了Lucene的复杂性,对外提供Restful 接口来操作索引、搜索。

    突出优点:

    1. 扩展性好,可部署上百台服务器集群,处理PB级数据。
    2.近实时的去索引数据、搜索数据。

    es和solr选择哪个?
    1.如果你公司现在用的solr可以满足需求就不要换了。
    2.如果你公司准备进行全文检索项目的开发,建议优先考虑elasticsearch,因为像Github这样大规模的搜索都在用
    它。

    es在项目中的应用方式:

    1)用户在前端搜索关键字
    2)项目前端通过http方式请求项目服务端
    3)项目服务端通过Http RESTful方式请求ES集群进行搜索
    4)ES集群从索引库检索数据。

    ES 快速入门

    创建索引库

    ES的索引库是一个逻辑概念,它包括了分词列表及文档列表,同一个索引库中存储了相同类型的文档。它就相当于
    MySQL中的表,或相当于Mongodb中的集合。
    关于索引这个语:
    索引(名词):ES是基于Lucene构建的一个搜索服务,它要从索引库搜索符合条件索引数据。
    索引(动词):索引库刚创建起来是空的,将数据添加到索引库的过程称为索引。

    1)使用postman或curl这样的工具创建:

    put http://localhost:9200/索引库名称

    {
      "settings":{
      "index":{
          "number_of_shards":1,
          "number_of_replicas":0
       }    
      }
    }

    number_of_shards:设置分片的数量,在集群中通常设置多个分片,表示一个索引库将拆分成多片分别存储不同
    的结点,提高了ES的处理能力和高可用性,入门程序使用单机环境,这里设置为1。
    number_of_replicas:设置副本的数量,设置副本是为了提高ES的高可靠性,单机环境设置为0.

     2)使用head插件创建

     创建映射

    在索引中每个文档都包括了一个或多个field,创建映射就是向索引库中创建field的过程,下边是document和field
    与关系数据库的概念的类比:
    文档(Document)----------------Row记录
    字段(Field)-------------------Columns 列

    我们要把课程信息存储到ES中,这里我们创建课程信息的映射,先来一个简单的映射,如下:
    发送:post http://localhost:9200/索引库名称 /类型名称/_mapping
    创建类型为xc_course的映射,共包括三个字段:name、description、studymondel
    由于ES6.0版本还没有将type彻底删除,所以暂时把type起一个没有特殊意义的名字。
    post 请求:http://localhost:9200/xc_course/doc/_mapping
    表示:在 xc_course索引库下的doc类型下创建映射。doc是类型名,可以自定义,在ES6.0中要弱化类型的概念,
    给它起一个没有具体业务意义的名称。

    {
      "properties": {   
               "name": {
                  "type": "text"
               },
               "description": {
                  "type": "text"
               },
               "studymodel": {
                  "type": "keyword"
               }
            }
    }

    映射创建成功,查看head界面:

      创建文档

    ES中的文档相当于MySQL数据库表中的记录。
    发送:put 或Post http://localhost:9200/xc_course/doc/id值
    (如果不指定id值ES会自动生成ID)
    http://localhost:9200/xc_course/doc/4028e58161bcf7f40161bcf8b77c0000

    {
      "name":"Bootstrap开发框架",
      "description":"Bootstrap是由Twitter推出的一个前台页面开发框架,在行业之中使用较为广泛。此开发框架包
    含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长页面开发的程序人员)轻松的实现一个不受浏览器限制的
    精美界面效果。",
      "studymodel":"201001"
    }

    使用postman测试:

     搜索文档

    1、根据课程id查询文档
    发送:get http://localhost:9200/xc_course/doc/4028e58161bcf7f40161bcf8b77c0000
    使用 postman测试:

     IK 分词器

    在添加文档时会进行分词,索引中存放的就是一个一个的词(term),当你去搜索时就是拿关键字去匹配词,最终
    找到词关联的文档。
    测试当前索引库使用的分词器:
    post 发送:localhost:9200/_analyze
    {"text":"测试分词器,后边是测试内容:spring cloud实战"}
    结果如下:

    会发现分词的效果将 “测试” 这个词拆分成两个单字“测”和“试”,这是因为当前索引库使用的分词器对中文就是单字
    分词。

    两种分词模式

    ik分词器有两种分词模式:ik_max_word和ik_smart模式。
    1、ik_max_word
    会将文本做最细粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为“中华人民共和国、中华人民、中华、
    华人、人民共和国、人民、共和国、大会堂、大会、会堂等词语。
    2、ik_smart
    会做最粗粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为中华人民共和国、人民大会堂。
    测试两种分词模式:

    发送: post localhost:9200/_analyze
    {"text":"中华人民共和国人民大会堂","analyzer":"ik_smart" }

     索引管理

    搭建工程

    ES客户端

    本教程准备采用 Java High Level REST Client,如果它有不支持的功能,则使用Java Low Level REST Client。
    添加依赖:

    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch‐rest‐high‐level‐client</artifactId>
        <version>6.2.1</version>
    </dependency>
    <dependency>
        <groupId>org.elasticsearch</groupId>
        <artifactId>elasticsearch</artifactId>
        <version>6.2.1</version>
    </dependency>

    创建搜索工程

    创建搜索工程(maven工程):xc-service-search,添加RestHighLevelClient依赖及junit依赖。
    pom.xml

    <?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‐search</artifactId>
        <dependencies>
            <dependency>
                <groupId>com.xuecheng</groupId>
                <artifactId>xc‐framework‐model</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>com.xuecheng</groupId>
                <artifactId>xc‐service‐api</artifactId>
                <version>1.0‐SNAPSHOT</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring‐boot‐starter‐web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring‐boot‐starter‐web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.elasticsearch.client</groupId>
                <artifactId>elasticsearch‐rest‐high‐level‐client</artifactId>
                <version>6.2.1</version>
            </dependency>
            <dependency>
                <groupId>org.elasticsearch</groupId>
                <artifactId>elasticsearch</artifactId>
                <version>6.2.1</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring‐boot‐starter‐test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
     </dependency>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons‐io</artifactId>
            </dependency>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons‐lang3</artifactId>
            </dependency>
        </dependencies>
    </project>
    View Code

    2、配置文件
    application.yml

    server:
      port: ${port:40100}
    spring:
      application:
        name: xc‐search‐service
    xuecheng:
      elasticsearch:
        hostlist: ${eshostlist:127.0.0.1:9200} #多个结点中间用逗号分隔

    3、配置类
    创建com.xuecheng.search.config包
    在其下创建配置类

    package com.xuecheng.search.config;
    import org.apache.http.HttpHost;
    import org.elasticsearch.client.RestClient;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    @Configuration
    public class ElasticsearchConfig {
        @Value("${xuecheng.elasticsearch.hostlist}")
        private String hostlist;
        @Bean
        public RestHighLevelClient restHighLevelClient(){
    //解析hostlist配置信息
            String[] split = hostlist.split(",");
            //创建HttpHost数组,其中存放es主机和端口的配置信息
            HttpHost[] httpHostArray = new HttpHost[split.length];
            for(int i=0;i<split.length;i++){
                String item = split[i];
                httpHostArray[i] new HttpHost(item.split(":")[0], Integer.parseInt(item.split(":")
    [1]), "http");
            }
            //创建RestHighLevelClient客户端
            return new RestHighLevelClient(RestClient.builder(httpHostArray));
        }
    //项目主要使用RestHighLevelClient,对于低级的客户端暂时不用    
        @Bean
        public RestClient restClient(){
            //解析hostlist配置信息
            String[] split = hostlist.split(",");
            //创建HttpHost数组,其中存放es主机和端口的配置信息
            HttpHost[] httpHostArray = new HttpHost[split.length];
            for(int i=0;i<split.length;i++){
                String item = split[i];
                httpHostArray[i] new HttpHost(item.split(":")[0], Integer.parseInt(item.split(":")
    [1]), "http");
            }
            return RestClient.builder(httpHostArray).build();
        }
    }
    View Code

    4、启动类

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

     创建索引库

    Java Client

    @SpringBootTest
    @RunWith(SpringRunner.class)
    public class TestIndex {
        @Autowired
        RestHighLevelClient client;
        @Autowired
        RestClient restClient;
       //创建索引库
        @Test
        public void testCreateIndex() throws IOException {
            //创建索引请求对象,并设置索引名称
            CreateIndexRequest createIndexRequest = new CreateIndexRequest("xc_course");
            //设置索引参数
            createIndexRequest.settings(Settings.builder().put("number_of_shards",1)
                                                .put("number_of_replicas",0));
            //设置映射
            createIndexRequest.mapping("doc"," {
    " +
                    " 	"properties": {
    " +
                    "           "name": {
    " +
                    "              "type": "text",
    " +
                    "              "analyzer":"ik_max_word",
    " +
                    "              "search_analyzer":"ik_smart"
    " +
                    "           },
    " +
                    "           "description": {
    " +
                    "              "type": "text",
    " +
                    "              "analyzer":"ik_max_word",
    " +
                    "              "search_analyzer":"ik_smart"
    " +
                    "           },
    " +
                    "           "studymodel": {
    " +
                    "              "type": "keyword"
    " +
                    "           },
    " +
                    "           "price": {
    " +
                    "              "type": "float"
    " +
                    "           }
    " +
                    "        }
    " +
                    "}", XContentType.JSON);
            //创建索引操作客户端
            IndicesClient indices = client.indices();
            //创建响应对象
            CreateIndexResponse createIndexResponse = indices.create(createIndexRequest);
            //得到响应结果
            boolean acknowledged = createIndexResponse.isAcknowledged();
            System.out.println(acknowledged);
        }
        //删除索引库
        @Test
        public void testDeleteIndex() throws IOException {
            //删除索引请求对象
    DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("xc_course");
            //删除索引
            DeleteIndexResponse deleteIndexResponse = client.indices().delete(deleteIndexRequest);
            //删除索引响应结果
            boolean acknowledged = deleteIndexResponse.isAcknowledged();
            System.out.println(acknowledged);
        }
    }
    View Code

     

    添加文档

    //添加文档
        @Test
        public void testAddDoc() throws IOException {
           //准备json数据
            Map<String, Object> jsonMap = new HashMap<>();
            jsonMap.put("name", "spring cloud实战");
            jsonMap.put("description", "本课程主要从四个章节进行讲解: 1.微服务架构入门 2.spring cloud
    基础入门 3.实战Spring Boot 4.注册中心eureka。");
            jsonMap.put("studymodel", "201001");
            SimpleDateFormat dateFormat =new SimpleDateFormat("yyyy‐MM‐dd HH:mm:ss");
            jsonMap.put("timestamp", dateFormat.format(new Date()));
            jsonMap.put("price", 5.6f);
            //索引请求对象
            IndexRequest indexRequest = new IndexRequest("xc_course","doc");
            //指定索引文档内容
            indexRequest.source(jsonMap);
            //索引响应对象
            IndexResponse indexResponse = client.index(indexRequest);
    //获取响应结果
            DocWriteResponse.Result result = indexResponse.getResult();
            System.out.println(result);
        }
    View Code

     

    查询文档

    //查询文档
    @Test
    public void getDoc() throws IOException {
        GetRequest getRequest new GetRequest(
                "xc_course",
                "doc",
                "4028e581617f945f01617f9dabc40000");
        GetResponse getResponse = client.get(getRequest);
        boolean exists = getResponse.isExists();
        Map<String, Object> sourceAsMap = getResponse.getSourceAsMap();
        System.out.println(sourceAsMap);
    }

  • 相关阅读:
    图的深度遍历
    判断森林中有多少棵树
    基于邻接矩阵的广度优先搜索
    第三届程序设计知识竞赛网络赛
    大数相乘
    a+b=x,ab=y
    poj3278
    不敢死队
    单链表中重复元素删除
    poj2506
  • 原文地址:https://www.cnblogs.com/anan-java/p/12259968.html
Copyright © 2011-2022 走看看