在數據庫中可以看到有兩個 SESSION 表:
一個是pre_common_adminsession,是管理員登錄后台的 SESSION 表;
另一個是 pre_common_session 表,是所有用戶在前台瀏覽頁面時的 SESSION 表。
這兩個表都是內存表(內存表的讀寫速度遠高於 MYISAM 表及文本文件)。
在 Discuz! X 中 SESSION 與 COOKIE 是分不開的,因為 SESSION 就是從客戶端讀取的 COOKIE ,
然后由瀏覽頁面時觸發相關的函數執行,再寫入數據庫 SESSION 表。
我以登錄流程為例來講解程序具體是如何執行的。
在前台首頁,點擊登錄后,彈出一個登錄窗口,填寫好數據后,提交。form表單提交的 URL 是:
數據提交到了 member.php 文件中,在程序中可看到下面的代碼:
1
2
3
4
5
6
7
8
9
10
11
|
$mod
= !in_array(
$discuz
->
var
[
'mod'
],
$modarray
) ?
'logging'
:
$discuz
->
var
[
'mod'
];
//mod的值即是接下來加載的php頁面
define(
'CURMODULE'
,
$mod
);
$modcachelist
=
array
(
'register'
=>
array
(
'modreasons'
,
'stamptypeid'
,
'fields_required'
,
'fields_optional'
,
'ipctrl'
));
$cachelist
=
array
();
if
(isset(
$modcachelist
[CURMODULE])) {
$cachelist
=
$modcachelist
[CURMODULE];
}
$discuz
->cachelist =
$cachelist
;
$discuz
->init();
runhooks();
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 被寫入數據庫。