頁面發佈
技術方案
本項目使用MQ實現頁面發佈的技術方案如下:
技術方案說明:
- 平臺包括多個站點,頁面歸屬不同的站點。
- 發佈一個頁面應將該頁面發佈到所屬站點的服務器上。
- 每個站點服務部署cms client程序,並與交換機綁定,綁定時指定站點Id爲routingKey。指定站點id爲routingKey就可以實現cms client只能接收到所屬站點的頁面發佈消息。
- 頁面發佈程序向MQ發佈消息時指定頁面所屬站點Id爲routingKey,將該頁面發佈到它所在服務器上的cms client。
頁面發佈流程圖如下:
- 前端請求cms執行頁面發佈。
- cms執行靜態化程序生成html文件。
- cms將html文件存儲到GridFS中。
- cms向MQ發送頁面發佈消息。
- MQ將頁面發佈消息通知給Cms Client。
- Cms Client從GridFS中下載html文件。
- Cms Client將html保存到所在服務器指定目錄。
頁面發佈消費方
需求
功能分析:
創建Cms Client
工程作爲頁面發佈消費方,將Cms Client
部署在多個服務器上,它負責接收到頁面發佈的消息後從GridFS
中下載文件在本地保存。
需求如下:
- 將cms Client部署在服務器,配置隊列名稱和站點ID。
- cms Client連接RabbitMQ並監聽各自的“頁面發佈隊列”。
- cms Client接收頁面發佈隊列的消息。
- 根據消息中的頁面id從mongodb數據庫下載頁面到本地。
工程搭建省略
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>
配置文件
server:
port: 31000
spring:
application:
name: xc-service-manage-cms-client
data:
mongodb:
database: xc_cms
host: 127.0.0.1
port: 27017
rabbitmq:
host: 127.0.0.1
port: 5672
username: xcEdu
password: 123456
virtualHost: /
xuecheng:
mq:
# cms客戶端監控的隊列名稱(不同的客戶端監控的隊列不能重複)
queue: queue_cms_postpage_01
# 此routingKey爲門戶站點ID
routingKey: 5a751fab6abb5044e0d19ea1
啓動類
package com.xuecheng.cms_manage_client;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@EntityScan(basePackages = "com.xuecheng.framework.domain.cms")
@ComponentScan(basePackages = "com.xuecheng.framework")
@ComponentScan(basePackages = "com.xuecheng.cms_manage_client")
public class ManageCmsClientApplication {
public static void main(String[] args) {
SpringApplication.run(ManageCmsClientApplication.class, args);
}
}
RabbitmqConfig(配置類)
package com.xuecheng.cms_manage_client.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@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();
}
}
Dao
數據訪問代碼參考xc-service-manage-cms
中的dao
,直接複製即可
PageService
package com.xuecheng.cms_manage_client.service;
import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.gridfs.GridFSDownloadStream;
import com.mongodb.client.gridfs.model.GridFSFile;
import com.xuecheng.cms_manage_client.dao.CmsPageRepository;
import com.xuecheng.cms_manage_client.dao.CmsSiteRepository;
import com.xuecheng.framework.domain.cms.CmsPage;
import com.xuecheng.framework.domain.cms.response.CmsCode;
import com.xuecheng.framework.exception.ExceptionCast;
import com.xuecheng.framework.service.BaseService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.gridfs.GridFsResource;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.stereotype.Service;
import java.io.*;
import java.util.Optional;
@Slf4j
@Service
public class PageService extends BaseService {
@Autowired
private GridFSBucket gridFSBucket;
@Autowired
private GridFsTemplate gridFsTemplate;
@Autowired
private CmsPageRepository cmsPageRepository;
@Autowired
private CmsSiteRepository cmsSiteRepository;
/**
* 保存指定頁面ID的html到服務器
*
* @param pageId 頁面ID
*/
public void savePageToServerPath(String pageId) {
CmsPage cmsPage = null;
// 查詢CmsPage
Optional<CmsPage> optionalCmsPage = cmsPageRepository.findById(pageId);
if (!optionalCmsPage.isPresent()) {
ExceptionCast.cast(CmsCode.CMS_EDITPAGE_NOTEXISTS);
}
cmsPage = optionalCmsPage.get();
// 下載文件
InputStream inputStream = downloadFileFromMongoDB(cmsPage.getHtmlFileId());
if (inputStream == null) {
log.error("[頁面發佈消費方] 下載頁面失敗, 流對象爲null, fileId = [{}]", cmsPage.getHtmlFileId());
return ;
}
// 寫入文件
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(new File(cmsPage.getPagePhysicalPath() + cmsPage.getPageName()));
IOUtils.copy(inputStream, fileOutputStream);
} catch (FileNotFoundException e) {
log.error("[頁面發佈消費方] 文件未找到, 文件路徑 = [{}]", cmsPage.getPagePhysicalPath() + cmsPage.getPageName());
} catch (IOException e) {
log.error("[頁面發佈消費方] 將文件寫入服務器失敗, 文件路徑 = [{}]", cmsPage.getPagePhysicalPath() + cmsPage.getPageName());
} finally {
try {
if (fileOutputStream != null) {
fileOutputStream.close();
}
inputStream.close();
} catch (IOException e) {
log.error("[頁面發佈消費方] 流對象關閉失敗, 錯誤信息 = ", e);
}
}
}
/**
* 下載文件
*
* @param fileId 文件ID
* @return 文件內容
*/
private InputStream downloadFileFromMongoDB(String fileId) {
GridFSFile gridFSFile = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(fileId)));
if (gridFSFile == null) {
ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_TEMPLATEISNULL);
}
//打開下載流對象
GridFSDownloadStream gridFSDownloadStream = gridFSBucket.openDownloadStream(gridFSFile.getObjectId());
//創建gridFsResource
GridFsResource gridFsResource = new GridFsResource(gridFSFile,gridFSDownloadStream);
//獲取流中的數據
try {
return gridFsResource.getInputStream();
} catch (IOException ignored) { }
return null;
}
}
**注意:**我使用的這一版本的數據庫中cms_site
中沒有物理路徑這一字段,但是在cms_page
中的物理字段使用的是絕對路徑,所以我這裏直接使用的cms_page
中的物理路徑。
ConsumerPostPage
編寫頁面發佈消費方代碼
package com.xuecheng.cms_manage_client.mq;
import com.alibaba.fastjson.JSON;
import com.xuecheng.cms_manage_client.dao.CmsPageRepository;
import com.xuecheng.cms_manage_client.service.PageService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Map;
@Slf4j
@Component
public class ConsumerPostPage {
@Autowired
private CmsPageRepository cmsPageRepository;
@Autowired
private PageService pageService;
@RabbitListener(queues = {"${xuecheng.mq.queue}"})
public void postPage(String msg) {
// 解析消息
Map map = JSON.parseObject(msg, Map.class);
log.info("[頁面發佈消費方] 收到消息, 消息內容爲: [{}]", map.toString());
// 獲取pageId
String pageId = (String) map.get("pageId");
if (StringUtils.isBlank(pageId)) {
log.error("[頁面發佈消費方] 收到的pageId爲空");
return ;
}
// 下載頁面到服務器
pageService.savePageToServerPath(pageId);
}
}
頁面發佈生產方
需求
管理員通過cms系統發佈“頁面發佈”的消費,cms系統作爲頁面發佈的生產方。
需求如下:
- 管理員進入管理界面點擊“頁面發佈”,前端請求cms頁面發佈接口。
- cms頁面發佈接口執行頁面靜態化,並將靜態化頁面存儲至GridFS中。
- 靜態化成功後,向消息隊列發送頁面發佈的消息。
- 獲取頁面的信息及頁面所屬站點ID。
- 設置消息內容爲頁面ID。(採用json格式,方便日後擴展)
- 發送消息給ex_cms_postpage交換機,並將站點ID作爲routingKey。
RabbitMQ配置
在xc-service-manage-cms
工程中,進行下列配置:
-
引入依賴(若沒有引入amqp的依賴,則需要引入)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
-
application.yml中新增Rabbitmq相關配置
spring: rabbitmq: host: 127.0.0.1 port: 5672 # 此賬號是我自行創建的,也可以直接使用默認賬號:guest username: xcEdu password: 123456 virtualHost: /
-
RabbitMQ配置類
package com.xuecheng.manage_cms.config; import org.springframework.amqp.core.Exchange; import org.springframework.amqp.core.ExchangeBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @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(); } }
CmsPageControllerApi
在CmsPageControllerApi
中新增接口定義
@ApiOperation("頁面發佈")
ResponseResult postPage(String pageId);
CmsPageController
/**
* 頁面發佈
*
* @param pageId 頁面ID
*/
@Override
@GetMapping("post/{pageId}")
public ResponseResult postPage(@PathVariable String pageId) {
return cmsPageService.postPage(pageId);
}
CmsPageService
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 頁面發佈
*
* @param pageId 頁面ID
*/
@Transactional
public CmsPageResult postPage(String pageId) {
// 執行靜態化
String htmlContent = genHtml(pageId);
isNullOrEmpty(htmlContent, CmsCode.CMS_GENERATEHTML_HTMLISNULL);
// 保存頁面
CmsPage cmsPage = saveHtml(pageId, htmlContent);
// 發送消息到MQ
sendMessage(cmsPage.getPageId());
}
/**
* 發送消息到MQ
*
* @param pageId 頁面ID
*/
private void sendMessage(String pageId) {
// 查詢CmsPage
CmsPage cmsPage = findByPageId(pageId);
isNullOrEmpty(cmsPage, CmsCode.CMS_EDITPAGE_NOTEXISTS);
// 構造消息
JSONObject message = new JSONObject();
message.put("pageId", pageId);
// 發送消息
rabbitTemplate.convertAndSend(RabbitmqConfig.EX_ROUTING_CMS_POSTPAGE, cmsPage.getSiteId(), message.toJSONString());
}
/**
* 保存靜態頁面
*
* @param pageId 頁面ID
* @param htmlContent 頁面內容
*/
private CmsPage saveHtml(String pageId, String htmlContent) {
// 查詢CmsPage
CmsPage cmsPage = findByPageId(pageId);
isNullOrEmpty(cmsPage, CmsCode.CMS_EDITPAGE_NOTEXISTS);
// 刪除原有html文件
String htmlFileId = cmsPage.getHtmlFileId();
if(StringUtils.isNotEmpty(htmlFileId)){
gridFsTemplate.delete(Query.query(Criteria.where("_id").is(htmlFileId)));
}
// 保存生成的html
InputStream inputStream = IOUtils.toInputStream(htmlContent);
ObjectId objectId = gridFsTemplate.store(inputStream, cmsPage.getPageName());
// 保存文件ID到CmsPage
cmsPage.setHtmlFileId(objectId.toString());
return cmsPageRepository.save(cmsPage);
}
前端
用戶操作流程:
- 用戶進入cms頁面列表。
- 點擊“發佈”請求服務端接口,發佈頁面。
- 提示“發佈成功”,或發佈失敗。
代碼實現如下
-
cms.js
中新增接口定義/** * 頁面發佈 */ export const postPage = (pageId) => { return http.requestQuickGet(apiUrl + '/cms/page/post/'+ pageId) }
-
在
page_list.vue
中新增發佈
按鈕,與頁面預覽
按鈕類似<el-button size="small" type="text" @click="postPage(scope.$index, scope.row)">發佈 </el-button>
-
在
page_list.vue
的方法區中,新增postPage
方法,用於調用頁面發佈接口postPage:function(index, data) { this.$confirm('確認發佈此頁面?', '提示', { confirmButtonText: '確定', cancelButtonText: '取消', type: 'warning' }).then(() => { // 頁面發佈 cmsApi.postPage(data.pageId).then(res => { // 提示消息 this.$message({ showClose: true, message: res.message, type: 'success' }) }) }) }
測試
-
設置頁面信息
-
點擊發布
-
查看是否生成文件
OK,文件成功發佈並保存。
課程管理
微服務工程導入(省略)
前端工程導入排坑
我這裏導入前端工程後,運行報錯!!!
錯誤信息:
Node Sass does not yet support your current environment: Windows 64-bit with Unsupported runtime (64)
???what f**k
然後我去node-sass
的github看了下,好像也有蠻多人遇到類似問題的。
https://github.com/microsoft/PartsUnlimited/issues/134
解決方法:
-
使用
npm update
更新版本,需要注意從npm v2.6.1
開始,npm update
只更新頂層模塊,而不更新依賴的模塊,以前版本是遞歸更新的。如果想取到老版本的效果,要使用下面的命令npm --depth 9999 update
-
重新build一下
node-sass
npm rebuild node-sass
然後就可以直接:npm run dev
成功運行。
課程管理實現
分析
- 課程列表查詢,該功能前端基本上沒什麼需要修改的地方了,直接寫後端的分頁查詢接口即可。
- 課程的新增和編輯(重點),前端已經幫我把課程中的課程分類,適用等級以及學習模式的查詢調用給我寫好了,我們只需要完成響應接口的編寫。
- 課程分類的查詢比較簡單,在課程微服務中
Category
表中就能夠取到數據,結果只需要封裝在CategoryNode
中即可。都是已經寫好的實體類。 - 適用等級和學習模式查詢,都在
mongodb
數據庫中,也就是之前的CMS相關的數據庫中,我們需要在CMS微服務中新增數據字典數據的查詢,實體類分別是:SysDictionary
和SysDictionaryValue
- 編輯的時候,還需要數據的回顯,這就需要完成查詢。
- 課程分類的查詢比較簡單,在課程微服務中
數據字典查詢實現
-
DictionaryController
package com.xuecheng.system.controller; import com.xuecheng.framework.domain.system.SysDictionary; import com.xuecheng.framework.model.response.CommonCode; import com.xuecheng.framework.web.BaseController; import com.xuecheng.system.service.DictionaryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("sys/dictionary") public class DictionaryController extends BaseController { @Autowired private DictionaryService dictionaryService; /** * 按type獲取數據字段值 * * @param type 數據類型 * @return SysDictionary */ @GetMapping("get/{type}") public SysDictionary getDictionaryByType(@PathVariable String type) { SysDictionary sysDictionary = dictionaryService.findByType(type); isNullOrEmpty(sysDictionary, CommonCode.PARAMS_ERROR); return sysDictionary; } }
-
DictionaryService
package com.xuecheng.system.service; import com.xuecheng.framework.domain.system.SysDictionary; import com.xuecheng.framework.service.BaseService; import com.xuecheng.manage_cms.dao.DictionaryRepository; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Slf4j @Service public class DictionaryService extends BaseService { @Autowired private DictionaryRepository dictionaryRepository; /** * 按type獲取數據字段值 * * @param type 數據類型 * @return SysDictionary */ public SysDictionary findByType(String type) { return dictionaryRepository.findByDType(type); } }
-
DictionaryRepository
package com.xuecheng.manage_cms.dao; import com.xuecheng.framework.domain.system.SysDictionary; import org.springframework.data.mongodb.repository.MongoRepository; public interface DictionaryRepository extends MongoRepository<SysDictionary, String> { SysDictionary findByDType(String type); }
注意:
因爲前端已經寫好了接口調用而且調用的端口號是cms微服務對應端口號,所以如果不想修改前端的調用端口的話,建議直接將數據字典查詢的代碼寫在cms微服務中。
課程管理實現
-
CourseController
package com.xuecheng.manage_course.controller; import com.xuecheng.api.course.CourseBaseControllerApi; import com.xuecheng.framework.domain.course.CourseBase; import com.xuecheng.framework.domain.course.request.CourseListRequest; import com.xuecheng.framework.domain.course.response.AddCourseResult; import com.xuecheng.framework.domain.course.response.CourseBaseResult; import com.xuecheng.framework.domain.course.response.CourseCode; import com.xuecheng.framework.model.response.CommonCode; import com.xuecheng.framework.model.response.QueryResponseResult; import com.xuecheng.framework.web.BaseController; import com.xuecheng.manage_course.service.CourseBaseService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("course/coursebase") public class CourseBaseController extends BaseController implements CourseBaseControllerApi { @Autowired private CourseBaseService courseBaseService; @Override @GetMapping("list/{page}/{size}") public QueryResponseResult findList(@PathVariable int page, @PathVariable int size, CourseListRequest queryPageRequest) { return courseBaseService.findList(page, size, queryPageRequest); } /** * 新增課程基本信息 * * @param courseBase 課程基本信息 */ @Override @PostMapping("add") public AddCourseResult addCourse(@RequestBody CourseBase courseBase) { isNullOrEmpty(courseBase, CommonCode.PARAMS_ERROR); CourseBase add = courseBaseService.add(courseBase); isNullOrEmpty(add, CommonCode.SERVER_ERROR); return new AddCourseResult(CommonCode.SUCCESS, add.getId()); } /** * 編輯課程基本信息 * * @param courseBase 課程基本信息 */ @Override @PutMapping("edit") public AddCourseResult editCourse(@RequestBody CourseBase courseBase) { isNullOrEmpty(courseBase, CommonCode.PARAMS_ERROR); courseBaseService.edit(courseBase); return null; } /** * 按課程ID查詢課程基本信息 * * @param courseId */ @Override @GetMapping("{courseId}") public CourseBaseResult findById(@PathVariable String courseId) { isNullOrEmpty(courseId, CommonCode.PARAMS_ERROR); CourseBase courseBase = courseBaseService.findById(courseId); isNullOrEmpty(courseBase, CourseCode.COURSE_NOT_EXIST); return CourseBaseResult.SUCCESS(courseBase); } }
-
CourseBaseService
package com.xuecheng.manage_course.service; import com.xuecheng.framework.domain.course.CourseBase; import com.xuecheng.framework.domain.course.request.CourseListRequest; import com.xuecheng.framework.domain.course.response.CourseCode; import com.xuecheng.framework.model.response.CommonCode; import com.xuecheng.framework.model.response.QueryResponseResult; import com.xuecheng.framework.model.response.QueryResult; import com.xuecheng.framework.service.BaseService; import com.xuecheng.manage_course.dao.CourseBaseRepository; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Example; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; @Slf4j @Service public class CourseBaseService extends BaseService { @Autowired private CourseBaseRepository courseBaseRepository; /** * 新增課程基本信息 * * @param courseBase 課程基本信息 * @return CourseBase */ public CourseBase add(CourseBase courseBase) { // 新增 return courseBaseRepository.save(courseBase); } /** * 編輯課程基本信息 * * @param courseBase 課程基本信息 * @return CourseBase */ public CourseBase edit(CourseBase courseBase) { CourseBase _courseBase = findById(courseBase.getId()); isNullOrEmpty(_courseBase, CourseCode.COURSE_NOT_EXIST); // 更新 _courseBase.setName(courseBase.getName()); _courseBase.setMt(courseBase.getMt()); _courseBase.setSt(courseBase.getSt()); _courseBase.setGrade(courseBase.getGrade()); _courseBase.setStudymodel(courseBase.getStudymodel()); _courseBase.setDescription(courseBase.getDescription()); return courseBaseRepository.save(_courseBase); } /** * 按ID查詢課程基本信息 * * @param courseBaseId 課程ID * @return CourseBase */ public CourseBase findById(String courseBaseId) { return courseBaseRepository.findById(courseBaseId).orElse(null); } /** * 分頁查詢課程基本信息 * * @param page 當前頁碼 * @param size 每頁記錄數 * @param queryPageRequest 查詢條件 * @return QueryResponseResult */ public QueryResponseResult findList(int page, int size, CourseListRequest queryPageRequest) { if (page < 0) { page = 1; } // 頁碼下標從0開始 page = page - 1; CourseBase params = new CourseBase(); if (StringUtils.isNotBlank(queryPageRequest.getCompanyId())) { params.setCompanyId(queryPageRequest.getCompanyId()); } Example<CourseBase> courseBaseExample = Example.of(params); // 分頁查詢 Page<CourseBase> pageResult = courseBaseRepository.findAll(courseBaseExample, PageRequest.of(page, size)); QueryResult<CourseBase> queryResult = new QueryResult<>(); queryResult.setTotal(pageResult.getTotalElements()); queryResult.setList(pageResult.getContent()); return new QueryResponseResult(CommonCode.SUCCESS, queryResult); } }
-
CourseBaseRepository
package com.xuecheng.manage_course.dao; import com.xuecheng.framework.domain.course.CourseBase; import org.springframework.data.jpa.repository.JpaRepository; /** * Created by Administrator. */ public interface CourseBaseRepository extends JpaRepository<CourseBase,String> { }
分類列表查詢
-
CategoryController
package com.xuecheng.manage_course.controller; import com.xuecheng.api.course.CategoryControllerApi; import com.xuecheng.framework.domain.course.ext.CategoryNode; import com.xuecheng.framework.web.BaseController; import com.xuecheng.manage_course.service.CategoryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("category") public class CategoryController extends BaseController implements CategoryControllerApi { @Autowired private CategoryService categoryService; /** * 查詢分類列表 * * @return CategoryNode */ @Override @GetMapping("list") public CategoryNode findCategoryList() { return categoryService.findCategoryList(); } }
-
CategoryService
package com.xuecheng.manage_course.service; import com.xuecheng.framework.domain.course.Category; import com.xuecheng.framework.domain.course.ext.CategoryNode; import com.xuecheng.manage_course.dao.CategoryRepository; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; import java.util.stream.Collectors; @Slf4j @Service public class CategoryService { @Autowired private CategoryRepository categoryRepository; /** * 查詢分類列表 * * @return CategoryNode */ public CategoryNode findCategoryList() { CategoryNode sourceCategoryNode = buildCategoryNode(categoryRepository.findByParentid("0").get(0)); // 查詢第二級分類列表 List<Category> secondaryCategoryList = categoryRepository.findByParentid(sourceCategoryNode.getId()); List<CategoryNode> secondaryCategoryNodeList = buildCategoryNodeList(secondaryCategoryList); sourceCategoryNode.setChildren(secondaryCategoryNodeList); return sourceCategoryNode; } /** * 構造分類節點列表 * * @param categoryList * @return List<CategoryNode> */ private List<CategoryNode> buildCategoryNodeList(List<Category> categoryList) { return categoryList.stream().map(secondaryCategory -> { CategoryNode categoryNode = buildCategoryNode(secondaryCategory); // 查詢子節點 List<Category> children = categoryRepository.findByParentid(categoryNode.getId()); List<CategoryNode> categoryNodes = buildCategoryNodeList(children); if (!categoryNodes.isEmpty()) { categoryNode.setChildren(categoryNodes); } return categoryNode; }).collect(Collectors.toList()); } /** * 構造分類節點數據 * * @param category 分類 * @return CategoryNode */ private CategoryNode buildCategoryNode(Category category) { CategoryNode categoryNode = new CategoryNode(); categoryNode.setId(category.getId()); categoryNode.setIsleaf(category.getIsleaf()); categoryNode.setLabel(category.getLabel()); categoryNode.setName(category.getName()); return categoryNode; } }
-
CategoryRepository
package com.xuecheng.manage_course.dao; import com.xuecheng.framework.domain.course.Category; import org.springframework.data.repository.CrudRepository; import java.util.List; public interface CategoryRepository extends CrudRepository<Category, String> { List<Category> findByParentid(String parentId); }
前端
前端基本沒什麼需要改動的地方,因爲我在課程管理中,使用了自己新建的返回對象CourseBaseResult
,所以前端就需要改一點東西,費力不討好啊,建議各位直接使用CourseBase
作爲查詢的返回值!
還有就是前端使用element-ui
版本,屬實有點太低了,很多效果是出不來的,所以我進行了版本更換,我更換到2.10.1
之後,課程的新增和編輯頁的頭部導航菜單出現了樣式錯誤,我修改了一下
修改之前,是用router-link
標籤包裹的el-menu-item
標籤。
基本就這些了。
課程計劃
課程計劃查詢
CoursePlanController
package com.xuecheng.manage_course.controller;
import com.xuecheng.api.course.CoursePlanControllerApi;
import com.xuecheng.framework.domain.course.ext.TeachplanNode;
import com.xuecheng.manage_course.service.CoursePlanService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("course/teachplan")
public class CoursePlanController implements CoursePlanControllerApi {
@Autowired
private CoursePlanService coursePlanService;
/**
* 查詢指定課程的課程ID
*
* @param courseId 課程ID
* @return TeachPlanNode
*/
@Override
@GetMapping("list/{courseId}")
public TeachplanNode findList(@PathVariable String courseId) {
return coursePlanService.findList(courseId);
}
}
CoursePlanService
package com.xuecheng.manage_course.service;
import com.xuecheng.framework.domain.course.ext.TeachplanNode;
import com.xuecheng.framework.service.BaseService;
import com.xuecheng.manage_course.dao.CoursePlanMapper;
import com.xuecheng.manage_course.dao.CoursePlanRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 課程計劃Service
*/
@Slf4j
@Service
public class CoursePlanService extends BaseService {
@Autowired
private CoursePlanRepository coursePlanRepository;
@Autowired
private CoursePlanMapper coursePlanMapper;
/**
* 查詢指定課程的課程ID
*
* @param courseId 課程ID
* @return TeachPlanNode
*/
public TeachplanNode findList(String courseId) {
return coursePlanMapper.findList(courseId);
}
}
CoursePlanMapper
package com.xuecheng.manage_course.dao;
import com.xuecheng.framework.domain.course.ext.TeachplanNode;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CoursePlanMapper {
/**
* 查詢課程計劃列表
*
* param courseId 課程ID
* @return TeachplanNode
*/
TeachplanNode findList(String courseId);
}
TeachplanMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.xuecheng.manage_course.dao.CoursePlanMapper">
<resultMap id="teachplanMap" type="com.xuecheng.framework.domain.course.ext.TeachplanNode">
<id column="one_id" property="id" />
<result column="one_name" property="pname" />
<collection property="children" ofType="com.xuecheng.framework.domain.course.ext.TeachplanNode">
<id column="two_id" property="id" />
<result column="two_name" property="pname" />
<collection property="children" ofType="com.xuecheng.framework.domain.course.ext.TeachplanNode">
<id column="three_id" property="id" />
<result column="three_name" property="pname" />
</collection>
</collection>
</resultMap>
<select id="findList" parameterType="java.lang.String"
resultMap="teachplanMap">
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>
</mapper>
測試
排坑
調用mapper
方法的時候報錯:
Invalid bound statement (not found): com.xuecheng.manage_course.dao.CoursePlanMapper.findList
原因是mybatis
沒有正確的加載到mapper.xml
文件,在application.yml
中加入下方配置
mybatis:
mapper-locations: classpath:com/xuecheng/manage_course/dao/*Mapper.xml
添加課程計劃
CoursePlanController
/**
* 新增課程計劃
*
* @param teachplan 課程計劃
* @return ResponseResult
*/
@Override
@PostMapping("add")
public ResponseResult add(@RequestBody Teachplan teachplan) {
if (teachplan == null || StringUtils.isBlank(teachplan.getCourseid())) {
return new ResponseResult(CommonCode.PARAMS_ERROR);
}
// 新增
Teachplan add = coursePlanService.add(teachplan);
isNullOrEmpty(add, CourseCode.COURSE_PLAN_ADD_ERROR);
return ResponseResult.SUCCESS();
}
CoursePlanService
/**
* 新增課程計劃
*
* @param teachplan 課程計劃
* @return Teachplan
*/
public Teachplan add(Teachplan teachplan) {
Teachplan root = getTeachplanRoot(teachplan.getCourseid());
// 設置父ID
if (StringUtils.isBlank(teachplan.getParentid())) {
teachplan.setParentid(root.getId());
}
// 設置節點級別
if (root.getId().equals(teachplan.getParentid())) {// 第二級別
teachplan.setGrade("2");
} else {// 第三級別
teachplan.setGrade("3");
}
return coursePlanRepository.save(teachplan);
}
/**
* 查詢指定課程的課程計劃根節點
*
* @param courseId 課程計劃
* @return Teachplan
*/
public Teachplan getTeachplanRoot(String courseId) {
// 查詢課程基本信息
Optional<CourseBase> courseBase = courseBaseRepository.findById(courseId);
if (!courseBase.isPresent()) {
ExceptionCast.cast(CourseCode.COURSE_NOT_EXIST);
}
// 查詢根節點下的所有二級節點
List<Teachplan> secondaryTeachplanList = coursePlanRepository.findByCourseidAndParentid(courseBase.get().getId(), "0");
if (secondaryTeachplanList == null || secondaryTeachplanList.isEmpty()) {// 若不存在根節點
// 創建根節點
Teachplan root = new Teachplan();
root.setCourseid(courseBase.get().getId());
root.setPname(courseBase.get().getName());
root.setParentid("0");// 根節點
root.setGrade("1");// 1級菜單
root.setStatus("0");// 未發佈
coursePlanRepository.save(root);
return root;
}
return secondaryTeachplanList.get(0);
}
CoursePlanRepository
package com.xuecheng.manage_course.dao;
import com.xuecheng.framework.domain.course.Teachplan;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
public interface CoursePlanRepository extends CrudRepository<Teachplan, String> {
/**
* 查詢課程指定父ID的所有節點列表
*
* @param courseId 課程ID
* @param parentId 父ID
* @return List<Teachplan>
*/
List<Teachplan> findByCourseidAndParentid(String courseId, String parentId);
}
前端
修改保存成功後,關閉表單輸入窗口,修改course_pan.vue
中的addTeachplan
方法
addTeachplan(){
//校驗表單
this.$refs.teachplanForm.validate((valid) => {
if (valid) {
//調用api方法
//將課程id設置到teachplanActive
this.teachplanActive.courseid = this.courseid
courseApi.addTeachplan(this.teachplanActive).then(res=>{
if(res.success){
this.$message({
showClose: true,
message: res.message,
type: 'success'
})
//關閉課程計劃信息錄入窗口
this.teachplayFormVisible = false
//刷新樹
this.findTeachplan()
}else{
this.$message.error(res.message)
}
})
}
})
}
編輯/刪除課程計劃
後端
-
CoursePlanController
/** * 編輯課程計劃 * * @param teachplan 課程計劃 * @return ResponseResult */ @Override @PutMapping("edit") public ResponseResult edit(@RequestBody Teachplan teachplan) { if (teachplan == null || StringUtils.isBlank(teachplan.getCourseid()) || StringUtils.isBlank(teachplan.getId())) { return new ResponseResult(CommonCode.PARAMS_ERROR); } // 編輯 Teachplan edit = coursePlanService.edit(teachplan); isNullOrEmpty(edit, CourseCode.COURSE_PLAN_ADD_ERROR); return ResponseResult.SUCCESS(); } /** * 刪除課程計劃 * * @param teachplanId 課程計劃ID * @return ResponseResult */ @Override @DeleteMapping("{teachplanId}") public ResponseResult delete(@PathVariable String teachplanId) { coursePlanService.deleteById(teachplanId); return ResponseResult.SUCCESS(); }
-
CoursePlanService
/** * 編輯課程計劃 * * @param teachplan 課程計劃 * @return Teachplan */ public Teachplan edit(Teachplan teachplan) { Optional<Teachplan> optionalTeachplan = coursePlanRepository.findById(teachplan.getId()); if (!optionalTeachplan.isPresent()) { ExceptionCast.cast(CourseCode.COURSE_NOT_EXIST); } Teachplan _teachplan = optionalTeachplan.get(); _teachplan.setGrade(teachplan.getGrade()); _teachplan.setPname(teachplan.getPname()); _teachplan.setCourseid(teachplan.getCourseid()); _teachplan.setDescription(teachplan.getDescription()); _teachplan.setOrderby(teachplan.getOrderby()); _teachplan.setStatus(teachplan.getStatus()); _teachplan.setTimelength(teachplan.getTimelength()); Teachplan root = getTeachplanRoot(teachplan.getCourseid()); // 設置父ID if (StringUtils.isBlank(teachplan.getParentid())) { _teachplan.setParentid(root.getId()); } return coursePlanRepository.save(_teachplan); } /** * 查詢指定ID的課程計劃 * * @param teachplanId 課程計劃ID * @return Teachplan */ public Teachplan findById(String teachplanId) { Optional<Teachplan> optionalTeachplan = coursePlanRepository.findById(teachplanId); if (!optionalTeachplan.isPresent()) { ExceptionCast.cast(CourseCode.COURSE_NOT_EXIST); } return optionalTeachplan.get(); } /** * 刪除指定ID的課程計劃 * * @param teachplanId 課程計劃ID */ public void deleteById(String teachplanId) { coursePlanRepository.deleteById(teachplanId); }
前端
-
新增API定義,修改
course.js
/*查詢課程計劃*/ export const findTeachplanById = teachplanById => { return http.requestQuickGet(apiUrl+'/course/teachplan/'+teachplanById) } /*編輯課程計劃*/ export const editTeachplan = teachplah => { return http.requestPut(apiUrl+'/course/teachplan/edit',teachplah) } /*刪除課程計劃*/ export const deleteTeachplan = teachplahId => { return http.requestDelete(apiUrl+'/course/teachplan/' + teachplahId) }
-
頁面修改,修改內容主要爲
- 點擊修改按鈕,彈出修改框並回顯數據,提交API調用
- 點擊刪除按鈕,彈出確認框,確認後API調用
edit(data){ // 查詢 courseApi.findTeachplanById(data.id).then(res => { this.teachplanActive = res if (res.grade === '2') { this.teachplanActive.parentid = '' } this.teachplayFormVisible = true }) //校驗表單 this.$refs.teachplanForm.validate((valid) => { if (valid) { //調用api方法 //將課程id設置到teachplanActive this.teachplanActive.courseid = this.courseid courseApi.editTeachplan(this.teachplanActive).then(res=>{ if(res.success){ this.$message({ showClose: true, message: res.message, type: 'success' }) this.teachplayFormVisible = false //刷新樹 this.findTeachplan() }else{ this.$message.error(res.message) } }) } }) }, remove(node, data) { // 執行刪除 this.$confirm('確認刪除該課程計劃?', '提示', { confirmButtonText: '確定', cancelButtonText: '取消', type: 'warning' }).then(() => { // 刪除 courseApi.deleteTeachplan(data.id).then(res => { // 提示消息 this.$message({ showClose: true, message: res.message, type: 'success' }) //刷新樹 this.findTeachplan() }) }) }
注意:
由於新增和編輯用的同一個表單按鈕,所以要做判斷,若ID爲空時調用新增,否則調用編輯
// 將按鈕調用改爲save方法 <el-button type="primary" v-on:click="save">提交</el-button> save() { if (this.teachplanActive.id) { this.edit(this.teachplanActive) } else { this.addTeachplan() } }
因爲我沒看課程目錄,後一天的內容居然是課程管理的實戰。。我在這一筆記裏面就自己把課程管理的實戰給做了,但是缺少課程營銷相關的內容,我就不做了,懶得做(懶人就是我😜😜😜)