页面需要展示通讯录信息,数据是存储在LDAP服务器中,使用zTree来展示通讯录信息。
LDAP中的数据如下图,树形结构是根据distinguishedName的值来分级的。
如其中的一个记录的distinguishedName为:
CN=丁XX,OU=市场部,OU=XX科技,OU=XX集团,DC=xxx,DC=com,DC=cn
大体实现思路为:最初只展示第一层的节点,而后面的节点,用户点击展开时,才去请求。
这样每次只查询下一级的节点,请求比较快。
后台接口
以下是底层查询。使用org.springframework.ldap
/**
* 查询LDAP中当前节点的1级子节点
* currentNodeDnPrefix,单前节点的完整DN去掉baseDN后的字符串,如完整DN为[OU=业务,OU=xxx,DC=com,DC=cn],去掉baseDN[OU=xxx,DC=com,DC=cn]后,传入OU=业务即可。为空字符串""时表示当前连接的根节点
* 查询过滤条件为objectClass=*---查询所有条目,objectClass=person---查询人员条目
* @param currentNodeDnPrefix
* @return
*/
public List<LdapUser> getSubnode4ContactTree (String currentNodeDnPrefix, int searchScope) {
List<LdapUser> subnode = new ArrayList<>(0);
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(searchScope); // 只查询当前节点的1级子节点
/*AndFilter andFilter = new AndFilter();
andFilter.and(new EqualsFilter("objectClass", "person"));*/
// 如果直接使用andFilter.and(new EqualsFilter("objectClass", "*"))会被自动转义,就换成手动拼接了
String filter = "objectClass=*"; // 查询所有条目
subnode = ldapTemplate.search(currentNodeDnPrefix, filter, searchControls, new LdapUserAttributeMapper());
return subnode;
}
查询条件中的
SearchControls.OBJECT_SCOPE----------对应当前查询的一个OBJECT节点
SearchControls.ONELEVEL_SCOPE-------对应当前节点的下1级(仅仅1级)节点
SearchControls.SUBTREE_SCOPE---------对应当前节点的所有子节点
以下是初次加载时的接口
/**
* 获取通讯录根目录下的1级子目录
* @return
*/
public List<LdapUser> getBaseContactTree () {
List<LdapUser> tree = new ArrayList<>(0);
LdapUser baseNode = new LdapUser();
// 查询当前根节点的DN,如OU=xxx,DC=com,DC=cn
List<LdapUser> baseNodeList = ldapUserService.getSubnode4ContactTree("", SearchControls.OBJECT_SCOPE);
if (baseNodeList.size() == 1) {
baseNode = baseNodeList.get(0);
if (!StringUtils.isEmpty(baseNodeList.get(0).getDistinguishedName())) {
baseDN = baseNodeList.get(0).getDistinguishedName();
}
} else {
return tree;
}
tree = ldapUserService.getSubnode4ContactTree("", SearchControls.ONELEVEL_SCOPE);
// 排序,要自己实现Comparator
Collections.sort(tree, new LdapUserComparator());
return tree;
}
以下是页面展开当前节点时,查询当前节点的1级子节点接口
/**
* 获取当前节点的1级子节点,
* @param currentNodeDn 当前节点的完整DN
* @return
*/
public List<LdapUser> newContactTreeSubnode(String currentNodeDn) {
List<LdapUser> contactTree = new ArrayList<>(0);
if (StringUtils.isEmpty(currentNodeDn) ) {
return contactTree;
}
// 查询当前节点下的1级子节点
List<LdapUser> currentNodeSubList = ldapUserService.getSubnode4ContactTree(currentNodeDn.substring(0, currentNodeDn.indexOf(baseDN)-1), SearchControls.ONELEVEL_SCOPE);
if (!CollectionUtils.isEmpty(currentNodeSubList)) {
//设置CN名称
for (LdapUser node : currentNodeSubList) {
if (StringUtils.isEmpty(node.getCn())) {
node.setCn(node.getDistinguishedName().substring(node.getDistinguishedName().indexOf("=") + 1, node.getDistinguishedName().indexOf(",")));
}
}
contactTree.addAll(currentNodeSubList);
Collections.sort(contactTree, new LdapUserComparator()); // 根据displayOrder从小到大排序
}
return contactTree;
}
排序用的排序比较器
import java.util.Comparator;
/**
* 根据displayOrder从小到大排序
*/
public class LdapUserComparator implements Comparator<LdapUser> {
@Override
public int compare(LdapUser o1, LdapUser o2) {
// 从小到大
if (o1 == null || o1.getDisplayOrder() == null ) {
if (o2 == null || o2.getDisplayOrder() == null ) {
return -1;
}
return 0;
}
if (o2 == null || o2.getDisplayOrder() == null ) {
return 1;
}
int retval = Integer.compare(Integer.parseInt(o1.getDisplayOrder()), Integer.parseInt(o2.getDisplayOrder()));
return retval;
}
}
----------------------------------------------------------------------------------------------------
页面展示
展开zTree某个父节点时,查询其子节点。
onExpand: function zTreeOnExpand(event, treeId, treeNode) {
var tree = JSON.parse(window.localStorage.getItem('contactTree'));
var newtree;
$.post(baseContextPath + "/xx/xxx/subnode", {
currentNodeDn:treeNode.distinguishedName
},function(res){
var res = JSON.parse(res);
if(res && res.success){
var array = res.data;
for (var i=0;i<array.length;i++){
if(!array[i].mail){ //通过mail判断是否为父节点
array[i].isParent = true;
}
}
if(treeNode.children.length==0){
var newNode = array;
var treeObj = $.fn.zTree.getZTreeObj("treeDemo");
newNode = treeObj.addNodes(treeNode, newNode,false);
}
}
})
}
===========================================================
而最初考虑后台使用递归查询出整个树结构,再传给页面展示。但在实现中,4个层级,不到1000的用户数据,查询所有数据用了15s左右,显然不行。
/**
* 递归查询数据,数据越多越慢
* @return
*/
public List<LdapUser> newContactTree() {
long sTime = System.currentTimeMillis();
List<LdapUser> tree = new ArrayList<>(0);
LdapUser baseNode = new LdapUser();
// 查询当前根节点的DN,如OU=xxx,DC=com,DC=cn
List<LdapUser> baseNodeList = ldapUserService.getSubnode4ContactTree("", SearchControls.OBJECT_SCOPE);
if (baseNodeList.size() == 1) {
baseNode = baseNodeList.get(0);
if (!StringUtils.isEmpty(baseNodeList.get(0).getDistinguishedName())) {
baseDN = baseNodeList.get(0).getDistinguishedName();
}
} else {
return tree;
}
// 递归获取1级子节点
baseNode = getLDAPSubNodeList(baseNode);
tree.addAll(baseNode.getChildren());
long fTime = System.currentTimeMillis();
System.out.println("展示通讯录消耗时间: " +(fTime - sTime)+ " ms");
return tree;
}
// 递归获取1级子节点
public LdapUser getLDAPSubNodeList(LdapUser fatherNode) {
String currentNodeDnPrefix = null;
if (baseDN.equals(fatherNode.getDistinguishedName())) {
currentNodeDnPrefix = "";
} else {
currentNodeDnPrefix = fatherNode.getDistinguishedName().substring(0, fatherNode.getDistinguishedName().indexOf(baseDN)-1);
}
List<LdapUser> subNodeList = ldapUserService.getSubnode4ContactTree(currentNodeDnPrefix, SearchControls.ONELEVEL_SCOPE);
if(CollectionUtils.isEmpty(subNodeList)) {
// 已经是叶子节点,不用继续查询
} else {
fatherNode.setChildren(subNodeList);
for(Iterator it = subNodeList.iterator(); it.hasNext();) {
LdapUser node = (LdapUser) it.next();
// 每条记录需要进行的单独处理
// 继续查询子节点
getLDAPSubNodeList(node);
}
}
return fatherNode;
}