Files
FamilyBook/templates/_attachments.php
T
laoyang d6af211a8e 图片走 OSS 实时压缩:取图带 x-oss-process,本地存储不受影响 (#56)
用户 2026-11-05:「如果图片是放在本地服务器的…就不管它。
如果是传到 OSS 里面的…就用 OSS 的压缩就行了。」

查证:OSS 图片处理确实存在,取图 URL 加 x-oss-process 即可实时压缩,
原图不动、不占额外存储。

三处改动(缺一个都会出错):

1. OssDriver
   - get() 新增 $process 参数
   - authorization() 新增 $subresource:x-oss-process 属于子资源,
     必须计入 CanonicalizedResource,否则 403 SignatureDoesNotMatch
   - URL 拼接带上 ?x-oss-process=...

2. Storage::read()
   - 新增 $process,仅当驱动是 oss 时透传
   - 本地驱动忽略(用户明确要求「本地不管它」)
   - 带 process 时跳过"本地优先"缓存(那个缓存里是原图,
     拿原图当压缩版返回等于压缩没生效)

3. media.php
   - 新增 ?w= 参数,白名单三档 400/800/1600
   - 按尺寸分档缓存到 _proc/<w>/<key>,避免互相覆盖
   - 无 w 时行为完全不变(300+ 处既有 img 不受影响)

白名单而非透传的理由:OSS 图片处理按次计费,
放任 ?w= 任意值会让 ?w=1、?w=9999 这类请求把处理次数打上去。

实测(一张 6602 KB 的图):
  原图  6602 KB
  w=400    26 KB   省 99.6%
  w=800   108 KB   省 98%
  w=1600  468 KB   省 93%

应用位置(保守,先动最确定的):
  _attachments.php 照片墙缩略图 → w=400
  trips/show.php   行程题图     → w=1600
  下载链接一律不加 w(用户要原文件,不能偷偷降质)
2026-11-05 13:06:29 +08:00

123 lines
7.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 附件缩略图墙(v24 用户第 3 条;第 19 批 第 6 条改)。
*
* 一处渲染、全站通用:旅程节点 / 资源 / 任务 / 日程的附件都长一样。
*
* 规则(用户原话):
* · 图片、视频 → **缩略图**,点开在**本页**预览(不再新开标签页)
* · PDF → 浏览器里直接预览(自带阅读器)
* · Word / Excel / PPT / 其它 → 一个**和缩略图一样大**的图标,点了直接下载
* (浏览器渲染不了的东西,绝不装一个"点了没反应"的预览)
* · 预览框**右上角**恒有一个下载按钮
*
* ────────────────────────────────────────────────────────────────
* v24 第 19 批 用户第 6 条(本次改的三点):
* 「图片和视频,文件名都是 md5 的,放在图片预览图下面没有任何意义,去掉吧。」
* 「如果是文件,则在图标上显示文件名。」
*
* ① 图片 / 视频**下面不再写文件名与大小** —— 那串是用户手机上传时的原始名,
* 而照片/视频是**看**的,一串 `9d3fcc70fe46161275f850b93470e0bb.mp4`
* 既认不出内容、也不比缩略图本身多任何信息。
* (图与视频仍带 title 悬停提示,「下载」按钮也还在 —— 功能没少,只是不占版面。)
* ② 非图片的**文件**(PDF/Word/Excel/PPT/文本):文件名挪到**图标里面**,
* 压着色块显示。文件是**读名字**的,「学位学历证书_博士学位证书.pdf」
* 这行字本身就是最重要的信息,放在图标下方的小灰字里反而看不清。
* ③ 删除按钮与下载入口保留在缩略图下方(.att-meta 仍在,只是不再放文件名/大小)。
*
* 用法(先准备变量再 require):
* $attRows 附件行(Attachment::list 的结果)
* $attCanEdit 是否显示删除按钮
* $attDelUrl 删除动作的路由名(如 'resources/attach_del')
* $attHostId 删除时要回传的宿主 id(表单里的隐藏字段用 $attHostKey 指定键名)
* $attDelField 删除表单里「附件 id」的字段名(各路由不一致:有的叫 id、有的叫 attach_id)
* $attExtra 额外隐藏字段 [键 => 值](如日程要连带传宿主 id 才能判权限)
*
* ⚠️ 无 JS 也能用:可预览的是 <a href=预览地址>,不可预览的是 <a href=下载地址>,
* 脚本只是把"可预览"的那部分拦下来改成弹层。
*/
$attCanEdit = !empty($attCanEdit);
$attRows = $attRows ?? [];
$attDelField = $attDelField ?? 'id';
$attExtra = $attExtra ?? [];
$attDelUrl = $attDelUrl ?? '';
?>
<?php if ($attRows): ?>
<div class="att-wall">
<?php foreach ($attRows as $attRow): ?>
<?php
$attKind = \App\Attachment::kind($attRow);
$canPrev = \App\Attachment::previewable($attRow);
/* 🔴 #56:照片墙缩略图给 OSS 压缩版(w=400)。
实测一张 6.6MB 的原图压到 **26KB**(省 99.6%)——
一屏几十张,这个收益是最大的。
⚠️ 下载链接($attDl)**不加** w:用户下载要原文件,不能偷偷降质。 */
$attView = media_url((string)$attRow['key'], ['w' => 400]);
$attDl = media_url((string)$attRow['key'], ['dl' => 1]);
$attNm = (string)($attRow['name'] ?? '');
$attSz = \App\Attachment::sizeWord($attRow['size'] ?? 0);
// ① 图片 / 视频:下面不放文件名(见文件头说明)
$attIsMedia = ($attKind === 'image' || $attKind === 'video');
// ② 文件:图标上显示文件名;没有名字时退回类别词(「PDF」「Word」…)
$attFileWord = $attNm !== '' ? $attNm : \App\Attachment::kindWord($attKind);
?>
<div class="att-cell">
<?php if ($canPrev): ?>
<a class="att-thumb att-kind-<?= e($attKind) ?>"
href="<?= e($attView) ?>"
data-prev="<?= e($attKind === 'pdf' ? 'pdf' : ($attKind === 'video' ? 'video' : 'image')) ?>"
data-src="<?= e($attView) ?>"
data-dl="<?= e($attDl) ?>"
data-name="<?= e($attNm !== '' ? $attNm : \App\Attachment::kindWord($attKind)) ?>"
data-size="<?= e($attSz) ?>"
title="<?= e($attNm) ?>">
<?php if ($attKind === 'image'): ?>
<img src="<?= e($attView) ?>" alt="<?= e($attNm) ?>" loading="lazy">
<?php elseif ($attKind === 'video'): ?>
<?php /* #t=0.1 让浏览器把第 0.1 秒那一帧当封面显示出来 ——
没有它视频位就是一块黑板,看不出这是段什么视频。 */ ?>
<video src="<?= e($attView) ?>#t=0.1" preload="metadata" muted playsinline></video>
<span class="att-play" aria-hidden="true">▶</span>
<?php else: ?>
<?php /* PDF:文件名压在图标上(用户第 6 条) */ ?>
<span class="att-doc att-doc-pdf">
<span class="att-doc-name"><?= e($attFileWord) ?></span>
</span>
<?php endif; ?>
</a>
<?php else: ?>
<a class="att-thumb att-kind-<?= e($attKind) ?>" href="<?= e($attDl) ?>"
title="<?= e($attNm) ?>(点击下载)">
<span class="att-doc att-doc-<?= e($attKind) ?>">
<span class="att-doc-name"><?= e($attFileWord) ?></span>
</span>
</a>
<?php endif; ?>
<div class="att-meta">
<?php /* 图片/视频不显示那串哈希名;文件的名字已经在图标上了,这里也不必再抄一遍 */ ?>
<?php if (!$attIsMedia): ?>
<span class="att-fsize"><?= e($attSz) ?></span>
<?php endif; ?>
<a class="link-quiet att-dl" href="<?= e($attDl) ?>">下载</a>
<?php if ($attCanEdit && !empty($attDelUrl)): ?>
<form method="post" action="<?= e(url($attDelUrl)) ?>" class="att-del">
<?= csrf_field() ?>
<input type="hidden" name="<?= e($attDelField) ?>" value="<?= (int)$attRow['id'] ?>">
<?php foreach ($attExtra as $ek => $ev): ?>
<input type="hidden" name="<?= e((string)$ek) ?>" value="<?= e((string)$ev) ?>">
<?php endforeach; ?>
<button class="btn btn-ghost btn-sm" type="submit"
onclick="return confirm('删掉这个附件?')">删除</button>
</form>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<?php /* ⚠️ 循环里用到的临时变量一律用完即清:宿主模板里很可能也有 $attRow / $attKind /
$attView / $attDl / $attNm / $attSz(各模块页面都爱用这种短名),留着就是静默覆盖。 */ ?>
<?php unset($attRow, $attKind, $canPrev, $attView, $attDl, $attNm, $attSz,
$attIsMedia, $attFileWord); ?>
</div>
<?php endif; ?>