DEV Community

Maomao
Maomao

Posted on

用 ThinkPHP 做资料站:图片上传成功,不代表功能就做好了

最近在做一个中文资料站,后台需要支持上传多张截图、编辑图片 Alt 文本,以及给图片添加水印。

最初觉得,这应该是一个很常规的功能:接入上传组件,保存图片地址,前台循环展示。

实际做下来,问题却集中在“上传成功”之后。

有的图片只有几百 KB,但尺寸很大;有的 Alt 文本可以输入,保存后却恢复成默认值;还有的水印明明重新生成了,浏览器显示的仍然是旧图。

下面整理几个值得提前考虑的实现细节。

一、文件大小和像素尺寸,要分别检查

图片只有 300KB,不代表处理它只需要很少的内存。

JPEG、PNG 等格式保存的是压缩数据。图片被解码后,占用空间主要与像素数量有关。

例如,一张 12000 × 8000 的图片,如果按每像素 4 字节估算,单份像素数据就需要:

12000 × 8000 × 4
= 384,000,000 字节
≈ 366 MiB
Enter fullscreen mode Exit fullscreen mode

这还没有包含图片库开销,以及缩放、添加水印时创建的额外缓冲区。

因此,上传限制不能只看:

文件不超过 10MB
Enter fullscreen mode Exit fullscreen mode

还需要考虑:

  • 图片宽度和高度;
  • 总像素数量;
  • 服务端处理时的内存预算;
  • 最终展示是否真的需要原始尺寸。

对于很长或很宽的截图,可以先在浏览器端等比例缩小,再上传。但客户端处理只是改善体验,服务端仍需独立验证文件类型、尺寸和大小。

更重要的是:如果图片处理失败,接口应该明确返回失败原因,不能让用户收到“成功”,最后却找不到图片。

二、自动生成的 Alt,不应该覆盖手动输入

自动补充 Alt 文本很方便。例如上传截图后,默认生成:

某机场节点测速截图 1
Enter fullscreen mode Exit fullscreen mode

但编辑者可能希望改成更具体的描述:

香港与日本节点的晚高峰延迟测试
Enter fullscreen mode Exit fullscreen mode

这里需要区分三个状态:

  1. 尚未填写;
  2. 自动生成的默认值;
  3. 用户手动编辑的值。

如果把它们混成一个字段,每次刷新表单或重新整理图片数据时,就可能把手动内容覆盖掉。

尤其要小心这样的输入框绑定:

<el-input :model-value="savedAlt || generatedAlt" />
Enter fullscreen mode Exit fullscreen mode

当用户清空文本时,空字符串会触发右侧的默认值,输入框马上又被填满,看起来就像“怎么都改不了”。

更合理的方式是给编辑过程单独保存草稿:

<script setup>
import { ref } from 'vue'

const altDraft = ref('')
</script>

<template>
  <el-input
    v-model="altDraft"
    placeholder="描述这张图片的内容"
  />
</template>
Enter fullscreen mode Exit fullscreen mode

初始化时读取已有内容,编辑时保留用户输入,保存时再应用明确的规则。不要在每次渲染时重新生成默认文本。

还有一个容易忽略的细节:装饰性图片可以使用空的 alt=""。如果系统需要支持这种情况,就应该提供明确的“装饰图片”选项,而不是把所有空值都理解成漏填。

三、图片地址,不适合承担所有身份识别工作

图片上传后,地址可能经历多次变化:

/uploads/test.webp
Enter fullscreen mode Exit fullscreen mode

添加缓存版本参数后:

/uploads/test.webp?v=2
Enter fullscreen mode Exit fullscreen mode

接入 CDN 后:

https://cdn.example.com/uploads/test.webp?v=2
Enter fullscreen mode Exit fullscreen mode

如果 Alt 文本使用完整 URL 作为唯一键,这些变化就可能导致原来的描述找不到对应图片。

更容易维护的数据结构,是为图片保留稳定的 ID:

{
  "id": "image_1042",
  "url": "/uploads/test.webp",
  "alt": "香港与日本节点的晚高峰延迟测试",
  "sort": 1
}
Enter fullscreen mode Exit fullscreen mode

这样,地址负责定位资源,ID 负责关联描述、排序等信息。

调整图片顺序、重新生成水印,或者更换资源域名时,也不必重新猜测哪段文本属于哪张图片。

四、水印更新了,缓存也要跟着更新

水印设置保存成功,并不代表用户会立刻看到新效果。

需要先确认两件事:

  • 修改设置后,是否真的重新处理了已有图片;
  • 如果图片已经重新生成,浏览器是否仍在使用旧缓存。

仅修改配置,通常只会影响后续处理的图片。已有文件要发生变化,需要另外执行重新生成操作。

重新生成时,最好保留原图,并始终从原图生成水印版本。否则多次调整后,可能出现水印叠加或画质逐步下降。

缓存方面,可以采用带版本的资源地址:

/uploads/test.webp?v=3
Enter fullscreen mode Exit fullscreen mode

版本应在图片内容改变后更新,而不是每次打开页面都生成一个随机值。这样既能获取新图片,也能保留正常的缓存收益。

如果使用了 CDN,还要确认它的缓存规则是否区分查询参数。

五、前台预览,也属于上传功能的一部分

上传功能不能只在后台验证。

对于文字密集的测速截图,前台缩略图能显示,并不代表内容能够看清楚。还需要考虑:

  • 是否按原始比例展示;
  • 是否被固定高度裁切;
  • 手机端是否出现横向溢出;
  • 点击后能否在当前页面放大;
  • 放大后能否方便地关闭或滚动查看。

在我做的 jcrank 中,截图主要是供读者查看信息,因此比起统一裁切成漂亮的卡片,完整展示内容更重要。

对这类图片,一列排列、保持比例,再提供放大查看,往往更符合实际阅读需求。

最后,按完整流程验证一次

我现在更倾向于这样检查图片功能:

  1. 上传两张不同比例的图片。
  2. 分别填写不同的 Alt。
  3. 调整图片顺序。
  4. 保存并重新打开编辑窗口。
  5. 确认描述仍对应正确的图片。
  6. 修改水印设置并重新生成。
  7. 在电脑和手机上检查展示与放大效果。

这些步骤并不复杂,但比只检查上传接口是否返回成功,更接近用户真正使用功能的过程。

图片上传并不是一个孤立按钮。它连接着文件处理、表单状态、数据关联、缓存和前台阅读体验。把这些环节一起考虑,才能减少“后台显示成功,前台却不对”的问题。

Top comments (0)