06_課程管理


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é)點

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容