页面静态化流程
页面静态化流程如下图:
、
- 静态化程序首先读取页面获取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做了笔记(献丑了)