1. 頁面靜態(tài)化需求
為什么要進行頁面管理?
本項目cms系統(tǒng)的功能就是根據(jù)運營需要,對門戶等子系統(tǒng)的部分頁面進行管理,從而實現(xiàn)快速根據(jù)用戶需求修改頁面內(nèi)容并上線的需求。如何修改頁面的內(nèi)容?
在開發(fā)中修改頁面內(nèi)容是需要人工編寫html及JS文件,CMS系統(tǒng)是通過程序自動化的對頁面內(nèi)容進行修改,通過頁面靜態(tài)化技術生成html頁面。如何對頁面進行靜態(tài)化?
一個頁面等于模板加數(shù)據(jù),在添加頁面的時候我們選擇了頁面的模板。
頁面靜態(tài)化就是將頁面模板和數(shù)據(jù)通過技術手段將二者合二為一,生成一個html網(wǎng)頁文件。-
頁面靜態(tài)化及頁面發(fā)布流程圖如下:
業(yè)務流程如下:
- 獲取模型數(shù)據(jù)
- 制作模板
- 對頁面進行靜態(tài)化
- 將靜態(tài)化生成的html頁面存放文件系統(tǒng)中
- 將存放在文件系統(tǒng)的html文件發(fā)布到服務器
2. 頁面靜態(tài)化
2.1 頁面靜態(tài)化流程
通過上邊對FreeMarker的研究我們得出:模板+數(shù)據(jù)模型=輸出,頁面靜態(tài)化需要準備數(shù)據(jù)模型和模板,先知道數(shù)據(jù)模型的結(jié)構(gòu)才可以編寫模板,因為在模板中要引用數(shù)據(jù)模型中的數(shù)據(jù),CMS頁面數(shù)據(jù)模型獲取、模板管理及靜態(tài)化的過程。
下邊討論一個問題:如何獲取頁面的數(shù)據(jù)模型?
CMS管理了各種頁面,CMS對頁面進行靜態(tài)化時需要數(shù)據(jù)模型,但是CMS并不知道每個頁面的數(shù)據(jù)模型的具體內(nèi)容,它只管執(zhí)行靜態(tài)化程序便可對頁面進行靜態(tài)化,所以CMS靜態(tài)化程序需要通過一種通用的方法來獲取數(shù)據(jù)模型。
在編輯頁面信息時指定一個DataUrl,此DataUrl便是獲取數(shù)據(jù)模型的Url,它基于Http方式,CMS對頁面進行靜態(tài)化時會從頁面信息中讀取DataUrl,通過Http遠程調(diào)用的方法請求DataUrl獲取數(shù)據(jù)模型。
管理員怎么知道DataUrl的內(nèi)容呢?
舉例說明:
此頁面是輪播圖頁面,它的DataUrl由開發(fā)輪播圖管理的程序員提供。
此頁面是精品課程推薦頁面,它的DataUrl由精品課程推薦的程序員提供。
此頁面是課程詳情頁面,它的DataUrl由課程管理的程序員提供。
頁面靜態(tài)化流程如下圖:
- 靜態(tài)化程序首先讀取頁面獲取DataUrl。
- 靜態(tài)化程序遠程請求DataUrl得到數(shù)據(jù)模型。
- 獲取頁面模板。
- 執(zhí)行頁面靜態(tài)化。

2.2 數(shù)據(jù)模型
2.2.1 需求分析
CMS中有輪播圖管理、精品課程推薦的功能,以輪播圖管理為例說明:輪播圖管理是通過可視化的操作界面由管理員指定輪播圖圖片地址,最后將輪播圖圖片地址保存在cms_config集合中,下邊是輪播圖數(shù)據(jù)模型:

針對首頁的輪播圖信息、精品推薦等信息的獲取統(tǒng)一提供一個Url供靜態(tài)化程序調(diào)用,這樣我們就知道了輪播圖頁面、精品課程推薦頁面的DataUrl,管理在頁面配置中將此Url配置在頁面信息中。
2.2.2 接口定義
輪播圖信息、精品推薦等信息存儲在MongoDB的cms_config集合中。

cms_config有固定的數(shù)據(jù)結(jié)構(gòu),如下:
@Data
@ToString
@Document(collection = "cms_config")
public class CmsConfig {
@Id
private String id;//主鍵
private String name;//數(shù)據(jù)模型的名稱
private List<CmsConfigModel> model;//數(shù)據(jù)模型項目
}
數(shù)據(jù)模型項目內(nèi)容如下:
@Data
@ToString
public class CmsConfigModel {
private String key;//主鍵
private String name;//項目名稱
private String url;//項目url
private Map mapValue;//項目復雜值
private String value;//項目簡單值
}
上邊的模型結(jié)構(gòu)可以對照cms_config中的數(shù)據(jù)進行分析。
其中,在mapValue 中可以存儲一些復雜的數(shù)據(jù)模型內(nèi)容。
根據(jù)配置信息Id查詢配置信息,定義接口如下:
package com.xuecheng.api.cms;
@Api(value="cms配置管理接口",description = "cms配置管理接口,提供數(shù)據(jù)模型的管理、查詢接口")
public interface CmsConfigControllerApi {
@ApiOperation("根據(jù)id查詢CMS配置信息")
public CmsConfig getmodel(String id);
}
2.2.3 Dao
package com.xuecheng.manage_cms.dao;
public interface CmsConfigRepository extends MongoRepository<CmsConfig,String> {
}
2.2.4 Service
定義CmsConfigService實現(xiàn)根據(jù)id查詢CmsConfig信息。
//根據(jù)id查詢cmsConfig
public CmsConfig getConfigById(String id){
Optional<CmsConfig> optional = cmsConfigRepository.findById(id);
if(optional.isPresent()){
CmsConfig cmsConfig = optional.get();
return cmsConfig;
}
return null;
}
2.2.5 Controller
@RestController
@RequestMapping("/cms/config")
public class CmsConfigController implements CmsConfigControllerApi {
@Autowired
CmsConfigService cmsConfigService;
@Override
@GetMapping("/getmodel/{id}")
public CmsConfig getmodel(@PathVariable("id") String id) {
return cmsConfigService.getConfigById(id);
}
}
2.2.6 測試
使用postman測試接口:
get請求:http://localhost:31001/cms/config/getmodel/5a791725dd573c3574ee333f (輪播圖信息)
2.3 遠程請求接口
SpringMVC提供 RestTemplate請求http接口,RestTemplate的底層可以使用第三方的http客戶端工具實現(xiàn)http 的請求,常用的http客戶端工具有Apache HttpClient、OkHttpClient等,本項目使用OkHttpClient完成http請求,原因也是因為它的性能比較出眾。
-
添加依賴
<dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> </dependency> -
配置RestTemplate
在SpringBoot啟動類中配置 RestTemplate
package com.xuecheng.manage_cms; @SpringBootApplication @EntityScan("com.xuecheng.framework.domain.cms")//掃描實體類 @ComponentScan(basePackages={"com.xuecheng.api"})//掃描接口 @ComponentScan(basePackages={"com.xuecheng.manage_cms"})//掃描本項目下的所有類 @ComponentScan(basePackages={"com.xuecheng.framework"})//掃描common包下的類 public class ManageCmsApplication { public static void main(String[] args) { SpringApplication.run(ManageCmsApplication.class,args); } @Bean public RestTemplate restTemplate(){ return new RestTemplate(new OkHttp3ClientHttpRequestFactory()); } } -
測試RestTemplate
根據(jù)url獲取數(shù)據(jù),并轉(zhuǎn)為map格式。
@Test public void testRestTemplate(){ ResponseEntity<Map> forEntity = restTemplate.getForEntity("http://localhost:31001/cms/config/get/5a791725dd573c3574ee333f", Map.class); System.out.println(forEntity); }
3. 模板管理
3.1 模板管理業(yè)務流程
CMS提供模板管理功能,業(yè)務流程如下:

- 要增加新模板首先需要制作模板,模板的內(nèi)容就是Freemarker ftl模板內(nèi)容。
- 通過模板管理模塊功能新增模板、修改模板、刪除模板。
- 模板信息存儲在MongoDB數(shù)據(jù)庫,其中模板信息存儲在cms_template集合中,模板文件存儲在GridFS文件系統(tǒng)中。
cms_template集合:
下邊是一個模板的例子:
{
"_id" : ObjectId("5a962b52b00ffc514038faf7"),
"_class" : "com.xuecheng.framework.domain.cms.CmsTemplate",
"siteId" : "5a751fab6abb5044e0d19ea1",
"templateName" : "首頁",
"templateParameter" : "",
"templateFileId" : "5a962b52b00ffc514038faf5"
}
上邊模板信息中templateFileId是模板文件的ID,此ID對應GridFS文件系統(tǒng)中文件ID。
3.2 . 模板制作
3.2.1 編寫模板文件
-
輪播圖頁面原型
在門戶的靜態(tài)工程目錄有輪播圖的靜態(tài)頁面,路徑是:
/include/index_banner.html。 -
數(shù)據(jù)模型為:
通過http 獲取到數(shù)據(jù)模型如下:
下圖數(shù)據(jù)模型的圖片路徑改成可以瀏覽的正確路徑。
{ "id": "5a791725dd573c3574ee333f", "name": "輪播圖", "model": [ { "key": "banner1", "name": "輪播圖1地址", "url": null, "mapValue": null, "value": "http://www.xuecheng.com/img/widget‐bannerB.jpg" }, { "key": "banner2", "name": "輪播圖2地址", "url": null, "mapValue": null, "value": "http://www.xuecheng.com/img/widget‐bannerA.jpg" }, { "key": "banner3", "name": "輪播圖3地址", "url": null, "mapValue": null, "value": "http://www.xuecheng.com/img/widget‐banner3.jpg" } ] } -
編寫模板
在freemarker測試工程中新建模板index_banner.ftl。
<!DOCTYPE html> <html lang="en"> <head> ...... </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> ...... </body> </html>
3.2.2 模板測試
在freemarker測試工程編寫一個方法測試輪播圖模板,代碼如下:
@Autowired
RestTemplate restTemplate;
@RequestMapping("/banner")
public String index_banner(Map<String, Object> map){
String dataUrl = "http://localhost:31001/cms/config/getmodel/5a791725dd573c3574ee333f";
ResponseEntity<Map> forEntity = restTemplate.getForEntity(dataUrl, Map.class);
Map body = forEntity.getBody();
map.putAll(body);
return "index_banner";
}
請求:http://localhost:8088/freemarker/banner

4. GridFS介紹
GridFS是MongoDB提供的用于持久化存儲文件的模塊,CMS使用MongoDB存儲數(shù)據(jù),使用GridFS可以快速集成開發(fā)。
它的工作原理是:
在GridFS存儲文件是將文件分塊存儲,文件會按照256KB的大小分割成多個塊進行存儲,GridFS使用兩個集合
(collection)存儲文件,一個集合是chunks, 用于存儲文件的二進制數(shù)據(jù);一個集合是files,用于存儲文件的元數(shù)據(jù)信息(文件名稱、塊大小、上傳時間等信息)。
從GridFS中讀取文件要對文件的各各塊進行組裝、合并。
4.1 GridFS 存取文件測試
4.1.1 存文件
使用GridFsTemplate存儲文件測試代碼:
向測試程序注入GridFsTemplate。
@Test
public void testGridFs() throws FileNotFoundException {
//要存儲的文件
File file = new File("d:/index_banner.html");
//定義輸入流
FileInputStream inputStram = new FileInputStream(file);
//向GridFS存儲文件
ObjectId objectId = = gridFsTemplate.store(inputStram, "輪播圖測試文件01", "");
//得到文件ID
String fileId = objectId.toString();
System.out.println(file);
}
存儲原理說明:
文件存儲成功得到一個文件id
此文件id是fs.files集合中的主鍵。
可以通過文件id查詢fs.chunks表中的記錄,得到文件的內(nèi)容。
4.1.2 讀取文件
-
在config包中定義Mongodb的配置類,如下:
GridFSBucket用于打開下載流對象
@Configuration public class MongoConfig { @Value("${spring.data.mongodb.database}") String db; @Bean public GridFSBucket getGridFSBucket(MongoClient mongoClient){ MongoDatabase database = mongoClient.getDatabase(db); GridFSBucket bucket = GridFSBuckets.create(database); return bucket; } } -
測試代碼如下
@SpringBootTest @RunWith(SpringRunner.class) public class GridFsTest { @Autowired GridFsTemplate gridFsTemplate; @Autowired GridFSBucket gridFSBucket; @Test public void queryFile() throws IOException { String fileId = "5b9c54e264c614237c271a99"; //根據(jù)id查詢文件 GridFSFile gridFSFile = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(fileId))); //打開下載流對象 GridFSDownloadStream gridFSDownloadStream = gridFSBucket.openDownloadStream(gridFSFile.getObjectId()); //創(chuàng)建gridFsResource,用于獲取流對象 GridFsResource gridFsResource = new GridFsResource(gridFSFile,gridFSDownloadStream); //獲取流中的數(shù)據(jù) String s = IOUtils.toString(gridFsResource.getInputStream(), "UTF‐8"); System.out.println(s); } ...
4.1.3 刪除文件
//刪除文件
@Test
public void testDelFile() throws IOException {
//根據(jù)文件id刪除fs.files和fs.chunks中的記錄
gridFsTemplate.delete(Query.query(Criteria.where("_id").is("5b32480ed3a022164c4d2f92")));
}
4.2 模板存儲
根據(jù)模板管理的流程,最終將模板信息存儲到MongoDB的cms_template中,將模板文件存儲到GridFS中。
4.2.1 添加模板
- 使用GridFS測試代碼存儲模板文件到GridFS,并得到文件id.
- 向cms_template添加記錄。

4.2.2 刪除模板
- 使用GridFS測試代碼根據(jù)文件id刪除模板文件。
- 根據(jù)模板id刪除cms_template中的記錄。
4.2.3 修改模板信息
使用Studio 3T修改cms_template中的記錄。
4.2.4 修改模板文件
通過Studio 3T修改模板文件(此方法限文件小于256K)
可以通過Studio 3T修改模板文件,先找到模板文件,再導入進去:

5. 靜態(tài)化測試
package com.xuecheng.manage_cms.service;
@Service
public class PageService {
@Autowired
CmsPageRepository cmsPageRepository;
@Autowired
CmsConfigRepository cmsConfigRepository;
@Autowired
RestTemplate restTemplate;
@Autowired
CmsTemplateRepository cmsTemplateRepository;
@Autowired
GridFsTemplate gridFsTemplate;
@Autowired
GridFSBucket gridFSBucket;
......
//頁面靜態(tài)化方法
public String getPageHtml(String pageId){
//獲取數(shù)據(jù)模型
Map model = getModelByPageId(pageId);
if(model == null){
//數(shù)據(jù)模型獲取不到
ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_DATAISNULL);
}
//獲取頁面的模板信息
String template = getTemplateByPageId(pageId);
if(StringUtils.isEmpty(template)){
ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_TEMPLATEISNULL);
}
//執(zhí)行靜態(tài)化
String html = generateHtml(template, model);
return html;
}
//執(zhí)行靜態(tài)化
private String generateHtml(String templateContent,Map model ){
//創(chuàng)建配置對象
Configuration configuration = new Configuration(Configuration.getVersion());
//創(chuàng)建模板加載器
StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();
stringTemplateLoader.putTemplate("template",templateContent);
//向configuration配置模板加載器
configuration.setTemplateLoader(stringTemplateLoader);
//獲取模板
try {
Template template = configuration.getTemplate("template");
//調(diào)用api進行靜態(tài)化
String content = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
return content;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//獲取頁面的模板信息
private String getTemplateByPageId(String pageId){
//取出頁面的信息
CmsPage cmsPage = this.getById(pageId);
if(cmsPage == null){
//頁面不存在
ExceptionCast.cast(CmsCode.CMS_PAGE_NOTEXISTS);
}
//獲取頁面的模板id
String templateId = cmsPage.getTemplateId();
if(StringUtils.isEmpty(templateId)){
ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_TEMPLATEISNULL);
}
//查詢模板信息
Optional<CmsTemplate> optional = cmsTemplateRepository.findById(templateId);
if(optional.isPresent()){
CmsTemplate cmsTemplate = optional.get();
//獲取模板文件id
String templateFileId = cmsTemplate.getTemplateFileId();
//從GridFS中取模板文件內(nèi)容
//根據(jù)文件id查詢文件
GridFSFile gridFSFile = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(templateFileId)));
//打開一個下載流對象
GridFSDownloadStream gridFSDownloadStream = gridFSBucket.openDownloadStream(gridFSFile.getObjectId());
//創(chuàng)建GridFsResource對象,獲取流
GridFsResource gridFsResource = new GridFsResource(gridFSFile,gridFSDownloadStream);
//從流中取數(shù)據(jù)
try {
String content = IOUtils.toString(gridFsResource.getInputStream(), "utf-8");
return content;
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
//獲取數(shù)據(jù)模型
public Map getModelByPageId(String pageId){
//取出頁面的信息
CmsPage cmsPage = this.getById(pageId);
if(cmsPage == null){
//頁面不存在
ExceptionCast.cast(CmsCode.CMS_PAGE_NOTEXISTS);
}
//取出頁面的dataUrl
String dataUrl = cmsPage.getDataUrl();
if(StringUtils.isEmpty(dataUrl)){
//頁面dataUrl為空
ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_DATAURLISNULL);
}
//通過restTemplate請求dataUrl獲取數(shù)據(jù)
ResponseEntity<Map> forEntity = restTemplate.getForEntity(dataUrl, Map.class);
Map body = forEntity.getBody();
return body;
}
}
6. 頁面預覽
6.1 配置Nginx代理
為了通過nginx請求靜態(tài)資源(css、圖片等),通過nginx代理進行頁面預覽。
在www.xuecheng.com虛擬主機配置:
#頁面預覽
location /cms/preview/ {
proxy_pass http://cms_server_pool/cms/preview/;
}
配置cms_server_pool將請求轉(zhuǎn)發(fā)到cms:
#cms頁面預覽
upstream cms_server_pool{
server 127.0.0.1:31001 weight=10;
}

重新加載nginx 配置文件。

從cms_page找一個頁面進行測試。注意:頁面配置一定要正確,需設置正確的模板id和dataUrl。
在瀏覽器打開:http://www.xuecheng.com/cms/preview/5a795ac7dd573c04508f3a56
5a795ac7dd573c04508f3a56 :輪播圖頁面的id

6.2 添加“頁面預覽”鏈接
在頁面列表添加“頁面預覽”鏈接,修改page_list.vue:
<template slot‐scope="page">
<el‐button @click="edit(page.row.pageId)" type="text" size="small">修改</el‐button>
<el‐button @click="del(page.row.pageId)" type="text" size="small">刪除</el‐button>
<el‐button @click="preview(page.row.pageId)" type="text" size="small">頁面預覽</el‐button>
...
添加preview方法:
//頁面預覽
preview(pageId){
window.open("http://www.xuecheng.com/cms/preview/"+pageId)
},
效果:

點擊輪播圖頁面的“頁面預覽”,預覽頁面效果。

