谁有比较完整的Discuz7源码分析(目录、文件结构、各文件的功能、模板结构、较完整的代码注释等),越全越好,谢谢?

解决方案 »

  1.   


    <?php
    /*
    [Discuz!] (C)2001-2009 Comsenz Inc.
    This is NOT a freeware, use is subject to license terms
    $Id: common.inc.php 17460 2008-12-24 01:46:38Z monkey $
    */
    //关闭magic_quotes_gpc  引用 ,设置关闭PHP错误报告
    error_reporting(0);
    set_magic_quotes_runtime(0);
    //获取脚本开始的时间,最后有脚本结束时间减去这个就会得到整个脚本运行的时间
    $mtime = explode(' ', microtime());
    $discuz_starttime = $mtime[1] + $mtime[0];//设置一些常量
    //SYSY_DEBUG是当前是否是调试状态
    //IN_DISCUZ是在一些不允许直接浏览的页面用的,如果直接浏览这些页面,就会退出并出现Access Denied
    //DISCUZ_ROOT是获取当前的论坛的主目录的绝对路径
    //MAGIC_QUOTES_GPC 是 当前的magic_quotes_gpc的状态
    //CURSCRIPT是当前运行的脚本的名称吧...现在设置为空...在其他脚本中会有值....
    define('SYS_DEBUG', FALSE);
    define('IN_DISCUZ', TRUE);
    define('DISCUZ_ROOT', substr(dirname(__FILE__), 0, -7));
    define('MAGIC_QUOTES_GPC', get_magic_quotes_gpc());
    !defined('CURSCRIPT') && define('CURSCRIPT', '');
    //为了兼容性
    if(PHP_VERSION < '4.1.0') {
    $_GET = &$HTTP_GET_VARS;
    $_POST = &$HTTP_POST_VARS;
    $_COOKIE = &$HTTP_COOKIE_VARS;
    $_SERVER = &$HTTP_SERVER_VARS;
    $_ENV = &$HTTP_ENV_VARS;
    $_FILES = &$HTTP_POST_FILES;
    }
    //为了安全性...因为脚本运行到这里之前这两个global数组是不会被定义的...
    if (isset($_REQUEST['GLOBALS']) OR isset($_FILES['GLOBALS'])) {
    exit('Request tainting attempted.');
    }
    //包含论坛的函数库...这个函数库里面有几乎整个论坛需要用到的函数
    require_once DISCUZ_ROOT.'./include/global.func.php';
    //测试下浏览者是什么玩意...是不是ROBOT是的话就退出显示403....
    getrobot();
    if(defined('NOROBOT') && IS_ROBOT) {
    exit(header("HTTP/1.1 403 Forbidden"));
    }
    //获取$_COOKIE  等等的值然后 设置里面的key为一个变量,值为其对应的值并添加引用....
    //比如说有$_COOKIE['discuz_auth']  = '123456' 就设置$discuz_auth = '123456'
    //以此类推
    foreach(array('_COOKIE', '_POST', '_GET') as $_request) {
    foreach($$_request as $_key => $_value) {
      $_key{0} != '_' && $$_key = daddslashes($_value);
    }
    }
    //过滤$_FILES,也就是添加引用
    if (!MAGIC_QUOTES_GPC && $_FILES) {
    $_FILES = daddslashes($_FILES);
    }
    //初始化一些变量
    $charset = $dbs = $dbcharset = $forumfounders = $metakeywords = $extrahead = $seodescription = $mnid = '';
    $plugins = $hooks = $admincp = $jsmenu = $forum = $thread = $language = $actioncode = $modactioncode = $lang = array();
    $_DCOOKIE = $_DSESSION = $_DCACHE = $_DPLUGIN = $advlist = array();//包含论坛的配置文件
    require_once DISCUZ_ROOT.'./config.inc.php';
    //$urlxssdefend是论坛访问页面防御开关,可避免用户通过非法的url地址对本站用户造成危害
    if($urlxssdefend && !empty($_SERVER['REQUEST_URI'])) {
    $temp = urldecode($_SERVER['REQUEST_URI']);
    if(strpos($temp, '<') !== false || strpos($temp, '"') !== false)
      exit('Request Bad url');
    }
    //$prelength为设置的cookie的前缀的长度
    //循环检查$_COOKIE里面带设置的前缀的变量,如果带前缀就填入到$_DCOOKIE里面并且不带前缀
    //最后 过滤值
    $prelength = strlen($cookiepre);
    foreach($_COOKIE as $key => $val) {
    if(substr($key, 0, $prelength) == $cookiepre) {
      $_DCOOKIE[(substr($key, $prelength))] = MAGIC_QUOTES_GPC ? $val : daddslashes($val);
    }
    }
    //销毁这些变量,都是对$_类数组操作用到的一些变量
    unset($prelength, $_request, $_key, $_value);
    //
    $inajax = !empty($inajax);
    $handlekey = !empty($handlekey) ? htmlspecialchars($handlekey) : '';
    $timestamp = time();//$attackevasive 论坛防御级别,可防止大量的非正常请求造成的拒绝服务攻击
    if($attackevasive && CURSCRIPT != 'seccode') {
    require_once DISCUZ_ROOT.'./include/security.inc.php';
    }
    //包含数据库类 的文件
    require_once DISCUZ_ROOT.'./include/db_'.$database.'.class.php';//$PHP_SELF为当前活动的脚本相对于网站主目录的路径
    //$BASESCRIPT为当前活动的脚本文件名字带扩展名
    //$BASEFILENAME为当前活动的脚本文件的名字不带扩展名
    //$boardurl为当前活动脚本的全网站路径去掉后面文件名,如果有api|archiver|wap文件夹就去掉...
    $PHP_SELF = dhtmlspecialchars($_SERVER['PHP_SELF'] ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME']);
    $BASESCRIPT = basename($PHP_SELF);
    list($BASEFILENAME) = explode('.', $BASESCRIPT);
    $boardurl = htmlspecialchars('http://'.$_SERVER['HTTP_HOST'].preg_replace("/\/+(api|archiver|wap)?\/*$/i", '', substr($PHP_SELF, 0, strrpos($PHP_SELF, '/'))).'/');
    //获得当前浏览者IP
    if(getenv('HTTP_CLIENT_IP') && strcasecmp(getenv('HTTP_CLIENT_IP'), 'unknown')) {
    $onlineip = getenv('HTTP_CLIENT_IP');
    } elseif(getenv('HTTP_X_FORWARDED_FOR') && strcasecmp(getenv('HTTP_X_FORWARDED_FOR'), 'unknown')) {
    $onlineip = getenv('HTTP_X_FORWARDED_FOR');
    } elseif(getenv('REMOTE_ADDR') && strcasecmp(getenv('REMOTE_ADDR'), 'unknown')) {
    $onlineip = getenv('REMOTE_ADDR');
    } elseif(isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], 'unknown')) {
    $onlineip = $_SERVER['REMOTE_ADDR'];
    }
    preg_match("/[\d\.]{7,15}/", $onlineip, $onlineipmatches);
    $onlineip = $onlineipmatches[0] ? $onlineipmatches[0] : 'unknown';
    unset($onlineipmatches);//include     settings的缓存 并且将里面的数组给extract了不懂的看这个函数解释...我不罗嗦了...
    $cachelost = (@include DISCUZ_ROOT.'./forumdata/cache/cache_settings.php') ? '' : 'settings';
    @extract($_DCACHE['settings']);//如果开启了GZIP压缩并且服务器有这个功能
    //并且当前脚本不是wap和attachment
    //并且inajax为FLASE
    //就ob_start('ob_gzhandler')否则就ob_start();
    if($gzipcompress && function_exists('ob_gzhandler') && !in_array(CURSCRIPT, array('attachment', 'wap')) && !$inajax) {
    ob_start('ob_gzhandler');
    } else {
    $gzipcompress = 0;
    ob_start();
    }
    //平衡负载用的,$loadctrl我不知道在哪里..汗一个
    if(!empty($loadctrl) && substr(PHP_OS, 0, 3) != 'WIN') {
    if($fp = @fopen('/proc/loadavg', 'r')) {
      list($loadaverage) = explode(' ', fread($fp, 6));
      fclose($fp);
      if($loadaverage > $loadctrl) {
       header("HTTP/1.0 503 Service Unavailable");
       include DISCUZ_ROOT.'./include/serverbusy.htm';
       exit();
      }
    }
    }
    //包含其他的缓存文件
    if(in_array(CURSCRIPT, array('index', 'forumdisplay', 'viewthread', 'post', 'topicadmin', 'register', 'archiver'))) {
    $cachelost .= (@include DISCUZ_ROOT.'./forumdata/cache/cache_'.CURSCRIPT.'.php') ? '' : ' '.CURSCRIPT;
    }
    //连接数据库,完毕之后设置这些值为NULL
    $db = new dbstuff;
    $db->connect($dbhost, $dbuser, $dbpw, $dbname, $pconnect, true, $dbcharset);
    $dbuser = $dbpw = $pconnect = $sdb = NULL;
    //乱七八糟的 ,反正就是找到了需要的sid并过滤了就是了 $transsidstatus我也没找到在哪
    //看看是不是后台设置了通过sid传输的那个东东,还有是不是通过wap访问的,
    //还有是不是有sid这个东东在$_GET或$_POST这两个的任何一个中,
    //以上结论都成立的话从GET中获得sid,不成立的话从$_DCOOKIE中获得。
    $sid = daddslashes(($transsidstatus || CURSCRIPT == 'wap') && (isset($_GET['sid']) || isset($_POST['sid'])) ?
    (isset($_GET['sid']) ? $_GET['sid'] : $_POST['sid']) :
    (isset($_DCOOKIE['sid']) ? $_DCOOKIE['sid'] : ''));
    //如果当前脚本是attachment         sid是通过GET方式获得就加密然后过滤它
    CURSCRIPT == 'attachment' && isset($_GET['sid']) && $sid = addslashes(authcode($_GET['sid'], 'DECODE', $_DCACHE['settings']['authkey']));
    //设置一个$discuz_auth_key,md5加密。。
    $discuz_auth_key = md5($_DCACHE['settings']['authkey'].$_SERVER['HTTP_USER_AGENT']);
    //获得$discuz_pw, $discuz_secques, $discuz_uid这三个变量,分别对应密码,提示问题和uid。
    //强制过滤了这3个值
    list($discuz_pw, $discuz_secques, $discuz_uid) = empty($_DCOOKIE['auth']) ? array('', '', 0) : daddslashes(explode("\t", authcode($_DCOOKIE['auth'], 'DECODE')), 1);//第一行是初始化变量用的(无论何时用变量都要考虑初始化,要不然安全性不值得一提)
    //接下来是判断是不是有sid,有的话就从cdb_session表中取来,然后连接一下cdb_members表取出东西
    //在$membertablefields这个变量里面已经全面写出来了
    //标记了一个sessionexist变量,表示这个会员是在线的。
    $prompt = $sessionexists = $seccode = 0;
    $membertablefields = 'm.uid AS discuz_uid, m.username AS discuz_user, m.password AS discuz_pw, m.secques AS discuz_secques,
    m.adminid, m.groupid, m.groupexpiry, m.extgroupids, m.email, m.timeoffset, m.tpp, m.ppp, m.posts, m.digestposts,
    m.oltime, m.pageviews, m.credits, m.extcredits1, m.extcredits2, m.extcredits3, m.extcredits4, m.extcredits5,
    m.extcredits6, m.extcredits7, m.extcredits8, m.timeformat, m.dateformat, m.pmsound, m.sigstatus, m.invisible,
    m.lastvisit, m.lastactivity, m.lastpost, m.prompt, m.accessmasks, m.editormode, m.customshow, m.customaddfeed';
    if($sid) {
    if($discuz_uid) {
      $query = $db->query("SELECT s.sid, s.styleid, s.groupid='6' AS ipbanned, s.pageviews AS spageviews, s.lastolupdate, s.seccode, $membertablefields
       FROM {$tablepre}sessions s, {$tablepre}members m
       WHERE m.uid=s.uid AND s.sid='$sid' AND CONCAT_WS('.',s.ip1,s.ip2,s.ip3,s.ip4)='$onlineip' AND m.uid='$discuz_uid'
       AND m.password='$discuz_pw' AND m.secques='$discuz_secques'");
    } else {
      $query = $db->query("SELECT sid, uid AS sessionuid, groupid, groupid='6' AS ipbanned, pageviews AS spageviews, styleid, lastolupdate, seccode
       FROM {$tablepre}sessions WHERE sid='$sid' AND CONCAT_WS('.',ip1,ip2,ip3,ip4)='$onlineip'");
    }
    if($_DSESSION = $db->fetch_array($query)) {
      $sessionexists = 1;
      if(!empty($_DSESSION['sessionuid'])) {
       $_DSESSION = array_merge($_DSESSION, $db->fetch_first("SELECT $membertablefields
        FROM {$tablepre}members m WHERE uid='$_DSESSION[sessionuid]'"));
      }
    } else {
      if($_DSESSION = $db->fetch_first("SELECT sid, groupid, groupid='6' AS ipbanned, pageviews AS spageviews, styleid, lastolupdate, seccode
       FROM {$tablepre}sessions WHERE sid='$sid' AND CONCAT_WS('.',ip1,ip2,ip3,ip4)='$onlineip'")) {
       clearcookies();
       $sessionexists = 1;
      }
    }
    }
    //如果不在线执行
    //如果COOKIE不正确就清除
    //如果IP是被办的  就被办的(标记了一下)
    //写入一个随机值写入到SID  SECCODE
    if(!$sessionexists) {
    if($discuz_uid) {
      if(!($_DSESSION = $db->fetch_first("SELECT $membertablefields, m.styleid
       FROM {$tablepre}members m WHERE m.uid='$discuz_uid' AND m.password='$discuz_pw' AND m.secques='$discuz_secques'"))) {
       clearcookies();
      }
    }
      

  2.   


    if(ipbanned($onlineip)) $_DSESSION['ipbanned'] = 1;
    $_DSESSION['sid'] = random(6);
    $_DSESSION['seccode'] = random(6, 1);
    }
    //获取时间格式,日期格式,还有时差的这些玩意
    $_DSESSION['dateformat'] = empty($_DSESSION['dateformat']) || empty($_DCACHE['settings']['userdateformat'][$_DSESSION['dateformat'] -1])? $_DCACHE['settings']['dateformat'] : $_DCACHE['settings']['userdateformat'][$_DSESSION['dateformat'] -1];
    $_DSESSION['timeformat'] = empty($_DSESSION['timeformat']) ? $_DCACHE['settings']['timeformat'] : ($_DSESSION['timeformat'] == 1 ? 'h:i A' : 'H:i');
    $_DSESSION['timeoffset'] = isset($_DSESSION['timeoffset']) && $_DSESSION['timeoffset'] != 9999 ? $_DSESSION['timeoffset'] : $_DCACHE['settings']['timeoffset'];
    //$membertablefields这玩意不需要了 给''掉了  $_DSESSION给extract掉了
    $membertablefields = '';
    @extract($_DSESSION);//这个玩意我不懂
    $newpm = $prompt & 1;
    $doingtask = $prompt & 2 ? 1 : 0;//判断是不是有上次访问的时间,有的话就没事,没有的话就减去24小时。
    //接下来给一个现在的时间,这里有一个时间的问题,所以把时间加上时差乘上3600秒就得到当前时间了。
    //PHP 5 能处理时差了,所以Discuz在这里也设置了一下,想得真全面!!
    $lastvisit = empty($lastvisit) ? $timestamp - 86400 : $lastvisit;
    $timenow = array('time' => gmdate("$dateformat $timeformat", $timestamp + 3600 * $timeoffset),
    'offset' => ($timeoffset >= 0 ? ($timeoffset == 0 ? '' : '+'.$timeoffset) : $timeoffset));
    if(PHP_VERSION > '5.1') {
    @date_default_timezone_set('Etc/GMT'.($timeoffset > 0 ? '-' : '+').(abs($timeoffset)));
    }//暂时不知道是什么变量
    $accessadd1 = $accessadd2 = $modadd1 = $modadd2 = $metadescription = '';
    //如果没有uid和user
    //设置memeber表里面的值为空
    //groupid一般设置为7 应该是游客  如果是6的话 应该就是被办的...
    //下面的userss不知道是做什么得到  没有过滤
    //adminid,accessmasks是memeber里面的
    //前者应该是管理权限,后者应该是进入权限
    if(empty($discuz_uid) || empty($discuz_user)) {
    $discuz_user = $extgroupids = '';
    $discuz_uid = $adminid = $posts = $digestposts = $pageviews = $oltime = $invisible
      = $credits = $extcredits1 = $extcredits2 = $extcredits3 = $extcredits4
      = $extcredits5 = $extcredits6 = $extcredits7 = $extcredits8 = 0;
    $groupid = empty($groupid) || $groupid != 6 ? 7 : 6;
    } else {
    $discuz_userss = $discuz_user;
    $discuz_user = addslashes($discuz_user);
    if($accessmasks) {
      $accessadd1 = ', a.allowview, a.allowpost, a.allowreply, a.allowgetattach, a.allowpostattach';
      $accessadd2 = "LEFT JOIN {$tablepre}access a ON a.uid='$discuz_uid' AND a.fid=f.fid";
    }
    if($adminid == 3) {
      $modadd1 = ', m.uid AS ismoderator';
      $modadd2 = "LEFT JOIN {$tablepre}moderators m ON m.uid='$discuz_uid' AND m.fid=f.fid";
    }
    }
    //这个变量我没找到不知道在哪,反正如果是有管理权限就开启错误报告
    if($errorreport == 2 || ($errorreport == 1 && $adminid > 0)) {
    error_reporting(E_ERROR | E_WARNING | E_PARSE);
    }
    //定义FORMHASH常量 根据用户信息获得的一个哈希值
    define('FORMHASH', formhash());
    //cache_settings里面的一个变量,如果开启统计而且页面不是ajax所需的就包含统计页面
    $statstatus && !$inajax && require_once DISCUZ_ROOT.'./include/counter.inc.php';//这个变量没找到
    $extra = isset($extra) && @preg_match("/^[&=;a-z0-9]+$/i", $extra) ? $extra : '';//初始化一些变量,第一个和RSS有关后面的和导航有关
    $rsshead = $navtitle = $navigation = '';
    //设置groupid
    //如果不能包含此groupid的缓存就检查是否有这个groupid,有的话就记录下面重新缓存
    //如果没有的话这个TYPE就是member
    $_DSESSION['groupid'] = $groupid = empty($ipbanned) ? (empty($groupid) ? 7 : intval($groupid)) : 6;
    if([email=!@include]!@include[/email] DISCUZ_ROOT.'./forumdata/cache/usergroup_'.$groupid.'.php') {
    $grouptype = $db->result_first("SELECT type FROM {$tablepre}usergroups WHERE groupid='$groupid'");
    if(!empty($grouptype)) {
      $cachelost .= ' usergroup_'.$groupid;
    } else {
      $grouptype = 'member';
    }
    }
    /*
    $link_login = 'logging.php?action=login';
    $link_logout = 'logging.php?action=logout&formhash='.FORMHASH;
    $link_register = $regname;
    */
    //$groupexpiry是用户组是否到期信息
    //如果已经登录这个变量不为空且这个变量小于现在的时间且当前的页面不是wap也不是member
    //就过期了就转入到过期页面
    //如果没过期,且当前的groupid和当前用户信息不符
    //就包含当前的groupid的缓存
    //可能是在更改了一个用户的用户组的时候用到
    if($discuz_uid && $_DSESSION) {
    if(!empty($groupexpiry) && $groupexpiry < $timestamp && !in_array(CURSCRIPT, array('wap', 'member'))) {
      dheader("Location: {$boardurl}member.php?action=groupexpiry");
    } elseif($grouptype && $groupid != getgroupid($discuz_uid, array
      (
      'type' => $grouptype,
      'creditshigher' => $groupcreditshigher,
      'creditslower' => $groupcreditslower
      ), $_DSESSION)) {
      @extract($_DSESSION);
      $cachelost .= (@include DISCUZ_ROOT.'./forumdata/cache/usergroup_'.intval($groupid).'.php') ? '' : ' usergroup_'.$groupid;
    }
    }
    //tpp是每页显示的帖子数,ppp是每页显示的回复数
    $tpp = intval(empty($_DSESSION['tpp']) ? $topicperpage : $_DSESSION['tpp']);
    $ppp = intval(empty($_DSESSION['ppp']) ? $postperpage : $_DSESSION['ppp']);//检查你的权限,如果你的管理权限不再 1 2 3里面你什么权限都没
    //如果你的权限在里面就赋予你权限咯 我不知道$radminid是做什么的
    if(!in_array($adminid, array(1, 2, 3))) {
    $alloweditpost = $alloweditpoll = $allowstickthread = $allowmodpost = $allowdelpost = $allowmassprune
      = $allowrefund = $allowcensorword = $allowviewip = $allowbanip = $allowedituser = $allowmoduser
      = $allowbanuser = $allowpostannounce = $allowviewlog = $disablepostctrl = 0;
    } elseif(isset($radminid) && $adminid != $radminid && $adminid != $groupid) {
    $cachelost .= (@include DISCUZ_ROOT.'./forumdata/cache/admingroup_'.intval($adminid).'.php') ? '' : ' admingroup_'.$groupid;
    }
    //论坛信息的一些东西..检查过滤了这些字符
    $page = isset($page) ? max(1, intval($page)) : 1;
    $tid = isset($tid) && is_numeric($tid) ? $tid : 0;
    $fid = isset($fid) && is_numeric($fid) ? $fid : 0;
    $typeid = isset($typeid) ? intval($typeid) : 0;
    $modthreadkey = isset($modthreadkey) && $modthreadkey == modthreadkey($tid) ? $modthreadkey : '';
    $auditstatuson = $modthreadkey ? true : false;
    //往forum这个数组里面写一些东西进去,比方说:发帖子得到的分数,回复得到的分数,帖子分类的类型等…
    if(!empty($tid) || !empty($fid)) {
    if(empty($tid)) {
      $forum = $db->fetch_first("SELECT f.fid, f.*, ff.* $accessadd1 $modadd1, f.fid AS fid
       FROM {$tablepre}forums f
       LEFT JOIN {$tablepre}forumfields ff ON ff.fid=f.fid $accessadd2 $modadd2
       WHERE f.fid='$fid'");
    } else {
      $forum = $db->fetch_first("SELECT t.tid, t.closed,".(defined('SQL_ADD_THREAD') ? SQL_ADD_THREAD : '')." f.*, ff.* $accessadd1 $modadd1, f.fid AS fid
       FROM {$tablepre}threads t
       INNER JOIN {$tablepre}forums f ON f.fid=t.fid
       LEFT JOIN {$tablepre}forumfields ff ON ff.fid=f.fid $accessadd2 $modadd2
       WHERE t.tid='$tid'".($auditstatuson ? '' : " AND t.displayorder>='0'")." LIMIT 1");
      $tid = $forum['tid'];
    }
    if($forum) {
      $fid = $forum['fid'];
      $forum['ismoderator'] = !empty($forum['ismoderator']) || $adminid == 1 || $adminid == 2 ? 1 : 0;
      foreach(array('postcredits', 'replycredits', 'threadtypes', 'threadsorts', 'digestcredits', 'postattachcredits', 'getattachcredits') as $key) {
       $forum[$key] = !empty($forum[$key]) ? unserialize($forum[$key]) : array();
      }
    } else {
      $fid = 0;
    }
    }
    //得到当前的style
    //用尽一切办法得到...还是得不到就从cache_settings里面获取
    //下面是看你是不是从其他地方设置了style设置了就变成它没设置就获取
    //没这个id的style缓存就填入到chachelost
    $styleid = intval(!empty($_GET['styleid']) ? $_GET['styleid'] :
      (!empty($_POST['styleid']) ? $_POST['styleid'] :
      (!empty($_DSESSION['styleid']) ? $_DSESSION['styleid'] :
      $_DCACHE['settings']['styleid'])));
    $styleid = intval(isset($styles[$styleid]) ? $styleid : $_DCACHE['settings']['styleid']);
    if(@!include DISCUZ_ROOT.'./forumdata/cache/style_'.intval(!empty($forum['styleid']) ? $forum['styleid'] : $styleid).'.php') {
    $cachelost .= (@include DISCUZ_ROOT.'./forumdata/cache/style_'.($styleid = $_DCACHE['settings']['styleid']).'.php') ? '' : ' style_'.$styleid;
    }
      

  3.   

    给个Discuz7目录、文件结构、各文件的功能就行了,有吗?
      

  4.   

    http://topic.csdn.net/u/20091026/09/d8dd9895-6065-424c-aed3-47232b27b813.html?seed=330938313&r=60671620#replyachor
    http://topic.csdn.net/u/20091026/09/8adf1c92-ecbc-4adf-8854-6593145173c8.html?seed=1723595294&r=60671834