如何使用 Eloquent 和 PostgreSQL 将 IP 地址保存为二进制文件?

How to save an IP address as binary using Eloquent and PostgreSQL?

首先,这是我获得信息的 SO 问答 - laravel 4 saving ip address to model

所以我的 table 可能有数百万行,因此为了保持较低的存储空间,我选择了选项 2 - 在帮助下使用架构构建器的 binary() 列和 converting/storing IP 作为二进制Eloquent 的 accessors/mutators.

这是我的 table:

Schema::create('logs', function ( Blueprint $table ) {
    $table->increments('id');
    $table->binary('ip_address'); // postgresql reports this column as BYTEA
    $table->text('route');
    $table->text('user_agent');
    $table->timestamp('created_at');
});

我 运行 遇到的第一个问题是保存 IP 地址。我在我的模型上设置了一个 accessor/mutator 以使用 inet_pton()inet_ntop() 将 IP 字符串转换为二进制。示例:

public function getIpAddressAttribute( $ip )
{
    return inet_ntop( $ip );
}

public function setIpAddressAttribute( $ip )
{
    $this->attributes['ip_address'] = inet_pton( $ip );
}

尝试保存 IP 地址导致整个请求失败 - nginx 只会 return 502 错误网关错误。

好的。所以我认为它必须与 Eloquent/PostgreSQL 在传递二进制数据时不能很好地协同工作。

我进行了一些搜索,找到了 pg_escape_bytea()pg_unescape_bytea() 函数。我更新了我的模型如下:

public function getIpAddressAttribute( $ip )
{
    return inet_ntop(pg_unescape_bytea( $ip ));
}

public function setIpAddressAttribute( $ip )
{
    $this->attributes['ip_address'] = pg_escape_bytea(inet_pton( $ip ));
}

现在,我可以毫不费力地保存 IP 地址(至少,它不会抛出任何错误)。

我遇到的新问题是当我尝试检索和显示 IP 时。 pg_unescape_bytea() 失败 pg_unescape_bytea() expects parameter 1 to be string, resource given

奇怪。所以我在accessor中dd()$ip,结果是resource(4, stream)。这是预期的吗?或者 Eloquent 在使用列类型时遇到问题?

我进行了更多搜索,发现 pg_unescape_bytea() 可能未正确转义数据 - https://bugs.php.net/bug.php?id=45964.

经过多次讨论和纠缠,很明显我可能从错误的方向处理这个问题,需要一些新的视角。

那么,我做错了什么?我应该通过更改列类型来使用 Postgres 的 BIT VARYING 而不是 BYTEA --

DB::statement("ALTER TABLE logs ALTER COLUMN ip_address TYPE BIT VARYING(16) USING CAST(ip_address AS BIT VARYING(16))");`

-- 或者我只是误用了 pg_escape_bytea / pg_unescape_bytea

感谢所有帮助!

就像在对您的问题的评论中已经说过的那样:在您的具体情况下,您应该使用相应的 PostgreSQL 数据类型,处理起来会容易得多。与 MySQL 相比,您在 PostgreSQL 中会有很多其他类型(例如 JSON),有一个 PostgreSQL data type overview page 供进一步参考。

也就是说,其他人可能会偶然发现 bytea 字段的类似问题。你得到 Resource 而不是 string 的原因是 PostgreSQL 将 bytea 字段视为流。一个非常幼稚的方法是首先获取流然后 return 数据:

public function getDataAttribute($value)
{
    // This will kill your server under high load with large values.
    $data =  fgets($value);

    return pg_unescape_bytea($data);
}

您可以想象,这可能是一个问题,因为多个人试图获取大文件(目前为数百 MiB 或几个 GiB),其中大数据对象需要服务器上的大量内存(这甚至可能在没有交换的情况下在移动设备上出现问题)。在这种情况下,您应该在服务器和客户端上使用流,并只在您真正需要的客户端上获取数据。