关于页面动态布局实现方案(四)

(2)加载用户定制页面的第二种方案

其实在前文(一)已经提到了,把页面、模块、布局都抽象成组件类,就如同Jsp标签一样

这种标签类负责输出每个模块的内容,当然包含了业务数据在里面,具体参考一下几个类

 

大家会发现:在每个组件类中都强制实现toString方法,其实就是使用FreeMarker的功能,每个组件类都有属性对应ftl模板,并且又有数据属性

所以使用FreeMarker的process方法,传入ftl模板和数据既可以把布局、模板都输出来

这才是为什么设计布局、模块表结构时,为什么要记录其对应的ftl模板路径

当然:要注意一点,因为所有ftl模板是已经定制好的,放在某个固定的目录,可以在系统启动之后先一次加载完所有模板

那么每个组件类只要传入(模板所在路径+数据)即可得到页面

(相信使用过FreeMarker来模拟Hibernate或者MyBatis来做动态SQL的同学都明白我的意思)

 

所有组件的父类:BaseTag

/**
 * 
 */
package com.yli.cshop.bean;

/**
 * 
 * 把页面/布局/模块都抽象成一个个组件,就好比Jsp标签一样<br>
 * BaseTag 则是所有组件的父类,强制子类重写toString方法
 * 
 * @author yli
 *
 */
public abstract class BaseTag {

	/**
	 * 重写toString()方法
	 */
	public abstract String toString();

}

 

 

负责输出页面的组件类:Page

package com.yli.cshop.bean;

import java.util.List;

/**
 * 页面可以认为是所有组件的父容器<br>
 * 它可以包含有固定部分和动态布局的部分<br>
 * 
 * @author yli
 * 
 */
public class Page extends BaseTag {

	/**
	 * 页面ID-系统生成的ID
	 */
	private long pageId;

	/**
	 * 页面名称-用户可自由添加页面,定义页面名称
	 */
	private String pageName;

	/**
	 * 页面序号-用户可以自由拖动页面排序,记录该序号
	 */
	private int sortNo;

	/**
	 * 当前页面包含的布局列表<br>
	 * 布局又包含了模块,所以组成了完整的页面
	 */
	private List<Layout> layoutList;

	public long getPageId() {
		return pageId;
	}

	public void setPageId(long pageId) {
		this.pageId = pageId;
	}

	public String getPageName() {
		return pageName;
	}

	public void setPageName(String pageName) {
		this.pageName = pageName;
	}

	public List<Layout> getLayoutList() {
		return layoutList;
	}

	public void setLayoutList(List<Layout> layoutList) {
		this.layoutList = layoutList;
	}

	public int getSortNo() {
		return sortNo;
	}

	public void setSortNo(int sortNo) {
		this.sortNo = sortNo;
	}

	public String generateHtml() {

		return null;
	}

	@Override
	public String toString() {
		if (null != layoutList) {
			StringBuffer buffer = new StringBuffer();
			for (Layout layout : layoutList) {
				buffer.append(layout.toString());
			}
			return buffer.toString();
		}
		return null;
	}

}

 

负责输出布局的组件类:Layout

package com.yli.cshop.bean;

import java.util.List;

import com.yli.cshop.util.FreeMakerParser;

/**
 * 布局可以认为是模块的父容器<br>
 * 用户可将模块自由添加进来,并对模块排序<br>
 * 布局其实也可以看做模块,只是它不负责具体的业务内容而已<br>
 * 
 * @author yli
 *
 */
public class Layout extends BaseTag {

	/**
	 * 布局ID-系统生成的ID
	 */
	private long layoutId;

	/**
	 * 所属页面ID-建立页面与布局的关系
	 */
	private long pageId;

	/**
	 * 布局开始html标签-对应一个FreeMarker模板地址
	 */
	private String beginTemplate;

	/**
	 * 布局结束html标签-对应一个FreeMarker模板地址
	 * 
	 */
	private String endTemplate;
	
	/**
	 * 布局序号-用户可在页面添加多个布局,并自由排序,记录该序号
	 */
	private int sortNo;

	/**
	 * 当前布局包含的模块列表
	 */
	private List<BaseModule> moduleList;

	public long getLayoutId() {
		return layoutId;
	}

	public void setLayoutId(long layoutId) {
		this.layoutId = layoutId;
	}

	public long getPageId() {
		return pageId;
	}

	public void setPageId(long pageId) {
		this.pageId = pageId;
	}

	public String getBeginTemplate() {
		return beginTemplate;
	}

	public void setBeginTemplate(String beginTemplate) {
		this.beginTemplate = beginTemplate;
	}

	public String getEndTemplate() {
		return endTemplate;
	}

	public void setEndTemplate(String endTemplate) {
		this.endTemplate = endTemplate;
	}
	
	public int getSortNo() {
		return sortNo;
	}

	public void setSortNo(int sortNo) {
		this.sortNo = sortNo;
	}

	public List<BaseModule> getModuleList() {
		return moduleList;
	}

	public void setModuleList(List<BaseModule> moduleList) {
		this.moduleList = moduleList;
	}

	@Override
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		String beginHtml = FreeMakerParser.process(beginTemplate, null);
		String endHtml = FreeMakerParser.process(endTemplate, null);
		buffer.append(beginHtml);
		for (BaseModule module : moduleList) {
			buffer.append(module.toString());
		}
		buffer.append(endHtml);
		return buffer.toString();
	}
}

 

特别注意:负责输出模块的组件类:BaseModule

package com.yli.cshop.bean;

import com.yli.cshop.util.FreeMakerParser;

/**
 * 虽然模块和布局一样,负责输出一段html标签<br>
 * 但是模块是负责各自业务模块的,比如有[公告信息模块][轮播广告模块]<br>
 * 意味着每个模块还会关联各自的业务内容,即关联到业务表<br>
 * 但是每个业务模块要[保存/更新/删除]的业务内容:所对应的的业务类、方法肯定不一样<br>
 * 所以把模块抽象出来的同时,把每个模块对应的业务类和业务内容定义成两个属性<br>
 * 
 * @author yli
 *
 */
public class BaseModule extends BaseTag {

	/**
	 * 模板ID-系统生成的ID
	 */
	private long moduleId;

	/**
	 * 布局的ID-建立布局与模块的关系
	 */
	private long layoutId;

	/**
	 * 模块名称-用户可以自定义模块名称
	 */
	private String moduleName;

	/**
	 * 模块序号-用户可自由拖动模块,记录该模块在当前布局的序号
	 */
	private int sortNo;

	/**
	 * 模块对应的FreeMarker模板路径-因为每个模块"长的样子肯定不一样"
	 */
	private String moduleTemplate;

	/**
	 * 模块包含的业务数据-不同的业务模块对应的数据和数据类型不一样
	 */
	private Object moduleContent;
	
	/**
	 * 模块对应的业务处理类-此处记录业务Bean名称即可
	 */
	private String moduleServiceBean;

	public long getModuleId() {
		return moduleId;
	}

	public void setModuleId(long moduleId) {
		this.moduleId = moduleId;
	}

	public long getLayoutId() {
		return layoutId;
	}

	public void setLayoutId(long layoutId) {
		this.layoutId = layoutId;
	}

	public String getModuleName() {
		return moduleName;
	}

	public void setModuleName(String moduleName) {
		this.moduleName = moduleName;
	}

	public int getSortNo() {
		return sortNo;
	}

	public void setSortNo(int sortNo) {
		this.sortNo = sortNo;
	}

	public String getModuleTemplate() {
		return moduleTemplate;
	}

	public void setModuleTemplate(String moduleTemplate) {
		this.moduleTemplate = moduleTemplate;
	}

	public Object getModuleContent() {
		return moduleContent;
	}

	public void setModuleContent(Object moduleContent) {
		this.moduleContent = moduleContent;
	}

	public String getModuleServiceBean() {
		return moduleServiceBean;
	}

	public void setModuleServiceBean(String moduleServiceBean) {
		this.moduleServiceBean = moduleServiceBean;
	}


	@Override
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		String moduleInfo = FreeMakerParser.process(moduleTemplate,	moduleContent);
		buffer.append(moduleInfo);
		return buffer.toString();
	}

}

 

所有组件类已经写好了,那么就准备布局、模块对应的FreeMarker模板文件即可,如下所示 

我为页面、布局和每种模块都定制了一个ftl页面,放在classpath目录的conf目录下

 
关于页面动态布局实现方案(四)_第1张图片

 

 

为了做演示,在MySql数据库针对页面、布局和模块分别建表,并插入演示的数据,如下所示

 

页面:
关于页面动态布局实现方案(四)_第2张图片

 

页面与布局关系 
关于页面动态布局实现方案(四)_第3张图片
 

 布局与模块关系
关于页面动态布局实现方案(四)_第4张图片
 

模块与业务表关系,以公告资讯为例
关于页面动态布局实现方案(四)_第5张图片
 

 

现在数据都准备好了,就可以输出页面了,此处举例说明如何实现

package com.yli.cshop.service.impl;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.yli.cshop.bean.BaseModule;
import com.yli.cshop.bean.Layout;
import com.yli.cshop.bean.Page;
import com.yli.cshop.service.BaseModuleService;
import com.yli.cshop.service.PageService;
import com.yli.sample.base.ServiceImplBase;

public class PageServiceImpl extends ServiceImplBase implements PageService{
	
	public  Map<String, BaseModuleService> moduleServiceCache;
	
	public void setModuleServiceCache(
			Map<String, BaseModuleService> moduleServiceCache) {
		this.moduleServiceCache = moduleServiceCache;
	}

	
	public String loadPage(){
		
		Map<String, Object> param = new HashMap<String, Object>();
		param.put("pageId", 100001);
		
		// 根据页面ID查询Page对象
		Page page = sopDalClient.queryForObject("com.yli.cshop.layout.query_page", param, Page.class);
		
		// 根据页面ID查询:该页面包含的布局列表,根据布局序号升序排序
		List<Layout> layoutList = sopDalClient.queryForList("com.yli.cshop.layout.query_layout_list", param, Layout.class);
		
		if(null != layoutList && !layoutList.isEmpty()) {
			// 设置布局列表到页面对象中
			page.setLayoutList(layoutList);
			
			// 根据布局ID查询:每个布局包含的模块列表,根据模块所在布局序号升序排序
			for(Layout layout : layoutList) {
				param.put("layoutId", layout.getLayoutId());
				List<BaseModule> moduleList = sopDalClient.queryForList("com.yli.cshop.layout.query_module_list", param, BaseModule.class);
				if(null != moduleList && !moduleList.isEmpty()) {
					// 设置模块列表到页面对象中
					layout.setModuleList(moduleList);
					for(BaseModule module : moduleList) {
						// 查询每个模块应该包含的业务内容,此处处理比较复杂
						BaseModuleService baseModuleService =  moduleServiceCache.get(module.getModuleServiceBean());
						Object moduleContent = baseModuleService.getModuleContent(module.getModuleId());
						// 将模块的业务内容设置到模块实例中
						module.setModuleContent(moduleContent);
					}
				}
			}
		}
		
		// toString() 其实就是使用FreeMarker的process方法达到[数据]+[模板]=[输出]的目的
		// 这个pageContent就是整个页面的内容了,只要显示在前端即可
		// 不管你用Jstl来读取,还是ajax来读取,都可以
		String pageContent = page.toString();
		System.out.println(pageContent);
		
		// 如果觉得组件类依赖了FreeMarker,被侵入,则也可以抽象出输出页面的工厂类
		// page 就只作为保存[数据]和[模板]的功能来设计
		// String pageContent = PageFactory.generatePage(page);
		
		return pageContent;
	}
}

 

 

最后就是效果图了,很简答的一个

 
关于页面动态布局实现方案(四)_第6张图片
 

 

 


 

你可能感兴趣的:(动态)