Discuz! X 不使用 PHP 內建 `$_SESSION`,而是實作一套基於 Cookie + 記憶體資料表的自訂 Session 機制,以應對高併發、分散式部署與效能需求。本文詳細拆解其核心流程、資料結構與關鍵程式碼路徑。
| 表名 | 用途 | 儲存引擎 |
|---|---|---|
pre_common_session | 前台用戶瀏覽 Session | MEMORY (HEAP) |
pre_common_adminsession | 後台管理員登入 Session | MEMORY (HEAP) |
選用 MEMORY 引擎:資料儲存在記憶體,讀寫速度遠超 MyISAM/InnoDB,適合高頻 Session 存取;重啟 MySQL 會清空,符合 Session 臨時性質。
URL:`member.php?mod=logging&action=login&loginsubmit=yes&...`
// member.php 片段
$mod = 'logging';
require DISCUZ_ROOT . './source/module/member/member_logging.php';
// 實例化 logging_ctl,呼叫 on_login()
在 `logging_ctl::on_login()` 中:
// 驗證帳號密碼
$result = userlogin($username, $password, ...);
if ($result['status'] > 0) {
// 設定登入狀態 → 寫入 Cookie
setloginstatus($result['member'], $cookietime);
}
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(不寫入 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
}
}
每次頁面請求結束輸出模板時,模板編譯後的 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;
}
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']}'");
}
}
}
// 取得當前 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 驗證 → 記憶體表儲存 → 模板觸發寫入」的三階段模型,對維護、遷移或開發相容外掛至關重要。