Chevereto 3.20+ 与 Windows Server 的兼容性问题

前言

最近在给各服务器升级系统/环境,顺便升级各站点的程序,到 Chevereto 的时候发现了很多问题。当然也和开发者联系过了,但回复是不会为 Windows 做特别兼容,在 Windows 下请使用 Docker。
虽然用 Windows 跑 PHP 的都没多少人,更别说 Chevereto 用户了,但存在即合理,官方不修就自己上吧,也为碰巧找到这篇文章的你提供一点点思路。本文基于:Chevereto 3.20.17 & PHP 7.4.28;Chevereto 4.0.0.beta.7 & PHP 8.1.4

路径分隔符不同导致的问题

Windows 系统的路径分隔符为反斜杠(\),而 Linux 系统是正斜杠(/),当代码中使用了 PHP 自带函数获取路径,再进行路径拼接的话就会出现这种结果:D:\A\B/C/D.jpg,这并没有什么问题,PHP 可以正常处理。但如果对这一路径字符串进行替换、比较等操作,就会出现问题,因为D:\A\B/C/D.jpgD:/A/B/C/D.jpg并不相等,但他们指向的是同一个文件。

在 Chevereto 3.20+ 版本中,此问题会导致上传用户头像后卡住,在 4.0+ 版本中,还会导致网站无法正常加载静态资源。修复很简单,将路径字符串中的反斜杠(\)全部替换成正斜杠(/)即可。

Chevereto 3.20.X 版本:/app/lib/classes/class.localstorage.php:62
Chevereto 4.0.X 版本:/app/src/Legacy/Classes/LocalStorage.php:63

$target_filename = str_replace('/.\/', '/', $target_filename);

改为

$target_filename = str_replace('\\', '/', $target_filename);

即可解决上传头像后卡住问题。

开发者原来用的/.\/我并不理解他是想把什么替换掉,理解的小伙伴欢迎留言告知。如果是用\作为转义想把/./看作非正则进行替换的话是不行的,已测试。

Chevereto 4.0.X 版本:/app/src/Legacy/functions.php:893

define('PATH_PUBLIC', dirname(__DIR__, 3) . '/');

改为

define('PATH_PUBLIC', str_replace('\\', '/', dirname(__DIR__, 3)) . '/');

即可解决静态资源加载问题。

Intervention Image 与 Windows 的兼容性问题

Intervention Image 的 save 方法在 Windows 下必须使用图片扩展名,而 Chevereto 在图片上传处理中全程使用tempnam()函数生成的 .tmp 文件,在用到 save 方法时就会出错 (上传中只有 jpg、bmp 格式用到该方法)。

修复这个问题我用了个简单但牺牲了点磁盘 I/O 的方法,save 时加上原图片扩展名,即存为 .tmp.xxx 文件,再将 .tmp.xxx 文件 rename 为 .tmp 文件。(为什么不一开始就存为图片扩展名的文件呢?因为 .tmp 文件是最先生成的,要这么改的话要改太多东西,可能会出别的问题)

以下更改仅供参考,如需使用请看清区别,做好备份,无脑 Ctrl C V 不可取。
Chevereto 3.20.X 版本:/app/lib/classes/class.upload.php:160
Chevereto 4.0.X 版本:/app/src/Legacy/Classes/Upload.php:220

if (array_key_exists('exif', $this->options)) {
    $this->source_image_exif = null;
    try {
        $this->source_image_exif = \exif_read_data($this->downstream);
    } catch(Throwable $e) {
    }
    if (isset($this->source_image_exif)) {
        $this->source_image_exif['FileName'] = $this->source_filename;
        if (isset($this->source_image_exif['Orientation'])) {
            ImageManagerStatic::make($this->downstream)->orientate()->save();
        }
    }
    if (!$this->options['exif']) {
        unset($this->source_image_exif);
        if(ImageManagerStatic::getManager()->config['driver'] === 'imagick') {
            $img = ImageManagerStatic::make($this->downstream);
            $img->getCore()->stripImage();
            $img->save();
        } else {
            $img = @imagecreatefromjpeg($this->downstream);
            if ($img) {
                imagejpeg($img, $this->downstream, 90);
                imagedestroy($img);
            } else {
                throw new UploadException("GD: Unable to create a new JPEG without Exif data", 444);
            }
        }
    }
}

改为

if (array_key_exists('exif', $this->options)) {
    $this->source_image_exif = null;
    $tmp_name_img = $this->downstream . '.' . $this->extension;
    try {
        $this->source_image_exif = \exif_read_data($this->downstream);
    } catch(Throwable $e) {
    }
    if (isset($this->source_image_exif)) {
        $this->source_image_exif['FileName'] = $this->source_filename;
        if (isset($this->source_image_exif['Orientation'])) {
            ImageManagerStatic::make($this->downstream)->orientate()->save($tmp_name_img);
            try {
                $renamed = rename($tmp_name_img, $this->downstream);
            } catch(Throwable $e) {
                throw new UploadException('Unable to rename tmp_name_img to downstream', 122);
            }
        }
    }
    if (!$this->options['exif']) {
        unset($this->source_image_exif);
        if(ImageManagerStatic::getManager()->config['driver'] === 'imagick') {
            $img = ImageManagerStatic::make($this->downstream);
            $img->getCore()->stripImage();
            $img->save($tmp_name_img);
            try {
                $renamed = rename($tmp_name_img, $this->downstream);
            } catch(Throwable $e) {
                throw new UploadException('Unable to rename tmp_name_img to downstream', 122);
            }
        } else {
            $img = @imagecreatefromjpeg($this->downstream);
            if ($img) {
                imagejpeg($img, $this->downstream, 90);
                imagedestroy($img);
            } else {
                throw new UploadException("GD: Unable to create a new JPEG without Exif data", 444);
            }
        }
    }
}

即可。

Chevereto 3.20.X 版本:/app/lib/classes/class.imageconvert.php:29
Chevereto 4.0.X 版本:/app/src/Legacy/Classes/ImageConvert.php改写法了,看着改就行。

public function __construct($source, $to, $destination, $quality=90)
{
    if(!in_array($to, ['jpg', 'gif', 'png'])) {
        return $source;
    }
    $image = ImageManagerStatic::make($source);
    $image->encode($to, $quality)->save($destination);
    $this->out = $destination;
}

改为

public function __construct($source, $to, $destination, $quality=90)
{
    if(!in_array($to, ['jpg', 'gif', 'png'])) {
        return $source;
    }
    $source_img = $destination . '.' . $to;
    $image = ImageManagerStatic::make($source);
    $image->encode($to, $quality)->save($source_img);
    try {
        $renamed = rename($source_img, $destination);
    } catch(Throwable $e) {
        throw new Exception('Unable to rename source_img to destination', 122);
    }
    $this->out = $destination;
}

即可。

其他问题

待发现。欢迎留言探讨。

点赞
  1. kiss先生说道:

    狗子你买的东西质保不????非常在意

    1. 小白-白说道:

      商品图上有写

  2. kiss先生说道:

    卖 打错字了

  3. 长夜未央说道:

    小白白居然还在更新,应该毕业了吧,真用爱发电呐 :a:

    1. 小白-白说道:

      现在是无业游民 博客看心情更 反正没什么成本

      1. 四季奶青全糖加冰说道:

        网站好漂亮哦 我可以仿一个不哈哈(逛php主题突然发现的

        1. 屋塔小貓说道:

          馬上就要變成俄羅斯套娃了 :huaji9:

kiss先生进行回复 取消回复

电子邮件地址不会被公开。必填项已用 * 标注