頁面靜態化流程
頁面靜態化流程如下圖:
、
- 靜態化程序首先讀取頁面獲取DataUrl。
- 靜態化程序遠程請求DataUrl得到數據模型。
- 獲取頁面模板。
- 執行頁面靜態化。
CMS模板文件上傳
CMS頁面模板文件上傳功能實現,該功能在新增或編輯模板的時候可進行模板文件的上傳
最終頁面效果如下:
後端
CmsTemplateControllerApi
新增接口定義
@ApiOperation("上傳模板文件")
String uploadTemplate(MultipartFile file);
@ApiOperation("移除模板文件")
void removeTemplateFile(String templateFileId);
CmsTemplateController
接口實現
@Override
@PostMapping("upload")
public String uploadTemplate(@RequestParam("file") MultipartFile file) {
// 上傳文件
String templateFileId = cmsTemplateService.uploadTemplateFile(file);
if (StringUtils.isBlank(templateFileId)) {
ExceptionCast.cast(CmsCode.CMS_TEMPLATE_FILE_UPLOAD_ERROR);
}
return templateFileId;
}
@Override
@DeleteMapping("file/remove/{templateFileId}")
public void removeTemplateFile(@PathVariable String templateFileId) {
cmsTemplateService.removeTemplateFile(templateFileId);
}
CmsTemplateService
完成模板文件上傳與刪除
@Autowired
private GridFsTemplate gridFsTemplate;
/**
* 上傳文件
*
* @param file 文件
*/
public String uploadTemplateFile(MultipartFile file) {
try {
return gridFsTemplate.store(file.getInputStream(), "template").toString();
} catch (Exception e) {
return "";
}
}
/**
* 移除文件
*
* @param templateFileId 模板文件ID
*/
public void removeTemplateFile(String templateFileId) {
Query query = new Query(Criteria.where("_id").is(templateFileId));
gridFsTemplate.delete(query);
}
修改刪除方法的邏輯,在刪除模板之前先刪除模板文件
/**
* 刪除指定ID的模板
*
* @param templateId 模板ID
*/
public void deleteById(String templateId) {
// 刪除模板文件
Optional<CmsTemplate> templateOptional = cmsTemplateRepository.findById(templateId);
if (templateOptional.isPresent()) {
Query query = new Query(Criteria.where("_id").is(templateOptional.get().getTemplateFileId()));
// 刪除文件
gridFsTemplate.delete(query);
// 刪除模板
cmsTemplateRepository.deleteById(templateId);
}
}
前端
API定義
修改src/module/cms/api/cms.js
,新增API定義
/**
* 按ID刪除模板
*/
export const removeTemplateFileById = (templateFileId) => {
return http.requestDelete(apiUrl + '/cms/template/file/remove/'+ templateFileId)
}
頁面內容新增
在template_add.vue
以及template_edit.vue
中新增文件上傳框
<el-form-item label="模板文件ID">
<el-upload
class="upload-demo"
drag
action="http://localhost:11000/api/cms/template/upload"
:multiple="multiple"
:limit="limit"
:on-success="uploadOnSuccess"
:on-remove="onRemove">
<i class="el-icon-upload"></i>
<div class="el-upload__text">將文件拖到此處,或<em>點擊上傳</em></div>
</el-upload>
</el-form-item>
新增方法
在template_add.vue
以及template_edit.vue
中新增需要調用的方法
// 模板文件上傳成功
uploadOnSuccess:function(response, file, fileList) {
if (response) {
this.cmsTemplate.templateFileId = response
this.$message({
showClose: true,
message: '模板文件上傳成功',
type: 'success'
})
}
},
// 移除模板文件
onRemove:function(file, fileList) {
// 調用API 刪除文件
cmsApi.removeTemplateFileById(this.cmsTemplate.templateFileId).then(res => {
this.$message({
showClose: true,
message: '刪除成功',
type: 'success'
})
})
}
數據模型接口實現
實體類
-
CmsConfig
package com.xuecheng.framework.domain.cms; import lombok.Data; import lombok.ToString; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; import java.util.List; /** * Created by admin on 2018/2/6. */ @Data @ToString @Document(collection = "cms_config") public class CmsConfig { @Id private String id; private String name; private List<CmsConfigModel> model; }
-
CmsConfigModel
package com.xuecheng.framework.domain.cms; import lombok.Data; import lombok.ToString; import java.util.Map; /** * Created by admin on 2018/2/6. */ @Data @ToString public class CmsConfigModel { private String key; private String name; private String url; private Map mapValue; private String value; }
-
CmsConfigResult
package com.xuecheng.framework.domain.cms.response; import com.xuecheng.framework.domain.cms.CmsConfig; import com.xuecheng.framework.model.response.ResponseResult; import com.xuecheng.framework.model.response.ResultCode; import lombok.Data; @Data public class CmsConfigResult extends ResponseResult { CmsConfig cmsConfig; public CmsConfigResult(ResultCode resultCode, CmsConfig cmsConfig) { super(resultCode); this.cmsConfig = cmsConfig; } }
CmsConfigRepository
package com.xuecheng.manage_cms.dao;
import com.xuecheng.framework.domain.cms.CmsConfig;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface CmsConfigRepository extends MongoRepository<CmsConfig, String> {
}
CmsConfigService
package com.xuecheng.manage_cms.service;
import com.xuecheng.framework.domain.cms.CmsConfig;
import com.xuecheng.manage_cms.dao.CmsConfigRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class CmsConfigService {
@Autowired
private CmsConfigRepository cmsConfigRepository;
/**
* 按ID查詢CMS配置信息
*
* @param id id
*/
public CmsConfig findById(String id) {
return cmsConfigRepository.findById(id).orElse(null);
}
}
CmsConfigController & CmsConfigControllerApi
-
CmsConfigController
package com.xuecheng.manage_cms.controller; import com.xuecheng.api.cms.CmsConfigControllerApi; import com.xuecheng.framework.domain.cms.CmsConfig; import com.xuecheng.framework.domain.cms.response.CmsCode; import com.xuecheng.framework.exception.ExceptionCast; import com.xuecheng.manage_cms.service.CmsConfigService; 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("cms/config") public class CmsConfigController implements CmsConfigControllerApi { @Autowired private CmsConfigService cmsConfigService; @Override @GetMapping("{id}") public CmsConfig getModel(@PathVariable String id) { CmsConfig cmsConfig = cmsConfigService.findById(id); if (cmsConfig == null) { ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_DATAURLISNULL); } return cmsConfig; } }
-
CmsConfigControllerApi
package com.xuecheng.api.cms; import com.xuecheng.framework.domain.cms.CmsConfig; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @Api(value = "cms配置管理接口", description = "cms配置管理接口,提供數據模型的管理、查詢接口") public interface CmsConfigControllerApi { @ApiOperation("根據id查詢CMS配置信息") CmsConfig getModel(String id); }
靜態化實現
CmsPageService
編寫靜態頁面生成方法
/**
* 根據頁面ID生成html
* 流程:
* 1、靜態化程序獲取頁面的DataUrl
* 2、靜態化程序遠程請求DataUrl獲取數據模型。
* 3、靜態化程序獲取頁面的模板信息
* 4、執行頁面靜態化
*
* @param pageId 頁面ID
*/
public String genHtml(String pageId) {
String html = null;
// 獲取數據模型
Map model = getModel(pageId);
// 獲取模板信息
String templateContent = getTemplate(pageId);
// 執行靜態化
try {
// 配置類
Configuration configuration = new Configuration(Configuration.getVersion());
// 模板加載器
StringTemplateLoader templateLoader = new StringTemplateLoader();
templateLoader.putTemplate("template", templateContent);
// 配置
configuration.setTemplateLoader(templateLoader);
// 獲取模板
Template template = configuration.getTemplate("template");
// 靜態化
html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
} catch (IOException e) {
// 獲取模板失敗
ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_TEMPLATEISNULL);
} catch (TemplateException e) {
// 靜態化失敗
ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_SAVEHTMLERROR);
}
return html;
}
/**
* 獲取模板內容
*
* @param pageId 頁面ID
* @return 模板內容
*/
private String getTemplate(String pageId) {
// 查詢頁面信息
CmsPage cmsPage = this.findByPageId(pageId);
isNullOrEmpty(cmsPage, CmsCode.CMS_EDITPAGE_NOTEXISTS);
isNullOrEmpty(cmsPage.getTemplateId(), CmsCode.CMS_EDITPAGE_NOTEXISTS);
// 查詢模板數據
CmsTemplate cmsTemplate = cmsTemplateService.findByTemplateId(cmsPage.getTemplateId());
isNullOrEmpty(cmsTemplate, CmsCode.CMS_GENERATEHTML_TEMPLATEISNULL);
// 查詢模板文件信息
isNullOrEmpty(cmsTemplate.getTemplateFileId(), CmsCode.CMS_GENERATEHTML_TEMPLATEISNULL);
// 下載文件
String fileContent = downloadFileFromMongoDB(cmsTemplate.getTemplateFileId());
isNullOrEmpty(fileContent, CmsCode.CMS_GENERATEHTML_TEMPLATEISNULL);
return fileContent;
}
/**
* 下載文件
*
* @param fileId 文件ID
* @return 文件內容
*/
private String 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);
//獲取流中的數據
String content = null;
try {
content = IOUtils.toString(gridFsResource.getInputStream(), "utf-8");
} catch (IOException ignored) { }
return content;
}
/**
* 根據pageId獲取模型數據
*
* @param pageId 頁面ID
* @return 模型數據
*/
private Map getModel(String pageId) {
// 查詢頁面信息
CmsPage cmsPage = this.findByPageId(pageId);
if (cmsPage == null) {
ExceptionCast.cast(CmsCode.CMS_EDITPAGE_NOTEXISTS);
}
if (StringUtils.isBlank(cmsPage.getDataUrl())) {
ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_DATAURLISNULL);
}
// 獲取模型數據
ResponseEntity<Map> forEntity = restTemplate.getForEntity(cmsPage.getDataUrl(), Map.class);
if (forEntity.getBody() == null) {
ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_DATAISNULL);
}
return forEntity.getBody();
}
效果測試
-
編寫模板文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="http://www.xuecheng.com/plugins/normalize-css/normalize.css" /> <link rel="stylesheet" href="http://www.xuecheng.com/plugins/bootstrap/dist/css/bootstrap.css" /> <link rel="stylesheet" href="http://www.xuecheng.com/css/page-learing-index.css" /> <link rel="stylesheet" href="http://www.xuecheng.com/css/page-header.css" /> </head> <body> <div class="banner-roll"> <div class="banner-item"> <#if model??> <#list model as item> <div class="item" style="background-image: url(${item.value});"></div> </#list> </#if> </div> <div class="indicators"></div> </div> <script type="text/javascript" src="http://www.xuecheng.com/plugins/jquery/dist/jquery.js"></script> <script type="text/javascript" src="http://www.xuecheng.com/plugins/bootstrap/dist/js/bootstrap.js"></script> <script type="text/javascript"> var tg = $('.banner-item .item'); var num = 0; for (i = 0; i < tg.length; i++) { $('.indicators').append('<span></span>'); $('.indicators').find('span').eq(num).addClass('active'); } function roll() { tg.eq(num).animate({ 'opacity': '1', 'z-index': num }, 1000).siblings().animate({ 'opacity': '0', 'z-index': 0 }, 1000); $('.indicators').find('span').eq(num).addClass('active').siblings().removeClass('active'); if (num >= tg.length - 1) { num = 0; } else { num++; } } $('.indicators').find('span').click(function() { num = $(this).index(); roll(); }); var timer = setInterval(roll, 3000); $('.banner-item').mouseover(function() { clearInterval(timer) }); $('.banner-item').mouseout(function() { timer = setInterval(roll, 3000) }); </script> </body> </html>
-
上傳模板文件到文件系統中,我在最開始已經實現了模板文件的上傳,所以我這裏只需要新增一個模板。
-
新建頁面並使用該模板
-
編寫測試方法
@Test public void testGenHtml() { // 此ID需要到數據庫中查看 String pageId = "5d7b85025f315734a084d61e"; // 生成html String s = cmsPageService.genHtml(pageId); System.out.println(s); }
-
運行
只截取了部分,大致效果差不多,我就不貼從頁面訪問的效果了,因爲這個圖片鏈接是
fastDFS
中的鏈接~ 😅😅😅😅。
頁面預覽
需求分析
頁面在發佈前增加頁面預覽的步驟,方便用戶檢查頁面內容是否正確。頁面預覽的流程如下:
- 用戶進入cms前端,點擊“頁面預覽”在瀏覽器請求cms頁面預覽鏈接。
- cms根據頁面id查詢DataUrl並遠程請求DataUrl獲取數據模型。
- cms根據頁面id查詢頁面模板內容。
- cms執行頁面靜態化。
- cms將靜態化內容響應給瀏覽器。
- 在瀏覽器展示頁面內容,實現頁面預覽的功能。
後端
引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
配置Freemarker
spring:
freemarker:
# 關閉緩存
cache: false
settings:
# 模板更新時間,正式環境可以設置較大
template_update_delay: 0
CmsPagePreviewController
package com.xuecheng.manage_cms.controller;
import com.xuecheng.framework.domain.cms.response.CmsCode;
import com.xuecheng.framework.web.BaseController;
import com.xuecheng.manage_cms.service.CmsPageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.ServletOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Slf4j
@Controller
public class CmsPagePreviewController extends BaseController {
@Autowired
private CmsPageService cmsPageService;
/**
* CMS頁面預覽
*
* @param pageId 預覽的頁面ID
*/
@RequestMapping("cms/preview/{pageId}")
public void preview(@PathVariable String pageId) {
// 獲取頁面內容
String htmlContent = cmsPageService.genHtml(pageId);
isNullOrEmpty(htmlContent, CmsCode.CMS_GENERATEHTML_HTMLISNULL);
// 輸出到頁面返回
try {
ServletOutputStream outputStream = response.getOutputStream();
outputStream.write(htmlContent.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
log.error("[CMS頁面預覽] 預覽頁面失敗,異常信息:{}", e);
}
}
}
前端
添加頁面預覽按鈕
修改page_list.vue
,在操作欄新增頁面預覽
按鈕
<el-button
size="small"
type="text"
@click="preview(scope.$index, scope.row)">頁面預覽
</el-button>
方法區新增頁面預覽方法
// 頁面預覽
preview:function(index, data) {
window.open("http://localhost:11000/cms/preview/" + data.pageId)
}
注意
我這裏沒有按照教程上面的設置nginx
,因爲我做到這裏的時候我連前端門戶工程都沒搭建(lazy,emmmm~),所以這裏就沒有使用nginx
理了。
第五天的內容全部都是RabbitMQ的教學,所有我就沒有整理成筆記了。
但是!但是!但是!我有另外兩篇文章專門對RabbitMQ做了筆記(獻醜了)