Day09_課程預覽 Eureka Feign
1 Eureka註冊中心
1.1 需求分析
在前後端分離架構中,服務層被拆分成了很多的微服務,微服務的資訊如何管理?Spring Cloud中提供服務註冊中 心來管理微服務資訊。
為什麼要用註冊中心?
1、微服務數量眾多,要進行遠端呼叫就需要知道服務端的ip地址和埠,註冊中心幫助我們管理這些服務的ip和 埠。
2、微服務會實時上報自己的狀態,註冊中心統一管理這些微服務的狀態,將存在問題的服務踢出服務列表,客戶 端獲取到可用的服務進行呼叫。
1.3 Eureka註冊中心
1.3.1 Eureka介紹
Spring Cloud Eureka 是對Netflix公司的Eureka的二次封裝,它實現了服務治理的功能,Spring Cloud Eureka提供服務端與客戶端,服務端即是Eureka服務註冊中心,客戶端完成微服務向Eureka服務的註冊與發現。服務端和 客戶端均採用Java語言編寫。下圖顯示了Eureka Server與Eureka Client的關係:
1、Eureka Server是服務端,負責管理各各微服務結點的資訊和狀態。
2、在微服務上部署Eureka Client程式,遠端訪問Eureka Server將自己註冊在Eureka Server。
3、微服務需要呼叫另一個微服務時從Eureka Server中獲取服務呼叫地址,進行遠端呼叫。
1.3.2 Eureka Server搭建
1.3.2.1 單機環境搭建
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工程新增:
<!-- 匯入Eureka服務的依賴-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
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 HackerStar * @create 2020-08-16 11:26 */ @EnableEurekaServer @SpringBootApplication public class GovernCenterApplication { public static void main(String[] args) { SpringApplication.run(GovernCenterApplication.class); } }
4、@EnableEurekaServer
需要在啟動類上用@EnableEurekaServer標識此服務為Eureka服務
5、從其它服務拷貝application.yml和logback-spring.xml。
application.yml的配置內容如下:
server:
port: 50101
spring:
application:
name: xc‐govern‐center #指定服務名
eureka:
client:
register-with-eureka: false #服務註冊,是否將自己註冊到Eureka服務中
fetchRegistry: false #服務發現,是否從Eureka中獲取註冊資訊
serviceUrl: #Eureka客戶端與Eureka服務端的互動地址,高可用狀態配置對方的地址,單機狀態配置自己(如果不配置則預設本機8761埠)
defaultZone: http://localhost:50101/eureka/
server:
enable‐self‐preservation: false #是否開啟自我保護模式
eviction‐interval‐timer‐in‐ms: 60000 #服務登錄檔清理間隔(單位毫秒,預設是60*1000)
registerWithEureka:被其它服務呼叫時需向Eureka註冊
fetchRegistry:需要從Eureka中查詢要呼叫的目標服務時需要設定為true
serviceUrl.defaultZone 配置上報Eureka服務地址高可用狀態配置對方的地址,單機狀態配置自己
enable-self-preservation:自保護設定,下邊有介紹
eviction-interval-timer-in-ms:清理失效結點的間隔,在這個時間段內如果沒有收到該結點的上報則將結點從服務 列表中剔除
5、啟動Eureka Server
啟動Eureka Server,瀏覽50101埠。
說明:
上圖紅色提示資訊:
THE SELF PRESERVATION MODE IS TURNED OFF.THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.
自我保護模式被關閉。在網路或其他問題的情況下可能不會保護例項失效。
Eureka Server有一種自我保護模式,當微服務不再向Eureka Server上報狀態,Eureka Server會從服務列表將此 服務刪除,如果出現網路異常情況(微服務正常),此時Eureka server進入自保護模式,不再將微服務從服務列 表刪除。
在開發階段建議關閉自保護模式。
1.3.2.2 高可用環境搭建
Eureka Server 高可用環境需要部署兩個Eureka server,它們互相向對方註冊。如果在本機啟動兩個Eureka需要 注意兩個Eureka Server的埠要設定不一樣,這裡我們部署一個Eureka Server工程,將埠設定成可配置,製作兩個Eureka Server啟動指令碼,啟動不同的埠,如下圖:
1、在實際使用時Eureka Server至少部署兩臺伺服器,實現高可用。
2、兩臺Eureka Server互相註冊。
3、微服務需要連線兩臺Eureka Server註冊,當其中一臺Eureka死掉也不會影響服務的註冊與發現。
4、微服務會定時向Eureka server傳送心跳,報告自己的狀態。
5、微服務從註冊中心獲取服務地址以RESTful方式發起遠端呼叫。
配置如下:
1、埠可配置
server:
port: ${PORT:50101} #服務埠
2、Eureka服務端的互動地址可配置
eureka:
client:
register-with-eureka: false #服務註冊,是否將自己註冊到Eureka服務中
fetchRegistry: false #服務發現,是否從Eureka中獲取註冊資訊
serviceUrl: #Eureka客戶端與Eureka服務端的互動地址,高可用狀態配置對方的地址,單機狀態配置自己(如果不配置則預設本機8761埠)
defaultZone: ${EUREKA_SERVER:http://eureka02:50102/eureka/}
3、配置hostname
Eureka 組成高可用,兩個Eureka互相向對方註冊,這裡需要通過域名或主機名訪問,這裡我們設定兩個Eureka服 務的主機名分別為 eureka01、eureka02。
完整的eureka配置如下:
server:
port: ${PORT:50101}
spring:
application:
name: xc‐govern‐center #指定服務名
eureka:
client:
register-with-eureka: true #服務註冊,是否將自己註冊到Eureka服務中
fetchRegistry: true #服務發現,是否從Eureka中獲取註冊資訊
serviceUrl: #Eureka客戶端與Eureka服務端的互動地址,高可用狀態配置對方的地址,單機狀態配置自己(如果不配置則預設本機8761埠)
defaultZone: ${EUREKA_SERVER:http://eureka02:50102/eureka/}
server:
enable‐self‐preservation: false #是否開啟自我保護模式
eviction‐interval‐timer‐in‐ms: 60000 #服務登錄檔清理間隔(單位毫秒,預設是60*1000)
instance:
hostname: ${EUREKA_DOMAIN:eureka01}
4、在IDEA中製作啟動指令碼
執行兩個啟動指令碼,分別瀏覽:
Eureka主畫面如下:
1.3.3 服務註冊
1.3.3.1 將cms註冊到Eureka Server
下邊實現cms向Eureka Server註冊。
1、在cms服務中新增依賴
<!-- 匯入Eureka客戶端的依賴-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2、在application.yml配置
eureka:
client:
registerWithEureka: true #服務註冊開關
fetchRegistry: true #服務發現開關
serviceUrl: #Eureka客戶端與Eureka服務端進行互動的地址,多箇中間用逗號分隔
defaultZone: ${EUREKA_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 ,表示它是一個Eureka的客戶端
4、重新整理Eureka Server檢視註冊情況
1.3.3.2 將manage-course註冊到Eureka Server
方法同上:
1、在manage-course工程中新增spring-cloud-starter-eureka依賴
2、在application.yml配置eureka
3、在啟動類上添加註解 @EnableDiscoveryClient
2 Feign遠端呼叫
在前後端分離架構中,服務層被拆分成了很多的微服務,服務與服務之間難免發生互動,比如:課程釋出需要呼叫 CMS服務生成課程靜態化頁面,本節研究微服務遠端呼叫所使用的技術。
下圖是課程管理服務遠端呼叫CMS服務的流程圖:
工作流程如下:
1、cms服務將自己註冊到註冊中心。
2、課程管理服務從註冊中心獲取cms服務的地址。
3、課程管理服務遠端呼叫cms服務。
2.1 Ribbon
2.1.1 Ribbon介紹
Ribbon是Netflix公司開源的一個負載均衡的專案(https://github.com/Netflilx/ribbon),它是一個基於HTTP、 TCP的客戶端負載均衡器。
1、什麼是負載均衡?
負載均衡是微服務架構中必須使用的技術,通過負載均衡來實現系統的高可用、叢集擴容等功能。負載均衡可通過 硬體裝置及軟體來實現,硬體比如:F5、Array等,軟體比如:LVS、Nginx等。
如下圖是負載均衡的架構圖:
使用者請求先到達負載均衡器(也相當於一個服務),負載均衡器根據負載均衡演算法將請求轉發到微服務。負載均衡 演算法有:輪訓、隨機、加權輪訓、加權隨機、地址雜湊等方法,負載均衡器維護一份服務列表,根據負載均衡演算法 將請求轉發到相應的微服務上,所以負載均衡可以為微服務叢集分擔請求,降低系統的壓力。
2、什麼是客戶端負載均衡?
上圖是服務端負載均衡,客戶端負載均衡與服務端負載均衡的區別在於客戶端要維護一份服務列表,Ribbon從 Eureka Server獲取服務列表,Ribbon根據負載均衡演算法直接請求到具體的微服務,中間省去了負載均衡服務。
如下圖是Ribbon負載均衡的流程圖:
1、在消費微服務中使用Ribbon實現負載均衡,Ribbon先從EurekaServer中獲取服務列表。
2、Ribbon根據負載均衡的演算法去呼叫微服務。
2.1.2 Ribbon測試
Spring Cloud引入Ribbon配合 restTemplate 實現客戶端負載均衡。Java中遠端呼叫的技術有很多,如: webservice、socket、rmi、Apache HttpClient、OkHttp等,網際網路專案使用基於http的客戶端較多,本專案使 用OkHttp。
1、在客戶端新增Ribbon依賴:
這裡在課程管理服務配置ribbon依賴。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring‐cloud‐starter‐ribbon</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
由於依賴了spring-cloud-starter-eureka,會自動新增spring-cloud-starter-ribbon依賴
2、配置Ribbon引數
這裡在課程管理服務的application.yml中配置ribbon引數
ribbon:
MaxAutoRetries: 2 #最大重試次數,當Eureka中可以找到服務,但是服務連不上時將會重試
MaxAutoRetriesNextServer: 3 #切換例項的重試次數
OkToRetryOnAllOperations: false #對所有操作請求都進行重試,如果是get則可以,如果是post,put等操作 沒有實現冪等的情況下是很危險的,所以設定為false
ConnectTimeout: 5000 #請求連線的超時時間
ReadTimeout: 6000 #請求處理的超時時間
3、負載均衡測試
1)啟動兩個Course服務,注意埠要不一致
啟動完成觀察Eureka Server的服務列表
2)定義RestTemplate,使用@LoadBalanced註解
在課程管理服務的啟動類中定義RestTemplate
package com.xuecheng.manage_course;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.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.web.client.RestTemplate;
/**
* @author Administrator
* @version 1.0
**/
@EnableDiscoveryClient
@SpringBootApplication
@EntityScan("com.xuecheng.framework.domain.course")//掃描實體類
@ComponentScan(basePackages = {"com.xuecheng.api"})//掃描介面
@ComponentScan(basePackages = {"com.xuecheng.manage_course"})
@ComponentScan(basePackages = {"com.xuecheng.framework"})//掃描common下的所有類
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的查詢頁面介面:
package com.xuecheng.manage_course.dao;
import com.xuecheng.framework.domain.cms.CmsPage;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate;
/**
* @author HackerStar
* @create 2020-08-16 17:45
*/
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestRibbon {
@Autowired
RestTemplate restTemplate;
//負載均衡呼叫
@Test
public void testRibbon() { //服務id
String serviceId = "XC‐SERVICE‐MANAGE‐CMS";
for (int i = 0; i < 10; i++) { //通過服務id呼叫
ResponseEntity<CmsPage> forEntity = restTemplate.getForEntity("http://" + serviceId + "/cms/page/get/5a754adf6abb500ad05688d9", CmsPage.class);
CmsPage cmsPage = forEntity.getBody();
System.out.println(cmsPage);
}
}
}
4)負載均衡測試
新增@LoadBalanced註解後,restTemplate會走LoadBalancerInterceptor攔截器,此攔截器中會通過 RibbonLoadBalancerClient查詢服務地址,可以在此類打斷點觀察每次呼叫的服務地址和埠,兩個cms服務會輪流被呼叫。
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介面
參考Cms的Swagger文件定義FeignClient,注意介面的Url、請求引數型別、返回值型別與Swagger介面一致。
3、啟動類新增@EnableFeignClients註解
4、測試
package com.xuecheng.manage_course.dao;
import com.xuecheng.framework.domain.cms.CmsPage;
import com.xuecheng.manage_course.client.CmsPageClient;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author HackerStar
* @create 2020-08-17 18:51
*/
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestFeign {
@Autowired
CmsPageClient cmsPageClient;
@Test
public void testFeign() {
//通過服務id呼叫cms的查詢頁面介面
CmsPage cmsPage = cmsPageClient.findById("5a754adf6abb500ad05688d9");
System.out.println(cmsPage);
}
}
Feign工作原理如下:
1、啟動類新增@EnableFeignClients註解,Spring會掃描標記了@FeignClient註解的介面,並生成此介面的代理物件
2、@FeignClient(value = "XC_SERVICE_MANAGE_CMS")即指定了cms的服務名稱,Feign會從註冊中心獲取cms服務列表,並通過負載均衡演算法進行服務呼叫
3、在介面方法中使用註解@GetMapping("/cms/page/get/{id}"),指定呼叫的url,Feign將根據url進行遠端呼叫
2.2.4 Feign注意點
SpringCloud對Feign進行了增強相容了SpringMVC的註解 ,我們在使用SpringMVC的註解時需要注意:
1、feignClient介面有引數在引數必須加@PathVariable("XXX")和@RequestParam("XXX")
2、feignClient返回值為複雜物件時其型別必須有無參建構函式
3 課程預覽技術方案
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 課程詳情頁面靜態化
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
2.3.3 靜態頁面測試
2.3.3.1頁面載入思路
開啟課程資料中的“靜 錄”中的課程詳情模板頁面,研究頁面載入的思路。
模板頁面路徑如下:
靜態頁面目錄\static\course\detail\course_main_template.html
1、主頁面
我們需要在主頁面中通過SSI載入:頁頭、頁尾、教育機構、教師資訊
2、非同步載入課程統計與教育機構統計資訊
課程統計資訊(json)、教育機構統計資訊(json)
3、馬上學習按鈕事件
使用者點選“馬上學習”會根據課程收費情況、課程購買情況執行下一步操作
2.3.3.2 靜態資源虛擬主機
1、配置靜態資源虛擬主機
靜態資源虛擬主機負責處理課程詳情、公司資訊、老師資訊、統計資訊等頁面的請求:
將課程資料中的“靜態頁面目錄”中的目錄拷貝到/Users/XinxingWang/Development/WebstormProjects/scEdu/static/下
在nginx中配置靜態虛擬主機如下:
#學成網靜態資源
server {
listen 91;
server_name localhost;
#公司資訊
location /static/company/ {
alias /Users/XinxingWang/Development/WebstormProjects/scEdu/static/company/;
}
#老師資訊
location /static/teacher/ {
alias /Users/XinxingWang/Development/WebstormProjects/scEdu/static/teacher/;
}
#統計資訊
location /static/stat/ {
alias /Users/XinxingWang/Development/WebstormProjects/scEdu/static/stat/;
}
location /course/detail/ {
alias /Users/XinxingWang/Development/WebstormProjects/scEdu/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;
}
2.3.3.3 門戶靜態資源路徑
門戶中的一些圖片、樣式等靜態資源統一通過/static路徑對外提供服務,在www.xuecheng.com虛擬主機中配置如下:
#靜態資源,包括系統所需要的圖片,js、css等靜態資源
location /static/img/ {
alias /Users/XinxingWang/Development/WebstormProjects/scEdu/scEduUI/xc-ui-pc-static-portal/img/;
}
location /static/css/ {
alias /Users/XinxingWang/Development/WebstormProjects/scEdu/scEduUI/xc-ui-pc-static-portal/css/;
}
location /static/js/ {
alias /Users/XinxingWang/Development/WebstormProjects/scEdu/scEduUI/xc-ui-pc-static-portal/js/;
}
location /static/plugins/ {
alias /Users/XinxingWang/Development/WebstormProjects/scEdu/scEduUI/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:允許客戶端跨域訪問的方法
2.3.3.4 頁面測試
請求:http://www.xuecheng.com/course/detail/course_main_template.html測試課程詳情頁面模板是否可以正常瀏覽。
2.3.3.5 頁面動態指令碼
為了方便日後的維護,我們將javascript實現的動態部分單獨編寫一個html 檔案,在門戶的include目錄下定義 course_detail_dynamic.html檔案,此檔案通過ssi包含在課程詳情頁面中。
檔案地址:資料\靜態頁面目錄\include\course_detail_dynamic.html
所有的課程公用一個頁面動態指令碼。
在課程詳情主頁面(course_detail_dynamic.html)下端新增如下程式碼,通過SSI技術包含課程詳情頁面動態指令碼檔案:
<script>var courseId = "template"</script>
<!‐‐#include virtual="/include/course_detail_dynamic.html"‐‐>
</body> </html>
本頁面使用vue.js動態獲取資訊,vue例項建立的程式碼如下:
<script>
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(){
let activeUser= checkActiveUser();
if(activeUser){
addOpencourse(this.courseId).then((res) => {
if(res.success){//報名成功
this.$message.success('報名成功');
this.getLearnstatus()
}else if(res.message){
this.$message.error(res.message);
}else{
this.$message.error('報名失敗,請重新整理頁面重試!');
}
})
}else{
//彈出登入框架
headVm.showlogin()
}
},
//立即購買
buy(){
let activeUser= getActiveUser();
if(activeUser){
$('.popup-box').show()
}else{
//彈出登入框架
headVm.showlogin()
}
},
createOrder(){
createOrder(this.courseId).then((res) => {
this.editLoading = false;
if(res.success){
this.$message.success('訂單建立成功');
//跳轉到支付頁面
window.location = "http://ucenter.xuecheng.com/#/pay/"+res.xcOrders.orderNumber
}else{
if(res.message){
this.$message.error(res.message);
}else{
this.$message.error('訂單建立失敗,請重新整理頁面重試');
}
}
},
(res) => {
this.editLoading = false;
});
},
getLearnstatus(){//獲取學習狀態
//初始學習狀態
//根據課程收費判斷
if(this.charge == '203001'){
this.learnstatus = 2 //免費,報名後即可學習
}else{
this.learnstatus = 3 //收費,需要購買後可學習
}
//如果使用者登入判斷該使用者的學習狀態
let activeUser= getActiveUser();
if(activeUser){
//判斷學生的選課狀態
/*queryLearnstatus(this.courseId).then((res)=>{
console.log(res)
if(res.success){
if(res.status == '501001'){//正常
this.learnstatus = 1 //選課狀態正常,立即學習
}
}
})*/
}
}
},
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)
}
})
</script>
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;
/**
* @author Administrator
* @version 1.0
**/
@Data
@NoArgsConstructor
@ToString
public class CourseView implements java.io.Serializable {
private CourseBase courseBase;
private CoursePic coursePic;
private CourseMarket courseMarket;
private TeachplanNode teachplanNode;
}
2、請求型別
String :課程id
3、介面定義如下
@Api(value = "課程管理介面", description = "課程管理介面,提供課程的增、刪、改、查")
public interface CourseControllerApi {
@ApiOperation("課程檢視查詢")
public CourseView courseview(String id);
}
4.2.2 Dao
需要對course_base、course_market、course_pic、teachplan等資訊進行查詢,
新建課程營銷的dao,其它dao已經存在不用再建。
public interface CourseMarketRepository extends JpaRepository<CourseMarket, String> {
}
4.2.3 Service(CourseService)
//課程檢視查詢
public CourseView getCourseView(String id) {
CourseView courseView = new CourseView();
//查詢課程基本資訊
Optional<CourseBase> optional = courseBaseRepository.findById(id);
if(optional.isPresent()){
CourseBase courseBase = optional.get();
courseView.setCourseBase(courseBase);
}
//查詢課程營銷資訊
Optional<CourseMarket> courseMarketOptional = courseMarketRepository.findById(id);
if(courseMarketOptional.isPresent()) {
CourseMarket courseMarket = courseMarketOptional.get();
courseView.setCourseMarket(courseMarket);
}
//查詢課程圖片資訊
Optional<CoursePic> picOptional = coursePicRepository.findById(id);
if(picOptional.isPresent()) {
CoursePic coursePic = picOptional.get();
courseView.setCoursePic(coursePic);
}
//查詢課程計劃資訊
TeachplanNode teachplanNode = teachplanMapper.selectList(id);
courseView.setTeachplanNode(teachplanNode);
return courseView;
}
4.2.4 Controller(CourseController)
@Override
@GetMapping("/courseview/{id}")
public CourseView courseview(@PathVariable("id") String id) {
return courseService.getCourseView(id);
}
4.2.5 測試
使用swagger-ui或postman測試本介面。
4.3 課程資訊模板設計
在確定了靜態化所需要的資料模型之後,就可以編寫頁面模板了,課程詳情頁面由多個靜態化頁面組成,所以我們 需要建立多個頁面模板,本章節建立課程詳情頁面的主模板,即課程資訊模板。
4.3.1 模板內容
完整的模板請參考 “資料\課程詳情頁面模板\course.ftl“ 檔案,下邊列出模板中核心的內容:
課程基本資訊:
<!--頁面頭部結束-->
<div class="article-banner">
<div class="banner-bg"></div>
<div class="banner-info">
<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>
</div>
</div>
課程計劃:
<div class="articleItem" style="display: none">
<div class="article-cont-catalog">
<div class="article-left-box">
<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>
<div>
頁頭:區域性程式碼如下
<body data-spy="scroll" data-target="#articleNavbar" data-offset="150">
<!-- 頁面頭部 -->
<!--#include virtual="/include/header.html"-->
頁尾:區域性程式碼如下:
<!-- 頁面底部 -->
<!--底部版權-->
<!--#include virtual="/include/footer.html"-->
動態指令碼檔案:
<script>
//課程id
var courseId = "template"
</script>
<!‐‐#include virtual="/include/course_detail_dynamic.html"‐‐>
教師資訊檔案:從課程資料中獲取課程所屬的教師Id,這裡由於教師資訊管理功能沒有開發我們使用固定的教師資訊檔案
<div class="content‐com course">
<div class="title"><span>課程製作</span></div>
<!‐‐#include virtual="/teacher/teacher_info_template01.html"‐‐>
</div>
教育機構檔案:同教師資訊一樣,由於教育機構功能模組沒有開發,這裡我們使用固定的教育機構檔案
<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) {
ResponseEntity<Map> forEntity =
restTemplate.getForEntity("http://localhost:31200/course/courseview/4028e581617f945f01617f9dabc40000", Map.class);
Map body = forEntity.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中儲存:
// 檔案儲存2
@Test
public void testStore2() throws FileNotFoundException {
File file = new File("/Users/XinxingWang/Development/Java/IDEA_Project/xcEduCode/test-freemarker/src/main/resources/templates/course.ftl");
FileInputStream inputStream = new FileInputStream(file); //儲存模版檔案內容
ObjectId gridFSFile = gridFsTemplate.store(inputStream, "course.ftl");
System.out.println(gridFSFile);//5f3b3e6b2e6a2cf5cf524c1a
}
儲存成功需要記錄模板檔案的id,即上邊程式碼中的fileId。
第二步:向cms_template表新增模板記錄(請不要重複新增)
使用Studio 3T連線mongodb,向cms_template新增記錄:
{
"_class" : "com.xuecheng.framework.domain.cms.CmsTemplate",
"siteId" : "5a751fab6abb5044e0d19ea1",
"templateName" : "課程詳情頁面正式模板",
"templateFileId" : "5f3b3e6b2e6a2cf5cf524c1a"
}
4.3.4 其它模板
除了課程詳情主頁面需要設計模板,所有靜態化的頁面都要設計模板,如下: 教育機構頁面模板、教師資訊頁面模板、課程統計資訊json模板、教育機構統計資訊json模板。 本專案我們實現課程詳情主頁面模板的製作和測試,其它頁面模板的開發參考課程詳情頁面去實現。
5 課程預覽功能開發
5.1 需求分析
課程預覽功能將使用cms系統提供的頁面預覽功能,業務流程如下:
1、使用者進入課程管理頁面,點選課程預覽,請求到課程管理服務
2、課程管理服務遠端呼叫cms新增頁面介面向cms新增課程詳情頁面
3、課程管理服務得到cms返回課程詳情頁面id,並拼接生成課程預覽Url
4、課程管理服務將課程預覽Url給前端返回
5、使用者在前端頁面請求課程預覽Url,開啟新視窗顯示課程詳情內容
5.2 CMS頁面預覽測試
CMS已經提供了頁面預覽功能,課程預覽功能要使用CMS頁面預覽介面實現,下邊通過cms頁面預覽介面測試課 程預覽的效果。
1、向cms_page表插入一條頁面記錄或者從cms_page找一個頁面進行測試。
注意:頁面配置一定要正確,需設定正確的模板id和dataUrl。
如下,是一條頁面的記錄:
{
"_class" : "com.xuecheng.framework.domain.cms.CmsPage",
"siteId" : "5a751fab6abb5044e0d19ea1",
"pageName" : "4028e581617f945f01617f9dabc40000.html",
"pageAliase" : "課程詳情頁面測試01",
"pageWebPath" : "/course/detail/",
"pagePhysicalPath" : "/course/detail/",
"pageType" : "1",
"templateId" : "5f3b3fbed8f7057adf10fa37",
"dataUrl" : "http://localhost:31200/course/courseview/4028e581617f945f01617f9dabc40000"
}
使用之前在cms_template中新新增資料的“_id”作為上面Json資料中的templatId
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/5f3b428dd8f7057adf10fa52傳入頁面Id,測試效果如下:
一直都不好使,只能顯示上面那種樣式的,睡了一覺,就好了。。。。。。
5.3 CMS 新增頁面介面
cms服務對外提供新增頁面介面,實現:如果不存在頁面則新增,否則就更新頁面資訊。
此介面由課程管理服務在課程預覽時呼叫。
在該工程下:xc-service-manage-cms建立以下模組。
5.3.1 Api介面
@Api(value = "cms頁面管理介面", description = "cms頁面管理介面,提供頁面的增、刪、改、查")
public interface CmsPageControllerApi {
@ApiOperation(" 儲存頁面")
public CmsPageResult save(CmsPage cmsPage);
}
5.3.2 Service
//儲存頁面,有則更新,沒有則新增
public CmsPageResult save(CmsPage cmsPage) {
//判斷頁面是否存在
CmsPage one = cmsPageRepository.findByPageNameAndSiteIdAndPageWebPath(cmsPage.getPageName(), cmsPage.getSiteId(), cmsPage.getPageWebPath());
if(one!=null){
//進行更新
return this.update(one.getPageId(),cmsPage);
}
return this.add(cmsPage);
}
5.3.3 Controller
@Override
@PostMapping("/save")
public CmsPageResult save(@RequestBody CmsPage cmsPage) {
return pageService.save(cmsPage);
}
5.4 課程預覽服務端
5.4.1 Api定義
此Api是課程管理前端請求服務端進行課程預覽的Api。
請求:課程Id
響應:課程預覽Url
1、定義響應型別
@Data
@ToString
@NoArgsConstructor
public class CoursePublishResult extends ResponseResult {
String previewUrl;//頁面預覽的url,必須得到頁面id才可以拼裝
public CoursePublishResult(ResultCode resultCode, String previewUrl) {
super(resultCode);
this.previewUrl = previewUrl;
}
}
2、介面定義如下
@Api(value = "課程管理介面", description = "課程管理介面,提供課程的增、刪、改、查")
public interface CourseControllerApi {
@ApiOperation("預覽課程")
public CoursePublishResult preview(String id);
}
5.4.2 建立 Feign Client
在課程管理工程建立CMS服務的Feign Client,通過此Client遠端請求cms新增頁面。
package com.xuecheng.manage_course.client;
import com.xuecheng.framework.client.XcServiceList;
import com.xuecheng.framework.domain.cms.CmsPage;
import com.xuecheng.framework.domain.cms.response.CmsPageResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
/**
* @author HackerStar
* @create 2020-08-17 18:47
*/
@FeignClient(value = "SC-SERVICE-MANAGE-CMS") //指定遠端呼叫的服務名
public interface CmsPageClient {
//儲存頁面
@PostMapping("/cms/page/save")
public CmsPageResult save(@RequestBody CmsPage cmsPage);
@GetMapping("/cms/page/get/{id}")
public CmsPage findById(@PathVariable("id") String id);
}
5.4.3 Service
xc-service-manage-course
1、配置新增頁面引數資訊
在application.yml中配置:
course-publish:
siteId: 5a751fab6abb5044e0d19ea1
templateId: 5f3b3fbed8f7057adf10fa37
previewUrl: http://www.xuecheng.com/cms/preview/
pageWebPath: /course/detail/
pagePhysicalPath: /course/detail/
dataUrlPre: http://localhost:31200/course/courseview/
2、Service程式碼如下:
@Service
public class CourseService {
@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;
//根據id查詢課程基本資訊
public CourseBase findCourseBaseById(String courseId){
Optional<CourseBase> baseOptional = courseBaseRepository.findById(courseId);
if(baseOptional.isPresent()){
CourseBase courseBase = baseOptional.get();
return courseBase;
}
ExceptionCast.cast(CourseCode.COURSE_DENIED_DELETE);
return null;
}
//課程預覽
public CoursePublishResult preview(String id) {
//查詢課程
CourseBase courseBaseById = this.findCourseBaseById(id);
//請求cms新增頁面
//準備cmsPage資訊
CmsPage cmsPage = new CmsPage();
cmsPage.setSiteId(publish_siteId);//站點id
cmsPage.setDataUrl(publish_dataUrlPre+id);//資料模型url
cmsPage.setPageName(id+".html");//頁面名稱
cmsPage.setPageAliase(courseBaseById.getName());//頁面別名,就是課程名稱
cmsPage.setPagePhysicalPath(publish_page_physicalpath);//頁面物理路徑
cmsPage.setPageWebPath(publish_page_webpath);//頁面webpath
cmsPage.setTemplateId(publish_templateId);//頁面模板id
//遠端呼叫cms
CmsPageResult cmsPageResult = cmsPageClient.save(cmsPage);
if(!cmsPageResult.isSuccess()){
return new CoursePublishResult(CommonCode.FAIL,null);
}
CmsPage cmsPage1 = cmsPageResult.getCmsPage();
String pageId = cmsPage1.getPageId();
//拼裝頁面預覽的url
String url = previewUrl+pageId;
//返回CoursePublishResult物件(當中包含了頁面預覽的url)
return new CoursePublishResult(CommonCode.SUCCESS,url);
}
}
5.4.4 Controller
public class CourseController implements CourseControllerApi {
@Override
@PostMapping("/preview/{id}")
public CoursePublishResult preview(@PathVariable("id") String id) {
return courseService.preview(id);
}
}
注意:需要在類CmsPageResult上新增@NoArgsConstructor註解
5.4.5 測試
這裡一旦成功就無法訪問該頁面了,原因是走到update方法是,templateId被更換了
原因是yml配置檔案的問題:
修改為:
注:此處是為了找到問題,重新向mongoDB中添加了資料,所以顯示的tempalteId和上面寫的不一樣
但是yml檔案修改完還是沒用,值還是會自動變,所以重啟IDEA。之後正常了。
5.5 前端開發
5.5.1 api 方法
/*預覽課程*/
export const preview = id => {
return http.requestPost(apiUrl + '/course/preview/' + id);
}
5.5.2 頁面
建立 course_pub.vue
<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>