您的位置:首页 > 其它

DISCUZ 之论坛首页加载过程,FORUM相关

2013-09-05 17:50 381 查看
可能有理解不透彻的地方,欢迎回帖拍砖,会多加改进

1、加载class_core.php可查看全局数据初始化的另外一个笔记

2、功能模块中哦跟你的mod对应了source/forum中指定的文件。

缓存模块根据当前所处的功能模块,加载必需的缓存内容,默认的缓存内容一般会在操作完指定模块之后存放在用二进制的形式序列化后存放在数据库表中

//BBS相关的功能模块

$modarray = array('ajax','announcement','attachment','forumdisplay',   
    'group','image','index','medal','misc','modcp','notice','post','redirect',   
    'relatekw','relatethread','rss','topicadmin','trade','viewthread','tag','collection','guide'
);


//缓存相关的数据模块

$modcachelist = array(   
    'index'     => array('announcements', 'onlinelist', 'forumlinks',   
            'heats', 'historyposts', 'onlinerecord', 'userstats', 'diytemplatenameforum'),   
    'forumdisplay'  => array('smilies', 'announcements_forum', 'globalstick', 'forums',   
            'onlinelist', 'forumstick', 'threadtable_info', 'threadtableids', 'stamps', 'diytemplatenameforum'),   
    'viewthread'    => array('smilies', 'smileytypes', 'forums', 'usergroups',   
            'stamps', 'bbcodes', 'smilies', 'custominfo', 'groupicon', 'stamps',   
            'threadtableids', 'threadtable_info', 'posttable_info', 'diytemplatenameforum'),   
    'redirect'  => array('threadtableids', 'threadtable_info', 'posttable_info'),   
    'post'      => array('bbcodes_display', 'bbcodes', 'smileycodes', 'smilies', 'smileytypes',   
            'domainwhitelist', 'albumcategory'),   
    'space'     => array('fields_required', 'fields_optional', 'custominfo'),   
    'group'     => array('grouptype', 'diytemplatenamegroup'),   
);


//对于不在modarray中的值,视为非法内容,直接替换成index。

//C::app()->var['mod']的值在加载class_core.php时初始化函数_init_input()中已经赋值,起始就是URL中的一个参数值

$mod = !in_array(C::app()->var['mod'], $modarray) ? 'index' : C::app()->var['mod'];


其中C是core的一个子类

C::app()->cachelist = $cachelist;   
C::app()->init();


如果说class_core.php是执行初始化的工作,或者说声明必要的内容,那么这里的C::app()->init()就是把基本上需要的内容都获取到,例如数据库连接,后台设置的内容,用户信息,session信息等等。具体往下看。

public function init() {   
        if(!$this->initated) {   
            $this->_init_db();   
            $this->_init_setting();   
            $this->_init_user();   
            $this->_init_session();   
            $this->_init_mobile();   
            $this->_init_cron();   
            $this->_init_misc();   
        }   
        $this->initated = true;   
}


一样,从各个函数中的名称可以稍微理解每个都会进行些什么相关的内容,这一点在加载class_core.php时就遇到了,core类的构造函数的操作方式就跟这个很是类似。

3、该函数的主要任务就是连接数据库,重点所连接的时候涉及到不同数据库版本对字符集的设置问题

private function _init_db() {   
        if($this->init_db) {   
            $driver = 'db_driver_mysql';   
            if(count(getglobal('config/db/slave'))) {   
                $driver = 'db_driver_mysql_slave';   
            }   
            DB::init($driver, $this->config['db']);   
        }   
}


DB类在加载class_core.php的最后继承了class DB extends discuz_database {}类discuz_database,稍微查看下该文件就可以看出,对于数据库相关的操作如获取数据库配置信息,连接数据库,查询、删除、更新数据库表等都重新封装过。

init_db变量在声明该类的时候就初始化默认为true,而db_driver_mysql中基本上封装了需要用到的数据库操作,而discuz_database就可以当作是一个代理,而正真完成操作的则为在更为底层的db_driver_mysql.

至于db_driver_mysql_slave暂时还没有怎么留意到,后续补充

public static function init($driver, $config) {   
        self::$driver = $driver;   
        self::$db = new $driver;   
        self::$db->set_config($config);   
        self::$db->connect();   
}


如上所说,这里的$driver=db_driver_mysql,其中set_config 就是为了得到数据的配置信息,HOST、名、用户、密码等包括数据库前缀

4、

private function _init_setting() {   
        if($this->init_setting) {   
            if(empty($this->var['setting'])) {   
                $this->cachelist[] = 'setting';   
            }   
  
            if(empty($this->var['style'])) {   
                $this->cachelist[] = 'style_default';   
            }   
  
            if(!isset($this->var['cache']['cronnextrun'])) {   
                $this->cachelist[] = 'cronnextrun';   
            }   
        }   
  
        !empty($this->cachelist) && loadcache($this->cachelist);   
  
        if(!is_array($this->var['setting'])) {   
            $this->var['setting'] = array();   
        }   
  
}


默认情况下,$this->cachelist[]数组中会加入setting,style_default,cronnextrun三个元素,重点在loadcache()函数

对于loadcache()函数,其中重点则在于

$cachedata = C::t('common_syscache')->fetch_all($caches);


文件table_common_syscache.php中的fetch_all函数。在看fetch_all()函数之前,留意下类table_common_syscache的构造函数

public function __construct() {   
  
        $this->_table = 'common_syscache';   
        $this->_pk    = 'cname';   
        $this->_pre_cache_key = '';   
        $this->_isfilecache = getglobal('config/cache/type') == 'file';   
        $this->_allowmem = memory('check');   
  
        parent::__construct();   
    }
这里除了获取些基本信息之外,还会检查系统现在默认使用的缓存方式(在配置文件config_global.php中可以设置,默认使用了SQL的数据库存放方式,另外还有一种文件缓存方式,下面根据默认的说下数据库缓存。)

留心发现还有一句

$this->_allowmem = memory('check');
这个就是PHP中用到的内存缓存的检查工作了,这种类型的缓存方式有redis,memcache,apc,xcache,eaccelerator,wincache,要使用哪一种方式,可以了解下PHP的内存缓存机制,不同的缓存方式对于不同的站点有不一样的效果,跟服务器、访问量都有些关系。

内存缓存是要服务器支持的情况下,在通过配置文件进行配置才可以使用。不同于下面将会继续说道的discuz中用到的文件缓存和数据库缓存,虽然同为缓存,但是使用的方式完全不一样。

接下来看fetch_all函数的内容

$cachenames = is_array($cachenames) ? $cachenames : array($cachenames);
discuz中很多地方都可以发现类似的写法,目的就是为了更直接的处理类似功能的操作,例如dhtmlspecialchars()函数,其实就是封装了PHP中默认的htmlspecialchars()函数,除了根据PHP版本做不同的编码工作,对有可能是数组形式的字符串数组做编码,这样也就为什么上面这句比较常见的原因了。

if($this->_allowmem) {//在构造函数中检查是否支持内存缓存,是的话就将   
            $data = memory('get', $cachenames);   
            $newarray = $data !== false ? array_diff($cachenames, array_keys($data)) : $cachenames;   
            if(empty($newarray)) {   
                return $data;   
            } else {   
                $cachenames = $newarray;   
            }   
        }
在构造函数中检测的结果就在这里用上了,当发现系统支持内存缓存的时候,就根据当前使用的内存缓存方式去系统内存中获取缓存数据。抛开封装相关的类函数不说,直接看source/class/memory/memory_driver_memcache.php当前目录的这些脚本,就是根据不同的内存缓存方式执行不同的操作,基本就涵盖了设置set,get,delete等操作。

if($this->_isfilecache) {   
            $lostcaches = array();   
            foreach($cachenames as $cachename) {   
                if(!@include_once(DISCUZ_ROOT.'./data/cache/cache_'.$cachename.'.php')) {   
                    $lostcaches[] = $cachename;   
                } elseif($this->_allowmem) {   
                    memory('set', $cachename, $data[$cachename]);   
                }   
            }   
            if(!$lostcaches) {   
                return $data;   
            }   
            $cachenames = $lostcaches;   
            unset($lostcaches);   
        }
若使用的是文件缓存方式,拿理所当然的所去缓存文件中获取数据了。缓存的数据根据缓存名称放在了data/cache目录下。留心发现还能找到在支持内存缓存的时候,代码会有个判定并加入缓存的操作。

memory('set', $cachename, $data[$cachename]);


$query = DB::query('SELECT * FROM '.DB::table($this->_table).' WHERE '.DB::field('cname', $cachenames));   
        while($syscache = DB::fetch($query)) {   
            $data[$syscache['cname']] = $syscache['ctype'] ? unserialize($syscache['data']) : $syscache['data'];   
            $this->_allowmem && (memory('set', $syscache['cname'], $data[$syscache['cname']]));   
            if($this->_isfilecache) {   
                $cachedata = '$data[\''.$syscache['cname'].'\'] = '.var_export($data[$syscache['cname']], true).";\n\n";   
                if(($fp = @fopen(DISCUZ_ROOT.'./data/cache/cache_'.$syscache['cname'].'.php', 'wb'))) {   
                    fwrite($fp, "<?php\n//Discuz! cache file, DO NOT modify me!\n//Identify: ".md5($syscache['cname'].$cachedata.getglobal('config/security/authkey'))."\n\n$cachedata?>");   
                    fclose($fp);   
                }   
            }   
        }
这段就是默认的是使用了数据库缓存所得到的缓存数据了,缓存数据被存放在表pre_common_syscache表中。一般情况下都是将常用的、不变的数据都会放在里面,例如后台的设置信息基本上都会有,存放的方式是有对应关系的数组以二进制的形式存放。

通过以上内存缓存、文件缓存、和数据库缓存,这块的缓存工作就基本上结束了,当然这里指的是读取缓存的工作,至于存储缓存的工作请看如下function_core.php文件中的savecache()函数:

function savecache($cachename, $data) {   
    C::t('common_syscache')->insert($cachename, $data);   
}


使用到了同个类table_common_syscache中的insert函数

public function insert($cachename, $data) {   
  
        parent::insert(array(   
            'cname' => $cachename,   
            'ctype' => is_array($data) ? 1 : 0,   
            'dateline' => TIMESTAMP,   
            'data' => is_array($data) ? serialize($data) : $data,   
        ), false, true);   
  
        if($this->_allowmem && memory('get', $cachename) !== false) {   
            memory('set', $cachename, $data);   
        }   
        $this->_isfilecache && @unlink(DISCUZ_ROOT.'./data/cache/cache_'.$cachename.'.php');   
    }
函数理所当然的包括了数据库存储,内存缓存设置,将旧的缓存文件删除。

$cachedata = C::t('common_syscache')->fetch_all($caches);   
        foreach($cachedata as $cname => $data) {   
            if($cname == 'setting') {   
                $_G['setting'] = $data;   
            } elseif($cname == 'usergroup_'.$_G['groupid']) {   
                $_G['cache'][$cname] = $_G['group'] = $data;   
            } elseif($cname == 'style_default') {   
                $_G['cache'][$cname] = $_G['style'] = $data;   
            } elseif($cname == 'grouplevels') {   
                $_G['grouplevels'] = $data;   
            } else {   
                $_G['cache'][$cname] = $data;   
            }   
        }
缓存获取的操作做完之后,DISCUZ系统习惯性的将这些数据存放进了$_G这个全局变量中了。

5、函数_init_user(),从名字可以大概知道其主要执行的是用户相关的操作。

if($auth = getglobal('auth', 'cookie')) {   
                $auth = daddslashes(explode("\t", authcode($auth, 'DECODE')));   
            }   
            list($discuz_pw, $discuz_uid) = empty($auth) || count($auth) < 2 ? array('', '') : $auth;   
  
            if($discuz_uid) {   
                $user = getuserbyuid($discuz_uid, 1);   
}
这一步是为了识别、并通过函数getuserbyuid()获取用户信息的,从名为auth的cookie中获取内容,同时通过具备加密解密功能的authcode()函数,对cookie的字符串auth进行解密得到用户的uid和密码。仔细观察客户端中的cookie值,auth是包含前缀名的,没错,往前面看的话别忘了class_core类中的init_input函数,之前讲过,对于discuz的cookie都会默认加上前缀,在获取到客户端数据的时候,就已经在init_input函数中去掉了前缀,这样得到的名称就更有识别性。

if(!empty($user) && $user['password'] == $discuz_pw) {   
                if(isset($user['_inarchive'])) {   
                    C::t('common_member_archive')->move_to_master($discuz_uid);   
                }   
                $this->var['member'] = $user;   
            } else {   
                $user = array();   
                $this->_init_guest();   
            }   
  
            if($user && $user['groupexpiry'] > 0 && $user['groupexpiry'] < TIMESTAMP && (getgpc('mod') != 'spacecp' || CURSCRIPT != 'home')) {   
                dheader('location: home.php?mod=spacecp&ac=usergroup&do=expiry');   
            }   
  
            $this->cachelist[] = 'usergroup_'.$this->var['member']['groupid'];   
            if($user && $user['adminid'] > 0 && $user['groupid'] != $user['adminid']) {   
                $this->cachelist[] = 'admingroup_'.$this->var['member']['adminid'];   
 }
if(empty($this->var['cookie']['lastvisit'])) {   
            $this->var['member']['lastvisit'] = TIMESTAMP - 3600;   
            dsetcookie('lastvisit', TIMESTAMP - 3600, 86400 * 30);   
        } else {   
            $this->var['member']['lastvisit'] = $this->var['cookie']['lastvisit'];   
        }   
  
        setglobal('uid', getglobal('uid', 'member'));   
        setglobal('username', getglobal('username', 'member'));   
        setglobal('adminid', getglobal('adminid', 'member'));   
        setglobal('groupid', getglobal('groupid', 'member'));   
  
        !empty($this->cachelist) && loadcache($this->cachelist);
剩下的这些基本上都是为了重新设置用户信息相关的,而这些信息主要为了设置几个在全局变量$_G中的一级元素uid,username,adminid,groupid,和member下的二级元素的值。

同时根据当前用户是否为游客则执行游客信息的初始化工作_init_guest(),其实这个过程也就是将几个默认的数据放入$_G['member']中;

如果是已登录用户,首先判断用户所在用户组groupexpiry是否已经过期,是的话则跳转到个人设置的用户组页面;

得到用户所在用户组关系后,在以usergroup为前缀的缓存数据中获取当前用户组的缓存信息,获取工作就交给接下来会执行的loadcache函数了。

6、函数init_session(),操作些session相关的东西,仔细研究DISCUZ的SESSION机制,会发现其跟PHP自带的SESSION机制所不一样的,这里所谓的SESSION基本上是DISCUZ自己重新定制的机制,至于为什么要独立使用自己的sessin机制而抛弃PHP自带的,具体原因就不是很清楚,但是查看DISCUZ安装后得到的数据库表结构,可以发现pre_common_session使用的数据库引擎方式居然是有别于其他myisam的memory,也就是所谓的内存表,该类型的表数据是放在内存中的,默认使用了hash索引,访问起来会非常快,这或许就是使用自己的一套机制的原因吧。

private function _init_session() {   
  
        $sessionclose = !empty($this->var['setting']['sessionclose']);   
        $this->session = $sessionclose ? new discuz_session_close() : new discuz_session();   
  
        if($this->init_session)  {   
            $this->session->init($this->var['cookie']['sid'], $this->var['clientip'], $this->var['uid']);   
            $this->var['sid'] = $this->session->sid;   
            $this->var['session'] = $this->session->var;   
  
            if(!empty($this->var['sid']) && $this->var['sid'] != $this->var['cookie']['sid']) {   
                dsetcookie('sid', $this->var['sid'], 86400);   
            }   
  
            if($this->session->isnew) {   
                if(ipbanned($this->var['clientip'])) {   
                    $this->session->set('groupid', 6);   
                }   
            }   
  
            if($this->session->get('groupid') == 6) {   
                $this->var['member']['groupid'] = 6;   
                sysmessage('user_banned');   
            }   
  
            if($this->var['uid'] && !$sessionclose && ($this->session->isnew || ($this->session->get('lastactivity') + 600) < TIMESTAMP)) {   
                $this->session->set('lastactivity', TIMESTAMP);   
                if($this->session->isnew) {   
                    C::t('common_member_status')->update($this->var['uid'], array('lastip' => $this->var['clientip'], 'lastvisit' => TIMESTAMP));   
                }   
            }   
  
        }   
    }


接下来说的是当$sessionclose==false的情况下的内容。关键操作也在类discuz_session中,如上代码中第六行。

public function init($sid, $ip, $uid) {   
        $this->old = array('sid' =>  $sid, 'ip' =>  $ip, 'uid' =>  $uid);   
        $session = array();   
        if($sid) {   
            $session = $this->table->fetch($sid, $ip, $uid);   
        }   
  
        if(empty($session) || $session['uid'] != $uid) {   
            $session = $this->create($ip, $uid);   
        }   
  
        $this->var = $session;   
        $this->sid = $session['sid'];   
    }
会发现cookie在discuz中发挥的重要作用了吧,sid就是保存在客户端中的该用户当前浏览器对应的服务器中的session的ID,除了session的id,这里所需要的参数还包括了用户的UID,客户端IP,大部分的重要信息都需要经过cookie的合作才能够顺利的进行,这里就出现疑问,在客户端禁止cookie的情况下discuz的运行又会是怎样的?后续需要了解。

下面的是table_common_session.php中的fetch函数

public function fetch($sid, $ip = false, $uid = false) {   
        if(empty($sid)) {   
            return array();   
        }   
        $this->checkpk();   
        $session = parent::fetch($sid);   
        if($session && $ip !== false && $ip != "{$session['ip1']}.{$session['ip2']}.{$session['ip3']}.{$session['ip4']}") {   
            $session = array();   
        }   
        if($session && $uid !== false && $uid != $session['uid']) {   
            $session = array();   
        }   
        return $session;   
    }


会调用其父类discuz_table中的fetch()函数

public function fetch($id, $force_from_db = false){   
        $data = array();   
        if(!empty($id)) {   
            if($force_from_db || ($data = $this->fetch_cache($id)) === false) {   
                $data = DB::fetch_first('SELECT * FROM '.DB::table($this->_table).' WHERE '.DB::field($this->_pk, $id));   
                if(!empty($data)) $this->store_cache($id, $data);   
            }   
        }   
        return $data;   
    }


数据库表类的父类discuz_table的两个基本函数fetch_cache()和store_cache()在这里的作用就是,在获取session的过程中,如果在内存缓存中能够得到session的内容,就会跳过在数据库表格common_session中查找内容,如果没有的话就进行查询,这里调试的结果是在没有支持内存缓存的环境下,会到数据库表格中进行查找并获取数据,获取到的数据会尝试将其保存在内存缓存中,也就是store_cache()的工作了。

执行完毕回到刚才类table_common_session中:

其一会在取得session内容的情况下,判断客户端IP跟获取到的session中的ip是否相同,不同的话就将session置空,因为当前用户所使用的客户端IP已经修改。

其二也会取得session内容的情况下,判断客户端相同ip,但是session中的uid已经不同,也就是客户端浏览器使用了另外一个用户登录,此时对应的session也需要重置。

到这里session的获取工作就基本上完成,而这里的获取session也就是获取了用户相关的客户端信息,如下面调试的数据:

array(14) (   
  [sid] => (string) cnksCz   
  [ip1] => (string) 127   
  [ip2] => (string) 0   
  [ip3] => (string) 0   
  [ip4] => (string) 1   
  [uid] => (string) 1   
  [username] => (string) admin   
  [groupid] => (string) 1   
  [invisible] => (string) 0   
  [action] => (string) 2   
  [lastactivity] => (string) 1378193039   
  [lastolupdate] => (string) 1378193039   
  [fid] => (string) 0   
  [tid] => (string) 0   
)
上面说到了可能获取的session为空的情况,往回看在init()函数中会有个create()函数,其实就是重新生成一个新的session数组,其中sid的值是通过random(6)随机生成的,其他都是根据当前情况赋予内容。再将相关值赋值到discuz_session对象的变量中,供init_session()函数完成接下来的工作。

剩下来的会判断当前的sid跟客户端的sid是否相同,否的话就重新设置dsetcookie()客户端的sid的cookie值;对于重新create出来的session,会判断用户访问的ip是否在被禁止访问的ip名单中,如果是的话会将用户置为groupid为6即“用户IP被禁止”的系统用户组中,并抛出提醒;而没有被禁止的用户可以继续执行后面的内容,后面的也就剩下当当前用户所在登录状态下,且超过了600秒的有效期之后,同时也就是现在也处于活动状态,就重新更新用户在session的最后活动时间以及用户状态表common_member_status中的状态信息。


7、函数_init_mobile()

判断在后台是否允许使用手机页面的访问,是的话就初始化各种跟手机页面访问所需要的数据,否则就return false,没有什么特别的操作,其中有个很有用的函数,在其他项目中可以考虑使用,就是检测当前客户端使用的是否为手机客户端。

function checkmobile() {   
    global $_G;   
    $mobile = array();   
    static $mobilebrowser_list =array('iphone', 'android', 'phone', 'mobile', 'wap', 'netfront', 'java', 'opera mobi', 'opera mini',   
                'ucweb', 'windows ce', 'symbian', 'series', 'webos', 'sony', 'blackberry', 'dopod', 'nokia', 'samsung',   
                'palmsource', 'xda', 'pieplus', 'meizu', 'midp', 'cldc', 'motorola', 'foma', 'docomo', 'up.browser',   
                'up.link', 'blazer', 'helio', 'hosin', 'huawei', 'novarra', 'coolpad', 'webos', 'techfaith', 'palmsource',   
                'alcatel', 'amoi', 'ktouch', 'nexian', 'ericsson', 'philips', 'sagem', 'wellcom', 'bunjalloo', 'maui', 'smartphone',   
                'iemobile', 'spice', 'bird', 'zte-', 'longcos', 'pantech', 'gionee', 'portalmmm', 'jig browser', 'hiptop',   
                'benq', 'haier', '^lct', '320x320', '240x320', '176x220');   
    $pad_list = array('pad', 'gt-p1000');   
  
    $useragent = strtolower($_SERVER['HTTP_USER_AGENT']);   
  
    if(dstrpos($useragent, $pad_list)) {   
        return false;   
    }   
    if(($v = dstrpos($useragent, $mobilebrowser_list, true))) {   
        $_G['mobile'] = $v;   
        return true;   
    }   
    $brower = array('mozilla', 'chrome', 'safari', 'opera', 'm3gate', 'winwap', 'openwave', 'myop');   
    if(dstrpos($useragent, $brower)) return false;   
  
    $_G['mobile'] = 'unknown';   
    if($_GET['mobile'] === 'yes') {   
        return true;   
    } else {   
        return false;   
    }   
}
函数_init_cron()执行的是discuz系统中的计划任务; 函数_init_misc()执行的是discuz系统除了上述之外的其他杂七杂八的初始化工作,无特殊之处;而前面的计划任务或许可以深究。

到这里论坛的初始化工作就基本上完成了,至于每个地方有什么用,都会很详尽地将需要的数据提供出来,从这个过程也可以看出DISCUZ的人在这么多年的积累还是做到非常之不错的,其中也有很多值得我们去学习的。

例如在使用某个变量的时候,可以发现它都会在该类的最前端初始化该变量;在服务端不需要使用到的数据,DISCUZ会直接过滤掉而不允许出现任何可能造成不安全的影响;其整个discuz的框架也分的很清楚,虽然这里没有说到,多了解下可以看到整个系统的执行过程循序渐进、各个功能块都分得很清楚,这里说到的数据初始化、缓存的定制、插件的自定义工作以及模板上所带有的MVC结构等。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: