【问题标题】:Right way of watermarking & storing & displaying images in PHPPHP中水印和存储和显示图像的正确方法
【发布时间】:2015-01-19 10:50:08
【问题描述】:

我正在构建一个基于 Web 的系统,该系统将托管大量的高分辨率图像,并且它们将可供出售。当然,我永远不会显示高分辨率图像,而是在浏览时人们只会看到低分辨率、带水印的图像。目前工作流程如下:

PHP脚本处理高分辨率图片上传,当图片上传时,它会自动调整为低分辨率图片和缩略图,并且两个文件都保存在服务器上,(不添加水印) .

当人们浏览时,页面会显示图像的缩略图,点击后,它会放大并显示带有水印的低分辨率图像。目前,每当打开低分辨率图像时,我都会即时应用水印。

我的问题是,正确的方法是什么:

1) 我应该只在第一次访问时保存带有缩略图的低分辨率图像的第二个副本吗?我的意思是,如果有人访问图像,我会即时添加水印,然后显示图像并将其存储在服务器上。如果存在水印副本,则下次访问相同的图像时,只需显示 wm 副本,否则即时应用水印。 (如果 watermark.png 被更改,只需删除带水印的图像,它们将在访问时重新创建。

2) 我是否应该像现在一样继续应用水印。

我最大的问题是 PHP file_exists() 和向图像添加水印之间的区别有多大,例如:

$image = new Imagick();
$image->readImage($workfolder.$event . DIRECTORY_SEPARATOR . $cat . DIRECTORY_SEPARATOR .$mit);
$watermark = new Imagick();
$watermark->readImage($workfolder.$event . DIRECTORY_SEPARATOR . "hires" . DIRECTORY_SEPARATOR ."WATERMARK.PNG");
$image->compositeImage($watermark, imagick::COMPOSITE_OVER, 0, 0);

所有低分辨率图片均为 1024x1024,JPG 质量设置为 45%,并移除了所有不必要的滤镜,因此低分辨率图片的文件大小约为 40Kb-80Kb。

this question有点关系,只是规模和场景有点不同。

我在一个专用服务器(至强 E3-1245v2)cpu、32 GB 内存、2 TB 存储)上,该站点总体上的流量并不大,但不时会出现巨大的峰值。当图像发布时,我们每小时会获得几千次点击,人们通过图像浏览、下载、购买等。所以在正常使用时,我确信动态生成是正确的方法,我有点担心关于高峰期。

需要说明的是,我使用 ImageMagick 库进行图像处理,而不是 GD。

感谢您的意见。

更新

没有一个完整的解决方案的答案,但这很好,因为我从未寻找过。接受哪一个以及给予谁赏金是一个艰难的决定。

@Ambroise-Maupate 解决方案很好,但它依赖 PHP 来完成这项工作。

@Hugo Delsing 建议使用 web 服务器来提供缓存文件,减少对 PHP 脚本的调用,这意味着使用的资源更少,另一方面它对存储并不友好。

我将使用 2 个答案的混合合并解决方案,通过 CRON 作业来清除垃圾。

感谢您的指导。

【问题讨论】:

  • 我认为在“高峰期”的情况下,保存带水印的图像可能是个好主意。另一方面,这将大大增加您在硬盘上使用的空间(在这种情况下,2TB 可以变得“小”)。我建议使用基于“查看次数”的方法:在您的数据库中,存储查看每个 img 的次数,然后仅保存最“流行”的水印版本。根据您网站的发展,您将能够增加或减少“已保存的带水印的 img”的数量。
  • 问题在于,我们存储了事件的照片(泥泞、运动等)。当一个事件结束时,人们会开始看起来像任何创造“尖峰”的图像,然后它就会冷却下来。下一波“尖峰”将是一个不同的事件,因此它与之前的图像没有关系,所以在我的计数器击中目标并开始保存图像之前,尖峰已经很酷了......如果这使得感觉...
  • 您绝对应该缓存它们而不是每次都生成它们。如果本地没有足够的存储空间,可以将它们缓存在 CDN 层中。
  • 好的,埃米尔,我明白了。所以在这种情况下,只保存最后一个事件的水印版本。此外,您可以“准备”以节省时间:当您放置新事件的图像时,删除先前事件的水印,创建新 img 的所有预标记版本,然后使用新事件打开商店。跨度>

标签: php image server-load


【解决方案1】:

我个人会以 CDN 的方式创建一个静态/无 cookie 子域来处理这类图像。主要原因是:

  1. 图像只创建一次
  2. 只创建访问的图像
  3. 创建后,图像会从缓存中提供,速度会快很多。

第一步是在指向空文件夹的子域上创建一个网站。使用 IIS/Apache 或其他设置来禁用这个新网站的会话。还要在网站上设置一些长缓存头,因为内容不应该改变

第二步是创建一个包含以下内容的.htaccess 文件。

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)   /create.php?path=$1  [L]

这将确保如果有人访问现有图像,它将直接显示图像,而不会受到PHP 的干扰。每个不存在的请求都将由create.php 脚本处理,这是您接下来应该添加的内容。

<?php
function NotFound()
{
    if (!headers_sent()) {
        $protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0');
        header($protocol . ' 404 Not Found');
        echo '<h1>Not Found</h1>';
        exit;
    }
}

$p = $_GET['path'];

//has path
if (strlen($p)<=1)
    NotFound();

$clean = explode('?', $p);
$clean = explode('#', $clean[0]);
$params = explode('/', substr($clean[0], 1)); //drop first /

//I use a check for two, because I dont allow images in the root folder
//I also use the path to determine how it should look
//EG: thumb/125/90/imagecode.jpg
if (count($params)<2)
    NotFound();

$type = $params[0];

//I use the type to handle different methods. For this example I only used the full sized image
//You could use the same to handle thumbnails or cropped/watermarked
switch ($type) {
    //case "crop":if (Crop($params)) return; else break;
    //case "thumb":if (Thumb($params)) return; else break;
    case "image":if (Image($params)) return; else break;
}
NotFound();
?>
<?php
/*
Just some example to show how you could create a responds
Since you already know how to create thumbs, I'm not going into details

Array
(
    [0] => image
    [1] => imagecode.JPG
)
*/
function Image($params) {
    $tmp = explode('.', $params[1]);
    if (count($tmp)!=2)
        return false;

    $code = $tmp[0];


    //WARNING!! SQL INJECTION
    //USE PROPER DB METHODS TO GET REALPATH, THIS IS JUST EXAMPLE
    $query = "SELECT realpath FROM images WHERE Code='".$code."'";
    //exec query here to $row
    $realpath = $row['realpath'];


    $f = file_get_contents($realpath);

    if (strlen($f)<=0)
        return false;

    //create folder structure
    @mkdir($params[0]);

    //if you had more folders, continue creating the structure
    //@mkdir($params[0].'/'.$params[1]);

    //store the image, so a second request won't access this script
    file_put_contents($params[0].'/'.$params[1], $f);

    //you could directly optimize the image for web to make it even better
    //optimizeImage($params[0].'/'.$params[1]);

    //now serve the file to the browser, because even the first request needs to show the image
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    header('Content-Type: '.finfo_file($finfo, $params[0].'/'.$params[1]));

    echo $f;

    return true;
}
?>

【讨论】:

  • 虽然这是一种有趣的方法,但我有一些反对意见。第一个是,我不想透露文件名,你的解决方案......会这样做。其次,您回答假设我将使用 Apache,但事实并非如此。我认为@ambrosie-maupete 的答案是一个更好的套件。但是不得不承认“跳过” PHP 的想法确实是一个非常好的想法。
  • 我没有假设你使用 Apache,我什至提到了 IIS。以同样的方式工作。我确信其他网络服务器也有类似的选项。文件名也很容易绕过。如果您发送代码而不是参数 1 和 2(甚至 3、4、5 等),您可以像现在一样获得实际的文件名。要点是您需要调用指向实际图像的URL,并且仅在PHP 内容尚不可用时才提供它。所以只要以.jpg.png或其他图片扩展名结尾,文件名可以是任何东西
  • @EmilBorconi,只是为了论证,我更改了代码以使用代码而不是真实姓名。
【解决方案2】:

我建议您即时创建带水印的图像并按照每个人的建议同时缓存它们。

然后您可以创建一个 garbage-collector PHP 脚本,它将每天执行(使用 cron)。此脚本将浏览您的缓存文件夹以读取每个图像访问时间。这可以使用fileatime() PHP 方法来完成。那么当一个缓存的wm图片在24或48小时内没有被访问过,就删除它。

使用此方法,您可以处理 spike 周期,因为图像在第一次请求时被缓存。并且您将节省您的硬盘空间,因为您的垃圾收集器脚本会为您删除未使用的图像。

此方法仅在您的服务器分区启用 atime 更新时才有效。

http://php.net/manual/en/function.fileatime.php

【讨论】:

  • 我很好奇,如果你缓存图片,你将如何跟踪它最后一次访问的时间?
  • 如果您缓存图像,您的应用程序必须通过更新您的 HTML 视图直接从 Apache 或 Nginx 提供此图像。然后每次打开文件或在进程中使用文件时,必须更新其atime(如果您的磁盘分区启用atime)。如果你不喜欢这种低级的 IO 进程,你总是可以在你的数据库中实现这个机制。每次请求图像时,您都可以更新数据库的 last_read 字段,并且您的 garbage-collector 脚本将从数据库而不是文件的 atime 读取。
  • atime 在 Windows 上不可用,只有 linux? atime 确实非常适合处理垃圾。在我看来,在数据库中存储一些东西会很糟糕,因为你要做的不仅仅是提供图像。所以你仍然需要PHP 来处理图片。
  • 我不知道,我只使用 Debian 和 MacOS 系统。有些人使用clearstatcache() 来刷新 IO 统计信息。
  • 数据库不在讨论范围内。这将需要更多的资源,我认为这是一个非常糟糕的主意。另一方面低级 IO,这是一个非常好的主意。
【解决方案3】:

对于大多数情况,懒惰地应用水印可能最有意义(在请求时动态生成带水印的图像然后缓存结果)但是,如果您的需求量很大,您正在为自己创建一个 DOS 机制:创建上传时加水印的版本。

【讨论】:

  • 这样做的问题是用于水印的图像可以随时更改。这可能是一个很好的起点,但需要进一步的工作。
【解决方案4】:

考虑到您的 HDD 存储容量和 Pikes。

如果被查看,我只会创建一个带水印的图像。(所以在运行中是的)这样你就不会使用太多的空间来处理一堆正在或可能不会被查看的文件。

我不会给缩略图添加水印,我宁愿制作一个过滤器来伪造水印并防止被保存。该过滤器将应用于所有缩略图,而无需创建第二张图像。

通过这种方式,您的所有缩略图都带有水印(顶部带有其他元素的假冒)。

然后,如果查看其中一个缩略图,它会生成一个带水印的图像(仅一次),因为在生成后您会加载新的带水印的图像。

这将是处理 HDD 存储和 Pikes 的最有效方式。

另一种选择是升级您的托管服务。 Godaddy 提供无限的存储空间和带宽,每年大约 50 美元。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-03-17
    • 1970-01-01
    • 2018-04-05
    • 2018-12-10
    • 2021-12-18
    • 1970-01-01
    • 2015-04-15
    • 1970-01-01
    相关资源
    最近更新 更多