作为程序员一定要保持良好的睡眠,才能好编程

将 Session 数据存入数据库

发布时间:2018-11-19

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.php

class 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 '写入成功';

writesuccess.png


一定要先注册session_set_save_handler 这个函数,

然后再 session_start()  否则数据不能写入到数据库



读取数据:

require 'SessionDbSaveHandler.php';

new SessionDbSaveHandler('localhost', 'root', 'song', 'test', 'session');

session_start();

print_r($_SESSION);

readsuccess.png



查看数据库中是否存在:

dbsucess.png


数据库表结构:

dbsca.png



整体项目下载:session2db.zip


如果需要扩展memcache 或  redis  按照这个结构 直接修改里面的代码 即可。