表中的DN265 DN325 DN377是否表带松紧度正确图解???

管径表示中De、DN、D的区别_百度文库
您的浏览器Javascript被禁用,需开启后体验完整功能,
享专业文档下载特权
&赠共享文档下载特权
&10W篇文档免费专享
&每天抽奖多种福利
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
管径表示中De、DN、D的区别
&&不同管材的管径采用不同管径表示方式
阅读已结束,下载本文需要
想免费下载本文?
定制HR最喜欢的简历
你可能喜欢FOLLOW MY HEART.保持一颗学习的心,打造一款伟大的产品!
公司管理员工信息以及组织架构的后台系统要和Active Directory目录服务系统打通,后台系统使用PHP开发,
折腾了二十多天,终于上线了,期间碰到过各种疑难问题,不过总算在GOOGLE大叔的帮忙下还有运维部AD管理员的帮助下解决了。
LDAP协议定义
LDAP(Lightweight Directory Access Protocol)轻量目录访问协议,定义了目录服务实现以及访问规范。
A directory is a specialized database specifically designed for searching and browsing, in additional to supporting basic lookup and update functions.
LDAP协议实现
0.基于TCP/IP的应用层协议 默认端口389 加密端口6361.客户端发送命令,服务器端响应2.目录主要操作& & 2.0 用户验证(bind操作)& & &2.1 添加节点& & &2.2 更新节点& & &2.3 移动节点& & &2.4 删除节点& & &2.5 节点搜索3.节点类型& & 3.0 节点属性规范(SCHEMA)4.节点& & 4.0 目录里的对象& & &4.1 属性即是节点的数据& & &4.2 目录中通过DN(Distinguished Name)唯一标识(可以认为是路径)& & &4.2.0 节点DN = RDN(Relative Distinguished Name) + 父节点的DN& & &4.3 目录是TREE结构,节点可以有子节点,也可以有父节点5.属性& & 5.0 同一个属性可以有多个值& & &5.1 包含属性名称,属性类型6.节点唯一标识DN说明& & 6.0 示例: dn:CN=John Doe,OU=Texas,DC=example,DC=com& & &6.1 从右到左 根节点 -& 子节点& & &6.2 DC:所在控制域 OU:组织单元 CN:通用名称7.目录规范(SCHEMA)& & 7.0 目录节点相关规则& & &7.1 Attribute Syntaxes& & &7.2 Matching Rules& & &7.3 Matching Rule Uses& & &7.4 Attribute Types& & &7.5 Object Classes& & &7.6 Name Forms& & &7.7 Content Rules& & &7.8 Structure Rule
LDAP服务器端的实现
openLDAP,Active Directory(Microsoft)等等,除了实现协议之外的功能,还对它进行了扩展
LDAP应用场景
0.单点登录(用户管理)1.局域网资源统一管理
封装的简单PHP类
适合AD服务器 其他的LDAP服务器需要做相应的修改
zend框架有个开源的LDAP库实现 完全面向对象
* @description LDAP客户端类
* @author WadeYu
* @version 0.0.1
9 class o_ldap{
private $_conn = NULL;
private $_sErrLog = '';
private $_sOperLog = '';
private $_aOptions = array(
'host' =& 'ldap://xxx.com',
'port' =& '389',
'dnSuffix' =& 'OU=xx,OU=xx,DC=xx,DC=com',
'loginUser' =& '',
'loginPass' =& '',
private $_aAllowAttrName = array(
'objectClass',
'objectGUID', //AD对象ID
'userPassword', //AD密码不是这个字段 密码暂时不能通过程序设置
'unicodePwd', //AD密码专用字段 $unicodePwd
= mb_convert_encoding('"' . $newPassword . '"', 'utf-16le');
'cn', //comman name 兄弟节点不能相同
'ou', //organizationalUnit
'description', //员工填工号
'displayName', //中文名
'name', //姓名
'sAMAccountName', //英文名(RTX账号,唯一)
'userPrincipalName', //登陆用户名 和 英文名一致
'ProtectedFromAccidentalDeletion', //对象删除保护
'givenName', //姓
'sn', //名
'employeeNumber', //一卡通卡号
'mailNickname',
'manager', //上级 (节点路径 示例:CN=Texas Poker9,OU=Texas Poker,OU=Dept,OU=BoyaaSZ,DC=by,DC=com)
'title', //头衔
'pager', //性别 0男 1女 -1未知
'userAccountControl', //用户账号策略(暂时不能设置) 资料说明地址:https://support.microsoft.com/en-gb/kb/305144
'department',
'managedBy',//部门负责人
'distinguishedName',
'pwdLastSet', //等于0时 下次登录时需要修改密码
public function __construct(array $aOptions = array()){
if (!extension_loaded('ldap')){
$this-&_log('LDAP extension not be installed.',true);
$this-&setOption($aOptions);
* @return exit || true
public function connect($force = false){
if (!$this-&_conn || $force){
$host = $this-&_aOptions['host'];
$port = $this-&_aOptions['port'];
$this-&_conn = ldap_connect($host,$port);
if ($this-&_conn === false){
$this-&_log("Connect LDAP SERVER Failure.[host:{$post}:{$port}]",true);
ldap_set_option($this-&_conn, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($this-&_conn, LDAP_OPT_REFERRALS, 3);
$this-&_bind();
return true;
* @return exit || true
private function _bind(){
$u = $this-&_aOptions['loginUser'];
$p = $this-&_aOptions['loginPass'];
$ret = @ldap_bind($this-&_conn,$u,$p);
if ($ret === false){
$this-&_log(__FUNCTION__.'----'.$this-&_getLastExecErrLog().'----'."u:{$u},p:{$p}",true);
return $ret;
public function setOption(array $aOptions = array()){
foreach($this-&_aOptions as $k =& $v){
if (isset($aOptions[$k])){
$this-&_aOptions[$k] = $aOptions[$k];
public function getOption($field,$default = ''){
return isset($this-&_aOptions[$field]) ? $this-&_aOptions[$field] : $default;
* @description 查询$dn下符合属性条件的节点 返回$limit条
* @return array [count:x,[[prop:[count:xx,[],[]]],....]]
public function getEntryList($dn,$aAttrFilter,array $aField=array(),$limit = 0,$bFixedDn = true){
if (!$dn = trim($dn)){
return array();
if (!$this-&_checkDn($dn)){
return array();
$limit = max(0,intval($limit));
$this-&connect();
if ($bFixedDn){
$dn = $this-&_getFullDn($dn);
$aOldTmp = $aAttrFilter;
$this-&_checkAttr($aAttrFilter);
if (!$aAttrFilter){
$this-&_log(__FUNCTION__.'---无效的搜索属性---'.json_encode($aOldTmp));
return array();
$sAttrFilter = $this-&_mkAttrFilter($aAttrFilter);
$attrOnly = 0;
$this-&_log(__FUNCTION__."---DN:{$dn}---sAttr:{$sAttrFilter}",false,'oper');
$rs = @ldap_search($this-&_conn,$dn,$sAttrFilter,$aField,$attrOnly,$limit);
if ($rs === false){
$this-&_log(__FUNCTION__."---dn:{$dn}---sAttr:{$sAttrFilter}---" . $this-&_getLastExecErrLog());
return array();
$aRet = @ldap_get_entries($this-&_conn,$rs);
ldap_free_result($rs);
if ($aRet === false){
$this-&_log(__FUNCTION__.'---'.$this-&_getLastExecErrLog());
return array();
return $aRet;
* @description 删除节点 暂时不考虑递归删除
* @return boolean
public function delEntry($dn,$bFixedDn = true,$force = 0){
if (!$dn = trim($dn)){
return false;
if (!$this-&_checkDn($dn)){
return false;
if ($bFixedDn){
$dn = $this-&_getFullDn($dn);
$this-&_log(__FUNCTION__."---DN:{$dn}",false,'oper');
$this-&connect();
/*if($force){
$aEntryList = $this-&getEntryList($dn,array('objectClass'=&'*'),array('objectClass'));
if ($aEntryList && ($aEntryList['count'] & 0)){
for($i = 0; $i & $aEntryList['count']; $i++){
$aDel[] = $aEntryList[$i]['dn'];
$aDel = array_reverse($aDel); //默认顺序 祖先-&子孙 需要先删除子孙节点
foreach($aDel as $k =& $v){
$ret &= @ldap_delete($this-&_conn,$v);
if ($ret === false){
$this-&_log(__FUNCTION__.'dn(recursive):'.$dn.'----'.$this-&_getLastExecErrLog());
$ret = @ldap_delete($this-&_conn,$dn);
if ($ret === false){
$this-&_log(__FUNCTION__.'----dn:'.$dn.'-----'.$this-&_getLastExecErrLog());
return $ret;
* @description 更新节点
* @return boolean
public function updateEntry($dn,$aAttr = array(),$bFixedDn = true){
if (!$dn = trim($dn)){
return false;
$this-&_checkAttr($aAttr);
if (!$aAttr){
return false;
if (!$this-&_checkDn($dn)){
return false;
if ($bFixedDn){
$dn = $this-&_getFullDn($dn);
$this-&_log(__FUNCTION__."---DN:{$dn}---aAttr:".str_replace("\n",'',var_export($aAttr,true)),false,'oper');
$this-&connect();
$ret = @ldap_modify($this-&_conn,$dn,$aAttr);
if ($ret === false){
$this-&_log(__FUNCTION__.'---'.$this-&_getLastExecErrLog().'---dn:'.$dn.'---attr:'.json_encode($aAttr));
return $ret;
* @description 添加节点
* @return boolean
public function addEntry($dn,$aAttr = array(), $type = 'employee'/*employee,group*/){
if (!$dn = trim($dn)){
return false;
$this-&_checkAttr($aAttr);
if (!$aAttr){
return false;
if (!$this-&_checkDn($dn)){
return false;
$aAttr['objectClass'] = (array)$this-&_getObjectClass($type);
$this-&_log(__FUNCTION__."---DN:{$dn}---aAttr:".str_replace("\n",'',var_export($aAttr,true)),false,'oper');
$this-&connect();
$dn = $this-&_getFullDn($dn);
$ret = @ldap_add($this-&_conn,$dn,$aAttr);
if ($ret === false){
$this-&_log(__FUNCTION__.'----dn:'.$dn.'----aAttr:'.json_encode($aAttr).'-----'.$this-&_getLastExecErrLog());
return $ret;
* @description 移动叶子节点 v3版才支持此方法
* @param $newDn 相对于$parentDn
* @param $parentDn 完整DN
* @param $bMoveRecur
* @return boolean
public function moveEntry($oldDn,$newDn,$parentDn,$bDelOld = true,$bFixDn = true,$bMoveRecur = true){
//对于AD服务器 此方法可以移动用户节点以及组织节点
//$newDn只能包含一个 比如OU=xxx
$oldDn = trim($oldDn);
$newDn = trim($newDn);
$parentDn = trim($parentDn);
if(!$oldDn || !$newDn || ($bFixDn && !$parentDn)){
return false;
if(!$this-&_checkDn($oldDn) || !$this-&_checkDn($newDn) || !$this-&_checkDn($parentDn)){
return false;
$this-&connect();
if($bFixDn){
$oldDn = $this-&_getFullDn($oldDn);
$parentDn = $this-&_getFullDn($parentDn);
$this-&_log(__FUNCTION__."---DN:{$oldDn} -& {$newDn},{$parentDn}",false,'oper');
$aTmpMove = $aDelDn = array();
$aTmpMove[] = array('old'=&$oldDn,'new'=&$newDn);
/*if($bMoveRecur){
$aDelDn[] = $oldDn;
$aTmpList = $this-&getEntryList($oldDn,array('objectClass'=&'*'),array('objectClass'),0,0);
if($aTmpList && ($aTmpList['count'] & 1)){
for($i = 1; $i & $aTmpList['count']; $i++){
if(!in_array('user',$aTmpList[$i]['objectclass'])){ //$bDelOld=true时,用户节点移动时会自动删除
$aDelDn[] = $aTmpList[$i]['dn'];
$aTmpSep = explode($oldDn,$aTmpList[$i]['dn']);
$aTmpMove[] = array(
'old' =& $aTmpList[$i]['dn'],
'new' =& $aTmpSep[0] . $newDn,
$bFlag = true;
foreach($aTmpMove as $k =& $v){
$bTmpFlag = ldap_rename($this-&_conn,$v['old'],$v['new'],$parentDn,(boolean)$bDelOld);
if(!$bTmpFlag){
$this-&_log(__FUNCTION__."---o:{$v['old']}-n:{$v['new']}-p:{$parentDn}-recur:{$bMoveRecur}-----".$this-&_getLastExecErrLog());
$bFlag &= $bTmpFlag;
/*if(!$bFlag){
$this-&_log(__FUNCTION__."---o:{$oldDn}-n:{$newDn}-p:{$parentDn}-recur:{$bMoveRecur}-----".$this-&_getLastExecErrLog());
/*if($bFlag && $bDelOld && $aDelDn){
$aDelDn = array_reverse($aDelDn);
foreach($aDelDn as $k =& $v){
$this-&delEntry($v,false);
return $bFlag;
public function modEntry($dn,$act = 'add',$aAttr = array()){
return false;
$dn = $this-&_getFullDn($dn);
$this-&_log(__FUNCTION__."---DN:{$dn}---Act:{$act}---aAttr:".str_replace("\n",'',var_export($aAttr,true)),false,'oper');
$this-&connect();
$ret = false;
switch($act){
case 'add': $ret = ldap_mod_add($this-&_conn,$dn,$aAttr); break;
case 'replace': $ret = ldap_mod_replace($this-&_conn,$dn,$aAttr); break;
case 'del': $ret = ldap_mod_del($this-&_conn,$dn,$aAttr); break;
if(!$ret){
$this-&_log(__FUNCTION__."---dn:{$dn}---act:{$act}---attr:".json_encode($aAttr).'---'.$this-&_getLastExecErrLog());
return $ret;
* @description 批量添加节点
* @return boolean
public function addBatchEntry($aNodeList = array()){
public function getAttrKv(array $aAttr = array()){
if(!isset($aAttr['count']) || ($aAttr['count'] & 1)){
return array();
$aRet = array();
for($i = 0; $i & $aAttr['count']; $i++){
$field = $aAttr[$i];
if (!isset($aAttr[$field])){
return array();
unset($aAttr[$field]['count']);
$aRet[$field] = $aAttr[$field];
if(isset($aAttr['dn'])){ //dn是字符串
$aRet['dn'] = $aAttr['dn'];
return $aRet;
private function _getObjectClass($type = 'employee'){
$aRet = array();
switch($type){
case 'employee' : $aRet = array('top','person','organizationalPerson','user'); break;
case 'group' : $aRet = array('top','organizationalUnit'); break;
return $aRet;
public function getFullDn($partDn = ''){
return $this-&_getFullDn($partDn);
private function _getFullDn($partDn = ''){
$partDn = trim($partDn);
$partDn = rtrim($partDn,',');
return "{$partDn},{$this-&_aOptions['dnSuffix']}";
private function _checkDn($dn = ''){
$dn = trim($dn,',');
$aDn = explode(',',$dn);
foreach($aDn as $k =& $v){
$aTmp = explode('=',$v);
$aTmp[0] = strtolower(trim($aTmp[0]));
$aTmp[1] = trim($aTmp[1]);
$flag = false;
switch($aTmp[0]){ //distingushed name 暂时只允许这3个field
case 'dc': $flag = $this-&_checkDc($aTmp[1]); break;
case 'ou': $flag = $this-&_checkOu($aTmp[1]); break;
case 'cn': $flag = $this-&_checkCn($aTmp[1]); break;
if (!$flag){
$this-&_log(__FUNCTION__.'----无效的节点路径----dn:'.$dn);
return false;
return true;
private function _checkOu($ou = ''){
if (!$ou){
return false;
if (preg_match('/[^a-zA-Z\s\d\.&\'\d]/',$ou)){
$this-&_log(__FUNCTION__.'----OU只能包含字母数字空格以及点');
return false;
return true;
private function _checkCn($cn = ''){
if (!$cn){
return false;
return true;
private function _checkDc($dc = ''){
if (!$dc){
return false;
if (preg_match('/[^a-zA-Z]/',$dc)){
$this-&_log(__FUNCTION__.'----DC只能包含英文字母');
return false;
return true;
private function _mkAttrFilter(array $aAttrFilter = array()){
$sStr = '(&';
foreach($aAttrFilter as $k =& $v){
$v = (string)$v;
if($k === 'objectGUID'){
$v = $this-&_GUIDtoStr($v);
$v = addcslashes($v,'()=');
$sStr .= "({$k}={$v})";
$sStr .= ')';
return $sStr;
//来自PHP.NET http://php.net/manual/en/function.ldap-search.php
//http://php.net/manual/en/function.ldap-get-values-len.php
//GUID关键字
private function _GUIDtoStr($binary_guid){
$hex_guid = unpack("H*hex", $binary_guid);
$hex = $hex_guid["hex"];
$j = 0;$str = '\\';
for($i = 0; $i & strlen($hex); $i++){
if($j == 2){
$str .= '\\';
$str .= $hex[$i];
return $str;
/*$hex1 = substr($hex, -26, 2) . substr($hex, -28, 2) . substr($hex, -30, 2) . substr($hex, -32, 2);
$hex2 = substr($hex, -22, 2) . substr($hex, -24, 2);
$hex3 = substr($hex, -18, 2) . substr($hex, -20, 2);
$hex4 = substr($hex, -16, 4);
$hex5 = substr($hex, -12, 12);
$guid_str = $hex1 . "-" . $hex2 . "-" . $hex3 . "-" . $hex4 . "-" . $hex5;
return $guid_*/
private function _checkAttr(& $aAttr = array()){
foreach((array)$aAttr as $k =& $v){
if (!in_array($k,$this-&_aAllowAttrName)){
unset($aAttr[$k]);
return true;
public function getErrLog(){
return $this-&_sErrL
public function getOperLog(){
return $this-&_sOperL
private function _log($str = '',$bExit = false,$type = 'err'/*err,oper*/){
if ($bExit){
die($str);
$date = date('Y-m-d H:i:s');
if($type === 'err'){
$this-&_sErrLog .= "{$date}----{$str}\n";
} else if ($type === 'oper'){
$this-&_sOperLog .= "{$date}----{$str}\n";
public function close(){
ldap_close($this-&_conn);
private function _getLastExecErrLog(){
$no = ldap_errno($this-&_conn);
$err = ldap_error($this-&_conn);
return "---exec Error:{$no}---{$err}";
辅助PHP类---汉字转拼音
* @desc 汉字转拼音 参考 http://www.php100.com/html/webkaifa/PHP/PHP/914.html
class o_lib_helper_pinyin{
private $_DataKey = "a|ai|an|ang|ao|ba|bai|ban|bang|bao|bei|ben|beng|bi|bian|biao|bie|bin|bing|bo|bu|ca|cai|can|cang|cao|ce|ceng|cha
|chai|chan|chang|chao|che|chen|cheng|chi|chong|chou|chu|chuai|chuan|chuang|chui|chun|chuo|ci|cong|cou|cu|
cuan|cui|cun|cuo|da|dai|dan|dang|dao|de|deng|di|dian|diao|die|ding|diu|dong|dou|du|duan|dui|dun|duo|e|en|er
|fa|fan|fang|fei|fen|feng|fo|fou|fu|ga|gai|gan|gang|gao|ge|gei|gen|geng|gong|gou|gu|gua|guai|guan|guang|gui
|gun|guo|ha|hai|han|hang|hao|he|hei|hen|heng|hong|hou|hu|hua|huai|huan|huang|hui|hun|huo|ji|jia|jian|jiang
|jiao|jie|jin|jing|jiong|jiu|ju|juan|jue|jun|ka|kai|kan|kang|kao|ke|ken|keng|kong|kou|ku|kua|kuai|kuan|kuang
|kui|kun|kuo|la|lai|lan|lang|lao|le|lei|leng|li|lia|lian|liang|liao|lie|lin|ling|liu|long|lou|lu|lv|luan|lue
|lun|luo|ma|mai|man|mang|mao|me|mei|men|meng|mi|mian|miao|mie|min|ming|miu|mo|mou|mu|na|nai|nan|nang|nao|ne
|nei|nen|neng|ni|nian|niang|niao|nie|nin|ning|niu|nong|nu|nv|nuan|nue|nuo|o|ou|pa|pai|pan|pang|pao|pei|pen
|peng|pi|pian|piao|pie|pin|ping|po|pu|qi|qia|qian|qiang|qiao|qie|qin|qing|qiong|qiu|qu|quan|que|qun|ran|rang
|rao|re|ren|reng|ri|rong|rou|ru|ruan|rui|run|ruo|sa|sai|san|sang|sao|se|sen|seng|sha|shai|shan|shang|shao|
she|shen|sheng|shi|shou|shu|shua|shuai|shuan|shuang|shui|shun|shuo|si|song|sou|su|suan|sui|sun|suo|ta|tai|
tan|tang|tao|te|teng|ti|tian|tiao|tie|ting|tong|tou|tu|tuan|tui|tun|tuo|wa|wai|wan|wang|wei|wen|weng|wo|wu
|xi|xia|xian|xiang|xiao|xie|xin|xing|xiong|xiu|xu|xuan|xue|xun|ya|yan|yang|yao|ye|yi|yin|ying|yo|yong|you
|yu|yuan|yue|yun|za|zai|zan|zang|zao|ze|zei|zen|zeng|zha|zhai|zhan|zhang|zhao|zhe|zhen|zheng|zhi|zhong|
zhou|zhu|zhua|zhuai|zhuan|zhuang|zhui|zhun|zhuo|zi|zong|zou|zu|zuan|zui|zun|zuo";
private $_DataValue = "-2|-2|-2|-2|-2|-2|-2|-2
|-1|-1|-1|-1|-1|-1|-1|-19725
|-1|-1|-1|-1|-1|-1|-1|-19263
|-1|-1|-1|-1|-1|-1|-1|-19003
|-1|-1|-1|-1|-1|-1|-1|-18697
|-1|-1|-1|-1|-1|-1|-1|-18211
|-1|-1|-1|-1|-1|-1|-1|-17922
|-1|-1|-1|-1|-1|-1|-1|-17468
|-1|-1|-1|-1|-1|-1|-1|-16664
|-1|-1|-1|-1|-1|-1|-1|-16407
|-1|-1|-1|-1|-1|-1|-1|-15959
|-1|-1|-1|-1|-1|-1|-1|-15652
|-1|-1|-1|-1|-1|-1|-1|-15369
|-1|-1|-1|-1|-1|-1|-1|-15128
|-1|-1|-1|-1|-1|-1|-1|-14914
|-1|-1|-1|-1|-1|-1|-1|-14645
|-1|-1|-1|-1|-1|-1|-1|-14149
|-1|-1|-1|-1|-1|-1|-1|-14087
|-1|-1|-1|-1|-1|-1|-1|-13658
|-1|-1|-1|-1|-1|-1|-1|-13340
|-1|-1|-1|-1|-1|-1|-1|-12888
|-1|-1|-1|-1|-1|-1|-1|-12585
|-1|-1|-1|-1|-1|-1|-1|-11847
|-1|-1|-1|-1|-1|-1|-1|-11055
|-1|-1|-1|-1|-1|-1|-1|-10780
|-1|-1|-1|-1|-1|-1|-1|-10274
|-1|-1|-10254";
private $_Data = array();
public function __construct(){
$_TDataKey = explode('|', str_replace(array("\t","\n","\r\n",' '),array('','','',''),$this-&_DataKey));
$_TDataValue = explode('|', str_replace(array("\t","\n","\r\n",' '),array('','','',''),$this-&_DataValue));
$_Data = (PHP_VERSION&='5.0') ? array_combine($_TDataKey, $_TDataValue) : $this-&_Array_Combine($_TDataKey, $_TDataValue);
arsort($_Data);
$this-&_Data = $_D
public function Pinyin($_String, $_Code='gb2312'){
reset($this-&_Data);
if($_Code != 'gb2312') $_String = $this-&_U2_Utf8_Gb($_String);
$_Res = '';$_aRes = array();$_en = '';
for($i=0; $i&strlen($_String); $i++)
$_P = ord(substr($_String, $i, 1));
if($_P & 160){
$_en .= chr($_P);
$_aRes[] = $_
$_en = '';
if($_P&160) { $_Q = ord(substr($_String, ++$i, 1)); $_P = $_P*256 + $_Q - 65536; }
//$_Res .= _Pinyin($_P, $_Data);
$_aRes[] = $this-&_Pinyin($_P, $this-&_Data);
foreach($_aRes as $k =& $v){
$v = preg_replace("/[^a-zA-Z0-9]*/", '', $v);
$v = ucfirst($v);
$_aRes[$k] = $v;
return implode(' ',$_aRes);
//return preg_replace("/[^a-zA-Z0-9]*/", '', $_Res);
private function _Pinyin($_Num, $_Data){
if ($_Num&0 && $_Num&160 ) return chr($_Num);
elseif($_Num&-20319 || $_Num&-10247) return '';
foreach($_Data as $k=&$v){ if($v&=$_Num) }
return $k;
private function _U2_Utf8_Gb($_C){
$_String = '';
if($_C & 0x80) $_String .= $_C;
elseif($_C & 0x800)
$_String .= chr(0xC0 | $_C&&6);
$_String .= chr(0x80 | $_C & 0x3F);
}elseif($_C & 0x10000){
$_String .= chr(0xE0 | $_C&&12);
$_String .= chr(0x80 | $_C&&6 & 0x3F);
$_String .= chr(0x80 | $_C & 0x3F);
} elseif($_C & 0x200000) {
$_String .= chr(0xF0 | $_C&&18);
$_String .= chr(0x80 | $_C&&12 & 0x3F);
$_String .= chr(0x80 | $_C&&6 & 0x3F);
$_String .= chr(0x80 | $_C & 0x3F);
return iconv('UTF-8', 'GB2312', $_String);
private function _Array_Combine($_Arr1, $_Arr2){
for($i=0; $i&count($_Arr1); $i++) $_Res[$_Arr1[$i]] = $_Arr2[$i];
return $_R
辅助PHP类---简单TREE处理
* @description 简单tree类
* @author WadeYu
* @version 0.0.1
class o_simpletree{
private $_aNodeList = array(); // [id =& []]
private $_aChildNodeList = array(); // [parentId =& [childId,childId,]]
private $_aTopNodeList = array(); //[id =& []]
private $_pk = '';
private $_parentPk = '';
private $_aTreeKV = array(); //tree key的显示的内容
private $_aJoinKey = array();
public function __construct(array $aData = array()/*[[id:0,fid:1],[]]*/, array $aOption = array()){
$this-&_pk = $aOption['pk'];
$this-&_parentPk = $aOption['parentPk'];
$this-&_aTreeKV = (array)$aOption['aTreeKV'];
$this-&_mkNodeList($aData);
$this-&_mkChildNodeList();
public function getTree($parentPk){
$aRet = array();
$aChild = array();
if (!isset($this-&_aChildNodeList[$parentPk])){
return $aR
$aChild = $this-&_aChildNodeList[$parentPk];
foreach((array)$aChild as $k =& $v){
$currNode = $this-&_aNodeList[$v];
$tmpK = '';
foreach($this-&_aTreeKV as $k2 =& $v2){
$tmpV = $currNode[$v2];
if($i == 0){
$tmpK .= $tmpV;
} else if ($i == 1){
$tmpK .= "({$tmpV})";
} else if ($i == 2){
$tmpK .= "[{$tmpV}]";
if (isset($this-&_aChildNodeList[$v])){
$aRet[$tmpK] = $this-&getTree($v);
$aRet[$tmpK] = 1;
return $aR
public function joinKey($aTree,$prefix = ''){
$prefix = trim($prefix);
if(!is_array($aTree)){
foreach((array)$aTree as $k =& $v){
if (is_array($v)){
$this-&joinKey($v,"{$prefix}{$k}/");
$this-&_aJoinKey["{$prefix}{$k}"] = 1;
public function getJoinKey(){
return $this-&_aJoinK
private function _mkNodeList(array $aData = array()){
foreach($aData as $k =& $v){
$this-&_aNodeList[$v[$this-&_pk]] = $v;
private function _mkChildNodeList(){
foreach($this-&_aNodeList as $k =& $v){
if ($v['fid']){
$this-&_aChildNodeList[$v[$this-&_parentPk]][] = $v[$this-&_pk];
$this-&_aTopNodeList[$v[$this-&_pk]] = $v;
LDAP开源实现:openLDAP
0.相关环境说明& & a.操作系统
1 [root@vm ldap]# lsb_release -a
2 LSB Version:
:core-3.1-amd64:core-3.1-ia32:core-3.1-noarch:graphics-3.1-amd64:graphics-3.1-ia32:graphics-3.1-noarch
3 Distributor ID:
4 Description:
CentOS release 5.4 (Final)
5 Release:
6 Codename:
& & a.yum -y install openldap-servers openldap-clients
2.配置& & a.配置HOST& & & & [root@vm ldap]# vi /etc/hosts& & & &127.0.0.1
test.com& & &b.创建证书
cd /etc/pki/tls/certs
[root@vm certs]# pwd
/etc/pki/tls/certs
[root@vm certs]# rm -rf slapd.pem
[root@vm certs]# make slapd.pem
#执行命令之后 显示如下信息 按照提示填写即可
umask 77 ; \
PEM1=`/bin/mktemp /tmp/openssl.XXXXXX` ; \
PEM2=`/bin/mktemp /tmp/openssl.XXXXXX` ; \
/usr/bin/openssl req -utf8 -newkey rsa:2048 -keyout $PEM1 -nodes -x509 -days 365 -out $PEM2 -set_serial 0 ; \
cat $PEM1 &
&& slapd. \
cat $PEM2 && slapd. \
rm -f $PEM1 $PEM2
Generating a 2048 bit RSA private key
.....................................+++
.........+++
writing new private key to '/tmp/openssl.IQ8972'
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
Country Name (2 letter code) [GB]:CN
State or Province Name (full name) [Berkshire]:GuangDong
Locality Name (eg, city) [Newbury]:ShenZhen
Organization Name (eg, company) [My Company Ltd]:Boyaa
Organizational Unit Name (eg, section) []:OA
Common Name (eg, your name or your server's hostname) []:Test LDAP
Email Address []:
[root@vm certs]#
& &c.生成管理员密码
& & & & & & root@vm ~]# slappasswd&
& & & & & & New password: & & & & & &Re-enter new password: & & & & & {SSHA}2eG1IBeHhSjfgS7pjoAci1bHz5p4AVeS
& & d.配置slapd.conf& & & & [root@vm certs]# vi /etc/openldap/slapd.conf
& & & &去掉TLS相关注释
& & & &设置数据库配置
& &&e.BerkeleyDb配置
& & & & [root@vm certs]# cd /etc/openldap/& & & & [root@vm openldap]# mv ./DB_CONFIG.example /var/lib/ldap/DB_CONFIG
& &&f.配置ldap.conf
& & & & [root@vm openldap]# vi ldap.conf
& & g.开启加密支持& & & & [root@vm ~]# vim /etc/sysconfig/ldap& & & & SLAPD_LDAPS=yes
3.使用slapadd命令添加根节点 未启动前
1 [root@vm ~]# cd ~
2 [root@vm ~]# vim root.ldif
3 dn: dc=test,dc=com
4 dc: test
5 objectClass: dcObject
6 objectClass: organizationalUnit
7 ou: test.com
8 [root@vm ~]# slapadd -v -n 1 -l root.ldif
4.启动slapd[root@vm ~]# slapd -h "ldap:// ldaps://"389非加密端口 636加密端口
[root@vm ~]# ldapsearch -x -H ldap://localhost
# extended LDIF
# base && with scope subtree
# filter: (objectclass=*)
# requesting: ALL
# test.com
dn: dc=test,dc=com
objectClass: dcObject
objectClass: organizationalUnit
ou: test.com
# search result
result: 0 Success
# numResponses: 2
# numEntries: 1
[root@vm ~]# ldapsearch -x -H ldaps://localhost
# extended LDIF
# base && with scope subtree
# filter: (objectclass=*)
# requesting: ALL
# test.com
dn: dc=test,dc=com
objectClass: dcObject
objectClass: organizationalUnit
ou: test.com
# search result
result: 0 Success
# numResponses: 2
# numEntries: 1
6.phpldapadmin客户端访问& & a.官网下载源码放入WEB目录下 下载页面:http://phpldapadmin.sourceforge.net/wiki/index.php/Download& & b.安装依赖的扩展gettext ldap这2个扩展& & c.按需配置 源码目录下config/config.php
1.ldaps无法访问/etc/openldap/ldap.conf TLS_REQCERT never加上这个
源码安装PHP扩展步骤
1.下载PHP源码2.切换到扩展源码所在目录 示例:cd /src path/ext/extname3.执行phpize命令 (1.PHP源代码或者PHP安装目录包含此命令 2.当前目录会生成一个检查编译扩展的环境脚本configure)4../configure --with-php-config=/usr/local/php/bin/php-config (1.编译环境配置检查 2.生成make命令需要的编译配置文件makefile)5.make && make install (编译安装)6.php.ini 加上配置 extension = "xxxxx.so"7.重启php service php-fpm restart
安装PHP LDAP扩展
示例:centos系统安装LDAP扩展1.安装依赖库openldap openldap-develyum install openldap yum install openldap-devel2.参考:源码安装PHP扩展步骤
补充(最新LDAP操作类LARAVEL框架版)
LDAP基础类
* @description LDAP客户端类
* @author WadeYu
* @version 0.0.1
9 namespace App\L
10 class Ldap{
private $_conn = NULL;
private $_sErrLog = '';
private $_sOperLog = '';
private $_aOptions = array(
'host' =& 'ldap://xxx.com',
'port' =& '389',
'dnSuffix' =& 'OU=xx,OU=xx,DC=xx,DC=com',
'loginUser' =& '',
'loginPass' =& '',
'logDir' =& '',
private $_aAllowAttrName = array(
'objectClass',
'objectGUID', //AD对象ID
'userPassword', //AD密码不是这个字段 密码暂时不能通过程序设置
'unicodePwd', //AD密码专用字段 $unicodePwd
= mb_convert_encoding('"' . $newPassword . '"', 'utf-16le');
'cn', //comman name 兄弟节点不能相同
'ou', //organizationalUnit
'description', //员工填工号
'displayName', //中文名
'name', //姓名
'sAMAccountName', //英文名(RTX账号,唯一)
'userPrincipalName', //登陆用户名 和 英文名一致
'ProtectedFromAccidentalDeletion', //对象删除保护
'givenName', //姓
'sn', //名
'employeeNumber', //一卡通卡号
'mailNickname',
'manager', //上级 (节点路径 示例:CN=Texas Poker9,OU=Texas Poker,OU=Dept,OU=BoyaaSZ,DC=by,DC=com)
'title', //头衔
'pager', //性别 0男 1女 -1未知
'userAccountControl', //用户账号策略(暂时不能设置) 资料说明地址:https://support.microsoft.com/en-gb/kb/305144
'department',
'managedBy',//部门负责人
'distinguishedName',
'pwdLastSet', //等于0时 下次登录时需要修改密码
'memberOf', //用户所属组
'member',//组成员
public function __construct(array $aOptions = array()){
if (!extension_loaded('ldap')){
$this-&_log('LDAP extension not be installed.',true);
$this-&setOption($aOptions);
* @return exit || true
public function connect($force = false){
if (!$this-&_conn || $force){
$host = $this-&_aOptions['host'];
$port = $this-&_aOptions['port'];
$this-&_conn = ldap_connect($host,$port);
if ($this-&_conn === false){
$this-&_log("Connect LDAP SERVER Failure.[host:{$post}:{$port}]",true);
ldap_set_option($this-&_conn, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($this-&_conn, LDAP_OPT_REFERRALS, 3);
$this-&_bind();
return $this-&_
* @return exit || true
private function _bind(){
$u = $this-&_aOptions['loginUser'];
$p = $this-&_aOptions['loginPass'];
$ret = @ldap_bind($this-&_conn,$u,$p);
if ($ret === false){
$this-&_log(__FUNCTION__.'----'.$this-&_getLastExecErrLog().'----'."u:{$u},p:{$p}",true);
return $ret;
public function setOption(array $aOptions = array()){
foreach($this-&_aOptions as $k =& $v){
if (isset($aOptions[$k])){
$this-&_aOptions[$k] = $aOptions[$k];
public function getOption($field,$default = ''){
return isset($this-&_aOptions[$field]) ? $this-&_aOptions[$field] : $default;
* @description 查询$dn下符合属性条件的节点 返回$limit条
* @return array [count:x,[[prop:[count:xx,[],[]]],....]]
public function getEntryList($dn,$aAttrFilter,array $aField=array(),$limit = 0,$bFixedDn = true){
if (!$dn = trim($dn)){
return array();
if (!$this-&_checkDn($dn)){
return array();
$limit = max(0,intval($limit));
$this-&connect();
if ($bFixedDn){
$dn = $this-&_getFullDn($dn);
$aOldTmp = $aAttrFilter;
$this-&_checkAttr($aAttrFilter);
if (!$aAttrFilter){
$this-&_log(__FUNCTION__.'---无效的搜索属性---'.json_encode($aOldTmp));
return array();
$sAttrFilter = $this-&_mkAttrFilter($aAttrFilter);
$attrOnly = 0;
$this-&_log(__FUNCTION__."---DN:{$dn}---sAttr:{$sAttrFilter}",false,'oper');
$aRet = [];
for($try = 1; $try &= 3; $try++){
$rs = @ldap_search($this-&_conn,$dn,$sAttrFilter,$aField,$attrOnly,$limit);
if ($rs === false){
$this-&_log(__FUNCTION__."---dn:{$dn}--try:{$try}---sAttr:{$sAttrFilter}---" . $this-&_getLastExecErrLog());
if($this-&_getLastErrNo() == -1){ //未连接上LDAP服务器至多重试3次
$this-&connect(true);
} else { //其它情况直接退出
$aRet = @ldap_get_entries($this-&_conn,$rs);
ldap_free_result($rs);
if ($aRet === false){
$this-&_log(__FUNCTION__.'---try:{$try}---'.$this-&_getLastExecErrLog());
if($this-&_getLastErrNo() == -1){
$this-&connect(true);
return $aRet;
* @description 删除节点 暂时不考虑递归删除
* @return boolean
public function delEntry($dn,$bFixedDn = true,$force = 0){
return false;
if (!$dn = trim($dn)){
return false;
if (!$this-&_checkDn($dn)){
return false;
if ($bFixedDn){
$dn = $this-&_getFullDn($dn);
$this-&_log(__FUNCTION__."---DN:{$dn}",false,'oper');
$this-&connect();
/*if($force){
$aEntryList = $this-&getEntryList($dn,array('objectClass'=&'*'),array('objectClass'));
if ($aEntryList && ($aEntryList['count'] & 0)){
for($i = 0; $i & $aEntryList['count']; $i++){
$aDel[] = $aEntryList[$i]['dn'];
$aDel = array_reverse($aDel); //默认顺序 祖先-&子孙 需要先删除子孙节点
foreach($aDel as $k =& $v){
$ret &= @ldap_delete($this-&_conn,$v);
if ($ret === false){
$this-&_log(__FUNCTION__.'dn(recursive):'.$dn.'----'.$this-&_getLastExecErrLog());
$ret = @ldap_delete($this-&_conn,$dn);
if ($ret === false){
$this-&_log(__FUNCTION__.'----dn:'.$dn.'-----'.$this-&_getLastExecErrLog());
return $ret;
* @description 更新节点
* @return boolean
public function updateEntry($dn,$aAttr = array(),$bFixedDn = true){
if (!$dn = trim($dn)){
return false;
$this-&_checkAttr($aAttr);
if (!$aAttr){
return false;
if (!$this-&_checkDn($dn)){
return false;
if ($bFixedDn){
$dn = $this-&_getFullDn($dn);
$this-&_log(__FUNCTION__."---DN:{$dn}---aAttr:".str_replace("\n",'',var_export($aAttr,true)),false,'oper');
$this-&connect();
$ret = false;
for($try = 1; $try &= 3; $try++){
$ret = @ldap_modify($this-&_conn,$dn,$aAttr);
if ($ret === false){
$this-&_log(__FUNCTION__.'---try:{$try}---'.$this-&_getLastExecErrLog().'---dn:'.$dn.'---attr:'.json_encode($aAttr));
if($this-&_getLastErrNo() == -1){ //未连上服务器至多重试3次
$this-&connect(true);
return $ret;
* @description 添加节点
* @return boolean
public function addEntry($dn,$aAttr = array(), $type = 'employee'/*employee,group*/){
if (!$dn = trim($dn)){
return false;
$this-&_checkAttr($aAttr);
if (!$aAttr){
return false;
if (!$this-&_checkDn($dn)){
return false;
$aAttr['objectClass'] = (array)$this-&_getObjectClass($type);
$this-&_log(__FUNCTION__."---DN:{$dn}---aAttr:".str_replace("\n",'',var_export($aAttr,true)),false,'oper');
$this-&connect();
$dn = $this-&_getFullDn($dn);
$ret = false;
for($try = 1; $try &= 3; $try++){
$ret = @ldap_add($this-&_conn,$dn,$aAttr);
if ($ret === false){
$this-&_log(__FUNCTION__.'----dn:'.$dn.'----aAttr:'.json_encode($aAttr).'-----'.$this-&_getLastExecErrLog());
if($this-&_getLastErrNo() == -1){ //未连上服务器至多重试3次
$this-&connect(true);
return $ret;
* @description 移动叶子节点 v3版才支持此方法
* @param $newDn 相对于$parentDn
* @param $parentDn 完整DN
* @param $bMoveRecur
* @return boolean
public function moveEntry($oldDn,$newDn,$parentDn,$bDelOld = true,$bFixDn = true,$bMoveRecur = true){
//对于AD服务器 此方法可以移动用户节点以及组织节点
//$newDn只能包含一个 比如OU=xxx
$oldDn = trim($oldDn);
$newDn = trim($newDn);
$parentDn = trim($parentDn);
if(!$oldDn || !$newDn || ($bFixDn && !$parentDn)){
return false;
if(!$this-&_checkDn($oldDn) || !$this-&_checkDn($newDn) || !$this-&_checkDn($parentDn)){
return false;
$this-&connect();
if($bFixDn){
$oldDn = $this-&_getFullDn($oldDn);
$parentDn = $this-&_getFullDn($parentDn);
$this-&_log(__FUNCTION__."---DN:{$oldDn} -& {$newDn},{$parentDn}",false,'oper');
$aTmpMove = $aDelDn = array();
$aTmpMove[] = array('old'=&$oldDn,'new'=&$newDn);
/*if($bMoveRecur){
$aDelDn[] = $oldDn;
$aTmpList = $this-&getEntryList($oldDn,array('objectClass'=&'*'),array('objectClass'),0,0);
if($aTmpList && ($aTmpList['count'] & 1)){
for($i = 1; $i & $aTmpList['count']; $i++){
if(!in_array('user',$aTmpList[$i]['objectclass'])){ //$bDelOld=true时,用户节点移动时会自动删除
$aDelDn[] = $aTmpList[$i]['dn'];
$aTmpSep = explode($oldDn,$aTmpList[$i]['dn']);
$aTmpMove[] = array(
'old' =& $aTmpList[$i]['dn'],
'new' =& $aTmpSep[0] . $newDn,
$bFlag = true;
foreach($aTmpMove as $k =& $v){
$bTmpFlag = ldap_rename($this-&_conn,$v['old'],$v['new'],$parentDn,(boolean)$bDelOld);
if(!$bTmpFlag){
$this-&_log(__FUNCTION__."---o:{$v['old']}-n:{$v['new']}-p:{$parentDn}-recur:{$bMoveRecur}-----".$this-&_getLastExecErrLog());
$bFlag &= $bTmpFlag;
/*if(!$bFlag){
$this-&_log(__FUNCTION__."---o:{$oldDn}-n:{$newDn}-p:{$parentDn}-recur:{$bMoveRecur}-----".$this-&_getLastExecErrLog());
/*if($bFlag && $bDelOld && $aDelDn){
$aDelDn = array_reverse($aDelDn);
foreach($aDelDn as $k =& $v){
$this-&delEntry($v,false);
return $bFlag;
public function modEntry($dn,$act = 'add',$aAttr = array()){
$dn = $this-&_getFullDn($dn);
$this-&_log(__FUNCTION__."---DN:{$dn}---Act:{$act}---aAttr:".str_replace("\n",'',var_export($aAttr,true)),false,'oper');
$this-&connect();
$ret = false;
switch($act){
case 'add': $ret = ldap_mod_add($this-&_conn,$dn,$aAttr); break;
case 'replace': $ret = ldap_mod_replace($this-&_conn,$dn,$aAttr); break;
case 'del': $ret = ldap_mod_del($this-&_conn,$dn,$aAttr); break;
if(!$ret){
$this-&_log(__FUNCTION__."---dn:{$dn}---act:{$act}---attr:".json_encode($aAttr).'---'.$this-&_getLastExecErrLog());
return $ret;
* @description 批量添加节点
* @return boolean
public function addBatchEntry($aNodeList = array()){
public function getAttrKv(array $aAttr = array()){
if(!isset($aAttr['count']) || ($aAttr['count'] & 1)){
return array();
$aRet = array();
for($i = 0; $i & $aAttr['count']; $i++){
$field = $aAttr[$i];
if (!isset($aAttr[$field])){
return array();
unset($aAttr[$field]['count']);
$aRet[$field] = $aAttr[$field];
if(isset($aAttr['dn'])){ //dn是字符串
$aRet['dn'] = $aAttr['dn'];
return $aRet;
private function _getObjectClass($type = 'employee'){
$aRet = array();
switch($type){
case 'employee' : $aRet = array('top','person','organizationalPerson','user'); break;
case 'group' : $aRet = array('top','organizationalUnit'); break;
return $aRet;
public function getFullDn($partDn = ''){
return $this-&_getFullDn($partDn);
private function _getFullDn($partDn = ''){
$partDn = trim($partDn);
$partDn = rtrim($partDn,',');
return "{$partDn},{$this-&_aOptions['dnSuffix']}";
private function _checkDn($dn = ''){
$dn = trim($dn,',');
$aDn = explode(',',$dn);
foreach($aDn as $k =& $v){
$aTmp = explode('=',$v);
$aTmp[0] = strtolower(trim($aTmp[0]));
$aTmp[1] = trim($aTmp[1]);
$flag = false;
switch($aTmp[0]){ //distingushed name 暂时只允许这3个field
case 'dc': $flag = $this-&_checkDc($aTmp[1]); break;
case 'ou': $flag = $this-&_checkOu($aTmp[1]); break;
case 'cn': $flag = $this-&_checkCn($aTmp[1]); break;
if (!$flag){
$this-&_log(__FUNCTION__.'----无效的节点路径----dn:'.$dn);
return false;
return true;
private function _checkOu($ou = ''){
if (!$ou){
return false;
if (preg_match('/[^a-zA-Z\s\d\.&\'\d]/',$ou)){
$this-&_log(__FUNCTION__.'----OU只能包含字母数字空格以及点');
return false;
return true;
private function _checkCn($cn = ''){
if (!$cn){
return false;
return true;
private function _checkDc($dc = ''){
if (!$dc){
return false;
if (preg_match('/[^a-zA-Z]/',$dc)){
$this-&_log(__FUNCTION__.'----DC只能包含英文字母');
return false;
return true;
private function _mkAttrFilter(array $aAttrFilter = array()){
$sStr = '(&';
foreach($aAttrFilter as $k =& $v){
$v = (string)$v;
if($k === 'objectGUID'){
$v = $this-&_GUIDtoStr($v);
$v = addcslashes($v,'()=');
$sStr .= "({$k}={$v})";
$sStr .= ')';
return $sStr;
//来自PHP.NET http://php.net/manual/en/function.ldap-search.php
//http://php.net/manual/en/function.ldap-get-values-len.php
//GUID关键字
private function _GUIDtoStr($binary_guid){
$hex_guid = unpack("H*hex", $binary_guid);
$hex = $hex_guid["hex"];
$j = 0;$str = '\\';
for($i = 0; $i & strlen($hex); $i++){
if($j == 2){
$str .= '\\';
$str .= $hex[$i];
return $str;
/*$hex1 = substr($hex, -26, 2) . substr($hex, -28, 2) . substr($hex, -30, 2) . substr($hex, -32, 2);
$hex2 = substr($hex, -22, 2) . substr($hex, -24, 2);
$hex3 = substr($hex, -18, 2) . substr($hex, -20, 2);
$hex4 = substr($hex, -16, 4);
$hex5 = substr($hex, -12, 12);
$guid_str = $hex1 . "-" . $hex2 . "-" . $hex3 . "-" . $hex4 . "-" . $hex5;
return $guid_*/
private function _checkAttr(& $aAttr = array()){
foreach((array)$aAttr as $k =& $v){
if (!in_array($k,$this-&_aAllowAttrName)){
unset($aAttr[$k]);
return true;
public function getErrLog(){
return $this-&_sErrL
public function getOperLog(){
return $this-&_sOperL
public function clearLog($type = 'err'){
if($type == 'err'){
$this-&_sErrLog = '';
$this-&_sOperLog = '';
private function _log($str = '',$bExit = false,$type = 'err'/*err,oper*/){
$date = date('Y-m-d H:i:s');
if ($bExit){
if($this-&_aOptions['logDir']){
if(!file_exists($this-&_aOptions['logDir'])){
mkdir($this-&_aOptions['logDir'],0666,true);
$file = rtrim($this-&_aOptions['logDir'],'\\/') . '/ldap_exit_' . date('Ym') . '.log';
file_put_contents($file,"{$date}---type:{$type}---{$str}\n",FILE_APPEND);
die($str);
if($type === 'err'){
$this-&_sErrLog .= "{$date}----{$str}\n";
} else if ($type === 'oper'){
$this-&_sOperLog .= "{$date}----{$str}\n";
public function close(){
ldap_close($this-&_conn);
private function _getLastExecErrLog(){
$no = $this-&_getLastErrNo();
$err = ldap_error($this-&_conn);
return "---exec Error:{$no}---{$err}";
public function getLastExecErrLog(){
return $this-&_getLastExecErrLog();
private function _getLastErrNo(){
return ldap_errno($this-&_conn);
组织架构信息更新类
* @description 测试用例说明
* @组织节点
* 0.添加是否正常
* 1.修改节点是否正常
1.0修改了中文名称是否正常
1.1修改了其它属性是否正常
1.2修改了上级组织是否正常
1.3修改了需要移动节点的属性也修改了其它属性是否正常
* 2.移动节点是否正常
2.0参考编号1
* 3.删除节点是否正常
3.0暂时未提供删除入口
* 4.没有修改时是否有更新操作
4.0原则上没有修改不应该更新AD服务器
* @用户节点
* 0.添加是否正常
0.0敏感属性设置是否正常 例如用户密码 用户账号策略控制
0.1RTX账号不能重复
0.2没有填的项不能设置 否则会覆盖掉老数据
0.3同一父节点下中文名称不能相同
* 1.修改或者移动是否正常
1.0RTX账号必须唯一
1.1姓名同一父节点下必须唯一
1.2修改了姓名或者修改了所在部门需要移动节点
1.3修改了其它属性 执行更新操作
1.4没有属性修改 原则上不更新AD服务器
1.5设置敏感属性信息是否正常
* 2.删除是否正常
2.0暂时未提供入口
* @description 碰到的问题说明
* 0.敏感属性只能通过加密连接设置
* 1.解决中文乱码 移动节点 只有LDAPV3协议支持
* 2.运维建议尽量只进行更新操作 域服务器OBJECTGUID数量有限制 超过了域服务器就不能使用了
41 namespace App\L
42 use App\Lib\L
43 use App\Lib\P
44 use App\Lib\
45 use DB;
46 use App\Http\Model\G
47 use App\Http\Model\U
48 use Mail;
49 class Hrldap{
private static $_aCfg = array();
private static $_oLdap = null;
private $_adGuidField = 'adguid';
private $_tbluser = 'pb_user';
private $_tblgroup = 'pb_group';
private $_isTestSvr = false;
public $aOpertor = array();
const STATUS_USER_LEAVE = 2; //员工离职状态
const STATUS_USER_NOLEAVE = 1; //员工在职状态
const STATUS_GROUP_DEL = 1; //组织架构删除状态
public function __construct(){
if (!self::$_aCfg){
self::$_aCfg = config('ldap');
if (self::$_oLdap === null){
self::$_oLdap = new Ldap($this-&_getReMapCfg());
$this-&_isTestSvr = (env('APP_ENV') == 'local');
$this-&aOpertor = ['username'=&'cron','id'=&0]; //设置一个默认值
private function _getReMapCfg(){
return array(
'host' =& self::$_aCfg['ldapHost'],
'port' =& self::$_aCfg['ldapPort'],
'dnSuffix' =& self::$_aCfg['ldapDnSuffix'],
'loginUser' =& self::$_aCfg['ldapUser'],
'loginPass' =& self::$_aCfg['ldapPass'],
'logDir' =& self::$_aCfg['logDir'],
public function getCfg($field = ''){
if($field && isset(self::$_aCfg[$field])){
return self::$_aCfg[$field];
return self::$_aCfg;
* @desc 通用入口
public function apiHr(array &$newData, $type = 'group'/*group,user*/,$aOpertor = [],$bLog = true){
//$newData 数据库最新的数据
$this-&aOpertor = $aOpertor;
switch($type){
case 'group':
$aRet = $this-&_frmGroupData($newData);
case 'user':
$aRet = $this-&_frmUserData($newData);
if($bLog){
//$this-&_log('endToFile');
$this-&_log('ldap');
//$this-&_log('ldapOperLog');
$this-&_sendMonitorMail();
return $aRet;
}catch(Exception $ex){
if($bLog){
//$this-&_log('endToFile');
$this-&_log('ldap');
$this-&_sendMonitorMail();
return $this-&_genRet(-9999,$ex-&getMessage());
public function afterHandleCb(){
//$this-&_log('endToFile');
$this-&_log('ldap');
//$this-&_log('ldapOperLog');
$this-&_sendMonitorMail();
* @desc 批量更新通用入口
* @param $aData [type:[id:{},...,]]
public function apiHrBat(array &$aData,$aOpertor = []){
if(!$aData || !is_array($aData)){
return false;
$this-&aOpertor = $aOpertor;
$aType = array('group' =& $this-&_tblgroup,'user' =& $this-&_tbluser);
$aColumns = array(
'group'=&'*',
'user'=&'id,username,cname,sex,groupid,position,mposition,utype,boss,phone,email,status,degree',
$bTmp = true;
$st = microtime(true);
foreach($aData as $k =& $v){
if(!isset($aType[$k])){
$aTmpIds = array_keys($v);
if(!$aTmpIds){
$sTmpIds = implode(',',$aTmpIds);
$aTmpList = DB::select( "SELECT {$aColumns[$k]} FROM {$aType[$k]} WHERE id in ({$sTmpIds})" );
foreach((array)$aTmpList as $k2 =& $v2){
$aTmpRet = array();
switch($k){
case 'group': $aTmpRet = $this-&_frmGroupData((array)$v2); break;
case 'user': $aTmpRet = $this-&_frmUserData((array)$v2); break;
$bTmp &= (($aTmpRet['sts'] === 0) ? true : false);
$et = microtime(true);
//$this-&_log('endToFile');
$this-&_log('ldap');
$this-&_log('def','def',array('f'=&'aApiHrBatCnt.log','s'=&date('Y-m-d H:i:s').'---批量更新消耗时间:'.($et-$st)."---更新状态:{$bTmp}\n"));
$this-&_log('def','def',array('f'=&'aApiHrBatData.log','s'=&date('Y-m-d H:i:s').json_encode($aData)."\n"));
//$this-&_log('ldapOperLog');
$this-&_sendMonitorMail();
return $bTmp;
* @desc 根据数据库数据批量更新信息到AD服务器(组织节点)
* @return boolean
public function apiSyncOuToAd(){
* @desc 根据数据库数据批量更新信息到AD服务器(员工节点)
* @return boolean
public function apiSyncUserToAd($bOnlyUpdate = false,$aUpdateField = array(),$aUid = array()){
public function getLdapObj(){
return self::$_oLdap;
* @desc 灰度开放部门
private function _checkOpOk($id = 1){//组织ID
return true; //开启全部部门
$aOuList = $this-&_getOuList();
if(!isset($aOuList[$id])){
return false;
$aIdLvl = array();
$aTmpOu = $aOuList[$id];
while($aTmpOu){
$aIdLvl[] = $aTmpOu['id'];
$aTmpOu = $aOuList[$aTmpOu['fid']];
$topId = array_pop($aIdLvl);
if(in_array($topId,array(328,/*业务支持中心*/))){ //暂时开放这些部门
return true;
return false;
private $_aNewestAdguidMap = array();
private function _frmGroupData(array &$newData){
$sNewDes = $newData['name'] = trim($newData['name']);
$iBoss = $newData['boss'] = intval($newData['boss']);
$id = $newData['id'] = intval($newData['id']);
if( !$sNewDes || !$id /*|| !$iBoss*/){
return $this-&_genRet(-1);
if(!$this-&_checkOpOk($id)){ //灰度测试 暂时只对部分部门开放
return $this-&_genRet(0);
if($newData['del'] == self::STATUS_GROUP_DEL){ //关闭节点 有在职员工 AD不移动到LEFTOUT
if( count(User::getOcolsByGroup($id)) & 0 ){
return $this-&_genRet(0);
//关闭节点 移动节点到OU=OU,OU=HasLeft
/*if($newData['del'] == 1){//关闭节点 AD服务器不做处理
return $this-&_genRet(0);
//AD服务器老节点存在 执行更新移动操作
//否则走添加新节点流程
//新添加节点业务机需要保存节点GUID
if(isset($this-&_aNewestAdguidMap[$id])){ //循环更新的话ADGUID可能会变化
$newData[$this-&_adGuidField] = $this-&_aNewestAdguidMap[$id];
$sNewAdName = $this-&_getOuPinYin($sNewDes,$id);
$this-&_getOuList($newData);//更新OU LIST 节点缓存
$aNewAttr = $this-&_getGroupData($sNewDes,$sNewAdName,$iBoss);
$sGuidField = $this-&_adGuidF
$sAdGuid = $newData[$sGuidField] ? $this-&_decodeAdGuid($newData[$sGuidField]) : ''; //AD服务器节点唯一标识
$aOldEntry = array();
$sTopOu = self::$_oLdap-&getOption('dnSuffix');
if($sAdGuid){ //没找到是否需要退出 ?
$aTmpEntryList = self::$_oLdap-&getEntryList($sTopOu,array('objectClass'=&'organizationalUnit','objectGUID'=&$sAdGuid),array(),0,0);
if($aTmpEntryList && ($aTmpEntryList['count'] & 0)){
$aOldEntry = $aTmpEntryList[0];
if(($newData['del'] == self::STATUS_GROUP_DEL) && !$aOldEntry){ //关闭节点 域中节点不存在的话 不做任何处理
return $this-&_genRet(0);
if($aOldEntry){ //AD服务器节点已存在 更新或者移动
$sTmpOldDn = str_replace(",{$sTopOu}",'',$aOldEntry['dn']);
$aTmpAttr = $this-&_getAttrDiff($aNewAttr,$aOldEntry);
unset($aTmpAttr['name']); //恶魔属性 不能更新
$bTmp = true;
if($aTmpAttr && ($newData['del'] != self::STATUS_GROUP_DEL)){
$bTmp = self::$_oLdap-&updateEntry($sTmpOldDn,$aTmpAttr);
$this-&_log('update','group',array('flag'=&$bTmp,'dn'=&$sTmpOldDn,'attr'=&$aTmpAttr),$newData,$aOldEntry);
if(!$bTmp){
return $this-&_genRet(-2,'部门ID:'.$id.'---节点更新失败---line:'.__LINE__);
$sTmp2 = "OU={$sNewAdName}";
if($newData['del'] == self::STATUS_GROUP_DEL){ //关闭节点 把节点移动到OU=OU,OU=HasLeft
$sTmp4 = self::$_aCfg['ldapDnHasLeftOu'];
$sTmp4 = $this-&_getOuDn($newData['fid']);
$sTmp5 = $sTmp2 . ($sTmp4 ? ",{$sTmp4}" : '');
if(strpos($sTmpOldDn,$sTmp5) === false){ //DN变了 需要移动节点
$bTmp = self::$_oLdap-&moveEntry($sTmpOldDn,$sTmp2,$sTmp4);
$this-&_log('move','group',array('flag'=&$bTmp,'path'=&"{$sTmpOldDn}-&{$sTmp5}"),$newData,$aOldEntry);
return $this-&_genRet($bTmp ? 0 : -3,'部门ID:'.$id.'---移动节点失败---line:'.__LINE__);
//走到这一步只剩下添加节点操作了
//父级节点不存在自动递归创建
//否则新组织节点也创建不了
$aAdd = array();
$aOuList = $this-&_getOuList(); //数据库中的组织架构列表
$aTmpOu = $newData;
if($aTmpOu[$sGuidField]){ //数据库中存在域对象的GUID AD域中存在的概率99% 还需要走AD服务器验证下?
$bTmpBreak = true;
if($this-&_isTestSvr){
$aTmpFilter = array('objectClass'=&'organizationalUnit','objectGUID'=&$this-&_decodeAdGuid($aTmpOu[$sGuidField]));
$aTmpEntryList = self::$_oLdap-&getEntryList($sTopOu,$aTmpFilter,array(),0,0);
if($aTmpEntryList && ($aTmpEntryList['count'] & 0)){
$bTmpBreak = false;
if($bTmpBreak){
$aAdd[] = $aTmpOu;
$aTmpOu = isset($aOuList[$aTmpOu['fid']]) ? $aOuList[$aTmpOu['fid']] : [];
}while($aTmpOu);
if(!$aAdd){
return $this-&_genRet(0);
$aAdd = array_reverse($aAdd); //祖先节点要先添加
$bTmpFlag = true;
$sPreFixDn = $this-&_getOuDn($aAdd[0]['fid']);
if(!$sPreFixDn){
return $this-&_genRet(-6,'部门ID:'.$id.'---新建节点失败---取第一个父节点(fid:'.$aAdd[0]['fid'].')DN失败');
foreach($aAdd as $k =& $v){
$sTmpOuPinYin = $this-&_getOuPinYin($v['name'],$v['id']);
$aTmpAttr = $this-&_getGroupData($v['name'],$sTmpOuPinYin,$v['boss']);
//$sTmpDn = $this-&_getOuDn($v['id']);
$sTmpDn = "OU={$sTmpOuPinYin}," . $sPreFixDn;
$bTmpFlag2 = self::$_oLdap-&addEntry($sTmpDn,$aTmpAttr,'group');
$bTmpFlag = $bTmpFlag2;
$this-&_log('add','group',array('flag'=&$bTmpFlag2,'dn'=&$sTmpDn,'attr'=&$aTmpAttr),$v,array());
if($bTmpFlag2){ //更新业务机组织表AD节点objectGUID
$this-&_addAdGuidMap($sTmpDn,$v['id'],array('name'=&$aTmpAttr['name'][0]),'group',true);
} else { //父节点都更新失败,子节点无地方落脚啊,果断不往下走
$sPreFixDn = $sTmpDn;
return $this-&_genRet($bTmpFlag ? 0 : -5,'部门ID:'.$id.'---新建节点失败');
private function _encodeAdGuid($str){
return base64_encode($str);
private function _decodeAdGuid($str){
return base64_decode($str);
private $_aCachePinYin = array();
private function _getOuPinYin($name,$id){
if(isset($this-&_aCachePinYin[$id])){
return $this-&_aCachePinYin[$id];
$this-&_aCachePinYin[$id] = (new Pinyin())-&Pinyin($name,1) . " {$id}"; //加上ID后缀 避免名称冲突
return $this-&_aCachePinYin[$id];
private function _addAdGuidMap($dn,$id,$aAttrFilter = array(),$type = 'group'/*group,user*/,$bUpdateGuid = false){
$adGuid = '';
$sErrFile = 'aLdapAddAdGuidMapErr.log';
$aTmpEntryList = self::$_oLdap-&getEntryList($dn,$aAttrFilter,array('objectGUID'));
if($aTmpEntryList && ($aTmpEntryList['count'] & 0)){
$adGuid = $aTmpEntryList[0]['objectguid'][0];
if(!$adGuid){
$this-&_debug(date('Y-m-d H:i:s')."---dn:{$dn}---id:{$id}---type:{$type}---取objectGUID失败\n",$sErrFile);
return false;
$aMapTbl = array(
'group' =& $this-&_tblgroup,
'user' =& $this-&_tbluser,
if(!isset($aMapTbl[$type])){
return false;
$tbl = $aMapTbl[$type];
$adGuid = $this-&_encodeAdGuid($adGuid);
$ret = DB::update( "UPDATE {$tbl} SET {$this-&_adGuidField} = '{$adGuid}' WHERE id = '{$id}'" );
if(!$ret){
$this-&_debug(date('Y-m-d H:i:s')."---dn:{$dn}---id:{$id}---type:{$type}---guid:{$adGuid}---objectGUID入库失败\n",$sErrFile);
if($bUpdateGuid && $ret){
switch($type){
case 'group':
$aTmpOuList = $this-&_getOuList();
$aTmpOu = $aTmpOuList[$id];
$aTmpOu[$this-&_adGuidField] = $adGuid;
$this-&_getOuList($aTmpOu);
$this-&_aNewestAdguidMap[$id] = $adGuid;
return $ret;
private function _debug($msg,$sFile){
$this-&_log('def','def',array('f'=&$sFile,'s'=&$msg));
* @desc 拼接域节点路径
private $_aCacheOuDn = array();
private function _getOuDn($id){
if($id == 1){ //一级部门OU = D
return self::$_aCfg['ldapDnDept']; //组织架构DN专有后缀;
$aRet = array();
$aOuList = (array)$this-&_getOuList();
$aTmpOu = $aOuList[$id];
if (!$aTmpOu){
throw new Exception(__FUNCTION__.'----非法的组织ID:'.$id.'---line:'.__LINE__);
$adguid = 'x';
if($aTmpOu[$this-&_adGuidField]){
$adguid = $aTmpOu[$this-&_adGuidField];
if(isset($this-&_aCacheOuDn[$id][$adguid])){
return $this-&_aCacheOuDn[$id][$adguid];
if(!isset($this-&_aCacheOuDn[$id])){
$this-&_aCacheOuDn[$id] = array();
if($aTmpOu[$this-&_adGuidField]){ //通过GUID找到当前节点DN 不通过规则拼接
$sTopOu = self::$_oLdap-&getOption('dnSuffix');
$sAdGuid = $this-&_decodeAdGuid($aTmpOu[$this-&_adGuidField]);
$aTmpEntryList = self::$_oLdap-&getEntryList($sTopOu,array('objectClass'=&'organizationalUnit','objectGUID'=&$sAdGuid),array('objectGUID'),0,0);
if($aTmpEntryList && ($aTmpEntryList['count'] & 0)){
$sTmpOldDn = str_replace(",{$sTopOu}",'',$aTmpEntryList[0]['dn']);
$this-&_aCacheOuDn[$id][$adguid] = $sTmpOldDn;
$this-&_aCacheOuDn[$id][$adguid] = '';
return $this-&_aCacheOuDn[$id][$adguid];
while($aTmpOu){
$ou = $this-&_getOuPinYin($aTmpOu['name'],$aTmpOu['id']);
$aRet[] = "OU={$ou}";
$aTmpOu = isset( $aOuList[$aTmpOu['fid']] ) ? $aOuList[$aTmpOu['fid']] : [];
$aRet[] = self::$_aCfg['ldapDnDept']; //组织架构DN专有后缀
$this-&_aCacheOuDn[$id][$adguid] = implode(',',$aRet);
return $this-&_aCacheOuDn[$id][$adguid];
* @desc 返回单个节点信息
private function _getOneEntry($dn,$aFilter){
$aTmp = array();
$aEntryList = self::$_oLdap-&getEntryList($dn,$aFilter);
if ($aEntryList && ($aEntryList['count'] & 0)){
$aTmp = $aEntryList[0];
return $aTmp;
* @desc 组织架构节点信息
* @return array
private $_aGroupNodeAttr = array( //组织节点属性
'description' =& '中文名', //组织中文名
'name' =& '拼音',//组织英文名称
'managedBy' =& '负责人', //部门负责人
private function _getGroupData($desc,$adname,$iBoss){
$aRet = array(
'description' =& array($desc), //组织中文名
'name' =& array($adname),//组织英文名称
$sManagedBy = $this-&_getUserObjectId($iBoss,true); //部门负责人域对象ID
if($sManagedBy){
$aRet['managedBy'] = array($sManagedBy); //部门负责人
return $aRet;
* @desc 用户节点对象ID
* @return String
private $_aCacheUserObjDn = array();
private function _getUserObjectId($uid,$bChecked = false){
if(isset($this-&_aCacheUserObjDn[$uid])){
return $this-&_aCacheUserObjDn[$uid];
//if(LOCAL){
$aUser = DB::select( "SELECT id,groupid AS pid,cname AS name FROM {$this-&_tbluser} WHERE id = '{$uid}'" );
//} else {
//$aUser = o('hr')-&getUser($uid);
if (!$aUser){
$this-&_aCacheUserObjDn[$uid] = '';
return '';
$aUser = (array)$aUser[0];
$sOuDn = $this-&_getOuDn($aUser['pid']);
if(!$sOuDn){
$this-&_aCacheUserObjDn[$uid] = '';
return '';
$dn = "CN={$aUser['name']},{$sOuDn}";
if ($bChecked){
$exists = $this-&_getOneEntry($dn,array('objectClass'=&'user'));
if (!$exists){
$dn = self::$_oLdap-&getFullDn($dn);
$this-&_aCacheUserObjDn[$uid] = $dn;
return $dn;
* @desc 组织架构信息
* @return array
private function _getOuList(array $aNewNode = array()){
static $aCache = NULL;
if ($aCache === NULL){
$aCache = array();
$aList = DB::select("SELECT * FROM {$this-&_tblgroup} WHERE fid & 0");
foreach((array)$aList as $k =& $v){
$v = (array)$v;
$aCache[$v['id']] = $v;
if($aNewNode && ($aNewNode['fid'] & 0)){
$aCache[$aNewNode['id']] = $aNewNode;
return true;
return $aCache;
private function _genRet($errno = 0,$errMsg = ''){
return array('sts' =& $errno, 'msg' =& $errMsg);
private $_cacheLog = '';
private $_cacheLogFile = 'aLdapOper_#1.log';
private $_ldapLogFile = 'aLdapOperErr_#1.log';
private $_ldapLogRawFile = 'aLdapOperRaw_#1.log';
private function _log($actType = '',$oType = 'normal',array $aPar = array(), array $aNewData = array(), array $aOldData = array()){
if(in_array($actType,array('endToFile','ldap','def','ldapOperLog'))){
$sDir = rtrim(self::$_aCfg['logDir'],'\\/') . '/';
if(!file_exists($sDir)){
mkdir($sDir,0666,true);
if ($actType === 'endToFile'){
$sFile = $sDir.str_replace('#1',date('Y'),$this-&_cacheLogFile);
file_put_contents($sFile,$this-&_cacheLog,FILE_APPEND);
$this-&_cacheLog = '';
//$this-&_sendMonitorMail();
return true;
} else if ($actType === 'ldap'){
$sFile = $sDir.str_replace('#1',date('Y'),$this-&_ldapLogFile);
$sTmp = date('Y-m-d H:i:s').'----Start----'.$this-&aOpertor['username'].'('.$this-&aOpertor['id'].')';
$sLdapLog = self::$_oLdap-&getErrLog();
file_put_contents($sFile,"{$sTmp}\n{$sLdapLog}\n",FILE_APPEND);
return true;
} else if ($actType === 'def'){
file_put_contents($sDir.$aPar['f'],$aPar['s'],FILE_APPEND);
return true;
} else if ($actType === 'ldapOperLog'){
$sFile = $sDir.str_replace('#1',date('Y'),$this-&_ldapLogRawFile);
$sTmp = date('Y-m-d H:i:s').'----Start----'.$this-&aOpertor['username'].'('.$this-&aOpertor['id'].')';
$sLdapLog = self::$_oLdap-&getOperLog();
file_put_contents($sFile,"{$sTmp}\n{$sLdapLog}\n",FILE_APPEND);
return true;
$this-&_logMail($actType,$oType,$aPar,$aNewData,$aOldData);
$aLog = array(
date('Y-m-d H:i:s'),
fn::serialize($aPar),
$this-&_cacheLog .= implode('##',$aLog);
$this-&_cacheLog .= "\n";
return true;
private $_aLogMail = array('group'=&array(),'user'=&array(),'delUserDn'=&array());//离职用户单独通知 删除权限组
private function _logMail($actType,$oType,$aPar,$aNewData,$aOldData){
if(!isset($this-&_aLogMail[$oType])){
return false;
if(!$aPar['flag']){ //没有更新成功过滤掉
return false;
$aFieldMap = array('group'=&$this-&_aGroupNodeAttr,'user'=&$this-&_aUserNodeAttr);
if($actType === 'move'){
if(($oType === 'user') && isset($aNewData['del']) && ($aNewData['del'] == self::STATUS_USER_LEAVE)){//离职用户单独通知
$sTmpDepart = $this-&_fixDepartment($aNewData['pid']);
$this-&_aLogMail['delUserDn'][] = "DN:{$aPar['dn']},RTX账号:{$aNewData['rtx']},部门:{$sTmpDepart}";
$this-&_aLogMail[$oType][$actType][] = "&tr&&td&{$aPar['path']}&/td&&/tr&";
}else if (in_array($actType,array('update','add'))){
$sTmp = '&tr&';
$aFieldTmp = $aFieldMap[$oType];
$sTmp .= '&td&'.(isset($aPar['dn']) ? $aPar['dn'] : $aPar['cn']).'&/td&';
foreach($aFieldTmp as $k =& $v){
$sK = strtolower($k);
if(isset($aOldData[$sK]['count'])){
unset($aOldData[$sK]['count']);
$sTmpVal = isset($aPar['attr'][$k]) ? ( (isset($aOldData[$sK]) ? implode(',',$aOldData[$sK]).' =& ' : '').implode(',',$aPar['attr'][$k]) ) : '';
$sTmp .= "&td&{$sTmpVal}&/td&";
$sTmp .= '&/tr&';
$this-&_aLogMail[$oType][$actType][] = $sTmp;
return true;
private function _sendMonitorMail(/*$bClear = true*/){
$sTmp = '';
$aFieldMap = array('group'=&$this-&_aGroupNodeAttr,'user'=&$this-&_aUserNodeAttr);
$aHeadMap = array('group' =& '','user'=&'');
foreach($aFieldMap as $k =& $v){
$aHeadMap[$k] = '&tr&';
$aHeadMap[$k] .= '&td&DN&/td&';
foreach($v as $k2 =& $v2){
$aHeadMap[$k] .= "&td&{$v2}&/td&";
$aHeadMap[$k] .= '&/tr&';
foreach($this-&_aLogMail as $k =& $v){
if(!$v || ($k === 'delUserDn')){
$sTmp .= "&h1&被操作实体:{$k}&/h1&";
foreach($v as $k2 =& $v2){
$sTmp .= "&h5&操作类型:{$k2}&/h5&";
$sTmp .= '&table border=1 cellspacing=1&';
if($k2 === 'move'){
$sTmp .= '&tr&&td&Old DN =& New DN&/td&&/tr&';
$sTmp .= $aHeadMap[$k];
$sTmp .= implode("\n",$v2);
$sTmp .= '&/table&';
$this-&_aLogMail[$k] = array();
if(!$sTmp){
return true;
$title = '【周知】BY后台同步信息到AD域服务器变更通知';
Mail::queue('emails.ldap_notify',['data'=&$sTmp],function($m)use($title){
$m-&to( self::$_aCfg['ldapMailTo'] )-&subject($title);
$this-&_isTestSvr && $this-&_log('def','def',array('f'=&'aMonitorLog.html','s'=&$sTmp));
if($this-&_aLogMail['delUserDn']){ //离职用户单独通知
$title = '【员工离职周知】BY后台员工变更为离职状态';
$content = '离职用户:&br /&'.implode('&br /&',$this-&_aLogMail['delUserDn']);
Mail::queue('emails.ldap_notify',['data'=&$content],function($m) use($title){
$m-&to( self::$_aCfg['ldapMailTo'] )-&subject($title);
$this-&_isTestSvr && $this-&_log('def','def',array('f'=&'aMonitorLog.html','s'=&$content));
$this-&_aLogMail['delUserDn'] = array();
return true;
* @desc 刷新用户节点信息
* @to do list 0.用户重要信息设置 1.用户说所在父节点不存在时自动递归创建
* @return boolean
private function _frmUserData(array &$newData,$bUpdate = false/*true只更新AD中已存在的员工节点*/,$aUpdateField=array()){
$rtx = trim($newData['username']);
$name = trim($newData['cname']);
$uid = intval($newData['id']);
$pid = intval($newData['groupid']); //部门ID
$oldData = array();
if(!$rtx || !$name || !$uid || !$pid){
return $this-&_genRet(-1,'参数不正确username,cname,id,groupid有空值---line:'.__LINE__);
if(!$this-&_checkOpOk($pid)){ //灰度测试 暂时只对部分部门开放
return $this-&_genRet(0);
//0 深圳总部 8000外包客服 80000泰国分公司 加入域 其他过滤掉
$uType = fn::getUidType($uid);
if(!in_array($uType,array(0,))){
return $this-&_genRet(0);
//-1.只对AD服务器执行添加 更新 移动操作 不执行删除操作
//0.同一节点下兄弟节点姓名需要唯一
//1.组织架构下RTX需要唯一
//2.节点存在 直接执行更新操作
2.0节点存在 工号不相同时 同一父节点下姓名冲突了 提示错误
//3.如果姓名修改了 如果老姓名存在 需要删除节点
3.0删除之前需要判断用户ID是否一致啊 一致才能删除啊
//4.更改了组织怎么处理?
4.0即使更改了组织 删除老节点就行了嘛
4.1是否可以移动节点呢
//5.RTX名必须唯一 否则会导致用户节点添加不成功
$sFixUid = $this-&_fixUid($uid);
$sTopOu = self::$_oLdap-&getOption('dnSuffix');
$aTmpEntryList = self::$_oLdap-&getEntryList($sTopOu,array('objectClass'=&'user','sAMAccountName'=&$rtx),array('description'),0,0);
if($aTmpEntryList && ($aTmpEntryList['count'] & 0)){
for($i = 0; $i & $aTmpEntryList['count']; $i++){
$aTmpEntry = $aTmpEntryList[$i];
if($aTmpEntry['description'][0] !== $sFixUid){
return $this-&_genRet(-2,"RTX:{$rtx}域服务器已存在---line:".__LINE__);
$dn = $this-&_getOuDn($pid);
$aTmpEntryList = self::$_oLdap-&getEntryList($dn,array('objectClass'=&'user','name'=&$name),array('description'));
if($aTmpEntryList && ($aTmpEntryList['count'] & 0)){
for($i = 0; $i & $aTmpEntryList['count']; $i++){
$aTmpEntry = $aTmpEntryList[$i];
if($aTmpEntry['description'][0] !== $sFixUid){
return $this-&_genRet(-3,"中文名:{$name}同一小组下域服务器已存在---line:".__LINE__);
$aAttr = $this-&_getUserData($newData);
//移动节点
//0.老节点存在 移动老节点到新节点
0.0名称修改了 或者部门修改了 才需要移动 否则只需要更新即可
0.1先更新老节点信息 再移动到指定的节点
0.3AD服务器老节点用户ID跟数据库中用户ID相同时,才能移动老节点,否则移动了其它人的节点,则悲剧了
//1.老节点不存在
1.0 新节点不存在 添加
1.1 新节点存在 更新
//2.无论节点属性怎么修改 工号是不会变化的
2.0根据工号找到AD服务器已存在的节点
2.1根据工号找出了多个用户节点 需要找运维开发人功能核对处理
$sNewCn = "CN={$name},".$dn; //未包含DN根路径
$nameDel = "{$name}{$sFixUid}";
if($newData['status'] == self::STATUS_USER_LEAVE){ //员工离职需要移动到HasLeft节点下
$dn = self::$_aCfg['ldapDnHasLeftUser'];
$sNewCn = "CN={$nameDel},".$dn;
$aTmpEntryList = self::$_oLdap-&getEntryList($sTopOu,array('objectClass'=&'user','description'=&$sFixUid),array(),0,0);
if($aTmpEntryList && ($aTmpEntryList['count'] & 0)){ //移动或者更新节点
if($aTmpEntryList['count'] & 1){
return $this-&_genRet(-4,"AD服务器ERR:UID工号({$sFixUid})不唯一,请找运维开发人工处理".__LINE__);
$sOldCn = $aTmpEntryList[0]['dn']; //完整的DN路径
$sOldCn = str_replace(",$sTopOu",'',$sOldCn); //为包含DN根路径
unset($aAttr['name']);
$sUacField = 'userAccountControl';
$ssUacField = strtolower($sUacField);
if($aTmpEntryList[0][$ssUacField][0] & 0x10000){//如果密码不过期策略存在,一直保留(运维同事要求)
$aAttr[$sUacField][0] = $aAttr[$sUacField][0] | 0x10000;
$sAccountField = 'sAMAccountName';
$ssAccountField = strtolower($sAccountField);
if( strtolower($aTmpEntryList[0][$ssAccountField][0]) === strtolower($aAttr[$sAccountField][0]) ){
unset($aAttr[$sAccountField],$aAttr['userPrincipalName'],$aAttr['mail']); //当RTX账号字母相同时【不区分大小写】,不更新RTX账号【运维同事要求】
} else { //不同时,不能更新AD账号,会影响深信服账号策略同步,发邮件周知
$this-&_notifyModAdAccount(array('new'=&$aAttr,'old'=&$aTmpEntryList[0],'acc1'=&$aAttr[$sAccountField][0],'acc2'=&$aTmpEntryList[0][$ssAccountField][0]));
unset($aAttr[$sAccountField],$aAttr['userPrincipalName'],$aAttr['mail']);
$aAttr = $this-&_getAttrDiff($aAttr,$aTmpEntryList[0]);
if($bUpdate && $aUpdateField){ //脚本批量更新某些字段
$aTmpAttr = array();
foreach($aUpdateField as $k =& $v){
if(isset($aAttr[$v])){
$aTmpAttr[$v] = $aAttr[$v];
$aAttr = $aTmpAttr;
//域管理员信息无权限修改 发邮件周知
if(isset($aTmpEntryList[0]['admincount']) && ($aTmpEntryList[0]['admincount']['count'] & 0)){
$this-&_notifyModAdAdmin(array('attr'=&$aAttr,'dn'=&array($sNewCn,$sOldCn,$sTopOu)));
return $this-&_genRet(0);
$bTmp = true;
if($aAttr){ //更新老节点属性
$bTmp = self::$_oLdap-&updateEntry($sOldCn,$aAttr);
$this-&_log('update','user',array('flag'=&$bTmp,'cn'=&$sOldCn,'attr'=&$aAttr),$newData,$aTmpEntryList[0]);
if(!$bTmp){
return $this-&_genRet(-5,'工号:'.$uid.'---更新节点属性失败---line:'.__LINE__);
if($bUpdate){
return $this-&_genRet(0);
if(strpos($sOldCn,$sNewCn) === false){ //节点DN路径修改了 需要移动节点
$sNewPartDn = "CN={$name}";
if($newData['status'] == 2){//员工离职需要移动到HasLeft节点下 加上工号
$sNewPartDn = "CN={$nameDel}";
$bTmp = self::$_oLdap-&moveEntry($sOldCn,$sNewPartDn,$dn);
$this-&_log('move','user',array('flag'=&$bTmp,'path'=&"{$sOldCn}-&{$sNewPartDn},{$dn}",'dn'=&"{$sNewPartDn},{$dn}"),$newData,$oldData);
return $this-&_genRet($bTmp ? 0 : -6,'工号:'.$uid.'---移动用户节点失败---line:'.__LINE__);
if($bUpdate){
return $this-&_genRet(0);
//走到这一步 只剩下向域服务器添加新节点了
$aAttr = array_merge($this-&_getUserDataForAdd(),$aAttr);
$bTmp = self::$_oLdap-&addEntry($sNewCn,$aAttr);
$this-&_log('add','user',array('flag'=&$bTmp,'cn'=&$sNewCn,'attr'=&$aAttr),$newData,$oldData);
return $this-&_genRet($bTmp ? 0 : -7,'工号:'.$uid.'---更新用户节点失败---line:'.__LINE__);
* @param $aPar = array(
'dn' =& array(new,old,top),
'attr' =& array(),
private function _notifyModAdAdmin(array $aPar = array()){
$sTmpMail = '';
if($aPar['attr']){
$sTmpHead = '&tr&&td&DN&/td&';
$sTmp .= "&tr&&td&{$aPar['dn'][1]},{$aPar['dn'][2]}&/td&";
foreach($aPar['attr'] as $k =& $v){
$sTmpHead .= "&td&{$k}&/td&";
$sTmp .= '&td&'.implode(',',$v).'&/td&';
$sTmpHead .= '&/tr&';
$sTmp .= '&/tr&';
$sTmpMail .= '&h2&更新属性信息&/h2&';
$sTmpMail .= '&table border=1 cellspacing=1&';
$sTmpMail .= $sTmpHead;
$sTmpMail .= $sTmp;
$sTmpMail .= '&/table&';
if($aPar['dn'] && ($aPar['dn'][0] !== $aPar['dn'][1])){
$sTmpMail .= '&h2&移动节点&/h2&';
$sTmpMail .= "&p&从{$aPar['dn'][1]},{$aPar['dn'][2]}移动到{$aPar['dn'][0]},{$aPar['dn'][2]}&/p&";
$title = 'BY后台尝试修改域管理员信息,请关注';
if($sTmpMail){
$this-&_isTestSvr && $this-&_log('def','def',array('f'=&__FUNCTION__.'.html','s'=&$sTmpMail));
Mail::queue('emails.ldap_notify',['data'=&$sTmpMail],function($m)use($title){
$m-&to( self::$_aCfg['ldapMailTo'] )-&subject($title);
* @param $aPar = array(
'dn' =& array(new,old,top),
'attr' =& array(),
private function _notifyModAdAccount(array $aPar = array()){
$sTmpMail = '';
if($aPar['acc1'] && $aPar['acc2']){
$sTmpMail = '&h2&更新账号&/h2&';
$sTmpMail .= "&p&老账号:{$aPar['acc2']} =& 新账号:{$aPar['acc1']}&/p&";
$title = 'BY后台尝试修改员工RTX账号,请关注';
if($sTmpMail){
$this-&_isTestSvr && $this-&_log('def','def',array('f'=&__FUNCTION__.'.html','s'=&$sTmpMail));
Mail::queue('emails.ldap_notify',['data'=&$sTmpMail],function($m)use($title){
$m-&to( self::$_aCfg['ldapMailTo'] )-&subject($title);
private function _getAttrDiff(array $aNewData = array(), array $aOldData = array()){
foreach($aNewData as $k =& $v){
$k = strtolower($k);
if(!isset($aOldData[$k])){
if($v[0] === $aOldData[$k][0]){
unset($aNewData[$k]);
return $aNewData;
* @desc 组装用户节点数据
* @return array
private $_aUserNodeAttr = array(
'givenName' =& '名', //名
'sn' =& '姓', //姓
'sAMAccountName' =& 'RTX', //RTX
'userPrincipalName' =& '登陆名', //登陆名
'description' =& '工号', //工号
'pager' =& '性别', //性别
'title' =& '职位头衔', //职位头衔
'displayName' =& '显示名', //显示名
'name' =& '姓名', //姓名
//'employeeNumber' =& array(), //一卡通号
'mail' =& '邮箱', //邮箱
//'mailNickname' =& array($rtx), //邮箱昵称 启用了邮箱才有这个属性
'department' =& '部门', //部门
'manager' =& '上级', //上级
'userAccountControl' =& '用户账号策略', //用户账号策略 0x0200正常账号 0x0002账号禁用
private function _getUserData(array $aData = array()){
//id,username,cname,sex,groupid,position,mposition,utype,boss,phone,email,status,degree
$rtx = trim($aData['username']);
$name = trim($aData['cname']);
$aNameSplit = $this-&_getFirstLastName($name);
$uid = intval($aData['id']);
$sex = trim($aData['sex']);
$pid = intval($aData['groupid']); //部门ID
$jobId = intval($aData['position']);
$jobId2 = intval($aData['mposition']);
$boss = intval($aData['boss']);
$del = intval($aData['status']);
$sSuffixDn = self::$_oLdap-&getOption('dnSuffix');
$sTmpDc = substr($sSuffixDn,strpos($sSuffixDn,'DC=')+3);
$sTmpDc = str_replace(',DC=','.',$sTmpDc);
$aAttr = array(
'givenName' =& array($aNameSplit[1]), //名
'sn' =& array($aNameSplit[0]), //姓
'sAMAccountName' =& array($rtx), //RTX
'userPrincipalName' =& array("{$r}

我要回帖

更多关于 机械表上弦的正确方法 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信