第七天課程預覽和發佈

一、Eureka註冊中心

1.1 Eureka介紹

Spring Cloud Eureka 是對Netflix公司的Eureka的二次封裝,它實現了服務治理的功能,Spring Cloud Eureka提供服務端與客戶端,服務端即是Eureka服務註冊中心,客戶端完成微服務向Eureka服務的註冊與發現。服務端和客戶端均採用Java語言編寫。下圖顯示了Eureka Server與Eureka Client的關係:

1.2 高可用Eureka搭建

Eureka Server 高可用環境需要部署兩個Eureka server,它們互相向對方註冊。如果在本機啓動兩個Eureka需要注意兩個Eureka Server的端口要設置不一樣,這裏部署一個Eureka Server工程,將端口設置爲可配置,製作兩個Eureka Server啓動腳本,啓動不同的端口,如下圖:

  • 在實際使用時Eureka Server至少部署兩臺服務器,實現高可用。
  • 兩臺Eureka Server互相註冊。
  • 微服務需要連接兩臺Eureka Server註冊,當其中一臺Eureka死掉也不會影響服務的註冊與發現。
  • 微服務會定時向Eureka server發送心跳,報告自己的狀態。
  • 微服務從註冊中心獲取服務地址以RESTful方式發起遠程調用。

1、創建xc-govern-center工程

包結構爲:com.xuecheng.govern.center

2、添加依賴

在父工程中添加:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>Finchley.SR1</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

在Eureka Server工程中添加:

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

3、啓動類

package com.xuecheng.govern.center;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

/**
 * @Author: 98050
 * @Time: 2019-05-10 14:43
 * @Feature: 註冊中心
 */
@EnableEurekaServer
@SpringBootApplication
public class GovernCenterApplication {

    public static void main(String[] args) {
        SpringApplication.run(GovernCenterApplication.class, args);
    }
}

4、@EnableEurekaServer

需要在啓動類上用@EnableEurekaServer標識此服務爲Eureka服務

5、配置文件

server:
  port: ${PORT:50101} #服務端口

spring:
  application:
    name: xc-govern-center #指定服務名
    
eureka:
  client:
    register-with-eureka: true #服務註冊,是否將自己註冊到Eureka服務中
    fetch-registry: true #服務發現,是否從Eureka中獲取註冊信息
    service-url:
      defaultZone: ${EUREAK_SERVER:http://eureka02:50102/eureka/}
  server:
    enable-self-preservation: false #是否開啓自我保護模式
    eviction-interval-timer-in-ms: 60000 #服務註冊表清理間隔,單位爲毫秒
  instance:
    hostname: ${EUREKA_DOMAIN:eureka01}

register-with-eureka:被其它服務調用時需向Eureka註冊,單機版本爲false,集羣爲true

fetch-registry:需要從Eureka中查找要調用的目標服務時需要設置爲true,單機版本爲false,集羣爲true

service-url.defaultZone 配置上報Eureka服務地址高可用狀態配置對方的地址,單機狀態配置自己的地址

enable-self-preservation:自保護設置。Eureka Server有一種自我保護模式,當微服務不再向Eureka Server上報狀態,Eureka Server會從服務列表將此服務刪除,如果出現網絡異常情況(微服務正常),此時Eureka server進入自保護模式,不再將微服務從服務列表刪除。在開發階段建議關閉自保護模式

eviction-interval-timer-in-ms:清理失效結點的間隔,在這個時間段內如果沒有收到該結點的上報則將結點從服務列表中剔除。

instance.hostname:Eureka 組成高可用,兩個Eureka互相向對方註冊,這裏需要通過域名或主機名訪問,這裏設置兩個Eureka服務的主機名分別爲 eureka01、eureka02

6、啓動腳本

7、啓動

1557475571333

1557475622971

1.3 服務註冊

1.3.1 將CMS註冊到Eureka Server

1、在cms服務中添加依賴

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

2、在application.yml配置

eureka:
  client:
    register-with-eureka: true #服務註冊開關
    fetch-registry: true #服務發現開關
    service-url:
      defaultZone: ${EUREAK_SERVER:http://localhost:50101/eureka/}
  instance:
    prefer-ip-address: true  #將自己的ip地址註冊到Eureka
    ip-address: ${IP_ADDRESS:127.0.0.1}
    instance-id: ${spring.application.name}:${server.port} #指定實例id

3、在啓動類上添加註解

@EnableDiscoveryClient

4、啓動cms,查看服務註冊情況

1.3.2 將manage-course註冊到Eureka Server

方法同上

最終結果:

1557481868600

1557481894889

二、Feign遠程調用

在前後端分離架構中,服務層被拆分成了很多的微服務,服務與服務之間難免發生交互,比如:課程發佈需要調用CMS服務生成靜態化頁面。

1、cms服務將自己註冊到註冊中心。

2、課程管理服務從註冊中心獲取cms服務的地址。

3、課程管理服務遠程調用cms服務。

2.1 Ribbon

2.1.1 Ribbon介紹

Ribbon是Netflix公司開源的一個負載均衡的項目(https://github.com/Netflix/ribbon),它是一個基於HTTP、TCP的客戶端負載均衡器。

1、什麼是負載均衡?

負載均衡是微服務架構中必須使用的技術,通過負載均衡來實現系統的高可用、集羣擴容等功能。負載均衡可通過硬件設備及軟件來實現,硬件比如:F5、Array等,軟件比如:LVS、Nginx等。

如下圖是負載均衡的架構圖:

用戶請求先到達負載均衡器(也相當於一個服務),負載均衡器根據負載均衡算法將請求轉發到微服務。負載均衡算法有:輪訓、隨機、加權輪訓、加權隨機、地址哈希等方法,負載均衡器維護一份服務列表,根據負載均衡算法將請求轉發到相應的微服務上,所以負載均衡可以爲微服務集羣分擔請求,降低系統的壓力。

2、什麼是客戶端負載均衡?

上圖是服務端負載均衡,客戶端負載均衡與服務端負載均衡的區別在於客戶端要維護一份服務列表,Ribbon從Eureka Server獲取服務列表,Ribbon根據負載均衡算法直接請求到具體的微服務,中間省去了負載均衡服務。

如下圖是Ribbon負載均衡的流程圖:

1、在消費微服務中使用Ribbon實現負載均衡,Ribbon先從Eureka Server中獲取服務列表

2、Ribbon根據負載均衡的算法去調用微服務

2.1.2 Ribbon測試

Spring Cloud引入Ribbon配合 restTemplate 實現客戶端負載均衡。Java中遠程調用的技術有很多,如:webservice、socket、rmi、Apache HttpClient、OkHttp等,互聯網項目使用基於http的客戶端較多,本項目使OkHttp。

1、在客戶端添加Ribbon依賴

在課程管理微服務中配置Ribbon依賴

2、配置Ribbon參數

ribbon:
  ConnectTimeout: 5000 # 連接超時時間(ms)
  ReadTimeout: 6000 # 通信超時時間(ms)
  OkToRetryOnAllOperations: false # 是否對所有操作重試,如果是get則可以,如果是post,put等操作在沒有實現冪等的情況下是很危險的,所以設置爲false
  MaxAutoRetriesNextServer: 3 # 同一服務不同實例的重試次數
  MaxAutoRetries: 2 # 同一實例的重試次數

3、負載均衡測試

1)啓動兩個CMS服務,注意端口要不一致

端口接收啓動參數,然後啓動兩個CMS服務,啓動完成後觀察Eureka Server的服務列表

2)定義RestTemplate,使用@LoadBalanced註解

在課程管理服務類中定義RestTemplate

package com.xuecheng.managecourse;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.client.RestTemplate;

import javax.sql.DataSource;


/**
 * @author 98050
 */
@SpringBootApplication
@EntityScan("com.xuecheng.framework.domain.course")//掃描實體類
@ComponentScan(basePackages={"com.xuecheng.api"})//掃描接口
@ComponentScan(basePackages={"com.xuecheng.managecourse"})
@ComponentScan(basePackages={"com.xuecheng.framework"})//掃描common下的所有類
@EnableTransactionManagement
@EnableDiscoveryClient
public class ManageCourseApplication {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(ManageCourseApplication.class, args);
    }
    
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
    }
}

3)測試代碼

在課程管理服務工程創建單元測試代碼,遠程調用cms的查詢頁面接口:

@Autowired
private RibbonLoadBalancerClient client;
//負載均衡調用
@Test
public void testRibbon(){
    //服務id
    String serviceId = "XC-SERVICE-MANAGE-CMS";
    for (int i = 0; i < 10; i++) {
        ServiceInstance instance = this.client.choose(serviceId);
        System.out.println(instance.getHost() + ":" + instance.getPort());
    }
}

4)負載均衡測試

添加@LoadBalanced註解後,通過RibbonLoadBalancerClient查詢服務地址,然後調用10次,查看輸出結果:

默認負載均衡策略是輪詢。

2.2 Feign

2.2.1 Feign介紹

Feign是Netflix公司開源的輕量級rest客戶端,使用Feign可以非常方便的實現Http 客戶端。Spring Cloud引入Feign並且集成了Ribbon實現客戶端負載均衡調用。

2.2.2 Feign測試

1、在客戶端添加依賴

在課程管理服務添加以下依賴:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<dependency>
    <groupId>com.netflix.feign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

2、定義FeignClient接口

package com.xuecheng.managecourse.client;

import com.xuecheng.api.cms.CmsPageControllerApi;
import com.xuecheng.framework.client.XcServiceList;
import org.springframework.cloud.openfeign.FeignClient;

/**
 * @Author: 98050
 * @Time: 2019-05-11 18:18
 * @Feature:
 */
@FeignClient(value = XcServiceList.XC_SERVICE_MANAGE_CMS)
public interface CmsPageClient extends CmsPageControllerApi{
}

直接繼承接口文檔即可~

3、啓動類添加@EnableFeignClients註解

4、測試

@Autowired
private CmsPageClient cmsPageClient;

@Test
public void testFeign(){
    CmsPageResult cmsPageResult = cmsPageClient.findById("5a795ac7dd573c04508f3a56");
    System.out.println(cmsPageResult);
}

三、課程預覽技術方案

3.1 需求分析

課程預覽是爲了保證課程發佈後的正確性,通過課程預覽可以直觀的通過課程詳情頁面看到課程的信息是否正確,通過課程預覽看到的頁面內容和課程發佈後的頁面內容是一致的。

下圖是課程詳情頁面的預覽圖:

3.2 課程詳情頁面技術方案

課程預覽所瀏覽到的頁面就是課程詳情頁面,需要先確定課程詳情頁面的技術方案後方可確定課程預覽的技術方案。

3.2.1 技術需求

課程詳情頁面是向用戶展示課程信息的窗口,課程相當於網站的商品,本頁面的訪問量會非常大。此頁面的內容設計不僅要展示出課程核心重要的內容而且用戶訪問頁面的速度要有保證,有統計顯示打開一個頁面超過4秒用戶就走掉了,所以本頁面的性能要求是本頁面的重要需求。

本頁面另一個需求就是SEO,要非常有利於爬蟲抓取頁面上信息,並且生成頁面快照,利於用戶通過搜索引擎搜索課程信息。

3.2.2 解決方案

如何在保證SEO的前提下提高頁面的訪問速度 :

方案1:

對於信息獲取類的需求,要想提高頁面速度就要使用緩存來減少或避免對數據庫的訪問,從而提高頁面的訪問速度。下圖是使用緩存與不使用緩存的區別

此頁面爲動態頁面,會根據課程的不同而不同,方案一採用傳統的JavaEE Servlet/jsp的方式在Tomcat完成頁面渲染,相比不加緩存速度會有提升。

優點:使用redis作爲緩存,速度有提升。

缺點:採用Servlet/jsp動態頁面渲染技術,服務器使用Tomcat,面對高併發量的訪問存在性能瓶頸。

方案2:

對於不會頻繁改變的信息可以採用頁面靜態化的技術,提前讓頁面生成html靜態頁面存儲在nginx服務器,用戶直接訪問nginx即可,對於一些動態信息可以訪問服務端獲取json數據在頁面渲染。

優點:使用Nginx作爲web服務器,並且直接訪問html頁面,性能出色。

缺點:需要維護大量的靜態頁面,增加了維護的難度。

最終選擇方案2作爲課程詳情頁面的技術解決方案,將課程詳情頁面生成Html靜態化頁面,併發布到Nginx上。

3.3 課程預覽技術方案

根據要求:課程詳情頁面採用靜態化技術生成Html頁面,課程預覽的效果要與最終靜態化的Html頁面內容一致。所以,課程預覽功能也採用靜態化技術生成Html頁面,課程預覽使用的模板與課程詳情頁面模板一致,這樣就可以保證課程預覽的效果與最終課程詳情頁面的效果一致。

操作流程:

1、製作課程詳情頁面模板

2、開發課程詳情頁面數據模型的查詢接口(爲靜態化提供數據)

3、調用cms課程預覽接口通過瀏覽器瀏覽靜態文件

四、課程詳情頁面靜態化

4.1 靜態頁面測試

4.1.1 頁面內容組成

我們在編寫一個頁面時需要知道哪些信息是靜態信息,哪些信息爲動態信息,下圖是頁面的設計圖:

打開靜態頁面,觀察每部分的內容。

紅色表示動態信息,紅色以外表示靜態信息。

紅色動態信息:表示一個按鈕,根據用戶的登錄狀態、課程的購買狀態顯示按鈕的名稱及按鈕的事件。
包括以下信息內容:

1、課程信息

課程標題、價格、課程等級、授課模式、課程圖片、課程介紹、課程目錄。

2、課程統計信息

課程時長、評分、收藏人數

3、教育機構信息

公司名稱、公司簡介

4、教育機構統計信息

好評數、課程數、學生人數

5、教師信息

老師名稱、老師介紹

4.1.2 頁面拆分

將頁面拆分成如下頁面:

1、頁頭

本頁頭文件和門戶使用的頁頭爲同一個文件。
參考:代碼\頁面與模板\include\header.html

2、頁面尾
本頁尾文件和門戶使用的頁尾爲同一個文件。
參考:代碼\頁面與模板\include\footer.html

3、課程詳情主頁面
每個課程對應一個文件,命名規則爲:課程id.html(課程id動態變化)
模板頁面參考:\代碼\頁面與模板\course\detail\course_main_template.html

4、教育機構頁面
每個教育機構對應一個文件,文件的命名規則爲:company_info_公司id.html(公司id動態變化)
參考:代碼\頁面與模板\company\company_info_template.html

5、老師信息頁面
每個教師信息對應一個文件,文件的命名規則爲:teacher_info_教師id.html(教師id動態變化)

參考:代碼\頁面與模板\teacher\teacher_info_template01.html

6、課程統計頁面
每個課程對應一個文件,文件的命名規則爲:course_stat_課程id.json(課程id動態變化)
參考:\代碼\頁面與模板\stat\course\course_stat_template.json

7、教育機構統計頁面
每個教育機構對應一個文件,文件的命名規則爲:company_stat_公司id.json(公司id動態變化)
參考:\代碼\頁面與模板\stat\company\company_stat_template.json

4.1.3 靜態頁面測試

4.1.3.1 頁面加載思路

頁面路徑如下:

靜態頁面目錄\static\course\detail\course_main_template.html

1、主頁面

我們需要在主頁面中通過SSI加載:頁頭、頁尾、教育機構、教師信息

2、異步加載課程統計與教育機構統計信息

課程統計信息(json)、教育機構統計信息(json)

3、馬上學習按鈕事件

用戶點擊“馬上學習”會根據課程收費情況、課程購買情況執行下一步操作。

4.1.3.2 靜態資源虛擬主機

靜態資源虛擬主機負責處理課程詳情、公司信息、老師信息、統計信息等頁面的請求:

將課程資料中的“靜態頁面目錄”中的目錄拷貝到任意一個文件夾下

在nginx中配置靜態虛擬主機如下:

    server {
        listen      91;
        server_name localhost;

        #公司信息
        location /static/company/{
            alias E:/Java_Demo/xuecheng/develop/xuecheng/static/company/;
        }

         #老師信息
        location /static/teacher/{
            alias E:/Java_Demo/xuecheng/develop/xuecheng/static/teacher/;
        }

         #統計信息
        location /static/stat/{
            alias E:/Java_Demo/xuecheng/develop/xuecheng/static/stat/;
        }

         #公司信息
        location /course/detail/{
            alias E:/Java_Demo/xuecheng/develop/xuecheng/static//course/detail/;
        } 
    }

2、通過www.xuecheng.com虛擬主機轉發靜態資源

由於課程頁面需要通過SSI加載頁頭和頁尾所以需要通過www.xuecheng.com虛擬主機轉發到靜態資源
在www.xuecheng.com虛擬主機加入如下配置:

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

配置upstream實現請求轉發到資源服務虛擬機:

	#靜態資源服務
    upstream static_server_pool{
        server 127.0.0.1:91 weight=10;
    }

4.1.3.3 門戶靜態資源路徑

門戶中的一些圖片、樣式等靜態資源統一通過/static路徑對外提供服務,在www.xuecheng.com虛擬主機中配置如下:

        location /static/img/{
            alias E:/Java_Demo/xuecheng/xcEdu_UI/xc-ui-pc-static-portal/img/;
        }
        location /static/css/{
            alias E:/Java_Demo/xuecheng/xcEdu_UI/xc-ui-pc-static-portal/css/;
        }
        location /static/js/{
            alias E:/Java_Demo/xuecheng/xcEdu_UI/xc-ui-pc-static-portal/js/;
        }
        location /static/plugins/{
            alias E:/Java_Demo/xuecheng/xcEdu_UI/xc-ui-pc-static-portal/plugins/;
            add_header Access‐Control‐Allow‐Origin http://ucenter.xuecheng.com;
            add_header Access‐Control‐Allow‐Credentials true;
            add_header Access‐Control‐Allow‐Methods GET;
        }

cors跨域參數:

  • Access-Control-Allow-Origin:允許跨域訪問的外域地址,如果允許任何站點跨域訪問則設置爲*,通常這是不建議的。
  • Access-Control-Allow-Credentials: 允許客戶端攜帶證書訪問
  • Access-Control-Allow-Methods:允許客戶端跨域訪問的方法

4.1.3.4 頁面測試

重啓nginx,請求http://www.xuecheng.com/course/detail/course_main_template.html

4.1.3.5 頁面動態腳本

爲了方便日後的維護,我們將javascript實現的動態部分單獨編寫一個html 文件,在門戶的include目錄下定義course_detail_dynamic.html文件,此文件通過ssi包含在課程詳情頁面中.

所有的課程公用一個 頁面動態腳本。

在課程詳情主頁面下端添加如下代碼,通過SSI技術包含課程詳情頁面動態腳本文件:

本頁面使用vue.js動態獲取信息,vue實例創建的代碼如下:

主要查看 created鉤子函數的內容。

var body= new Vue({   //創建一個Vue的實例
     el: "#body", //掛載點是id="app"的地方

     data: {
         editLoading: false,
         title:'測試',
         courseId:'',
         charge:'',//203001免費,203002收費
         learnstatus:0,//課程狀態,1:馬上學習,2:立即報名、3:立即購買
         course:{},
         companyId:'template',
         company_stat:[],
         course_stat:{"s601001":"","s601002":"","s601003":""}


     },
     methods: {
         //學習報名
         addopencourse(){
            
         },
         //立即購買
         buy(){
             
         },
         createOrder(){
             
         },
         getLearnstatus(){//獲取學習狀態

         }

     },
    created() {
        this.courseId = courseId;
        this.charge = charge
        this.getLearnstatus();
        //獲取教育機構的統計數據
        queryCompanyStat(this.companyId).then((res)=>{
            console.log(res)
            if(res.stat){
                this.company_stat = res.stat
                console.log(this.company_stat)
            }

        })
        //獲取課程的統計數據
        queryCourseStat(this.courseId).then((res)=>{
            console.log(res)
            if(res.stat){
                let stat = res.stat
               for(var i=0;i<stat.length;i++){
                   this.course_stat['s'+stat[i].id] = stat[i].value
               }
            }
            console.log(this.course_stat)

        })
     },
     mounted(){
        // alert(courseId)

     }
 })

4.2 課程數據模型查詢接口

靜態化操作需要模型數據方可進行靜態化,課程數據模型由課程管理服務提供,僅供課程靜態化程序調用使用。

4.2.1 接口定義

1、響應結果類型

package com.xuecheng.framework.domain.course.ext;

import com.xuecheng.framework.domain.course.CourseBase;
import com.xuecheng.framework.domain.course.CourseMarket;
import com.xuecheng.framework.domain.course.CoursePic;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.io.Serializable;

/**
 * @Author: 98050
 * @Time: 2019-05-15 21:46
 * @Feature:
 */
@Data
@ToString
@NoArgsConstructor
public class CourseView implements Serializable {

    private static final long serialVersionUID = -915749022529361861L;
    /**
     * 課程基礎信息
     */
    private CourseBase courseBase;
    /**
     * 課程營銷信息
     */
    private CourseMarket courseMarket;
    /**
     * 課程圖片
     */
    private CoursePic coursePic;
    /**
     * 教學計劃
     */
    private TeachplanNode teachplanNode;
}

2、請求類型

String:課程id

3、接口定義

/**
 * 課程視圖查詢
 * @param id 課程id
 * @return
 */
@ApiOperation("課程視圖查詢")
@ApiImplicitParams({
        @ApiImplicitParam(name = "id",value = "課程id",required = true,paramType = "path",dataType = "String"),
})
@GetMapping("/courseview/{id}")
CourseView courseView(@PathVariable("id") String id);

4.2.2 DAO

無需新建dao

4.2.3 Service

接口:

在CourseService中添加方法:

/**
 * 查詢課程詳情
 * @param id 課程id
 * @return
 */
CourseView getCourseView(String id);

實現:

/**
 * 查詢課程詳情
 * @param id 課程id
 * @return
 */
@Override
public CourseView getCourseView(String id) {
    CourseView courseView = new CourseView();
    final CountDownLatch countDownLatch = new CountDownLatch(4);
    ExecutorService executorService = Executors.newFixedThreadPool(4);
    try {
        CourseBase courseBaseResult = executorService.submit(() -> {
            //1.查詢課程基礎信息
            countDownLatch.countDown();
            Optional<CourseBase> courseBase = this.courseBaseRepository.findById(id);
            return courseBase.orElse(null);
        }).get();
        CourseMarket courseMarketResult = executorService.submit(() -> {
            //2.查詢課程營銷信息
            countDownLatch.countDown();
            Optional<CourseMarket> courseMarket = this.courseMarketRepository.findById(id);
            return courseMarket.orElse(null);
        }).get();
        CoursePic coursePicResult = executorService.submit(() -> {
            //3.查詢課程圖片
            countDownLatch.countDown();
            Optional<CoursePic> coursePic = this.coursePicRepository.findById(id);
            return coursePic.orElse(null);
        }).get();
        TeachplanNode teachplanNodeResult = executorService.submit(() -> {
            //4.查詢課程教學計劃
            countDownLatch.countDown();
            return this.teachPlanMapper.selectList(id);
        }).get();
        countDownLatch.await();
        courseView.setCourseBase(courseBaseResult);
        courseView.setCourseMarket(courseMarketResult);
        courseView.setCoursePic(coursePicResult);
        courseView.setTeachplanNode(teachplanNodeResult);
        return courseView;
    }catch (Exception e){
        ExceptionCast.cast(CourseCode.COURSE_VIEW_QUERY_ERROR);
    }
    return courseView;
}

採用多線程的方式去查詢,用線程池來管理線程,線程使用Callable,用submit來提交。

如果查詢使用其他服務的話這樣可以縮短查詢時間,模板代碼!!!!!!!!!!

4.2.4 Controller

@Override
public CourseView courseView(@PathVariable("id") String id) {
    return this.courseService.getCourseView(id);
}

4.2.5 測試

4.3 課程信息模板設計

在確定了靜態化所需要的數據模型之後,就可以編寫頁面模板了,課程詳情頁面由多個靜態化頁面組成,所以我們需要創建多個頁面模板,本章節創建課程詳情頁面的主模板,即課程信息模板。

4.3.1 模板內容

完整的模板請參考 “課程詳情頁面模板\course.ftl“ 文件,下邊列出模板中核心的內容:

1.課程基本信息

<div class="banner-left">
            <p class="tit">${courseBase.name}</p>
            <p class="pic"><span class="new-pic">特惠價格¥${(courseMarket.price)!""}</span> <span class="old-pic">原價¥${(courseMarket.price_old)!""}</span></p>
            <p class="info">
                <a href="http://ucenter.xuecheng.com/#/learning/${courseBase.id}/0"  target="_blank" v-if="learnstatus == 1" v-cloak>馬上學習</a>
                <a href="#"  @click="addopencourse" v-if="learnstatus == 2" v-cloak>立即報名</a>
                <a href="#"  @click="buy" v-if="learnstatus == 3" v-cloak>立即購買</a>
                <span><em>難度等級</em>
		 <#if courseBase.grade=='200001'>
		低級
                <#elseif courseBase.grade=='200002'>
		中級
		 <#elseif courseBase.grade=='200003'>
		高級
		</#if>
                </span>
                <span><em>課程時長</em><stat v-text="course_stat.s601001"></stat>
                </span>
                <span><em>評分</em><stat v-text="course_stat.s601002"></stat></span>
                <span><em>授課模式</em>
                  <#if courseBase.studymodel=='201001'>
		自由學習
                <#elseif courseBase.studymodel=='201002'>
		任務式學習
		</#if>
                </span>
            </p>
        </div>
        <div class="banner-rit">
	    
	    <#if (coursePic.pic)??>
	     <p><img src="http://img.xuecheng.com/${coursePic.pic}" alt="" width="270" height="156"> </p>
	     <#else>
		 <p><img src="/static/img/widget-video.png" alt="" width="270" height="156"> </p>
	    </#if>
           
            <p class="vid-act"><span> <i class="i-heart"></i>收藏 <stat v-text="course_stat.s601003"></stat> </span> <span>分享 <i class="i-weixin"></i><i class="i-qq"></i></span></p>
        </div>

2.課程計劃

<div class="content">
    <#if (teachplanNode.children)??>
        <#list teachplanNode.children as firstNode>
            <div class="item">
                <div class="title act"><i class="i-chevron-top"></i>${firstNode.pname}</div>
                <div class="about">${firstNode.description!}</div>
                <div class="drop-down" style="height: ${firstNode.children?size * 50}px;">
                    <ul class="list-box">
                        <#list firstNode.children as secondNode>
                            <li>${secondNode.pname}</li>
                            </#list>
                    </ul>
                </div>
            </div>
            </#list>
        </#if>
</div>

3.頁頭

局部代碼如下:

<body data-spy="scroll" data-target="#articleNavbar" data-offset="150">
<!-- 頁面頭部 -->
<!--#include virtual="/include/header.html"-->

4.頁尾

局部代碼如下:

<!-- 頁面底部 -->
<!--底部版權-->
<!--#include virtual="/include/footer.html"-->

5.動態腳本文件

<script>var courseId = "${courseBase.id}"</script>
<!--#include virtual="/include/course_detail_dynamic.html"-->

6.教師信息文件

從課程數據中獲取課程所屬的教師Id,這裏由於教師信息管理功能沒有開發我們使用固定的教師信息文件:

<div class="content-com course">
<div class="title"><span>課程製作</span></div>
<!--#include virtual="/teacher/teacher_info_template01.html"-->
</div>

7.教育機構文件

同教師信息一樣,由於教育機構功能模塊沒有開發,這裏我們使用固定的教育機構文件:

<div class="about-teach">
<!--機構信息-->
<!--#include virtual="/company/company_info_template.html"-->
</div>

4.3.2 模板測試

使用test-freemarker工程測試模板

編寫模板過程採用test-freemarker工程測試模板。

將course.ftl拷貝到test-freemarker工程的resources/templates下,並在test-freemarker工程的controller中添加測試方法

@RequestMapping("/course")
public String course(Map<String,Object> map){
    String dataUrl = "http://localhost:31200/course/courseview/4028e58161bd3b380161bd3bcd2f0000";
    ResponseEntity<Map> entity = restTemplate.getForEntity(dataUrl,Map.class);
    Map body = entity.getBody();
    map.putAll(body);
    return "course";
}

結果:

注意:上邊的測試頁面不顯示樣式,原因是頁面通過SSI包含了頁面頭,而使用test-freemarker工程無法加載頁頭,測試模板主要查看html頁面內容是否正確,待課程預覽時解決樣式不顯示問題。

4.3.3 模板保存

模板編寫並測試通過後要在數據庫保存:

1、模板信息保存在xc_cms數據庫(mongodb)的cms_template表

2、模板文件保存在mongodb的GridFS中。

第一步:將模板文件上傳到GridFS中

由於本教學項目中模板管理模塊沒有開發,所以我們使用Junit代碼向GridFS中保存:

/**
 * 存儲文件
 * @throws FileNotFoundException
 */
@Test
public void testGridFs2() throws FileNotFoundException {
    //1.要存儲的文件
    File file = new File("D:\\course.ftl");
    //2.定義輸入流
    FileInputStream inputStream = new FileInputStream(file);
    //3.向GridFS存儲文件
    ObjectId objectId = gridFsTemplate.store(inputStream, "課程詳情模板文件","");
    //4.得到文件ID
    String fileId = objectId.toString();
    System.out.println(fileId);
}

第二步:向cms_template表添加模板記錄:

{ 
    "_class" : "com.xuecheng.framework.domain.cms.CmsTemplate", 
    "siteId" : "5a751fab6abb5044e0d19ea1", 
    "templateName" : "課程詳情正式模板", 
    "templateParameter" : "courseid", 
    "templateFileId" : "5cde5670591f428a74df26e7"
}

4.3.4 其他模板

除了課程詳情主頁面需要設計模板所有靜態化的頁面都要設計模板,如下:

教育機構頁面模板、教師信息頁面模板、課程統計信息json模板、教育機構統計信息json模板。

在這裏只實現課程詳情主頁面模板的製作和測試,其它頁面模板的開發參考課程詳情頁面去實現。

五、課程預覽功能開發

5.1 需求分析

課程預覽功能將使用cms系統提供的頁面預覽功能,業務流程如下:

1、用戶進入課程管理頁面,點擊課程預覽,請求到課程管理服務

2、課程管理服務遠程調用cms添加頁面接口向cms添加課程詳情頁面

3、課程管理服務得到cms返回課程詳情頁面id,並拼接生成課程預覽Url

4、課程管理服務將課程預覽Url給前端返回

5、用戶在前端頁面請求課程預覽Url,打開新窗口顯示課程詳情內容

5.2 CMS頁面預覽測試

CMS已經提供了頁面預覽功能,課程預覽功能要使用CMS頁面預覽接口實現,下邊通過cms頁面預覽接口測試課程預覽的效果。

1、啓動CMS內容管理頁面,新增課程詳情頁面。

選擇課程詳情模板,然後填寫dataUrl,結果:

2、課程詳情頁面使用SSI

由於Nginx先請求cms的課程預覽功能得到html頁面,再解析頁面中的ssi標籤,這裏必須保證cms頁面預覽返回的頁面的Content-Type爲text/html;charset=utf-8

在cms頁面預覽的controller方法中添加:

response.setHeader("Content-type", "text/html;charset=utf-8");

3、測試

訪問:http://www.xuecheng.com/cms/preview/5cde60e0591f4218d484e013

總結:課程預覽的時候只要把課程信息對應的cms_page插入到數據庫即可,然後調用cms服務提供的頁面預覽接口完成頁面預覽!

5.3 CMS添加頁面接口

現有的接口是爲前端CMS管理提供的接口

/**
 * 頁面新增
 * @param cmsPage
 * @return
 */
@ApiOperation("頁面添加")
@ApiResponses({
        @ApiResponse(code = 10000,message = "操作成功"),
        @ApiResponse(code = 11111,message = "操作失敗")
})
@PostMapping("/add")
CmsPageResult add(@RequestBody CmsPage cmsPage);

現在要做的是爲課程預覽時,要向cms_page中添加或更新課程詳情頁面。爲什麼不直接調用add接口呢?

因爲對那些不存在的頁面執行添加操作,已存在的頁面執行更新操作。所以要專門開發一個接口,此接口

由課程管理服務在課程預覽時調用。

5.3.1 Api接口

/**
 * 此接口由課程管理服務在課程預覽時調用,更新或者新增課程詳情頁面
 * @param cmsPage 頁面內容
 * @return
 */
@ApiOperation("頁面保存")
@ApiResponses({
        @ApiResponse(code = 10000,message = "操作成功"),
        @ApiResponse(code = 11111,message = "操作失敗")
})
@PostMapping("/save")
CmsPageResult save(CmsPage cmsPage);

5.3.2 Service

接口:

/**
 * 保存頁面,更新或者新增課程詳情頁面
 * @param cmsPage
 * @return
 */
CmsPageResult save(CmsPage cmsPage);

實現:

/**
 * 保存頁面,更新或者新增課程詳情頁面
 * @param cmsPage
 * @return
 */
@Override
public CmsPageResult save(CmsPage cmsPage) {
    //1.檢查頁面是否存在,根據頁面名稱、站點Id、頁面webPath查詢
    CmsPage cmsPage1 = this.cmsPageRepository.findByPageNameAndSiteIdAndPageWebPath(cmsPage.getPageName(), cmsPage.getSiteId(), cmsPage.getPageWebPath());
    if (cmsPage1 != null){
        //更新
        return this.update(cmsPage1.getPageId(), cmsPage);
    }else {
        //添加
        return this.add(cmsPage);
    }
}

5.3.3 Controller

@Override
public CmsPageResult save(@RequestBody CmsPage cmsPage) {
    return this.cmsService.save(cmsPage);
}

5.4 課程預覽服務端

5.4.1 Api定義

此Api是課程管理前端請求服務端進行課程預覽的Api

請求:課程Id

響應:課程預覽Url

1、定義響應類型

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

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

/**
 * @Author: 98050
 * @Time: 2019-05-17 16:46
 * @Feature:
 */
@Data
@ToString
@NoArgsConstructor
public class CoursePublicResult extends ResponseResult {
    private String previewUrl;
    public CoursePublicResult(ResultCode resultCode,String previewUrl){
        super(resultCode);
        this.previewUrl = previewUrl;
    }
}

2、接口定義

/**
 * 課程詳情頁面預覽
 * @param id 課程id
 * @return
 */
@ApiOperation("課程預覽")
@ApiImplicitParams({
        @ApiImplicitParam(name = "id",value = "課程id",required = true,paramType = "path",dataType = "String"),
})
@PostMapping("/preview/{id}")
CoursePublicResult preview(@PathVariable("id") String id);

5.4.2 創建Feign Client

在課程管理工程創建CMS服務的Feign Client,通過此Client遠程請求cms添加頁面

package com.xuecheng.managecourse.client;

import com.xuecheng.api.cms.CmsPageControllerApi;
import com.xuecheng.framework.client.XcServiceList;
import org.springframework.cloud.openfeign.FeignClient;

/**
 * @Author: 98050
 * @Time: 2019-05-11 18:18
 * @Feature:
 */
@FeignClient(value = XcServiceList.XC_SERVICE_MANAGE_CMS)
public interface CmsPageClient extends CmsPageControllerApi{
}

5.4.3 Service

1、配置信息

因爲在進行課程預覽的時候需要添加cms_page信息,所以將一個固定的信息通過配置文件的方式進行獲取,而不是直接寫死。

course‐publish:
  siteId: 5a751fab6abb5044e0d19ea1  #站點id
  templateId: 5aec5dd70e661808240ab7a6 #模板id
  previewUrl: http://www.xuecheng.com/cms/preview/  #預覽地址
  pageWebPath: /course/detail/  #訪問路徑
  pagePhysicalPath: /course/detail/ #物理路徑
  dataUrlPre: http://localhost:31200/course/courseview/  #數據地址

2、接口

/**
 * 課程詳情頁面預覽
 * @param courseId 課程id
 * @return
 */
CoursePublicResult preview(String courseId);

3、實現

/**
 * 課程詳情頁面預覽
 * @param courseId 課程id
 * @return
 */
@Override
public CoursePublicResult preview(String courseId) {

    CourseBase courseBase = this.getCourseById(courseId);

    //1.發佈課程預覽頁面,構建cmsPage對象
    CmsPage cmsPage = new CmsPage();
    cmsPage.setSiteId(publish_siteId);
    cmsPage.setTemplateId(publish_templateId);
    cmsPage.setPageWebPath(publish_page_webPath);
    cmsPage.setDataUrl(publish_dataUrlPre + courseId);
    cmsPage.setPagePhysicalPath(publish_page_physicalPath);
    cmsPage.setPageName(courseId+".html");
    cmsPage.setPageAliase(courseBase.getName());
    //2.保存cms信息
    CmsPageResult save = this.cmsPageClient.save(cmsPage);
    if (!save.isSuccess()){
        return new CoursePublicResult(CommonCode.FAIL,null);
    }
    //3.頁面id
    String pageId = save.getCmsPage().getPageId();
    //4.頁面url
    String pageUrl = previewUrl + pageId;
    return new CoursePublicResult(CommonCode.SUCCESS,pageUrl);
}

5.4.4 Controller

@Override
public CoursePublicResult preview(@PathVariable("id") String id) {
    return this.courseService.preview(id);
}

5.5 前端開發

5.5.1 api方法

// 預覽課程
export const preview = id => {
  return http.requestPost(apiUrl + '/course/preview/' + id)
}

5.5.2 頁面

<template>
  <div>
    <template>
      <div>
        <el-card class="box-card">
          <div slot="header" class="clearfix">
            <span>課程預覽</span>
          </div>
          <div class="text item">
            <el-button type="primary"  @click.native="preview" >課程預覽</el-button>
            <br/><br/>
            <span v-if="previewurl && previewurl!=''"><a :href="previewurl" target="_blank">點我查看課程預覽頁面 </a> </span>
          </div>
        </el-card>
      </div>
    </template>
  </div>
</template>
<script>
  import * as sysConfig from '@/../config/sysConfig';
  import * as courseApi from '../../api/course';
  import utilApi from '../../../../common/utils';
  import * as systemApi from '../../../../base/api/system';
export default{

  data() {
    return {
      dotype: '',
      courseid: '',
      course: {"id": "", "name": "", "status": ""},
      previewurl: ''
    }
  },
  methods:{
    //預覽
    preview(){
        //調用課程管理服務的預覽接口,得到課程預覽url
      courseApi.preview(this.courseid).then((res) => {
        if(res.success){
          this.$message.error('預覽頁面生成成功,請點擊下方預覽鏈接');
          if(res.previewUrl){
            //預覽url
            this.previewurl = res.previewUrl
          }
        }else{
          this.$message.error(res.message);
        }
      });
    },
  },
  mounted(){
    //課程id
    this.courseid = this.$route.params.courseid;
  }

  }
</script>
<style>

</style>

六、課程發佈

6.1 需求分析

課程發佈後將生成正式的課程詳情頁面,課程發佈後用戶即可瀏覽課程詳情頁面,並開始課程的學習。
課程發佈生成課程詳情頁面的流程與課程預覽業務流程相同,如下:

1、用戶進入教學管理中心,進入某個課程的管理界面

2、點擊課程發佈,前端請求到課程管理服務

3、課程管理服務遠程調用CMS生成課程發佈頁面,CMS將課程詳情頁面發佈到服務器

4、課程管理服務修改課程發佈狀態爲 “已發佈”,並向前端返回發佈成功

5、用戶在教學管理中心點擊“課程詳情頁面”鏈接,查看課程詳情頁面內容

6.2 CMS一鍵發佈

6.2.1 需求分析

根據需求分析內容,需要在cms服務增加頁面發佈接口供課程管理服務調用,此接口的功能如下:

1、接收課程管理服務發佈的頁面信息

2、將頁面信息添加到 數據庫(mongodb)

3、對頁面信息進行靜態化

4、將頁面信息發佈到服務器

6.2.2 接口定義

1、創建響應結果類型

頁面發佈成功cms返回頁面的url

頁面URL=cmsSite.siteDomain+cmsSite.siteWebPath+ cmsPage.pageWebPath + cmsPage.pageName

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

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

/**
 * @Author: 98050
 * @Time: 2019-05-20 22:27
 * @Feature: 課程詳情頁面發佈返回結果
 */
@Data
@NoArgsConstructor
public class CmsPostPageResult extends ResponseResult {
    private String pageUrl;

    public CmsPostPageResult(ResultCode resultCode,String pageUrl) {
        super(resultCode);
        this.pageUrl = pageUrl;
    }
}

2、在api工程定義頁面發佈接口

/**
 * 課程頁面詳情發佈
 * @param cmsPage
 * @return
 */
@ApiOperation("一鍵發佈頁面")
@ApiResponses({
        @ApiResponse(code = 10000,message = "操作成功"),
        @ApiResponse(code = 11111,message = "操作失敗")
})
@PostMapping("/postPageQuick")
CmsPostPageResult postPageQuick(@RequestBody CmsPage cmsPage);

6.2.3 Service

接口:

/**
 * 發佈課程詳情頁面
 * @param cmsPage
 * @return
 */
CmsPostPageResult postCoursePage(CmsPage cmsPage);

實現:

/**
 * 發佈課程詳情頁面
 * @param cmsPage
 * @return
 */
@Override
public CmsPostPageResult postCoursePage(CmsPage cmsPage) {
    //1.添加頁面
    CmsPageResult result = this.save(cmsPage);
    if (!result.isSuccess()){
        return new CmsPostPageResult(CommonCode.FAIL,null);
    }
    CmsPage cmsPage1 = result.getCmsPage();
    //2.要發佈的頁面Id
    String pageId = cmsPage1.getPageId();
    //3.發佈頁面
    ResponseResult responseResult = this.postPage(pageId);
    if (!responseResult.isSuccess()){
        return new CmsPostPageResult(CommonCode.FAIL,null);
    }
    //4.得到頁面的url,url = 站點域名+站點webpath+頁面webpath+頁面名稱
    //4.1獲取站點信息
    Optional<CmsSite> optional = this.cmsSiteRepository.findById(cmsPage.getSiteId());
    if (!optional.isPresent()){
        ExceptionCast.cast(CmsCode.CMS_SITE_NOTEXISTS);
    }
    CmsSite cmsSite = optional.get();
    //4.2拼裝url
    return new CmsPostPageResult(CommonCode.SUCCESS, cmsSite.getSiteDomain() + cmsSite.getSiteWebPath() + cmsPage1.getPageWebPath() + cmsPage1.getPageName());
}

6.2.4 Controller

@Override
public CmsPostPageResult postPageQuick(@RequestBody CmsPage cmsPage) {
    return  this.cmsService.postCoursePage(cmsPage);
}

6.3 課程發佈接口

課程發佈就是將靜態化的頁面保存到服務器指定路徑下,並且修改課程的當前狀態、課程索引、課程緩存等。

課程狀態的數據字典如下:

6.3.1 API接口

此Api接口由課程管理提供,由課程管理前端調用此Api接口,實現課程發佈。

在api工程下課程管理包下定義接口:

/**
 * 課程詳情頁面發佈
 * @param courseId
 * @return
 */
@ApiOperation("發佈課程")
@PostMapping("/publish/{id}")
CoursePublicResult publish(@PathVariable("id") String courseId);

6.3.2 Service

1、配置課程發佈頁面的參數

因爲要把頁面發佈到服務器指定的路徑下,所以要對配置參數進行修改。先看一下cm_client服務中的頁面發佈功能的實現,需要獲取頁面最後的存放路徑:

//3.拼接頁面的物理
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(pagePhysicalPath).append(sitePhysicalPath).append(cmsPage.getPageName());

所以要對pagePhysicalPath和sitePhysicalPath進行修改

course‐publish:
  siteId: 5ce3ecba591f42a4e44ce10b  #站點id
  templateId: 5aec5dd70e661808240ab7a6 #模板id
  previewUrl: http://www.xuecheng.com/cms/preview/  #預覽地址
  pageWebPath: /course/detail/  #訪問路徑
  pagePhysicalPath: E:/Java_Demo/xuecheng/develop/xuecheng/static #頁面的物理路徑
  dataUrlPre: http://localhost:31200/course/courseview/  #數據地址

2、接口

/**
 * 課程詳情頁面發佈
 * @param courseId 課程id
 * @return
 */
CoursePublicResult publish(String courseId);

3、實現

/**
 * 課程詳情頁面發佈
 * @param courseId 課程id
 * @return
 */
@Override
@Transactional(rollbackFor = Exception.class)
public CoursePublicResult publish(String courseId) {
    //1.發佈課程詳情頁面
    CmsPostPageResult cmsPostPageResult = publishPage(courseId);
    if (!cmsPostPageResult.isSuccess()){
        ExceptionCast.cast(CourseCode.COURSE_PUBLISH_ERROR);
    }
    //2.更新課程狀態
    CourseBase courseBase = saveCoursePubState(courseId);
    //3.課程索引
    //4.課程緩存
    //5.封裝返回結果
    return new CoursePublicResult(CommonCode.SUCCESS,cmsPostPageResult.getPageUrl());
}
private CmsPostPageResult publishPage(String courseId) {
    //構造cms_page對象
    CmsPage cmsPage = constructCmsPage(courseId);
    return cmsPageClient.postPageQuick(cmsPage);
}
private CmsPage constructCmsPage(String courseId) {
    CourseBase courseBase = this.getCourseById(courseId);

    CmsPage cmsPage = new CmsPage();
    cmsPage.setSiteId(publish_siteId);
    cmsPage.setTemplateId(publish_templateId);
    cmsPage.setPageWebPath(publish_page_webPath);
    cmsPage.setDataUrl(publish_dataUrlPre + courseId);
    cmsPage.setPagePhysicalPath(publish_page_physicalPath);
    cmsPage.setPageName(courseId+".html");
    cmsPage.setPageAliase(courseBase.getName());

    return cmsPage;
}
private CourseBase saveCoursePubState(String courseId) {
    CourseBase courseBase = this.getCourseById(courseId);
    //更新發布狀態
    courseBase.setStatus("202002");
    return this.courseBaseRepository.save(courseBase);
}

6.3.3 Controller

@Override
public CoursePublicResult publish(@PathVariable("id") String courseId) {
    return this.courseService.publish(courseId);
}

6.4 測試

6.4.1 Nginx中進行配置

在nginx配置課程詳情頁面的虛擬主機,實現訪問:http://www.xuecheng.com/course/detail/4028e58161bd3b380161bd3bcd2f0000.html

1558492889063

靜態資源服務器:

cms會將課程預覽頁面發佈到服務器的E:/Java_Demo/xuecheng/develop/xuecheng/static/course/detail/下,然後通過http://www.xuecheng.com/course/detail/進行訪問!

6.4.2 準備工作

1、檢查課程詳情頁面的站點信息

這裏面的站點id一定要與配置文件中的一致:

2、檢查課程詳情模板信息

通過測試代碼添加模板,如果有則不用重複添加,這裏返回的模板id一定要與上面的配置文件中的模板id一致。

/**
 * 存儲文件
 * @throws FileNotFoundException
 */
@Test
public void testGridFs2() throws FileNotFoundException {
    //1.要存儲的文件
    File file = new File("D:\\course.ftl");
    //2.定義輸入流
    FileInputStream inputStream = new FileInputStream(file);
    //3.向GridFS存儲文件
    ObjectId objectId = gridFsTemplate.store(inputStream, "課程詳情模板文件","");
    //4.得到文件ID
    String fileId = objectId.toString();
    System.out.println(fileId);
}

6.4.3 cms_client啓動

啓動兩個cms_client服務,分別對不同的隊列進行監聽

監聽門戶主站和課程詳情

配置文件:

server:
  port: ${PORT:31000}
spring:
  application:
    name: xc-service-manage-cms-client
  data:
    mongodb:
      uri: mongodb://root:root@localhost:27017
      database: xc_cms
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /
xuecheng:
  mq:
    #cms客戶端監控的隊列名稱(不同的客戶端監控的隊列不能重複)
#    queue: queue_cms_postpage_01
#    routingKey: 5a751fab6abb5044e0d19ea1  #此routingKey爲門戶站點ID
    queue: ${QUEUE:queue_cms_postpage_03}
    routingKey: ${ROUTINGKEY:5ce3ecba591f42a4e44ce10b} #此routingKey爲課程詳情站點ID

啓動配置:

1558496121135

1558496140303

6.4.4 單元測試

1、啓動RabbitMQ服務

2、啓動cms服務

3、啓動cms_client

頁面發佈前:

1558583164684

Swagger接口測試:

1558583206628

結果:

1558583228955

1558583241640

訪問:http://www.xuecheng.com//course/detail/4028e58161bd3b380161bd3bcd2f0000.html

1558583261292

6.5 前端開發

6.5.1 API方法

// 發佈課程
export const publish = id => {
  return http.requestPost(apiUrl + '/course/publish/' + id)
}

6.5.2 頁面

<template>
  <div>
    <template>
      <div>
        <el-card class="box-card">
          <div slot="header" class="clearfix">
            <span>課程預覽</span>
          </div>
          <div class="text item">
            <el-button type="primary"  @click.native="preview" >課程預覽</el-button>
            <br/><br/>
            <span v-if="previewurl && previewurl!=''"><a :href="previewurl" target="_blank">點我查看課程預覽頁面 </a> </span>
          </div>
        </el-card>
        <el-card class="box-card">
          <div slot="header" class="clearfix">
            <span>課程發佈</span>
          </div>
          <div class="text item">
            <div v-if="course.status == '202001'">
              狀態:製作中<br/>
              <el-button type="primary"  @click.native="publish" >新課程發佈</el-button>
            </div>
            <div v-else-if="course.status == '202003'">
              狀態:已下線
              <br/><br/>
              <span><a :href="'http://www.xuecheng.com/course/detail/'+this.courseid+'.html'" target="_blank">點我查看課程詳情頁面 </a> </span>
            </div>
            <div v-else-if="course.status == '202002'">
              狀態:已發佈<br/>
              <el-button type="primary"  @click.native="publish" >修改發佈</el-button>
              <br/><br/>
              <span><a :href="'http://www.xuecheng.com/course/detail/'+this.courseid+'.html'" target="_blank">點我查看課程詳情頁面 </a> </span>
            </div>
          </div>
        </el-card>
      </div>
    </template>
  </div>
</template>
<script>
  import * as sysConfig from '@/../config/sysConfig';
  import * as courseApi from '../../api/course';
  import utilApi from '../../../../common/utils';
  import * as systemApi from '../../../../base/api/system';
export default{

  data() {
    return {
      dotype: '',
      courseid: '',
      course: {"id": "", "name": "", "status": ""},
      previewurl: ''
    }
  },
  methods:{
    //預覽
    preview(){
        //調用課程管理服務的預覽接口,得到課程預覽url
      courseApi.preview(this.courseid).then((res) => {
        if(res.success){
          this.$message.error('預覽頁面生成成功,請點擊下方預覽鏈接');
          if(res.previewUrl){
            //預覽url
            this.previewurl = res.previewUrl
          }
        }else{
          this.$message.error(res.message);
        }
      });
    },
    publish(){
      //課程發佈
      courseApi.publish(this.courseid).then(res=>{
          if(res.success){
              this.$message.success("發佈成功,請點擊下邊的鏈接查詢課程詳情頁面")

          }else{
            this.$message.error(res.message)
          }

      })
    },
    getCourseView(){
      courseApi.findCourseView(this.courseid).then(res=>{
        if(res && res.courseBase){
            //獲取課程狀態
            this.course.status = res.courseBase.status;
        }

      })
    }

  },
  mounted(){
    //課程id
    this.courseid = this.$route.params.courseid;
    //查詢課程信息
    this.getCourseView();
  }

  }
</script>
<style>

</style>

發佈成功重新查詢課程,如果課程狀態已更改則顯示課程詳情頁面的鏈接。

在鉤子方法中查詢課程信息

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章