首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

Discuz! X Session 機制深度解析:Cookie、資料庫與自動更新流程

     編輯:PHP 相關知識

前言

Discuz! X 不使用 PHP 內建 `$_SESSION`,而是實作一套基於 Cookie + 記憶體資料表的自訂 Session 機制,以應對高併發、分散式部署與效能需求。本文詳細拆解其核心流程、資料結構與關鍵程式碼路徑。

一、資料結構:兩張 Session 表

表名用途儲存引擎
pre_common_session前台用戶瀏覽 SessionMEMORY (HEAP)
pre_common_adminsession後台管理員登入 SessionMEMORY (HEAP)

選用 MEMORY 引擎:資料儲存在記憶體,讀寫速度遠超 MyISAM/InnoDB,適合高頻 Session 存取;重啟 MySQL 會清空,符合 Session 臨時性質。

二、核心流程:登入與 Session 建立

1. 登入表單提交

URL:`member.php?mod=logging&action=login&loginsubmit=yes&...`

2. 模組載入與控制器分派

// member.php 片段
$mod = 'logging';
require DISCUZ_ROOT . './source/module/member/member_logging.php';
// 實例化 logging_ctl,呼叫 on_login()

3. 驗證與寫入 Cookie

在 `logging_ctl::on_login()` 中:

// 驗證帳號密碼
$result = userlogin($username, $password, ...);
if ($result['status'] > 0) {
    // 設定登入狀態 → 寫入 Cookie
    setloginstatus($result['member'], $cookietime);
}

4. `setloginstatus()` 關鍵操作

function setloginstatus($member, $cookietime) {
    global $_G;
    $_G['uid'] = $member['uid'];
    $_G['username'] = $member['username'];
    // ... 設定群組、formhash 等 ...

    // 核心:寫入加密 auth Cookie
    dsetcookie('auth', authcode("{$member['password']}\t{$member['uid']}", 'ENCODE'), $cookietime, 1, true);
    // 同步標記 session 為新建
    $_G['core']->session->isnew = 1;
}

此時僅寫入 Cookie,尚未寫入資料庫 Session 表。

三、Session 類別架構與初始化

1. 核心類別 `discuz_session` (source/class/class_core.php)

關鍵屬性:

  • `$var`:Session 資料陣列(sid, uid, ip, lastactivity, groupid...)
  • `$sid`:Session ID(6 位隨機字串)
  • `$isnew`:是否為新 Session

2. 兩大核心方法

// 建立新 Session(不寫入 DB)
function create($ip, $uid) {
    $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 到 DB
function update() {
    if ($this->sid !== null) {
        $data = daddslashes($this->var);
        if ($this->isnew) {
            $this->delete(); // 刪除舊同 sid(理論上不存在)
            DB::insert('common_session', $data, false, false, true); // REPLACE INTO
        } else {
            DB::update('common_session', $data, "sid='$data[sid]'");
        }
        dsetcookie('sid', $this->sid, 86400); // 同步 sid Cookie
    }
}

四、自動觸發機制:模板中的 `updatesession()`

每次頁面請求結束輸出模板時,模板編譯後的 PHP 會包含:

{eval updatesession();}

此函數定義於 `source/function/function_core.php`:

function updatesession($force = false) {
    global $_G;
    static $updated = false;
    if (!$updated) {
        $discuz = discuz_core::instance();
        // 同步 $_G['member'] 變更到 session 物件
        foreach ($discuz->session->var as $k => $v) {
            if (isset($_G['member'][$k]) && $k != 'lastactivity') {
                $discuz->session->set($k, $_G['member'][$k]);
            }
        }
        // 同步 action 參數
        foreach ($_G['action'] as $k => $v) {
            $discuz->session->set($k, $v);
        }
        $discuz->session->update(); // 真正寫入 DB
        $updated = true;
    }
    return $updated;
}

五、核心類初始化 `_init_session()` (每請求自動執行)

function _init_session() {
    $this->session = new discuz_session();
    if ($this->init_session) {
        // 從 Cookie 讀取 sid, ip, uid
        $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 不符)
        if ($this->var['sid'] != $this->var['cookie']['sid']) {
            dsetcookie('sid', $this->var['sid'], 86400);
        }

        // 新 Session 檢查 IP 封鎖
        if ($this->session->isnew && ipbanned($this->var['clientip'])) {
            $this->session->set('groupid', 6); // 封鎖群組
        }

        // Session 過期檢查(10 分鐘無活動)
        if ($this->var['uid'] && ($this->session->isnew || ($this->session->get('lastactivity') + 600) < TIMESTAMP)) {
            $this->session->set('lastactivity', TIMESTAMP);
            $update = ['lastip' => $this->var['clientip'], 'lastactivity' => TIMESTAMP];
            if ($this->session->isnew) $update['lastvisit'] = TIMESTAMP;
            DB::update('common_member_status', $update, "uid='{$this->var['uid']}'");
        }
    }
}

六、完整生命週期時序圖

  1. 用戶登入 → `setloginstatus()` 寫 `auth` Cookie、標記 `isnew=1`。
  2. 頁面重導/下一請求 → 攜帶 `auth`、`sid` Cookie。
  3. 核心類初始化 → `_init_session()` 讀取 Cookie,`discuz_session::init()` 依 `sid` 從 DB 載入或建立新 Session。
  4. 業務邏輯執行 → 可能修改 `$_G['member']`、`$_G['action']`。
  5. 模板輸出階段 → `{eval updatesession();}` 觸發 `discuz_session::update()` 將最新資料寫入 `pre_common_session` 表、更新 `sid` Cookie。

七、關鍵設計亮點

  • Cookie 與 DB 分離:`auth` Cookie 存加密憑證(密碼雜湊+uid),`sid` Cookie 只存 Session ID,敏感資訊不入 DB。
  • 延遲寫入:Session 只在模板輸出時寫入 DB,減少不必要的寫操作(純 API/靜態資源請求可跳過)。
  • 記憶體表:高頻讀寫無磁碟 I/O 瓶頸。
  • 多用戶共用瀏覽器偵測:比對 Cookie `sid` 與 Server 端 `sid`,不符則發新 `sid` Cookie,避免 Session 混淆。
  • 活躍度同步:`lastactivity` 每 10 分鐘同步一次到 `common_member_status`,平衡即時性與寫入壓力。

八、二次開發常見操作

// 取得當前 Session 資料
$_G['session']['uid'];        // 用戶 ID
$_G['session']['groupid'];    // 群組 ID
$_G['session']['lastactivity'];

// 手動更新 Session(非模板頁面,如 AJAX 端點)
updatesession();

// 強制登出
dsetcookie('auth', '');
dsetcookie('sid', '');
require_once libfile('function/member');
userlogout();

結語

Discuz! X 的 Session 機制展示了大型 PHP 應用如何在無原生 Session 支援下,構建高效能、可擴展的會話管理體系。理解其「Cookie 驗證 → 記憶體表儲存 → 模板觸發寫入」的三階段模型,對維護、遷移或開發相容外掛至關重要。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved