学成在线笔记四:页面静态化
页面静态化

页面静态化流程

页面静态化流程如下图:

  1. 静态化程序首先读取页面获取DataUrl。
  2. 静态化程序远程请求DataUrl得到数据模型。
  3. 获取页面模板。
  4. 执行页面静态化。

CMS模板文件上传

CMS页面模板文件上传功能实现,该功能在新增或编辑模板的时候可进行模板文件的上传

最终页面效果如下:

后端

CmsTemplateControllerApi

新增接口定义

1
2
3
4
5
@ApiOperation("上传模板文件")
String uploadTemplate(MultipartFile file);

@ApiOperation("移除模板文件")
void removeTemplateFile(String templateFileId);

CmsTemplateController

接口实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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

完成模板文件上传与删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@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);
}

修改删除方法的逻辑,在删除模板之前先删除模板文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 删除指定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定义

1
2
3
4
5
6
/**
* 按ID删除模板
*/
export const removeTemplateFileById = (templateFileId) => {
return http.requestDelete(apiUrl + '/cms/template/file/remove/'+ templateFileId)
}

页面内容新增

template_add.vue以及template_edit.vue中新增文件上传框

1
2
3
4
5
6
7
8
9
10
11
12
13
<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中新增需要调用的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 模板文件上传成功
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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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

1
2
3
4
5
6
7
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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

编写静态页面生成方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/**
* 根据页面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();
}

效果测试

  1. 编写模板文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    <!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>
  2. 上传模板文件到文件系统中,我在最开始已经实现了模板文件的上传,所以我这里只需要新增一个模板。

  3. 新建页面并使用该模板

  4. 编写测试方法

    1
    2
    3
    4
    5
    6
    7
    8
    @Test
    public void testGenHtml() {
    // 此ID需要到数据库中查看
    String pageId = "5d7b85025f315734a084d61e";
    // 生成html
    String s = cmsPageService.genHtml(pageId);
    System.out.println(s);
    }
  5. 运行

    只截取了部分,大致效果差不多,我就不贴从页面访问的效果了,因为这个图片链接是fastDFS中的链接~ 😅😅😅😅。

页面预览

需求分析

页面在发布前增加页面预览的步骤,方便用户检查页面内容是否正确。页面预览的流程如下:

  1. 用户进入cms前端,点击“页面预览”在浏览器请求cms页面预览链接。
  2. cms根据页面id查询DataUrl并远程请求DataUrl获取数据模型。
  3. cms根据页面id查询页面模板内容。
  4. cms执行页面静态化。
  5. cms将静态化内容响应给浏览器。
  6. 在浏览器展示页面内容,实现页面预览的功能。

后端

引入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

配置Freemarker

1
2
3
4
5
6
7
spring:
freemarker:
# 关闭缓存
cache: false
settings:
# 模板更新时间,正式环境可以设置较大
template_update_delay: 0

CmsPagePreviewController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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,在操作栏新增页面预览按钮

1
2
3
4
5
<el-button
size="small"
type="text"
@click="preview(scope.$index, scope.row)">页面预览
</el-button>

方法区新增页面预览方法

1
2
3
4
// 页面预览
preview:function(index, data) {
window.open("http://localhost:11000/cms/preview/" + data.pageId)
}

注意

我这里没有按照教程上面的设置nginx,因为我做到这里的时候我连前端门户工程都没搭建(lazy,emmmm~),所以这里就没有使用nginx理了。

第五天的内容全部都是RabbitMQ的教学,所有我就没有整理成笔记了。

但是!但是!但是!我有另外两篇文章专门对RabbitMQ做了笔记(献丑了)

RabbitMQ详解

代码获取

代码获取

文章作者: imxushuai
文章链接: https://www.imxushuai.com/2020/06/10/22.学成在线笔记四:页面静态化/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 imxushuai
支付宝打赏
微信打赏