PHP的session会话是将用户信息存储在服务器上。一般情况下,一个sessionId会存储在客户端的Cookie中,然后通过SessionId 去维护session的声明周期的建立和保持。
可是如果客户端的SessionId一旦被其他程序劫持,那么就可以随便访问我们的应用程序了,且我们知道session默认情况是是保存在服务器的某一个目录的文件中,且在分布式上也是不能共享的,
因此我们考虑是否将session可以保存到数据库或缓存数据库中?
答案:当然是可以的,但并不一定是一个最优的方法,比如说数据访问特别大,是不是放在数据库中,给后台数据库带来了很大的压力。这个需要根据自己的场景来选择!
1、自定义Session接口函数session_set_save_hanlder
下面我来看下 php 是如何把session保存在数据库端的。session_set_save_handler(
'custom_session_open',
'custom_session_close',
'custom_session_read',
'custom_session_write',
'custom_session_destroy',
'custom_session_gc'
);
PHP提供了这样的一个函数,重新设置 session 保存的对象。
第一个回调函数open($path,$sessionId) 方法有两个参数。一个是保存的路径,一个是sessionId。
第二个回调函数close()
第三个回调函数read($sessionId)
第四个回调函数write($sessionId,$data)
第五个回调函数destroy($sessionId)
第六个回调函数gc($lifetime)
定时清理,这个时间和php.ini 设置的 session.gc_maxlifetime 有关系
2、数据库表设计create table session(
sessionid varchar(128) primary key,
content mediumblob,
createtime int(14),
ip int(10) DEFAULT 0,
key(createtime,sessionid)
);
content为什么使用 mediublob 类型? 因为会话数据库既有字符串也可能有二进制数据,正是因为此原因,在数据库中使用Blob二进制类型的字符,这样不仅保障数据的安全,而且占用的存储空间也会变少。
我们在设计表时,使用了一个复合键,如果数据特别多的时候,可以使用进行优化,这里就不做优化了。
就执行上边的表结构 创建session表。
3、SessionDbSaveHandler类 命名为SessionDbSaveHandler.phpclass SessionDbSaveHandler {
public $host;
public $user;
public $password;
public $database;
public $table = 'session';
/**
* @var PDO
*/
public static $db;
public function __construct($host, $user, $password, $database, $table = '') {
$this->host = $host;
$this->user = $user;
$this->password = $password;
$this->database = $database;
$table && $this->table = $table;
//自动注册session 保存到数据库
session_set_save_handler(
[$this, 'open'],
[$this, 'close'],
[$this, 'read'],
[$this, 'write'],
[$this, 'destroy'],
[$this, 'gc']
);
$this->connection();
}
private function connection() {
if (!static::$db) {
$dsn = 'mysql:host=' . $this->host . ';dbname=' . $this->database;
$params = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
];
static::$db = new PDO($dsn, $this->user, $this->password, $params);
}
}
/**
* 打开session
* @param $path
* @param $id
*/
public function open($path, $id) {
}
/**
* 关闭session
*/
public function close() {
static::$db = null;
}
/**
* 读取session
* @param $id
* @return string
*/
public function read($id) {
$sql = 'select * from session where sessionid=?';
$statement = self::$db->prepare($sql);
$statement->execute([$id]);
$data = $statement->fetch(PDO::FETCH_OBJ);
return $data ? $data->content : '';
}
/**
* 保存session 或更新session
* @param $id
* @param $data
* @return int
*/
public function write($id, $data) {
$sql = 'replace into session(`sessionid`,`content`,`ip`,`createtime`)values(?,?,?,?)';
$statement = self::$db->prepare($sql);
$result = $statement->execute([$id, $data, $this->ip2long(), time()]);
if ($result) {
return $statement->rowCount();
}
}
/**
* @param $id
* @return int
*/
public function destroy($id) {
$sql = 'delete from session where sessionid=?';
$statement = self::$db->prepare($sql);
$statement->execute([$id]);
return $statement->rowCount();
}
/**
* 定期自动回收
* @param $lifetime
* @return int
*/
public function gc($lifetime) {
$sql = 'delete from session where createtime';
$statement = self::$db->prepare($sql);
$statement->execute([time() - $lifetime]);
return $statement->rowCount();
}
public function __destruct() {
$this->host = null;
$this->user = null;
$this->password = null;
$this->database = null;
session_write_close();
}
private function ip2long($ip = null) {
return sprintf('%u', is_null($ip) ? $_SERVER['REMOTE_ADDR'] : $ip);
}
}
4、测试数据是否正常 写入 读出
写入session:require 'SessionDbSaveHandler.php';
new SessionDbSaveHandler('localhost', 'root', 'song', 'test', 'session');
session_start();
$_SESSION['user'] = '15675736645666';
$_SESSION['random_num'] = mt_rand(1000, 9999);
echo '写入成功';
一定要先注册session_set_save_handler 这个函数,
然后再 session_start() 否则数据不能写入到数据库
读取数据:require 'SessionDbSaveHandler.php';
new SessionDbSaveHandler('localhost', 'root', 'song', 'test', 'session');
session_start();
print_r($_SESSION);
查看数据库中是否存在:
数据库表结构:
如果需要扩展memcache 或 redis 按照这个结构 直接修改里面的代码 即可。