`

Discuz! X 中 SESSION 机制讲解

    博客分类:
  • PHP
阅读更多
 
  1. Discuz! X 中 SESSION 机制讲解   
  2.   
  3.   在Discuz! X中一如继往的,SESSION 并没有使用 PHP 自带的 SESSION 机制,而是系统的一套自带的机制。   
  4.   
  5.   在数据库中可以看到有两个 SESSION 表:   
  6.     一个是pre_common_adminsession,是管理员登录后台的 SESSION 表;   
  7.     另一个是 pre_common_session 表,是所有用户在前台浏览页面时的 SESSION 表。   
  8.   这两个表都是内存表(内存表的读写速度远高于 MYISAM 表及文本文件)。   
  9.   
  10.   在 Discuz! X 中 SESSION 与 COOKIE 是分不开的,因为 SESSION 就是从客户端读取的 COOKIE ,   
  11.   然后由浏览页面时触发相关的函数执行,再写入数据库 SESSION 表。   
  12.   
  13.   我以登录流程为例来讲解程序具体是如何执行的。   
  14.   在前台首页,点击登录后,弹出一个登录窗口,填写好数据后,提交。form表单提交的 URL 是:   
  15.   
  16. 1   <a href="http://ux.com/member.php?mod=logging&action=login&loginsubmit=yes&floatlogin=yes&inajax=1">http://ux.com/member.php?mod=logging&action=login&loginsubmit=yes&floatlogin=yes&inajax=1</a>   
  17.    
  18. 数据提交到了 member.php 文件中,在程序中可看到下面的代码:   
  19. 01  $mod = !in_array($discuz->var['mod'], $modarray) ? 'logging' : $discuz->var['mod'];   //mod的值即是接下来加载的php页面   
  20. 02  define('CURMODULE'$mod);   
  21. 03  $modcachelist = array('register' => array('modreasons''stamptypeid''fields_required''fields_optional''ipctrl'));   
  22. 04  $cachelist = array();   
  23. 05  if(isset($modcachelist[CURMODULE])) {   
  24. 06      $cachelist = $modcachelist[CURMODULE];   
  25. 07  }   
  26. 08  $discuz->cachelist = $cachelist;   
  27. 09  $discuz->init();   
  28. 10  runhooks();   
  29. 11  require DISCUZ_ROOT.'./source/module/member/member_'.$mod.'.php';  //完成程序的包含操作   
  30.   
  31. 打开source/module/member/member_logging.php文件,是一个类,在类的前面可看到下面三句代码:   
  32.   
  33.    
  34. $ctl_obj = new logging_ctl();   
  35. $method = 'on_'.$_G['gp_action'];  // $_G['gp_action'] 等于action的值即 login   
  36. $ctl_obj->$method();   //$ctl_obj->on_login();   
  37.   
  38. 在类中可找到login方法,在方法中,大约 56 行有下面一个判断语句:   
  39.   
  40.    
  41. if(!submitcheck('loginsubmit', 1, $seccodecheck)) {   
  42.   
  43. 判断语句是当游客浏览时,submitcheck 函数的返回值是假,取反,为真。   
  44. 当用户登录时,程序走的是else部分,在里面可看到下面五句代码:   
  45.   
  46.    
  47. else {   
  48.             $_G['uid'] = $_G['member']['uid'] = 0;   
  49.             $_G['username'] = $_G['member']['username'] = $_G['member']['password'] = '';    //变量赋值   
  50.             $result = userlogin($_G['gp_username'], $_G['gp_password'], $_G['gp_questionid'], $_G['gp_answer'], $_G['setting']['autoidselect'] ? 'auto' : $_G['gp_loginfield']);  //从数据库查询用户数据,并返回相应的信息   
  51.       
  52.             if($result['status'] > 0) {  //状态值大于 0 ,说明有此用户,可以登录   
  53.                 setloginstatus($result['member'], $_G['gp_cookietime'] ? 2592000 : 0);  //设置登录状态,即是写 COOKIE 操作,COOKIE 中的数据即是 SESSION 中相应的数据,但此函数并不负责写 SESSION 的操作   
  54.   
  55. 我们来看一下 source/function/function_login.php中的 setloginstatus 函数,是普通的写 COOKIE 操作,不再具体讲解:   
  56.   
  57.    
  58. function setloginstatus($member$cookietime) {   
  59.     global $_G;   
  60.     $_G['uid'] = $member['uid'];   
  61.     $_G['username'] = $member['username'];   
  62.     $_G['adminid'] = $member['adminid'];   
  63.     $_G['groupid'] = $member['groupid'];   
  64.     $_G['formhash'] = formhash();   
  65.     $_G['session']['invisible'] = getuserprofile('invisible');   
  66.     $_G['member'] = $member;   
  67.     $_G['core']->session->isnew = 1;   
  68.       
  69.     dsetcookie('auth', authcode("{$member['password']}\t{$member['uid']}"'ENCODE'), $cookietime, 1, true);   //authcode加密   
  70.     dsetcookie('loginuser');   
  71.     dsetcookie('activationauth');   
  72.     dsetcookie('pmnum');   
  73. }   
  74.   
  75. 到这里可以说是登录流程大部分已经走完,但是 COOKIE 不清除时,会一直存在于客户端,如果超时,程序中会在判断弃用此 COOKIE,并重新写入。   
  76.   
  77. 下面我们来看一下 DZX 中 SESSION 操作的类,在 source/class/calss_core.php 文件中:   
  78. 程序中每次请求都会加载 SESSION ,这是由核心类 discuz_core 中的 _init_session 方法来执行的,此方法被置于 类的 init方法中,说明每次加载类,会自动将 SESSION 写入。   
  79.   
  80.    
  81. function _init_session() {   
  82.     
  83.     $this->session = new discuz_session();   //创建 SESSION 类   
  84.     
  85.     if($this->init_session) {   
  86.         //从 COOKIE 中读取数据   
  87.         $this->session->init($this->var['cookie']['sid'], $this->var['clientip'], $this->var['uid']);   
  88.         $this->var['sid'] = $this->session->sid;   
  89.         $this->var['session'] = $this->session->var;   
  90.         //判断 SID 是否相等,不等,说明是多个用户在同一主机上登录网站,需要重新写 COOKIE   
  91.         if($this->var['sid'] != $this->var['cookie']['sid']) {   
  92.             dsetcookie('sid'$this->var['sid'], 86400);   
  93.         }   
  94.     
  95.         if($this->session->isnew) {   
  96.             if(ipbanned($this->var['clientip'])) {   
  97.                 $this->session->set('groupid', 6);   
  98.             }   
  99.         }   
  100.     
  101.         if($this->session->get('groupid') == 6) {   
  102.             $this->var['member']['groupid'] = 6;   
  103.             sysmessage('user_banned');   
  104.         }   
  105.         //UID 不为空,且需要更新 SESSION 或是 SESSION 超时,更改用户状态,需要用户重新登录   
  106.         if($this->var['uid'] && ($this->session->isnew || ($this->session->get('lastactivity') + 600) < TIMESTAMP)) {   
  107.     
  108.             $this->session->set('lastactivity', TIMESTAMP);   
  109.     
  110.             $update = array('lastip' => $this->var['clientip'], 'lastactivity' => TIMESTAMP);   
  111.             if($this->session->isnew) {   
  112.                 $update['lastvisit'] = TIMESTAMP;   
  113.             }   
  114.             DB::update('common_member_status'$update"uid='".$this->var['uid']."'");   
  115.         }   
  116.     
  117.     }   
  118. }   
  119.   
  120. 操作 SESSION 的类是 discuz_session ,我们看这个类里面的两个方法:   
  121.   
  122.    
  123. //此函数负责产生新的 SESSION,但并不负责写入数据库   
  124.     function create($ip$uid) {   
  125. //创建SESSION,执行插入数据,由随机函数产生一个六位随机数即是session的唯一值时间为当前时间,sid为cookie中的sid   
  126.         $this->isnew = true;   
  127.         $this->var = $this->newguest;   
  128.         $this->set('sid', random(6));   
  129.         $this->set('uid'$uid);   
  130.         $this->set('ip'$ip);   
  131.         $this->set('lastactivity', time());   
  132.         $this->sid = $this->var['sid'];   
  133.       
  134.         return $this->var;   
  135.     }   
  136. //此函数负责更新 SESSION   
  137.     function update() {   
  138.         if($this->sid !== null) {   
  139.       
  140.             $data = daddslashes($this->var);   
  141.       
  142.             if($this->isnew) {   
  143.                 $this->delete();   
  144.                 DB::insert('common_session'$data, false, false, true);   
  145.             } else {   
  146.                 DB::update('common_session'$data"sid='$data[sid]'");   
  147.             }   
  148.             dsetcookie('sid'$this->sid, 86400);   
  149.         }   
  150.     }   
  151.   
  152. 至此我们知道了 SESSION 插入数据库的具体函数,与 COOKIE 的联系,但还不清楚是如何触发此操作的。   
  153. 打开 source/function/function_core.php 文件,找到函数,updatesession ,此函数负责更新 SESSION :   
  154.   
  155.    
  156. function updatesession($force = false) {   
  157.       
  158.     global $_G;   
  159.     static $updated = false;   
  160.     if(!$updated) {   
  161.         $discuz = & discuz_core::instance();   
  162.         foreach($discuz->session->var as $k => $v) {   
  163.             if(isset($_G['member'][$k]) && $k != 'lastactivity') {   
  164.                 $discuz->session->set($k$_G['member'][$k]);   
  165.             }   
  166.         }   
  167.       
  168.         foreach($_G['action'as $k => $v) {   
  169.             $discuz->session->set($k$v);   
  170.         }   
  171.       
  172.         $discuz->session->update();   
  173.       
  174.         $updated = true;   
  175.     }   
  176.     return $updated;   
  177. }   
  178.   
  179. 我们在程序源码中搜索此函数,可以看到在很多的模板中都有下面一句代码:   
  180.   
  181.    
  182. {eval updatesession();}   
  183.   
  184. 浏览页面时将触发此函数,并将 SESSION 写入数据库。   
  185.   
  186. 整理一下思绪:   
  187. 第一步:用户登录,程序将 COOKIE 写入客户端,这些 COOKIE 即是 SESSION 的部分数据,如SID、IP、TIME,不包含用户名、密码等关键信息。   
  188. 第二步,登录成功后,程序会自动刷新页面,向服务器再次发送请求,服务器加载 discuz_core 核心类,并从 COOKIE 中读取到 SESSION 的相关信息,但还没有写入数据库。   
  189. 第三步,核心类加载完成,程序继续执行,最后加载模板,触发 updatesession 函数,SESSION 被写入数据库。  
Discuz! X 中 SESSION 机制讲解

  在Discuz! X中一如继往的,SESSION 并没有使用 PHP 自带的 SESSION 机制,而是系统的一套自带的机制。

  在数据库中可以看到有两个 SESSION 表:
    一个是pre_common_adminsession,是管理员登录后台的 SESSION 表;
    另一个是 pre_common_session 表,是所有用户在前台浏览页面时的 SESSION 表。
  这两个表都是内存表(内存表的读写速度远高于 MYISAM 表及文本文件)。

  在 Discuz! X 中 SESSION 与 COOKIE 是分不开的,因为 SESSION 就是从客户端读取的 COOKIE ,
  然后由浏览页面时触发相关的函数执行,再写入数据库 SESSION 表。

  我以登录流程为例来讲解程序具体是如何执行的。
  在前台首页,点击登录后,弹出一个登录窗口,填写好数据后,提交。form表单提交的 URL 是:

1	<a href="http://ux.com/member.php?mod=logging&action=login&loginsubmit=yes&floatlogin=yes&inajax=1">http://ux.com/member.php?mod=logging&action=login&loginsubmit=yes&floatlogin=yes&inajax=1</a>

数据提交到了 member.php 文件中,在程序中可看到下面的代码:
01	$mod = !in_array($discuz->var['mod'], $modarray) ? 'logging' : $discuz->var['mod'];   //mod的值即是接下来加载的php页面
02	define('CURMODULE', $mod);
03	$modcachelist = array('register' => array('modreasons', 'stamptypeid', 'fields_required', 'fields_optional', 'ipctrl'));
04	$cachelist = array();
05	if(isset($modcachelist[CURMODULE])) {
06	    $cachelist = $modcachelist[CURMODULE];
07	}
08	$discuz->cachelist = $cachelist;
09	$discuz->init();
10	runhooks();
11	require DISCUZ_ROOT.'./source/module/member/member_'.$mod.'.php';  //完成程序的包含操作

打开source/module/member/member_logging.php文件,是一个类,在类的前面可看到下面三句代码:


$ctl_obj = new logging_ctl();
$method = 'on_'.$_G['gp_action'];  // $_G['gp_action'] 等于action的值即 login
$ctl_obj->$method();   //$ctl_obj->on_login();

在类中可找到login方法,在方法中,大约 56 行有下面一个判断语句:


if(!submitcheck('loginsubmit', 1, $seccodecheck)) {

判断语句是当游客浏览时,submitcheck 函数的返回值是假,取反,为真。
当用户登录时,程序走的是else部分,在里面可看到下面五句代码:


} else {
            $_G['uid'] = $_G['member']['uid'] = 0;
            $_G['username'] = $_G['member']['username'] = $_G['member']['password'] = '';    //变量赋值
            $result = userlogin($_G['gp_username'], $_G['gp_password'], $_G['gp_questionid'], $_G['gp_answer'], $_G['setting']['autoidselect'] ? 'auto' : $_G['gp_loginfield']);  //从数据库查询用户数据,并返回相应的信息
   
            if($result['status'] > 0) {  //状态值大于 0 ,说明有此用户,可以登录
                setloginstatus($result['member'], $_G['gp_cookietime'] ? 2592000 : 0);  //设置登录状态,即是写 COOKIE 操作,COOKIE 中的数据即是 SESSION 中相应的数据,但此函数并不负责写 SESSION 的操作

我们来看一下 source/function/function_login.php中的 setloginstatus 函数,是普通的写 COOKIE 操作,不再具体讲解:


function setloginstatus($member, $cookietime) {
    global $_G;
    $_G['uid'] = $member['uid'];
    $_G['username'] = $member['username'];
    $_G['adminid'] = $member['adminid'];
    $_G['groupid'] = $member['groupid'];
    $_G['formhash'] = formhash();
    $_G['session']['invisible'] = getuserprofile('invisible');
    $_G['member'] = $member;
    $_G['core']->session->isnew = 1;
   
    dsetcookie('auth', authcode("{$member['password']}\t{$member['uid']}", 'ENCODE'), $cookietime, 1, true);   //authcode加密
    dsetcookie('loginuser');
    dsetcookie('activationauth');
    dsetcookie('pmnum');
}

到这里可以说是登录流程大部分已经走完,但是 COOKIE 不清除时,会一直存在于客户端,如果超时,程序中会在判断弃用此 COOKIE,并重新写入。

下面我们来看一下 DZX 中 SESSION 操作的类,在 source/class/calss_core.php 文件中:
程序中每次请求都会加载 SESSION ,这是由核心类 discuz_core 中的 _init_session 方法来执行的,此方法被置于 类的 init方法中,说明每次加载类,会自动将 SESSION 写入。


function _init_session() {
 
    $this->session = new discuz_session();   //创建 SESSION 类
 
    if($this->init_session) {
        //从 COOKIE 中读取数据
        $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;
        //判断 SID 是否相等,不等,说明是多个用户在同一主机上登录网站,需要重新写 COOKIE
        if($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');
        }
        //UID 不为空,且需要更新 SESSION 或是 SESSION 超时,更改用户状态,需要用户重新登录
        if($this->var['uid'] && ($this->session->isnew || ($this->session->get('lastactivity') + 600) < TIMESTAMP)) {
 
            $this->session->set('lastactivity', TIMESTAMP);
 
            $update = array('lastip' => $this->var['clientip'], 'lastactivity' => TIMESTAMP);
            if($this->session->isnew) {
                $update['lastvisit'] = TIMESTAMP;
            }
            DB::update('common_member_status', $update, "uid='".$this->var['uid']."'");
        }
 
    }
}

操作 SESSION 的类是 discuz_session ,我们看这个类里面的两个方法:


//此函数负责产生新的 SESSION,但并不负责写入数据库
    function create($ip, $uid) {
//创建SESSION,执行插入数据,由随机函数产生一个六位随机数即是session的唯一值时间为当前时间,sid为cookie中的sid
        $this->isnew = true;
        $this->var = $this->newguest;
        $this->set('sid', random(6));
        $this->set('uid', $uid);
        $this->set('ip', $ip);
        $this->set('lastactivity', time());
        $this->sid = $this->var['sid'];
   
        return $this->var;
    }
//此函数负责更新 SESSION
    function update() {
        if($this->sid !== null) {
   
            $data = daddslashes($this->var);
   
            if($this->isnew) {
                $this->delete();
                DB::insert('common_session', $data, false, false, true);
            } else {
                DB::update('common_session', $data, "sid='$data[sid]'");
            }
            dsetcookie('sid', $this->sid, 86400);
        }
    }

至此我们知道了 SESSION 插入数据库的具体函数,与 COOKIE 的联系,但还不清楚是如何触发此操作的。
打开 source/function/function_core.php 文件,找到函数,updatesession ,此函数负责更新 SESSION :


function updatesession($force = false) {
   
    global $_G;
    static $updated = false;
    if(!$updated) {
        $discuz = & discuz_core::instance();
        foreach($discuz->session->var as $k => $v) {
            if(isset($_G['member'][$k]) && $k != 'lastactivity') {
                $discuz->session->set($k, $_G['member'][$k]);
            }
        }
   
        foreach($_G['action'] as $k => $v) {
            $discuz->session->set($k, $v);
        }
   
        $discuz->session->update();
   
        $updated = true;
    }
    return $updated;
}

我们在程序源码中搜索此函数,可以看到在很多的模板中都有下面一句代码:


{eval updatesession();}

浏览页面时将触发此函数,并将 SESSION 写入数据库。

整理一下思绪:
第一步:用户登录,程序将 COOKIE 写入客户端,这些 COOKIE 即是 SESSION 的部分数据,如SID、IP、TIME,不包含用户名、密码等关键信息。
第二步,登录成功后,程序会自动刷新页面,向服务器再次发送请求,服务器加载 discuz_core 核心类,并从 COOKIE 中读取到 SESSION 的相关信息,但还没有写入数据库。
第三步,核心类加载完成,程序继续执行,最后加载模板,触发 updatesession 函数,SESSION 被写入数据库。

 

分享到:
评论

相关推荐

    Discuz!X中SESSION机制实例详解

    X中的具体操作流程来进一步分析其SESSION机制。当用户在前台点击登录按钮,弹出登录窗口后,填写好登录信息并提交表单。此时,数据被发送至member.php文件,该文件会根据提交的参数(如mod和action)决定后续加载的...

    Discuz! X3.4源码

    在本文中,我们将深入探讨Discuz! X3.4的核心特性、架构设计以及源码分析,帮助开发者更好地理解和利用这一强大的工具。 一、核心特性 1. 强大的社区功能:Discuz! X3.4提供论坛、博客、问答、家园等多种社区模块...

    Discuz! X3.4 繁体中文BIG5 R20171001.zip

    X3.4在继承和完善Discuz! X3.3的基础上,针对PHP7进行了优化,对于X3.3用户来说,X3.4已继承了X3.3的补丁修复工作。 Discuz! X3.4 繁体中文BIG5 R20171001 更新日志: 增加新触屏版;去除云平台相关代码;优化...

    Discuz!X3.5 X3.4腾讯云验证码插件 1.0.0.zip

    Discuz!X3.5和X3.4是流行的开源社区论坛系统,它们为企业和个人提供了构建互动社区的强大工具。为了增强这些平台的安全性,腾讯云开发了一款名为“tencentcloud_captcha”的官方插件。这款插件的核心功能是集成腾讯...

    Discuz! X3.1

    X3.1"指的是 Discuz! 系统的一个具体版本,即X3.1。Discuz! 是一个非常知名的开源社区论坛软件,由康盛创想(Comsenz)开发。X3.1是其在某个时间点推出的更新版本,通常会包含对前一版本的改进、新功能的添加以及...

    Discuz! X3.4 正式版 简体中文 GBK v20180101.zip

    X3.4 在继承和完善 Discuz! X3.3 的基础上,针对 PHP7 进行了优化。对于 X3.2 用户来说,X3.3 已继承了 X3.2 的补丁修复工作。安全稳定的程序为站长提供更加可靠的保障。 Discuz! 安装说明: 全新安装:请参照...

    Discuz! X2.5 PHP7.0 GBK.zip

    X3.4在继承和完善Discuz! X3.3的基础上,针对PHP7进行了优化,对于X3.3用户来说,X3.4已继承了X3.3的补丁修复工作。 Discuz! X2.5 PHP7.0 GBK 更新日志:增加新触屏版;去除云平台相关代码;优化优化缓存机制改为...

    Discuz!X1.0开发手册

    X1.0开发手册》是Comsenz公司官方发布的一份详细指导文档,旨在帮助开发者深入了解和熟练运用Discuz!X1.0这一开源社区论坛系统。手册内容涵盖Discuz!X1.0的核心功能、架构设计、模块开发、模板制作、插件集成等多个...

    Discuz! X3.4 正式版 繁体中文 BIG5 20171001.zip

    X3.4 在继承和完善 Discuz! X3.3 的基础上,针对 PHP7 进行了优化。对于 X3.2 用户来说,X3.3 已继承了 X3.2 的补丁修复工作。安全稳定的程序为站长提供更加可靠的保障。 Discuz! 安装说明: 全新安装:请参照...

    Discuz!X3.1 全新安装图文教程

    ### Discuz!X3.1 全新安装图文教程知识点详解 #### 一、Discuz!X3.1 概述 **Discuz!X3.1**是Comsenz公司开发的一款非常流行的社区论坛软件,它基于PHP语言和MySQL数据库,支持多种操作系统和Web服务器环境。Discuz...

    Discuz! x2.5深黑经典模板

    x2.5深黑经典模板" 涉及的主要知识点是Discuz! X2.5论坛系统以及其配套的主题模板设计。Discuz! 是一款非常流行的开源社区建站软件,主要功能是帮助用户快速搭建属于自己的论坛网站。X2.5是Discuz! 的一个特定版本...

    解决与论坛冲突(Discuz! X3.1)问题

    标题中的“解决与论坛冲突(Discuz! X3.1)问题”指的是在使用Discuz! X3.1这款开源论坛软件时遇到的兼容性或功能冲突问题。Discuz! 是一个广泛使用的PHP和MySQL构建的社区论坛系统,X3.1是其特定的版本,可能包含了...

    Discuz!X3.5 X3.4腾讯云全局配置插件 1.0.0.zip

    X3.5 X3.4腾讯云全局配置插件 1.0.0.zip" 提供了一个专为Discuz! X3.5和X3.4论坛系统设计的插件,它整合了与腾讯云相关的各种服务,并允许用户在一个中心化的界面进行统一管理和配置。 【描述】中的关键信息表明,...

    Discuz! X插件开发

    Discuz! X插件开发

    Discuz! X3.1 GBK 正式版.zip

    X3.1 在继承和完善 Discuz! X3.0 的基础上,针对广告垃圾防御进行了大幅度的调整,新防水墙、帐号保镖、云验证码等功能为社区的健康运转提供更加可靠的保障。 全新安装: 请参照以往版本的安装过程进行,上传程序...

    Discuz! X2.5 愤怒的小鸟插件V1.0.rar

    X2.5 愤怒的小鸟插件V1.0》是一款专为Discuz! X2.5论坛系统设计的趣味插件,同时也兼容Discuz! X1.5、X2版本。这款插件的独特之处在于它并未对论坛的系统文件进行任何修改,而是通过外部实现方式,确保了用户在安装...

    Discuz! X1.5导航插件

    X1.5导航插件是一款专为Discuz! X1.5平台设计的强大辅助工具,它旨在帮助用户更好地管理和优化社区网站的导航功能,提升用户体验,增强网站的互动性和实用性。这款插件在www.58119.com上有着生动的展示,通过实际...

    Discuz! X3.2 GBK 正式版.zip

    X3.2 在继承和完善 Discuz! X3.1 的基础上,针对社区移动端进行了新的尝试。推出微信登录、微社区等功能。安全稳定的程序为站长提供更加可靠的保障。 说明: 全新安装:请参照以往版本的安装过程进行,上传程序,...

    Discuz! X3.4 简体中文版

    X3.4在继承和完善Discuz! X3.3的基础上进行了大量工作。这意味着X3.4不仅包含了X3.3的所有功能,还修复了之前版本存在的问题和漏洞,提高了系统的安全性。对于已经使用X3.3的用户来说,升级到X3.4无需担心兼容性...

Global site tag (gtag.js) - Google Analytics