【问题标题】:How can I ensure unique file names for images uploaded to server?如何确保上传到服务器的图像文件名唯一?
【发布时间】:2014-02-25 01:28:00
【问题描述】:

我正在创建一个允许用户将图像上传到服务器的 Web 应用程序。

当用户上传图片时,如何防止重复的图片文件名?

如何将文件名更改为不重复任何先前图像名称(或可能上传的下一个图像)的唯一名称?

编辑:

如果我使用主键自动增量作为每个图像的图像文件名?有用吗?

【问题讨论】:

  • 有很多方法可以做到这一点,我更喜欢使用带有com_create_guid() 的 GUID - 这是一个仅限 Windows 的功能,但请检查 cmets 是否可以替换 Linux:php.net/com_create_guid
  • @s1lence,正如许多文章所述,仅使用uniqid() 不足以保证文件名的唯一性。例如,如果您部署此应用程序的多个实例,可以将文件上传到同一目录,则仅依赖uniqid()(即使使用更多熵)将面临名称冲突的风险。因此,您所指的“可能重复”在这方面只是一个低质量的答案。我的两分钱。
  • 这正是我在下面的回答中提出的观点。但你是对的,即使另一个问题是完全重复的,但他们都没有一个令人满意的接受答案......

标签: php


【解决方案1】:

我之前already answered这样的问题。我更新了我的代码,为生成的 id 添加了更多随机性(熵)。

此类生成伪唯一、非连续、非数字 ID。

class IdGenerator {

    static private function _nextChar() {
        return base_convert(mt_rand(0, 35), 10, 36);
    }

    static public function generate() {
        $parts = explode('.', uniqid('', true));

        $id = str_pad(base_convert($parts[0], 16, 2), 56, mt_rand(0, 1), STR_PAD_LEFT)
            . str_pad(base_convert($parts[1], 10, 2), 32, mt_rand(0, 1), STR_PAD_LEFT);
        $id = str_pad($id, strlen($id) + (8 - (strlen($id) % 8)), mt_rand(0, 1), STR_PAD_BOTH);

        $chunks = str_split($id, 8);

        $id = array();
        foreach ($chunks as $key => $chunk) {
            if ($key & 1) {  // odd
                array_unshift($id, $chunk);
            } else {         // even
                array_push($id, $chunk);
            }
        }

        // add random seeds
        $prefix = str_pad(base_convert(mt_rand(), 10, 36), 6, self::_nextChar(), STR_PAD_BOTH);
        $id = str_pad(base_convert(implode($id), 2, 36), 19, self::_nextChar(), STR_PAD_BOTH);
        $suffix = str_pad(base_convert(mt_rand(), 10, 36), 6, self::_nextChar(), STR_PAD_BOTH);

        return $prefix . self::_nextChar() . $id . $suffix;
    }
}

如果你执行这个脚本

header('Content-type: text/plain; charset=utf-8');

for ($i=0; $i<10; $i++) {
    $uid = IdGenerator::generate();

    echo $uid . " = " . strlen($uid) . "\n";
}

你会得到这样的东西:

x0i8eea3c8kw4lgudmoss4c4w03db6wl = 32
byqrfgc6hilr9d1ot4wow8gw4syugtvz = 32
ta075al22zp3v6awtlw4kgkk446mjbiv = 32
hqqa90p27e9desx99q8skokcc46fujx4 = 32
uqc000q7g20l1k9zlwko80gsow5e59e7 = 32
gxx2r5d5oa0p8iykvc4ckgc4kc0teekv = 32
ayysoos5ltfua3d0m80ccocc0kcfhqyb = 32
dtj31vi4tzmh6lhk1iccc0os4cgsze1e = 32
fvn41hh2gnk6lbrq4w0wwgko8k5ihda8 = 32
oxamsba3qh0ro6xehkw8cg400s10tiyq = 32

** 编辑 **

那么,为什么这一切?为什么不直接使用uniqid()?因为uniqid() 是连续的并且是可预测的。因为你需要增加更多的熵。这个类不仅使用uniqid()“更多熵”参数,它还使用mt_rand()来填充生成的值。此处提供的类也将始终生成一个 32 字节(256 位)的字符串。

这个函数有多随机?要拥有重复的 ID,需要同时调用uniqid()准确,而mt_rand() 需要在同一时间返回准确相同的随机值订购......连续七次。底线是它是相当随机的。

** 编辑 2 **

您可能还对pure PHP UUID 实现感兴趣。

** 编辑 3 **

使用主键 (PK) 作为唯一文件名的问题在于它是可预测的。如果您打算直接从 URI 路由提供这些文件,则生成的非顺序值更安全。如果您打算以其他方式提供这些文件,那么无论如何都必须为这些文件分配一些唯一的密钥......并且由于完全相同的原因,这个密钥不能是连续的。因此,无论用例如何,使用非顺序唯一键作为文件名都是一个好主意。

【讨论】:

  • 但是其中一个值可以重复吗?
  • 不太可能,因为它基于 PHP 的 uniqid 函数。阅读here 了解独特性。对于您的需要(文件上传),我认为您不太可能遇到竞争状况。
  • 显然这仍然会产生重复。然而,发生这种情况的可能性很小。如果您只是简单地添加一个检查以查看是否已经存在同名的文件,然后选择其他文件,那么您会比较安全(同时由于并发上传,重复的可能性仍然很小)
  • @s1lence 因为uniqid() 是连续的并且可能提供一些可预测性。这个功能没有。 注意:我正在更新答案以使其更加“随机”:)
【解决方案2】:

保证文件名唯一的最简单方法是使用一个简单的序列,每个新图像都会增加。

序列

如果您使用像 MySQL 这样的数据库来存储图像的附加信息,您可以使用自动分配的主键列 ID 和 AUTO_INCREMENT,或者您也可以将当前序列值存储在简单的文本中文件。

请注意,使用文件存储当前值的选项是危险的,因为通过简单地访问它,两个并发文件上传可能会产生相同的文件名。您可以使用文件锁(参见flock 的文档)来规避这种情况,但这可能效率低下。

散列函数

使用散列函数不能保证产生唯一的 id,因为两个不同的输入可以产生相同的输出(通常概率很低)。此外,如果在非常短的时间范围内上传两个文件,时间戳也可能不准确,因此会导致文件名相同。

【讨论】:

  • 对于本示例或任何示例,在 url 中向用户显示主键是否存在任何安全问题?
  • 不,这实际上是一种很常见的方法。
【解决方案3】:

例如可以使用当前时间戳*随机的MD5

$date = date('Y-m-d H:i:s');
$filename = md5(uniqid($date, true) * rand()) . ".png";

也可以看看这里:PHP: How to generate a random, unique, alphanumeric string?

在这里:http://php.net/uniqid

【讨论】:

  • @Dev777 还可以看看其他 stackoverflow 问题,这应该可以让您到达您需要的地方,他们在这里的每个人都回答得更好! :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-06
  • 2019-02-20
  • 2011-09-19
相关资源
最近更新 更多