完美地解决 TP3 老系统数据的平滑读取
为了彻底、完美地解决 TP3 老系统数据的平滑读取,必须解决两个核心痛点:
TP3 的盲猜反序列化机制(标量存原样,数组/对象存原生
serialize,同时兼容json)。TP3 与 TP6 的缓存前缀(Prefix)不一致。
以下是为你量身定制的终极落地解决方案,通过自定义 TP6 驱动和平滑迁移中间件两步,既能保障新系统无缝读取旧数据,又能保障新写入的数据不破坏老系统。
第一步:在 TP6 中创建“1:1 完美平替”驱动
这个自定义驱动直接继承 TP6 的 Redis 驱动,但重写了底层的 serialize 和 unserialize 方法,使其行为与 TP3 保持 100% 互通。
新建文件:app/common/cache/driver/Tp3CompatibleRedis.php
php
<?php
namespace app\common\cache\driver;
use think\cache\driver\Redis;
/**
* 专门用于 1:1 兼容 TP3 读写行为的 Redis 驱动
*/
class Tp3CompatibleRedis extends Redis
{
/**
* 写入数据:严格模拟 TP3 行为
* - 标量(字符串/数字/布尔值)直接存原样,绝不包裹序列化
* - 数组或对象采用 PHP 原生 serialize
*/
protected function serialize($data): string
{
if (is_scalar($data)) {
return (string)$data;
}
return serialize($data);
}
/**
* 读取数据:严格模拟 TP3 盲猜反序列化
* - 优先尝试原生反序列化
* - 其次尝试 JSON 解码
* - 失败则作为普通字符串原样返回
*/
protected function unserialize(string $data)
{
// 1. 尝试原生反序列化
$unserializeData = @unserialize($data);
if ($unserializeData !== false || $data === 'b:0;') {
return $unserializeData;
}
// 2. 尝试 JSON 解码(兼容TP3中部分手动存json的魔改数据)
$jsonData = json_decode($data, true);
if (json_last_error() === JSON_ERROR_NONE && !is_numeric($data)) {
return $jsonData;
}
// 3. 兜底:说明老系统当年存的就是纯字符串/Token,直接原样返回
return $data;
}
}
请谨慎使用此类代码。
第二步:精准配置 TP6 缓存(对齐前缀)
打开 TP6 的 config/cache.php,将 redis 驱动指向你刚才创建的兼容类。最关键的是:前缀(prefix)必须与 TP3 保持完全一致!
php
return [
'default' => 'redis',
'stores' => [
'redis' => [
// 1. 指向刚刚创建的兼容驱动类
'type' => \app\common\cache\driver\Tp3CompatibleRedis::class,
'host' => '127.0.0.1',
'port' => 6379,
'password' => '',
'select' => 0,
// 2. 注意:关闭 TP6 内置的序列化配置,交由我们重写的类自主处理
'serialize' => [],
// 3. 【致命关键】必须对齐 TP3 的 C('DATA_CACHE_PREFIX')
// 如果老系统没配前缀,这里必须留空字符串 ''
'prefix' => '',
'expire' => 0,
],
],
];
请谨慎使用此类代码。
第三步:重构你的 unsession 助手函数
有了上面两步的底层铺垫,你在 TP6 里重构旧系统的 unsession 函数就变得极其轻量且安全了。在 app/common.php 中这样重写:
php
<?php
// app/common.php
use think\facade\Cache;
if (!function_exists('unsession')) {
/**
* 兼容老系统的 unsession 函数
* @param string|null $key 旧系统传入的 SessionKey 或 Token
* @return bool
*/
function unsession(?string $key = null): bool
{
if (empty($key)) {
return false;
}
// 此时 Cache 已经完全兼容了 TP3 的读写和前缀规则
// 如果需要先读取老数据做业务校验:
$oldData = Cache::get($key);
if ($oldData) {
// 执行你的清理或强退日志记录...
// 彻底从 Redis 中销毁该会话/数据
return Cache::delete($key);
}
return false;
}
}
请谨慎使用此类代码。
💡 平滑迁移的终极防坑校验
核对 TP3 的真实 Key 值
在老系统(TP3)中运行一次M()->execute()或找个地方打印echo C('DATA_CACHE_PREFIX');。如果老系统输出了tp3_,那么 TP6 的'prefix'必须写tp3_。如果老系统没有配,TP6 必须写''。两边只要有一点点对不上,新系统就相当于在读空气。避免在代码里手动拼
think:前缀
TP6 官方默认文档和很多教程喜欢在代码里手动拼think:。既然是彻底兼容老系统,请在新系统(TP6)的业务代码中,统一使用老系统的 Key 命名规范。老系统的
session_id()怎么拿?
如果老系统的unsession依赖于原生的PHPSESSID,在 TP6 中请通过$request->cookie('PHPSESSID')或者是通过前端Header传参获取,绝不要再调用原生的session_id(),因为 TP6 默认是不启动原生 Session 容器的。