假设公司需要快速构建一个CMS系统,同时支持灵活多变的内容展示形式。该CMS系统需要支持用户自定义内容的属性、值、以及展示方式。因此,不能仅仅使用固定的表字段来表示某个内容或者栏目的属性。同时,栏目与内容的展示,在某一程度上应该遵循固定的数据格式,便于最大限度的代码复用。
参考Jspxcms本文中将涉及几种主要数据结构:
栏目命名为节点(Node)
某一具体内容命名为信息(Info)
页面展示模板资源命名为模型(Model)
可附加的属性定义(AttribueDefine)
属性值(AttributeValue)
主要包含栏目(Node), 栏目属性关联(NodeAttribute)两个实体
记录栏目的基本属性,其中记录父节点和根两个属性即可令Node集合形成树,从而支持复杂的栏目组合。
@Id
@Column(name = "node_id")
private int nodeId;//栏目ID
@Column(name = "node_name", length = 100)
private String nodeName;//栏目名称
@Column(name = "parent_id")
private int parentId;//父节点
@Column(name = "node_model_id")
private int nodeModelId;//栏目模板
@Column(name = "node_img_url", length = 1024)
private String nodeImgUrl;//栏目图URL
@Column(name = "node_sequence", length = 3)
private String nodeSequence;//栏目顺序号
@Column(name = "is_root", length = 1)
private String isRoot;//是否根栏目
@Column(name = "is_show", length = 1)
private String isShow;//是否启用
@Column(name = "home_flag", length = 1)
private String homeFlag;//首页展示
@Column(name = "version_no", length = 17)
private String versionNo;//版本号
记录外挂属性定义与属性值的Id,通过外挂形式支持无限的属性/值集合。这里将值直接记录在该实体中。
@Id
@Column(name= "node_id")
private int nodeId;//栏目id
@Column(name = "attribute_define_id")
private Long attributeDefineId;//属性定义id
@Id
@Column(name = "key_code", length = 50)
private String keyCode;//key
@Id
@Column(name = "attribute_value", length = 2000)
private String attributeValue;//属性值
Info作为栏目的内容,挂在某个栏目之下,同时也支持外挂属性定义和属性值。
@Id
@Column(name = "info_id",unique=true,nullable=false)
private Long infoId;//内容ID info_id bigint 主键
@Column(name = "node_id",nullable=false)
private Integer nodeId;//栏目ID node_id int not null
@Column(name = "priority",length=2)
private String priority;//优先级 priority
@Column(name = "info_title",length=150,nullable=false)
private String infoTitle;//标题 info_title varchar(150) not null
@Column(name = "info_subtitle",length=150)
private String infoSubtitle;//副标题 info_subtitle varchar(150)
@Column(name = "info_link",length=1024)
private String infoLink;//转向链接 info_link varchar(1024)
@Column(name = "is_new_window",length=1)
private String isNewWindow;//是否在新窗口打开 is_new_window char(1) 0否、1是
@Column(name = "info_path",length=1024)
private String infoPath;//信息路径 info_path varchar(1024)
@Column(name = "info_template_id")
private int infoTemplateId;//内容模板
@Column(name = "meta_description",length=255)
private String metaDescription;//描述 meta_description varchar(255)
@Column(name = "info_author",length=100)
private String infoAuthor;//作者 info_author varchar(100)
@Column(name = "start_date",length=8)
private String itartDate;//有效期起日期 start_date char(8)
@Column(name = "stop_date",length=8)
private String stopDate;//有效期止日期 stop_date char(8)
@Column(name = "version_no",length=17,nullable=false)
private String versionNo;//版本号 version_no varchar (17) not null 新增和更新时填写
通过外挂形式支持无限属性定义与属性值。这里将值直接记录在该实体中。
@Id
@Column(name = "info_id")
private Long infoId;//内容ID
@Column(name = "attribute_define_id")
private Long attributeDefineId;//属性定义ID
@Id
@Column(name = "key_code", length = 50)
private String keyCode;//key
@Id
@Column(name = "attribute_type")
private String attributeType;//值
将属性定义与属性值区分开,其中属性值(AttributeValue)仅与属性定义连接,作为记录某属性的多个备选值列表用。实际应用是,使用的值将直接记录在(NodeAttribute或者InfoAttribute中)。
@Id
@Column(name = "attribute_define_id")
private Long attributeDefineId;//属性定义id
@Column(name ="attribute_type", length =1)
private String attributeType;//属性类型
@Column(name = "input_type", length = 50)
private String inputType;//输入类型
@Column(name ="field_name", length = 100)
private String fieldName;//字段名称
@Column(name = "field_code", length = 50)
private String fieldCode;//字段代码
@Column(name = "attribute_sequence", length = 3)
private String attributeSequence;//字段顺序
@Column(name = "is_mandatory",length = 1)
private String isMandatory;//是否必填
@Column(name = "default_value", length = 100)
private String defaultValue;//默认值
@Column(name = "information", length = 255)
private String information;//提示信息
@Id
@Column(name = "define_value_id")
private Long defineValueId;//属性定义值id
@Column(name = "attribute_define_id")
private Long attributeDefineId;//属性定义id
@Column(name = "value_attribute", length = 100)
private String valueAttribute;//值
@Column(name = "sequence_attribute", length = 3)
private String sequenceAttribute;//排序
因为需要同时返回栏目(内容)以及相应属性,因此需要将数据集合封装好并回传。以栏目封装为例。
最外层为NodeItem,其中包含Node以及一个属性/值集合NodeAttributeDefinePair 。类定义如下
public class NodeItem {
Node node;
List<NodeAttributeDefinePair> pairs;
public NodeItem(){}
public NodeItem(Node node, List<NodeAttributeDefinePair> pairs){
this.node =node;
this.pairs = pairs;
}
public Node getNode() {
return node;
}
public void setNode(Node node) {
this.node = node;
}
public List<NodeAttributeDefinePair> getPairs() {
return pairs;
}
public void setPairs(List<NodeAttributeDefinePair> pairs) {
this.pairs = pairs;
}
}
public class NodeAttributeDefinePair {
private NodeAttribute nodeAttribute;
private AttributeDefine define;
private AttributeDefineValue value;
public NodeAttributeDefinePair(){}
public NodeAttributeDefinePair(NodeAttribute attr, AttributeDefine define, AttributeDefineValue value){
this.nodeAttribute = attr;
this.define = define;
this.value = value;
}
public NodeAttributeDefinePair(NodeAttribute attr, AttributeDefine define){
this.nodeAttribute =attr;
this.define = define;
}
public NodeAttribute getNodeAttribute() {
return nodeAttribute;
}
public void setNodeAttribute(NodeAttribute nodeAttribute) {
this.nodeAttribute = nodeAttribute;
}
public AttributeDefine getDefine() {
return define;
}
public void setDefine(AttributeDefine define) {
this.define = define;
}
public AttributeDefineValue getValue() {
return value;
}
public void setValue(AttributeDefineValue value) {
this.value = value;
}
}
初步考虑,用于获取数据的接口应该包含一下几个:
1、获取某栏目以及相应属性
2、获取某内容以及相应属性
3、获取某栏目的子栏目列表以及属性
4、获取某栏目的内容列表以及属性
就是根据父子关系按照id进行搜索,比较简单,结合数据包装返回即可,因此不做过多描述。
根据上节的接口描述,可以将实现上述接口的方法统一封装为模块,并在需要的地方注入并调用即可。【使用$q 实现异步获取】
angular.module('nodeInfo',[])
/*列表类型服务*/
.service('nodeInfolistService', function(){
this.listSubNodes = function($scope, $http,$q,paramsxxxx){
var q = $q.defer();
var url = ctx + 'api/xxxxxxx';
var params = {
params:paramsxxxx
};
$http({method:'GET', params:params,url:url})
.success(function(response){
q.resolve(response);
})
.error(function(response){
q.reject('服务器开小差了,请稍后重试');
});
return q.promise;
};
//......略
})
/*内容获取类型服务*/
.service('nodeInfoitemService', function(){
//......略
});
外部使用的时候,首先在页面引入相应的js文件,在页面的APP申明中注入该模块,并引用服务即可。
代码如下:
/*注入nodeInfo*/
var app = angular.module('app',['nodeInfo']);
app.service('appService',function(){
});
/*引入服务nodeInfolistService*/
app.controller('appCtrl', function($scope, $http,$q, appService, nodeInfolistService){
var currentNodeId = $('#currentNodeId').text();
var parentId = $('#parentId').text();
//promise方式异步获取
var subPromise = nodeInfolistService.listSubNodes($scope, $http, $q, currentNodeId);
subPromise.then(
function(subNodes){
$scope.subNodes = subNodes;
},
function(err){
console.log(err);
}
)
});
通过归纳整理通用的数据获取接口库,并封装数据获取的相应服务,使网站的任意形式内容均可以以统一的方式获取,极大的简化了后期开发的代码量。同时,若后台逻辑变化时,也仅需要修改封装的服务即可,不需要修改其它页面。