我在我的 Linux 服务器上收到了数千张用户上传的数千张图片,该服务器由 1and1.com 托管(我相信他们使用 CentOS,但不确定版本)。这是一个与语言无关的问题,但是,为了供您参考,我正在使用 PHP。

我的第一个想法是将它们全部转储到同一个目录中,但是,我记得不久前,一个目录中可以删除的文件或目录的数量是有限制的。

我的第二个想法是根据用户的电子邮件地址对目录内的文件进行分区(因为无论如何这是我用于用户名的地址),但我不想遇到目录中目录的限制......

无论如何,对于来自 user@domain.com 的图像,我将这样做:

/images/domain.com/user/images...

这样做明智吗?如果成千上万的用户都说“gmail”怎么办?也许我可以更深入,就像这样

/images/domain.com/[first letter of user name]/user/images...

所以对于 mike@gmail.com 来说,这将是......

/images/domain.com/m/mike/images...

这是一个不好的方法吗?其他人都在做什么?我也不想遇到太多目录的问题......


有关的:

有帮助吗?

解决方案

我会做以下事情:

  1. 获取每个图像的 MD5 哈希值。
  2. 将 MD5 哈希值写入您要跟踪这些内容的数据库中。
  3. 将它们存储在目录结构中,您可以使用 MD5 哈希十六进制字符串的前几个字节作为目录名称。因此,如果哈希值是“abcdef1234567890”,您会将其存储为“a/b/abcdef1234567890”。

使用哈希还可以合并多次上传的同一图像。

其他提示

扩展 Joe Beda 的方法:

  • 数据库
  • 数据库
  • 数据库

如果您关心按用户、原始文件名、上传日期、拍照日期 (EXIF) 等分组或查找文件,请将此元数据存储在数据库中,并使用适当的查询来挑选适当的文件。

使用数据库主键(无论是文件散列还是自动递增数字)在一组固定的目录中定位文件(或者,每个目录使用固定的最大文件数 N,当填满时,转到下一篇,例如这 k第一张照片应存储在 {somepath}/aaaaaa/bbbb.jpg 其中 aaaaaa = floor(k/N),格式为十进制或十六进制,bbbb = mod(k,N),格式为十进制或十六进制。如果这对您来说太扁平化,请使用类似的东西 {somepath}/aa/bb/cc/dd/ee.jpg)

不要直接向用户公开目录结构。如果他们使用 Web 浏览器通过 HTTP 访问您的服务器,请为他们提供一个类似 www.myserver.com/images/{primary key} 的 URL,并在 Content-Type 标头中编码正确的文件类型。

这是我不久前为这种情况编写的两个函数。它们已经在一个拥有数千名成员的网站上使用了一年多,每个成员都有大量文件。

本质上,这个想法是使用每个成员唯一数据库ID的最后一位数字来计算目录结构,为每个人提供一个唯一的目录。使用最后一位数字而不是第一位数字可以确保目录分布更均匀。每个成员都有一个单独的目录意味着维护任务要简单得多,而且您可以看到人们的东西在哪里(就像在视觉上一样)。

// checks for member-directories & creates them if required
function member_dirs($user_id) {

    $user_id = sanitize_var($user_id);

    $last_pos = strlen($user_id);
    $dir_1_pos = $last_pos - 1;
    $dir_2_pos = $last_pos - 2;
    $dir_3_pos = $last_pos - 3;

    $dir_1 = substr($user_id, $dir_1_pos, $last_pos);
    $dir_2 = substr($user_id, $dir_2_pos, $last_pos);
    $dir_3 = substr($user_id, $dir_3_pos, $last_pos);

    $user_dir[0] = $GLOBALS['site_path'] . "files/members/" . $dir_1 . "/";
    $user_dir[1] = $user_dir[0] . $dir_2 . "/";
    $user_dir[2] = $user_dir[1] . $dir_3 . "/";
    $user_dir[3] = $user_dir[2] . $user_id . "/";
    $user_dir[4] = $user_dir[3] . "sml/";
    $user_dir[5] = $user_dir[3] . "lrg/";

    foreach ($user_dir as $this_dir) {
        if (!is_dir($this_dir)) { // directory doesn't exist
            if (!mkdir($this_dir, 0777)) { // attempt to make it with read, write, execute permissions
                return false; // bug out if it can't be created
            }
        }
    }

    // if we've got to here all directories exist or have been created so all good
    return true;

}

// accompanying function to above
function make_path_from_id($user_id) {

    $user_id = sanitize_var($user_id);

    $last_pos = strlen($user_id);
    $dir_1_pos = $last_pos - 1;
    $dir_2_pos = $last_pos - 2;
    $dir_3_pos = $last_pos - 3;

    $dir_1 = substr($user_id, $dir_1_pos, $last_pos);
    $dir_2 = substr($user_id, $dir_2_pos, $last_pos);
    $dir_3 = substr($user_id, $dir_3_pos, $last_pos);

    $user_path = "files/members/" . $dir_1 . "/" . $dir_2 . "/" . $dir_3 . "/" . $user_id . "/";
    return $user_path;

}

sanitize_var() 是一个用于清理输入并确保其为数字的支持函数,$GLOBALS['site_path'] 是服务器的绝对路径。希望它们能够不言自明。

我用于另一个要求但可以满足您的需求的是使用一个简单的约定。

加1得到新号码的长度,然后加上这个号码的前缀。

例如:

假设'a'是一个用最后一个id设置的var。

a = 564;
++a;
prefix = length(a);
id = prefix + a; // 3565

然后,您可以使用目录的时间戳,使用以下约定:

20092305 (yyyymmdd)

然后你可以像这样分解你的路径:

2009/23/05/3565.jpg

(或者更多)

这很有趣,因为您可以按日期和数字保留排序订单(有时很有用),并且您仍然可以在更多目录中分解路径

Joe Beda 的回答几乎是完美的,但请注意,MD5 已被证明在笔记本电脑上 iirc 2 小时内可碰撞?

也就是说,如果您实际上按照所描述的方式使用文件的 MD5 哈希值,您的服务将容易受到攻击。攻击会是什么样子?

  1. 黑客不喜欢某张照片
  2. 他确保这是你正在使用的纯MD5(image+secret_string的MD5可以把他吓跑)
  3. 他使用了一种神奇的方法,将哈希的图片(在这里发挥你的想象力)与他不喜欢的照片碰撞
  4. 他像平常一样上传照片
  5. 您的服务会用新服务覆盖旧服务并显示两者

有人说:那么我们就不要覆盖它。然后,如果可以预测某人会上传某些内容(例如网络上流行的图片可能会被上传),可以先获取它的“哈希位置”。用户在上传小猫的图片时会很高兴,他会发现它实际上看起来像(在这里发挥你的想象力)。我说:使用 SHA1,因为 iirc 127 年来它已被证明可以被 10,000 台计算机集群破解?

在这方面可能会迟到。但一种解决方案(如果它适合您的用例)可能是文件名哈希。这是一种使用文件名创建易于复制的文件路径的方法,同时还创建分布良好的目录结构。例如,您可以使用文件名的哈希码的字节作为路径:

String fileName = "cat.gif";
int hash = fileName.hashCode();
int mask = 255;
int firstDir = hash & mask;
int secondDir = (hash >> 8) & mask;

这将导致路径为:

/172/029/cat.gif

然后你可以找到 cat.gif 通过再现算法在目录结构中。

使用十六进制作为目录名称就像转换 int 价值观:

String path = new StringBuilder(File.separator)
        .append(String.format("%02x", firstDir))
        .append(File.separator)
        .append(String.format("%02x", secondDir)
        .toString();

导致:

/AC/1D/cat.gif

几年前我写了一篇关于此的文章,最近将其移至 Medium。它有更多细节和一些示例代码: 文件名哈希:创建哈希目录结构. 。希望这可以帮助!

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top