严重修复:图片压缩参数被应用到了视频上,导致视频播不了 (#56)
用户报(2026-11-05):「你好像把 .mp4 后面也带上了问号,写成了 1600
这样的参数…你刚才没有进行图片压缩的时候,我自己播放好像还是好的。
是你更新了这个功能之后,出了这个问题。」
用户完全正确,这是我的错。
根因:media.php 只看 $imgW,没判是不是图片:
Storage::read($familyId, $key, $imgW > 0 ? $OssSizes[$imgW] : );
⇒ 视频 URL 带 ?w=1600 后,给 .mp4 对象传了
x-oss-process=image/resize,l_1600
⇒ OSS 图片处理服务对非图片返回错误内容
⇒ <video> 拿不到有效数据 ⇒ 播不了
三道防线:
1. _attachments.php(直接触发点)
原来对所有附件都传 w=1600。改为 $attKind === image 才传。
视频只传 v(版本号,破缓存用)。
2. media.php(核心防线)
$imgW 与 $isImageKey 绑定,只有图片才允许压缩。
$isImageKey 判定提前到 Storage::read 之前,
并扩到扩展名兜底(jpg/png/webp...),
视频/文档扩展名强制 false。
3. media_url()(前置防线)
生成 URL 时按扩展名过滤:非图片自动去掉 w。
这样即使调用方传错,URL 里也不会出现无效参数。
验证(模拟事故场景,URL 带 ?w=1600 请求 .mp4):
HTTP 206,Content-Type: video/mp4
无 hvc1,含 avc1
ffprobe: h264/yuv420p,2.19MB ✅ 正常
media_url 生成视频 URL:...mp4&v=2295119(无 w)
media_url 生成图片 URL:...jpg&w=1600&v=609017(有 w)
This commit is contained in:
@@ -123,6 +123,26 @@ function media_url(?string $key, array $extra = [], int $ver = 0): string
|
|||||||
return $key;
|
return $key;
|
||||||
}
|
}
|
||||||
$q = array_merge(['k' => ltrim($key, '/')], $extra);
|
$q = array_merge(['k' => ltrim($key, '/')], $extra);
|
||||||
|
|
||||||
|
/* 🔴🔴 #56 严重修复(用户 2026-11-05:「你好像把 .mp4 后面也带上了问号,
|
||||||
|
写成了 1600 这样的参数…是你更新了这个功能之后,出了这个问题」):
|
||||||
|
|
||||||
|
**`w`(压缩档位)只允许给图片。** 给视频传
|
||||||
|
`x-oss-process=image/resize,l_1600` 会让 OSS 图片处理服务
|
||||||
|
对非图片返回错误内容 ⇒ `<video>` 播不了。
|
||||||
|
|
||||||
|
这里用扩展名判断(只有 key、没有 MIME):
|
||||||
|
· 图片扩展名 → 保留 w
|
||||||
|
· 其它(mp4/pdf…) → **去掉 w**
|
||||||
|
⚠️ `v` 版本号**所有类型都保留**:视频转码后也要靠它破缓存。
|
||||||
|
它只是查询串,不参与 OSS 签名。 */
|
||||||
|
if (isset($q['w'])) {
|
||||||
|
$isImg = (bool)preg_match('#\.(jpe?g|png|gif|webp|bmp|avif|heic)$#i', $key);
|
||||||
|
if (!$isImg) {
|
||||||
|
unset($q['w']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($ver > 0) {
|
if ($ver > 0) {
|
||||||
$q['v'] = $ver;
|
$q['v'] = $ver;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,25 @@ $OssSizes = [
|
|||||||
if (!array_key_exists($imgW, $OssSizes)) {
|
if (!array_key_exists($imgW, $OssSizes)) {
|
||||||
$imgW = 0; // 非法值一律按原图处理
|
$imgW = 0; // 非法值一律按原图处理
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 这个 key 是图片吗(#56)。
|
||||||
|
*
|
||||||
|
* ⚠️ **必须在 Storage::read 之前算好** —— 压缩参数只允许给图片,
|
||||||
|
* 给视频传 `x-oss-process=image/...` 会让 OSS 返回错误内容,
|
||||||
|
* 表现就是"视频播不了"。(这就是 2026-11-05 那次事故的根因。)
|
||||||
|
*
|
||||||
|
* ⚠️ 除了路径前缀,再看扩展名兜底:`files/xxx.mp4` 不在前缀里,
|
||||||
|
* 但万一将来有图片挪到别的目录,扩展名还能挡住视频。
|
||||||
|
*/
|
||||||
|
$isImageKey = (bool)preg_match('#^avatars/|^bookcovers/|^posters/|^photos/|^tickets/#', $key);
|
||||||
|
if (!$isImageKey && preg_match('#\.(jpe?g|png|gif|webp|bmp|avif)$#i', $key)) {
|
||||||
|
$isImageKey = true;
|
||||||
|
}
|
||||||
|
/* 视频/文档扩展名强制非图片(防止上面的扩展名兜底误判) */
|
||||||
|
if (preg_match('#\.(mp4|mov|m4v|webm|avi|mkv|mp3|m4a|pdf|docx?|xlsx?|pptx?|rar|zip)$#i', $key)) {
|
||||||
|
$isImageKey = false;
|
||||||
|
}
|
||||||
$stageTok = (string)($_GET['stage'] ?? '');
|
$stageTok = (string)($_GET['stage'] ?? '');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -188,7 +207,6 @@ foreach (explode('/', $key) as $seg) {
|
|||||||
不让缓存无限长大把磁盘吃满。
|
不让缓存无限长大把磁盘吃满。
|
||||||
|
|
||||||
⚠️ 顺序不能反:先放锁、再取内容(取内容可能要几百毫秒)。 */
|
⚠️ 顺序不能反:先放锁、再取内容(取内容可能要几百毫秒)。 */
|
||||||
$isImageKey = (bool)preg_match('#^avatars/|^bookcovers/|^posters/|^photos/|^tickets/#', $key);
|
|
||||||
if ($isImageKey) {
|
if ($isImageKey) {
|
||||||
// 图片是"只读、可重复取、可静态化"的,两次都用不到会话写权限
|
// 图片是"只读、可重复取、可静态化"的,两次都用不到会话写权限
|
||||||
if (session_status() === PHP_SESSION_ACTIVE) {
|
if (session_status() === PHP_SESSION_ACTIVE) {
|
||||||
@@ -196,7 +214,23 @@ if ($isImageKey) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 🔴🔴 #56 严重修复(用户 2026-11-05:「你好像把 .mp4 后面也带上了问号…
|
||||||
|
是你更新了这个功能之后,出了这个问题」):
|
||||||
|
|
||||||
|
**图片压缩参数绝不能应用到视频上。**
|
||||||
|
|
||||||
|
原来的写法只看 `$imgW`,不看这是不是图片:
|
||||||
|
|
||||||
$res = Storage::read($familyId, $key, $imgW > 0 ? $OssSizes[$imgW] : '');
|
$res = Storage::read($familyId, $key, $imgW > 0 ? $OssSizes[$imgW] : '');
|
||||||
|
|
||||||
|
⇒ 视频请求带上 `?w=1600` 后,会给 **.mp4 对象**传
|
||||||
|
`x-oss-process=image/resize,l_1600` ⇒ OSS 图片处理服务
|
||||||
|
对非图片返回错误/空内容 ⇒ `<video>` 拿不到有效数据 ⇒ **播不了**。
|
||||||
|
|
||||||
|
⇒ 现与 `$isImageKey` 绑定:**只有图片才允许压缩**。
|
||||||
|
视频/PDF/文档一律走原样。 */
|
||||||
|
$res = Storage::read($familyId, $key,
|
||||||
|
($imgW > 0 && $isImageKey) ? $OssSizes[$imgW] : '');
|
||||||
$status = (int)($res['status'] ?? 0);
|
$status = (int)($res['status'] ?? 0);
|
||||||
$body = (string)($res['body'] ?? '');
|
$body = (string)($res['body'] ?? '');
|
||||||
|
|
||||||
|
|||||||
@@ -49,14 +49,20 @@ $attDelUrl = $attDelUrl ?? '';
|
|||||||
<?php
|
<?php
|
||||||
$attKind = \App\Attachment::kind($attRow);
|
$attKind = \App\Attachment::kind($attRow);
|
||||||
$canPrev = \App\Attachment::previewable($attRow);
|
$canPrev = \App\Attachment::previewable($attRow);
|
||||||
/* 🔴 #56(用户:「400压缩的太多了。800也不够,只保留一个1600。」)
|
/* 🔴🔴 #56(用户 2026-11-05:「你好像把 .mp4 后面也带上了问号,
|
||||||
照片墙改用 1600 档 —— 缩略图看着小,但点开就是大图,
|
写成了 1600 这样的参数…是你更新了这个功能之后,出了这个问题」)
|
||||||
用同一份缓存比"缩略图 400 + 大图 1600"两套更省事,
|
|
||||||
而且手机上不会糊。
|
**压缩档位只能给图片。** 原来这里对**所有附件**都传 `w=1600`:
|
||||||
⚠️ 下载链接($attDl)**不加** w:要原文件,不能偷偷降质。 */
|
视频 URL 变成 `media.php?k=xxx.mp4&w=1600` ⇒ OSS 收到
|
||||||
/* #57:第三个参数传 size 当版本号 —— 转码后体积变了,URL 就变,
|
`x-oss-process=image/resize,l_1600` 去处理 .mp4 ⇒ 返回错误内容
|
||||||
浏览器不会再用旧的 HEVC 缓存。 */
|
⇒ `<video>` 拿不到有效数据 ⇒ **播不了**。
|
||||||
$attView = media_url((string)$attRow['key'], ['w' => 1600],
|
|
||||||
|
⇒ `$attKind === 'image'` 才传 `w`。
|
||||||
|
|
||||||
|
⚠️ `v`(版本号)**所有类型都传** —— 视频转码后靠它破缓存。
|
||||||
|
⚠️ 下载链接 `$attDl` 一律不带 `w`(要原文件,不能偷偷降质)。 */
|
||||||
|
$attOpts = $attKind === 'image' ? ['w' => 1600] : [];
|
||||||
|
$attView = media_url((string)$attRow['key'], $attOpts,
|
||||||
(int)($attRow['size'] ?? 0));
|
(int)($attRow['size'] ?? 0));
|
||||||
$attDl = media_url((string)$attRow['key'], ['dl' => 1]);
|
$attDl = media_url((string)$attRow['key'], ['dl' => 1]);
|
||||||
$attNm = (string)($attRow['name'] ?? '');
|
$attNm = (string)($attRow['name'] ?? '');
|
||||||
|
|||||||
Reference in New Issue
Block a user