1 頁面發(fā)布
1.1 技術(shù)方案

技術(shù)方案說明:
1、平臺包括多個站點,頁面歸屬不同的站點。
2、發(fā)布一個頁面應(yīng)將該頁面發(fā)布到所屬站點的服務(wù)器上。
3、每個站點服務(wù)部署cms client程序,并與交換機綁定,綁定時指定站點Id為routingKey。
指定站點id為routingKey就可以實現(xiàn)cms client只能接收到所屬站點的頁面發(fā)布消息。
4、頁面發(fā)布程序向MQ發(fā)布消息時指定頁面所屬站點Id為routingKey,將該頁面發(fā)布到它所在服務(wù)器上的cms
client。
路由模式分析如下:
發(fā)布一個頁面,需發(fā)布到該頁面所屬的每個站點服務(wù)器,其它站點服務(wù)器不發(fā)布。
比如:發(fā)布一個門戶的頁面,需要發(fā)布到每個門戶服務(wù)器上,而用戶中心服務(wù)器則不需要發(fā)布。
所以本項目采用routing模式,用站點id作為routingKey,這樣就可以匹配頁面只發(fā)布到所屬的站點服務(wù)器上。
頁面發(fā)布流程圖如下:

1、前端請求cms執(zhí)行頁面發(fā)布。
2、cms執(zhí)行靜態(tài)化程序生成html文件。
3、cms將html文件存儲到GridFS中。
4、cms向MQ發(fā)送頁面發(fā)布消息
5、MQ將頁面發(fā)布消息通知給Cms Client
6、Cms Client從GridFS中下載html文件
7、Cms Client將html保存到所在服務(wù)器指定目錄
1.2 頁面發(fā)布消費方
1.2.1需求分析
功能分析:
創(chuàng)建Cms Client工程作為頁面發(fā)布消費方,將Cms Client部署在多個服務(wù)器上,它負責(zé)接收到頁面發(fā)布 的消息后從
GridFS中下載文件在本地保存。
需求如下:
1、將cms Client部署在服務(wù)器,配置隊列名稱和站點ID。
2、cms Client連接RabbitMQ并監(jiān)聽各自的“頁面發(fā)布隊列”
3、cms Client接收頁面發(fā)布隊列的消息
4、根據(jù)消息中的頁面id從mongodb數(shù)據(jù)庫下載頁面到本地
調(diào)用dao查詢頁面信息,獲取到頁面的物理路徑,調(diào)用dao查詢站點信息,得到站點的物理路徑
頁面物理路徑=站點物理路徑+頁面物理路徑+頁面名稱。
從GridFS查詢靜態(tài)文件內(nèi)容,將靜態(tài)文件內(nèi)容保存到頁面物理路徑下。
1.2.2創(chuàng)建Cms Client工程
1、創(chuàng)建maven工程
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‐manage‐cms‐client</artifactId>
<dependencies>
<dependency>
<groupId>com.xuecheng</groupId>
<artifactId>xc‐framework‐model</artifactId>
<version>1.0‐SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐data‐mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons‐io</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
</dependencies>
</project>
=================================
2、配置文件
在resources下配置application.yml和logback-spring.xml。
application.yml的內(nèi)容如下:
=============================
server:
port: 31000
spring:
application:
name: xc‐service‐manage‐cms‐client
data:
mongodb:
uri: mongodb://root:123@localhost:27017
database: xc_cms
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtualHost: /
xuecheng:
mq:
#cms客戶端監(jiān)控的隊列名稱(不同的客戶端監(jiān)控的隊列不能重復(fù))
queue: queue_cms_postpage_01
routingKey: 5a751fab6abb5044e0d19ea1 #此routingKey為門戶站點ID
===============================
說明:在配置文件中配置隊列的名稱,每個 cms client在部署時注意隊列名稱不要重復(fù)
3、啟動類
=====================
@SpringBootApplication
@EntityScan("com.xuecheng.framework.domain.cms")//掃描實體類
@ComponentScan(basePackages={"com.xuecheng.framework"})//掃描common下的所有類
@ComponentScan(basePackages={"com.xuecheng.manage_cms_client"})
public class ManageCmsClientApplication {
public static void main(String[] args) {
SpringApplication.run(ManageCmsClientApplication.class, args);
}
}
====================
1.2.3 RabbitmqConfifig配置類
消息隊列設(shè)置如下:
1、創(chuàng)建“ex_cms_postpage”交換機
2、每個Cms Client創(chuàng)建一個隊列與交換機綁定
3、每個Cms Client程序配置隊列名稱和routingKey,將站點ID作為routingKey。
==================================
@Configuration
public class RabbitmqConfig {
//隊列bean的名稱
public static final String QUEUE_CMS_POSTPAGE = "queue_cms_postpage";
//交換機的名稱
public static final String EX_ROUTING_CMS_POSTPAGE="ex_routing_cms_postpage";
//隊列的名稱
@Value("${xuecheng.mq.queue}")
public String queue_cms_postpage_name;
//routingKey 即站點Id
@Value("${xuecheng.mq.routingKey}")
public String routingKey;
/**
* 交換機配置使用direct類型
* @return the exchange
*/
@Bean(EX_ROUTING_CMS_POSTPAGE)
public Exchange EXCHANGE_TOPICS_INFORM() {
return ExchangeBuilder.directExchange(EX_ROUTING_CMS_POSTPAGE).durable(true).build();
}
//聲明隊列
@Bean(QUEUE_CMS_POSTPAGE)
public Queue QUEUE_CMS_POSTPAGE() {
Queue queue = new Queue(queue_cms_postpage_name);
return queue;
}
/**
* 綁定隊列到交換機
*
* @param queue the queue
* @param exchange the exchange
* @return the binding
*/
@Bean
public Binding BINDING_QUEUE_INFORM_SMS(@Qualifier(QUEUE_CMS_POSTPAGE) Queue queue,
@Qualifier(EX_ROUTING_CMS_POSTPAGE) Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(routingKey).noargs();
}
}
===================================
1.2.4 定義消息格式
消息內(nèi)容采用json格式存儲數(shù)據(jù),如下:
頁面id:發(fā)布頁面的id
?================
{
"pageId":""
}
=================
1.2.5 PageDao
1、使用CmsPageRepository 查詢頁面信息
=====================
public interface CmsPageRepository extends MongoRepository<CmsPage,String> {
}
=======================
2、使用CmsSiteRepository查詢站點信息,主要獲取站點物理路徑
===============
public interface CmsSiteRepository extends MongoRepository<CmsSite,String> {
}
===============
1.2.6 PageService
在Service中定義保存頁面靜態(tài)文件到服務(wù)器物理路徑方法:
===========================
@Service
public class PageService {
@Autowired
CmsPageRepository cmsPageRepository;
@Autowired
CmsSiteRepository cmsSiteRepository;
@Autowired
GridFsTemplate gridFsTemplate;
@Autowired
GridFSBucket gridFSBucket;
//將頁面html保存到頁面物理路徑
public void savePageToServerPath(String pageId){
Optional<CmsPage> optional = cmsPageRepository.findById(pageId);
if(!optional.isPresent()){
ExceptionCast.cast(CmsCode.CMS_PAGE_NOTEXISTS);
}
//取出頁面物理路徑
CmsPage cmsPage = optional.get();
//頁面所屬站點
CmsSite cmsSite = this.getCmsSiteById(cmsPage.getSiteId());
//頁面物理路徑
String pagePath = cmsSite.getSitePhysicalPath() + cmsPage.getPagePhysicalPath() +
cmsPage.getPageName();
//查詢頁面靜態(tài)文件
String htmlFileId = cmsPage.getHtmlFileId();
InputStream inputStream = this.getFileById(htmlFileId);
if(inputStream == null){
ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_HTMLISNULL);
}
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(new File(pagePath));
//將文件內(nèi)容保存到服務(wù)物理路徑
IOUtils.copy(inputStream,fileOutputStream);
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//根據(jù)文件id獲取文件內(nèi)容
public InputStream getFileById(String fileId){
try {
GridFSFile gridFSFile =
gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(fileId)));
GridFSDownloadStream gridFSDownloadStream =
gridFSBucket.openDownloadStream(gridFSFile.getObjectId());
GridFsResource gridFsResource = new GridFsResource(gridFSFile,gridFSDownloadStream);
return gridFsResource.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
//根據(jù)站點id得到站點
public CmsSite getCmsSiteById(String siteId){
Optional<CmsSite> optional = cmsSiteRepository.findById(siteId);
if(optional.isPresent()){
CmsSite cmsSite = optional.get();
return cmsSite;
}
return null;
}
}
===========================
1.2.6ConsumerPostPage
在cms client工程的mq包下創(chuàng)建ConsumerPostPage類,ConsumerPostPage作為發(fā)布頁面的消費客戶端,監(jiān)聽
頁面發(fā)布隊列的消息,收到消息后從mongodb下載文件,保存在本地。
========================
@Component
public class ConsumerPostPage {
private static final Logger LOGGER = LoggerFactory.getLogger(ConsumerPostPage.class);
@Autowired
CmsPageRepository cmsPageRepository;
@Autowired
PageService pageService;
@RabbitListener(queues={"${xuecheng.mq.queue}"})
public void postPage(String msg){
//解析消息
Map map = JSON.parseObject(msg, Map.class);
LOGGER.info("receive cms post page:{}",msg.toString());
//取出頁面id
String pageId = (String) map.get("pageId");
//查詢頁面信息
Optional<CmsPage> optional = cmsPageRepository.findById(pageId);
if(!optional.isPresent()){
LOGGER.error("receive cms post page,cmsPage is null:{}",msg.toString());
return ;
}
//將頁面保存到服務(wù)器物理路徑
pageService.savePageToServerPath(pageId);
}
}
==========================
1.3 頁面發(fā)布生產(chǎn)方
1.3.1 需求分析
管理員通過 cms系統(tǒng)發(fā)布“頁面發(fā)布”的消費,cms系統(tǒng)作為頁面發(fā)布的生產(chǎn)方。
需求如下:
1、管理員進入管理界面點擊“頁面發(fā)布”,前端請求cms頁面發(fā)布接口。
2、cms頁面發(fā)布接口執(zhí)行頁面靜態(tài)化,并將靜態(tài)化頁面存儲至GridFS中。
3、靜態(tài)化成功后,向消息隊列發(fā)送頁面發(fā)布的消息。
1) 獲取頁面的信息及頁面所屬站點ID。
2) 設(shè)置消息內(nèi)容為頁面ID。(采用json格式,方便日后擴展)
3) 發(fā)送消息給ex_cms_postpage交換機,并將站點ID作為routingKey。
1.3.2 RabbitMQ配置
1、配置Rabbitmq的連接參數(shù)
在application.yml添加如下配置:
1、配置Rabbitmq的連接參數(shù)? ?
在cms的application.yml添加如下配置:
=============
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtualHost: /
=============
2、在pom.xml添加依賴
===================
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐amqp</artifactId>
</dependency>
====================
3、RabbitMQConfifig配置
由于cms作為頁面發(fā)布方要面對很多不同站點的服務(wù)器,面對很多頁面發(fā)布隊列,所以這里不再配置隊列,只需要
配置交換機即可。
在cms工程只配置交換機名稱即可。
======================
@Configuration
public class RabbitmqConfig {
//交換機的名稱
public static final String EX_ROUTING_CMS_POSTPAGE="ex_routing_cms_postpage";
/**
* 交換機配置使用direct類型
* @return the exchange
*/
@Bean(EX_ROUTING_CMS_POSTPAGE)
public Exchange EXCHANGE_TOPICS_INFORM() {
return ExchangeBuilder.directExchange(EX_ROUTING_CMS_POSTPAGE).durable(true).build();
}
}
=====================
1.3.3 Api接口
在api工程定義頁面發(fā)布接口:
===============
@ApiOperation("發(fā)布頁面")
public ResponseResult post(String pageId);
===============
1.3.4 PageService
在PageService中定義頁面發(fā)布方法,代碼如下:
@Autowired
RabbitTemplate rabbitTemplate;
//頁面發(fā)布
public ResponseResult postPage(String pageId){
//執(zhí)行靜態(tài)化
String pageHtml = this.getPageHtml(pageId);
if(StringUtils.isEmpty(pageHtml)){
ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_HTMLISNULL);
}
//保存靜態(tài)化文件
CmsPage cmsPage = saveHtml(pageId, pageHtml);
//發(fā)送消息
sendPostPage(pageId);
return new ResponseResult(CommonCode.SUCCESS);
}
//發(fā)送頁面發(fā)布消息
private void sendPostPage(String pageId){
CmsPage cmsPage = this.getById(pageId);
if(cmsPage == null){
ExceptionCast.cast(CmsCode.CMS_PAGE_NOTEXISTS);
}
Map<String,String> msgMap = new HashMap<>();
msgMap.put("pageId",pageId);
//消息內(nèi)容
String msg = JSON.toJSONString(msgMap);
//獲取站點id作為routingKey
String siteId = cmsPage.getSiteId();
//發(fā)布消息
this.rabbitTemplate.convertAndSend(RabbitmqConfig.EX_ROUTING_CMS_POSTPAGE,siteId, msg);
}
//保存靜態(tài)頁面內(nèi)容
private CmsPage saveHtml(String pageId,String content){
//查詢頁面
Optional<CmsPage> optional = cmsPageRepository.findById(pageId);
if(!optional.isPresent()){
ExceptionCast.cast(CmsCode.CMS_PAGE_NOTEXISTS);
}
CmsPage cmsPage = optional.get();
//存儲之前先刪除
String htmlFileId = cmsPage.getHtmlFileId();
if(StringUtils.isNotEmpty(htmlFileId)){
gridFsTemplate.delete(Query.query(Criteria.where("_id").is(htmlFileId)));
}
//保存html文件到GridFS
InputStream inputStream = IOUtils.toInputStream(content);
ObjectId objectId = gridFsTemplate.store(inputStream, cmsPage.getPageName());
//文件id
String fileId = objectId.toString();
//將文件id存儲到cmspage中
cmsPage.setHtmlFileId(fileId);
cmsPageRepository.save(cmsPage);
return cmsPage;
}
1.3.5 CmsPageController
編寫Controller實現(xiàn)api接口,接收頁面請求,調(diào)用service執(zhí)行頁面發(fā)布。
========================
@Override
@PostMapping("/postPage/{pageId}")
public ResponseResult post(@PathVariable("pageId") String pageId) {
return pageService.postPage(pageId);
}
========================
1.4 頁面發(fā)布前端
在 cms前端添加 api方法
在 cms前端添加 api方法。
===================
/*發(fā)布頁面*/
export const page_postPage= id => {
return http.requestPost(apiUrl+'/cms/page/postPage/'+id)
}
===================
1.4.2 頁面
修改page_list.vue,添加發(fā)布按鈕
================
<el‐table‐column label="發(fā)布" width="80">
<template slot‐scope="scope">
<el‐button
size="small" type="primary" plain @click="postPage(scope.row.pageId)">發(fā)布
</el‐button>
</template>
</el‐table‐column>
=================
添加頁面發(fā)布事件:
postPage (id) {
this.$confirm('確認發(fā)布該頁面嗎?', '提示', {
}).then(() => {
cmsApi.page_postPage(id).then((res) => {
if(res.success){
console.log('發(fā)布頁面id='+id);
this.$message.success('發(fā)布成功,請稍后查看結(jié)果');
}else{
this.$message.error('發(fā)布失敗');
}
});
}).catch(() => {
});
},
=============
1.5 測試?
1:修改數(shù)據(jù)庫中cms_side表中門戶 站點的路徑: 你本地xc-ui-pc-static-portal的 路徑;
"sitePthsicalpath" : "E:/webworkspace/xc-ui-pc-static-portal",
修改cms_page 中要測試的頁面中的pageName? 為你本地要修改文件名字
這里測試輪播圖頁面修改、發(fā)布的流程:
1、修改輪播圖頁面模板或修改輪播圖地址
注意:先修改頁面原型,頁面原型調(diào)試正常后再修改頁面模板。
2、執(zhí)行頁面預(yù)覽
3、執(zhí)行頁面發(fā)布,查看頁面是否寫到網(wǎng)站目錄
4、刷新門戶首頁并觀察輪播圖是否變化。
1.6 思考
1、如果發(fā)布到服務(wù)器的頁面內(nèi)容不正確怎么辦?
2、一個頁面需要發(fā)布很多服務(wù)器,點擊“發(fā)布”后如何知道詳細的發(fā)布結(jié)果?
3、一個頁面發(fā)布到多個服務(wù)器,其中有一個服務(wù)器發(fā)布失敗時怎么辦?
2 課程管理
2.1 需求分析
在線教育平臺的課程信息相當(dāng)于電商平臺的商品。課程管理是后臺管理功能中最重要的模塊。本項目為教學(xué)機構(gòu)提
供課程管理功能,教學(xué)機構(gòu)可以添加屬于自己的課程,供學(xué)生在線學(xué)習(xí)。
課程管理包括如下功能需求:
1、分類管理
2、新增課程
3、修改課程
4、預(yù)覽課程
5、發(fā)布課程
用戶的操作流程如下:
1、進入我的課程

2、點擊“添加課程”,進入添加課程界面

3、輸入課程基本信息,點擊提交
4、課程基本信息提交成功,自動進入“管理課程”界面,點擊“管理課程”也可以進入“管理課程”界面

5、編輯圖片
上傳課程圖片。

6、編輯課程營銷信息
營銷信息主要是設(shè)置課程的收費方式及價格。

7、編輯課程計劃

添加課程計劃:

2.2 教學(xué)方法
資料:
鏈接:https://pan.baidu.com/s/1Ubg4stN0h6TmglEUjQl1tg
提取碼:ir56
本模塊對課程信息管理功能的教學(xué)方法采用實戰(zhàn)教學(xué)方法,旨在通過實戰(zhàn)提高接口編寫的能力,具體教學(xué)方法如
下:
1、前后端工程導(dǎo)入
教學(xué)管理前端工程采用與系統(tǒng)管理工程相同的技術(shù),直接導(dǎo)入后在此基礎(chǔ)上開發(fā)。
課程管理服務(wù)端工程采用Spring Boot技術(shù)構(gòu)建,技術(shù)層技術(shù)使用Spring data Jpa(與Spring data Mongodb類
似)、Mybatis,直接導(dǎo)入后在此基礎(chǔ)上開發(fā)。
2、課程計劃功能
課程計劃功能采用全程教學(xué)。
3、我的課程、新增課程、修改課程、課程營銷
我的課程、新增課程、修改課程、課程營銷四個功能采用實戰(zhàn)方式,課堂上會講解每個功能的需求及技術(shù)點,講解
完成學(xué)生開始實戰(zhàn),由導(dǎo)師進行技術(shù)指導(dǎo)。
4、參考文檔
實戰(zhàn)結(jié)束提供每個功能的開發(fā)文檔,學(xué)生參考文檔并修正功能缺陷。
2.3 環(huán)境搭建
2.3.1 搭建數(shù)據(jù)庫環(huán)境
1) 創(chuàng)建數(shù)據(jù)庫
課程管理使用MySQL數(shù)據(jù)庫,
1:創(chuàng)建課程管理數(shù)據(jù)庫:xc_course。
2:導(dǎo)入xc_course.sql腳本

2) 數(shù)據(jù)表介紹
課程信息內(nèi)容繁多,將課程信息分類保存在如下表中:

數(shù)據(jù)表結(jié)構(gòu)如下:





2.3.2導(dǎo)入課程管理服務(wù)工程
1)持久層技術(shù)介紹:
課程管理服務(wù)使用MySQL數(shù)據(jù)庫存儲課程信息,持久層技術(shù)如下:
1、spring data jpa:用于表的基本CRUD。
2、mybatis:用于復(fù)雜的查詢操作。
3、druid:使用阿里巴巴提供的spring boot 整合druid包druid-spring-boot-starter管理連接池。
druid-spring-boot-starter地址:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
2)導(dǎo)入工程
導(dǎo)入資料下的“xc-service-manage-course.zip”。
導(dǎo)入為moudle
2.3.3 導(dǎo)入課程管理前端工程
課程管理屬于教學(xué)管理子系統(tǒng)的功能,使用用戶為教學(xué)機構(gòu)的管理人員和老師,為保證系統(tǒng)的可維護性,單獨創(chuàng)建
一個教學(xué)管理前端工程。 教學(xué)管理前端工程與系統(tǒng)管理前端的工程結(jié)構(gòu)一樣,也采用vue.js框架來實現(xiàn)。

從課程資料目錄拷貝xc-ui-pc-teach.zip到工程,使用webstorm打開,啟動工程:
效果圖如下:

3 課程計劃
3.1 需求分析北京市昌平區(qū)建材城西路金燕龍辦公樓一層 電話:400-618-9090
什么是課程計劃?
課程計劃定義了課程的章節(jié)內(nèi)容,學(xué)生通過課程計劃進行在線學(xué)習(xí),下圖中右側(cè)顯示的就是課程計劃。
課程計劃包括兩級,第一級是課程的大章節(jié)、第二級是大章節(jié)下屬的小章節(jié),每個小章節(jié)通常是一段視頻,學(xué)生點
擊小章節(jié)在線學(xué)習(xí)。
教學(xué)管理人員對課程計劃如何管理?
功能包括:添加課程計劃、刪除課程計劃、修改課程計劃等。
3.2 課程計劃查詢
3.2.1需求分析
課程計劃查詢是將某個課程的課程計劃內(nèi)容完整的顯示出來,如下圖所示:

左側(cè)顯示的就是課程計劃,課程計劃是一個樹型結(jié)構(gòu),方便擴展課程計劃的級別。
在上邊頁面中,點擊“添加課程計劃”即可對課程計劃進行添加操作。
點擊修改可對某個章節(jié)內(nèi)容進行修改。
點擊刪除可刪除某個章節(jié)。
3.2.2 頁面原型
3.2.2.1 tree組件介紹
本功能使用element-ui 的tree組件來完成

在course_plan.vue文件中添加tree組件的代碼,進行測試:
1、組件標(biāo)簽
=========================
<el‐tree
:data="data"
show‐checkbox
node‐key="id"
default‐expand‐all
:expand‐on‐click‐node="false"
:render‐content="renderContent">
</el‐tree>
=======================
2、數(shù)據(jù)對象
=======================
let id = 1000;
export default {
data() {
return {
data : [{
id: 1,
label: '一級 1',
children: [{
id: 4,
label: '二級 1‐1',
children: [{
id: 9,
label: '三級 1‐1‐1'
}, {
id: 10,
label: '三級 1‐1‐2'
}]
}]
}]
}
}
}
=======================
3.2.2.2 webstorm配置JSX
本組件用到了JSX語法,如下所示:

JSX 是Javascript和XML結(jié)合的一種格式,它是React的核心組成部分,JSX和XML語法類似,可以定義屬性以及子元
素。唯一特殊的是可以用大括號來加入JavaScript表達式。遇到 HTML 標(biāo)簽(以 < 開頭),就用 HTML 規(guī)則解析;
遇到代碼塊(以 { 開頭),就用 JavaScript 規(guī)則解析。
下面是官方的一個例子:

設(shè)置方法 如下:


preferences -> Editor -> File Types 中找到上邊框中HTML 在下邊加一個 *.vue
如果已經(jīng)在vue template 中已存在.vue 則把它改為.vue2(因為要在Html中添加.vue)

3.2.3 API接口
3.2.3.1 數(shù)據(jù)模型
1、表結(jié)構(gòu)

2、模型類
課程計劃為樹型結(jié)構(gòu),由樹根(課程)和樹枝(章節(jié))組成,為了保證系統(tǒng)的可擴展性,在系統(tǒng)設(shè)計時將課程計劃
設(shè)置為樹型結(jié)構(gòu)。
========================
@Data
@ToString
@Entity
@Table(name="teachplan")
@GenericGenerator(name = "jpa‐uuid", strategy = "uuid")
public class Teachplan implements Serializable {
private static final long serialVersionUID = ‐916357110051689485L;
@Id
@GeneratedValue(generator = "jpa‐uuid")
@Column(length = 32)
private String id;
private String pname;
private String parentid;
private String grade;
private String ptype;
private String description;
private String courseid;
private String status;
private Integer orderby;
private Double timelength;
private String trylearn;
}
========================
3.2.3.2 自定義模型類
前端頁面需要樹型結(jié)構(gòu)的數(shù)據(jù)來展示Tree組件,如下:
========================
[{
id: 1,
label: '一級 1',
children: [{
id: 4,
label: '二級 1‐1'
}]
}]
========================
自定義課程計劃結(jié)點類如下:
==================
@Data
@ToString
public class TeachplanNode extends Teachplan {
List<TeachplanNode> children;
}
}
=================
3.2.3.3 接口定義
根據(jù)課程id查詢課程的計劃接口如下,在api工程創(chuàng)建course包,創(chuàng)建CourseControllerApi接口類并定義接口方法
如下:
=================
public interface CourseControllerApi {
@ApiOperation("課程計劃查詢")
public TeachplanNode findTeachplanList(String courseId);
}
===============
3.2.3 課程管理服務(wù)
3.2.3.1 Sql
課程計劃是樹型結(jié)構(gòu),采用表的自連接方式進行查詢,sql語句如下:
mabbit: 是對sql語句的優(yōu)化
springjpa: 面向?qū)ο箝_發(fā)
=========================
SELECT
a.id one_id,
a.pname one_pname,
b.id two_id,
b.pname two_pname,
c.id three_id,
c.pname three_pname
FROM
teachplan a
LEFT JOIN teachplan b
ON a.id = b.parentid
LEFT JOIN teachplan c
ON b.id = c.parentid
WHERE a.parentid = '0'
AND a.courseid = '402885816243d2dd016243f24c030002'
ORDER BY a.orderby,
b.orderby,
c.orderby
=========================
3.2.3.2 Dao
1) mapper接口? 在course工程中定義
=========================
@Mapper
public interface TeachplanMapper {
public TeachplanNode selectList(String courseId);
}
=========================
2)mapper映射文件
=======================
<resultMap type="com.xuecheng.framework.domain.course.ext.TeachplanNode" id="teachplanMap" >
<id property="id" column="one_id"/>
<result property="pname" column="one_name"/>
<collection property="children"
ofType="com.xuecheng.framework.domain.course.ext.TeachplanNode">
<id property="id" column="two_id"/>
<result property="pname" column="two_name"/>
<collection property="children"
ofType="com.xuecheng.framework.domain.course.ext.TeachplanNode">
<id property="id" column="three_id"/>
<result property="pname" column="three_name"/>
</collection>
</collection>
</resultMap>
<select id="selectList" resultMap="teachplanMap" parameterType="java.lang.String" >
SELECT
a.id one_id,
a.pname one_name,
b.id two_id,
b.pname two_name,
c.id three_id,
c.pname three_name
FROM
teachplan a LEFT JOIN teachplan b
ON a.id = b.parentid
LEFT JOIN teachplan c
ON b.id = c.parentid
WHERE a.parentid = '0'
<if test="_parameter!=null and _parameter!=''">
and a.courseid=#{courseId}
</if>
ORDER BY a.orderby,
b.orderby,
c.orderby
</select>
=======================
3.4.3.3 Service
創(chuàng)建CourseService類,定義查詢課程計劃方法。
=======================
@Service
public class CourseService {
@Autowired
TeachplanMapper teachplanMapper;
//查詢課程計劃
public TeachplanNode findTeachplanList(String courseId){
TeachplanNode teachplanNode = teachplanMapper.selectList(courseId);
return teachplanNode;
}
}
=======================
3.4.3.4 Controller
=======================
@RestController
@RequestMapping("/course")
public class CourseController implements CourseControllerApi {
@Autowired
CourseService courseService;
//查詢課程計劃
@Override
@GetMapping("/teachplan/list/{courseId}")
public TeachplanNodefindTeachplanList(@PathVariable("courseId") String courseId) {
System.out.print("ccccc? "+courseId);
? ? return courseService.findTeachplanList(courseId);
}
=======================
3.4.3.5 測試
使用postman或swagger-ui測試查詢接口。
Get 請求:http://localhost:31200/course/teachplan/list/402885816243d2dd016243f24c030002
====================
3.2.4 前端頁面
3.2.4.1Api方法
定義課程計劃查詢的api方法:
====================
/*查詢課程計劃*/
export const findTeachplanList = courseid => {
return http.requestQuickGet(apiUrl+'/course/teachplan/list/'+courseid)
}
63}
===================
3.2.4.2Api調(diào)用
1、在mounted鉤子方法 中查詢 課程計劃
定義查詢課程計劃的方法,賦值給數(shù)據(jù)對象teachplanList
================================
findTeachplan(){
courseApi.findTeachplanList(this.courseid).then((res) => {
this.teachplanList = [];//清空樹
if(res.children){
this.teachplanList = res.children;
}
});
=================================
2)在mounted鉤子中查詢課程計劃
=================================
mounted(){
//課程id
this.courseid = this.$route.params.courseid;
//課程計劃
this.findTeachplan();
}
=================================
3)修改樹結(jié)點的標(biāo)簽屬性
課程計劃信息中pname為結(jié)點的名稱,需要修改樹結(jié)點的標(biāo)簽屬性方可正常顯示課程計劃名稱,如下:
=================================
defaultProps: {
children: 'children',
label: 'pname'
}
=================================
3.2.4.3 測試

3.3 添加課程計劃
3.3.1 需求分析
用戶操作流程:
1、進入課程計劃頁面,點擊“添加課程計劃”
2、打開添加課程計劃頁面,輸入課程計劃信息

上級結(jié)點說明:
不選擇上級結(jié)點表示當(dāng)前添加的課程計劃的父節(jié)點為該課程的根結(jié)點。
當(dāng)添加該課程在課程計劃中還沒有節(jié)點時要自動添加課程的根結(jié)點。
3、點擊提交。
3.3.1.1 頁面原型說明
添加課程計劃采用彈出窗口組件Dialog。
1、視圖部分
在course_plan.vue頁面添加添加課程計劃的彈出窗口代碼:
=====================
<el‐dialog title="添加課程計劃" :visible.sync="teachplayFormVisible" >
<el‐form ref="teachplayForm" :model="teachplanActive" label‐width="140px"
style="width:600px;" :rules="teachplanRules" >
<el‐form‐item label="上級結(jié)點" >
<el‐select v‐model="teachplanActive.parentid" placeholder="不填表示根結(jié)點">
<el‐option
v‐for="item in teachplanList"
:key="item.id"
:label="item.pname"
:value="item.id">
</el‐option>
</el‐select>
</el‐form‐item>
<el‐form‐item label="章節(jié)/課時名稱" prop="pname">
<el‐input v‐model="teachplanActive.pname" auto‐complete="off"></el‐input>
</el‐form‐item>
<el‐form‐item label="課程類型" >
<el‐radio‐group v‐model="teachplanActive.ptype">
<el‐radio class="radio" label='1'>視頻</el‐radio>
<el‐radio class="radio" label='2'>文檔</el‐radio>
</el‐radio‐group>
</el‐form‐item>
<el‐form‐item label="學(xué)習(xí)時長(分鐘) 請輸入數(shù)字" >
<el‐input type="number" v‐model="teachplanActive.timelength" auto‐complete="off" ></el‐
input>
</el‐form‐item>
<el‐form‐item label="排序字段" >
<el‐input v‐model="teachplanActive.orderby" auto‐complete="off" ></el‐input>
</el‐form‐item>
<el‐form‐item label="章節(jié)/課時介紹" prop="description">
<el‐input type="textarea" v‐model="teachplanActive.description" ></el‐input>
</el‐form‐item>
<el‐form‐item label="狀態(tài)" prop="status">
<el‐radio‐group v‐model="teachplanActive.status" >
<el‐radio class="radio" label="0" >未發(fā)布</el‐radio>
<el‐radio class="radio" label='1'>已發(fā)布</el‐radio>
</el‐radio‐group>
</el‐form‐item>
<el‐form‐item >
<el‐button type="primary" v‐on:click="addTeachplan">提交</el‐button>
<el‐button type="primary" v‐on:click="resetForm">重置</el‐button>
</el‐form‐item>
</el‐form>
</el‐dialog>
=======================
2、數(shù)據(jù)模型
在數(shù)據(jù)模型中添加如下變量:
==========================
chplayFormVisible:false,
teachplanRules: {
pname: [
{required: true, message: '請輸入課程計劃名稱', trigger: 'blur'}
],
status: [
{required: true, message: '請選擇狀態(tài)', trigger: 'blur'}
]
},
teachplanActive:{},
==========================
3、 添加按鈕
通過變量teachplayFormVisible控制彈出窗口是否顯示。
==========================
<el‐button type="primary" @click="teachplayFormVisible = true">添加課程計劃</el‐button>
==========================
4、定義表單提交方法和重置方法
//提交課程計劃
addTeachplan(){
alert()
},
//重置表單
resetForm(){
this.teachplanActive = {}
},
===================
3.3.3 API接口
1)添加課程計劃
===================
@ApiOperation("添加課程計劃")
public ResponseResult addTeachplan(Teachplan teachplan);
===================
3.3.4 課程管理服務(wù)
3.3.3.1 Dao
========================
public interface TeachplanRepository extends JpaRepository<Teachplan, String> {
//定義方法根據(jù)課程id和父結(jié)點id查詢出結(jié)點列表,可以使用此方法實現(xiàn)查詢根結(jié)點
public List<Teachplan> findByCourseidAndParentid(String courseId,String parentId);
}
========================
3.3.3.2 Service
==================================
//獲取課程根結(jié)點,如果沒有則添加根結(jié)點
public String getTeachplanRoot(String courseId){
//校驗課程id
Optional<CourseBase> optional = courseBaseRepository.findById(courseId);
if(!optional.isPresent()){
return null;
}
CourseBase courseBase = optional.get();
//取出課程計劃根結(jié)點
List<Teachplan> teachplanList = teachplanRepository.findByCourseidAndParentid(courseId,
"0");
if(teachplanList == null || teachplanList.size()==0){
//新增一個根結(jié)點
Teachplan teachplanRoot = new Teachplan();
teachplanRoot.setCourseid(courseId);
teachplanRoot.setPname(courseBase.getName());
teachplanRoot.setParentid("0");
teachplanRoot.setGrade("1");//1級
teachplanRoot.setStatus("0");//未發(fā)布
teachplanRepository.save(teachplanRoot);
return teachplanRoot.getId();
}
Teachplan teachplan = teachplanList.get(0);
return teachplan.getId();
}
//添加課程計劃
@Transactional
public ResponseResult addTeachplan(Teachplan teachplan){
//校驗課程id和課程計劃名稱
if(teachplan == null ||
StringUtils.isEmpty(teachplan.getCourseid()) ||
StringUtils.isEmpty(teachplan.getPname())){
ExceptionCast.cast(CommonCode.INVALIDPARAM);
}
//取出課程id
String courseid = teachplan.getCourseid();
//取出父結(jié)點id
String parentid = teachplan.getParentid();
if(StringUtils.isEmpty(parentid)){
//如果父結(jié)點為空則獲取根結(jié)點
parentid= getTeachplanRoot(courseid);
}
//取出父結(jié)點信息
Optional<Teachplan> teachplanOptional = teachplanRepository.findById(parentid);
if(!teachplanOptional.isPresent()){
ExceptionCast.cast(CommonCode.INVALIDPARAM);
}
//父結(jié)點
Teachplan teachplanParent = teachplanOptional.get();
//父結(jié)點級別
String parentGrade = teachplanParent.getGrade();
//設(shè)置父結(jié)點
teachplan.setParentid(parentid);
teachplan.setStatus("0");//未發(fā)布
//子結(jié)點的級別,根據(jù)父結(jié)點來判斷
if(parentGrade.equals("1")){
teachplan.setGrade("2");
}else if(parentGrade.equals("2")){
teachplan.setGrade("3");
}
//設(shè)置課程id
teachplan.setCourseid(teachplanParent.getCourseid());
teachplanRepository.save(teachplan);
return new ResponseResult(CommonCode.SUCCESS);
}
=================================
3.3.3.3 controller
===================
//添加課程計劃
@Override
@PostMapping("/teachplan/add")
public ResponseResult addTeachplan(@RequestBody Teachplan teachplan) {
return courseService.addTeachplan(teachplan);
}
===================
3.3.5前端
3.3.5.1 Api調(diào)用
1、定義 api方法
========================
/*添加課程計劃*/
export const addTeachplan = teachplah => {
return http.requestPost(apiUrl+'/course/teachplan/add',teachplah)
}
========================
2、調(diào)用 api
========================
addTeachplan(){
this.$refs.teachplayForm.validate((valid) => {
if (valid) {
//添加課程計劃時帶上課程id
this.teachplanActive.courseid = this.courseid;
courseApi.addTeachplan(this.teachplanActive).then((res) => {
if(res.success){
this.$message.success('提交成功');
//清空表單
this.teachplanActive = {}
//刷新整個樹
this.findTeachplan();
}else{
this.$message.error('提交失敗');
}
});
}
})
},
========================
3.3.5 測試
測試流程:
1、新建一個課程
2、向新建課程中添加課程計劃
添加一級結(jié)點
添加二級結(jié)點