<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Maomao</title>
    <description>The latest articles on DEV Community by Maomao (@maomao_1123987).</description>
    <link>https://dev.to/maomao_1123987</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4116989%2F6ab70f66-912e-4cef-b7a8-5ea57ef936e4.png</url>
      <title>DEV Community: Maomao</title>
      <link>https://dev.to/maomao_1123987</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maomao_1123987"/>
    <language>en</language>
    <item>
      <title>用 ThinkPHP 做资料站：图片上传成功，不代表功能就做好了</title>
      <dc:creator>Maomao</dc:creator>
      <pubDate>Wed, 09 Sep 2026 07:19:38 +0000</pubDate>
      <link>https://dev.to/maomao_1123987/yong-thinkphp-zuo-zi-liao-zhan-tu-pian-shang-chuan-cheng-gong-bu-dai-biao-gong-neng-jiu-zuo-hao-liao-25m0</link>
      <guid>https://dev.to/maomao_1123987/yong-thinkphp-zuo-zi-liao-zhan-tu-pian-shang-chuan-cheng-gong-bu-dai-biao-gong-neng-jiu-zuo-hao-liao-25m0</guid>
      <description>&lt;p&gt;最近在做一个中文资料站，后台需要支持上传多张截图、编辑图片 Alt 文本，以及给图片添加水印。&lt;/p&gt;

&lt;p&gt;最初觉得，这应该是一个很常规的功能：接入上传组件，保存图片地址，前台循环展示。&lt;/p&gt;

&lt;p&gt;实际做下来，问题却集中在“上传成功”之后。&lt;/p&gt;

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

&lt;p&gt;下面整理几个值得提前考虑的实现细节。&lt;/p&gt;

&lt;h2&gt;
  
  
  一、文件大小和像素尺寸，要分别检查
&lt;/h2&gt;

&lt;p&gt;图片只有 300KB，不代表处理它只需要很少的内存。&lt;/p&gt;

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

&lt;p&gt;例如，一张 &lt;code&gt;12000 × 8000&lt;/code&gt; 的图片，如果按每像素 4 字节估算，单份像素数据就需要：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;12000 × 8000 × 4
= 384,000,000 字节
≈ 366 MiB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;这还没有包含图片库开销，以及缩放、添加水印时创建的额外缓冲区。&lt;/p&gt;

&lt;p&gt;因此，上传限制不能只看：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;文件不超过 10MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;还需要考虑：&lt;/p&gt;

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

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

&lt;p&gt;更重要的是：如果图片处理失败，接口应该明确返回失败原因，不能让用户收到“成功”，最后却找不到图片。&lt;/p&gt;

&lt;h2&gt;
  
  
  二、自动生成的 Alt，不应该覆盖手动输入
&lt;/h2&gt;

&lt;p&gt;自动补充 Alt 文本很方便。例如上传截图后，默认生成：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;某机场节点测速截图 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;但编辑者可能希望改成更具体的描述：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;香港与日本节点的晚高峰延迟测试
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;这里需要区分三个状态：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;尚未填写；&lt;/li&gt;
&lt;li&gt;自动生成的默认值；&lt;/li&gt;
&lt;li&gt;用户手动编辑的值。&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;尤其要小心这样的输入框绑定：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;el-input&lt;/span&gt; &lt;span class="na"&gt;:model-value=&lt;/span&gt;&lt;span class="s"&gt;"savedAlt || generatedAlt"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;更合理的方式是给编辑过程单独保存草稿：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;altDraft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;el-input&lt;/span&gt;
    &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"altDraft"&lt;/span&gt;
    &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"描述这张图片的内容"&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;h2&gt;
  
  
  三、图片地址，不适合承担所有身份识别工作
&lt;/h2&gt;

&lt;p&gt;图片上传后，地址可能经历多次变化：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/uploads/test.webp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;添加缓存版本参数后：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/uploads/test.webp?v=2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;接入 CDN 后：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://cdn.example.com/uploads/test.webp?v=2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;更容易维护的数据结构，是为图片保留稳定的 ID：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image_1042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/uploads/test.webp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"alt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"香港与日本节点的晚高峰延迟测试"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;这样，地址负责定位资源，ID 负责关联描述、排序等信息。&lt;/p&gt;

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

&lt;h2&gt;
  
  
  四、水印更新了，缓存也要跟着更新
&lt;/h2&gt;

&lt;p&gt;水印设置保存成功，并不代表用户会立刻看到新效果。&lt;/p&gt;

&lt;p&gt;需要先确认两件事：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;修改设置后，是否真的重新处理了已有图片；&lt;/li&gt;
&lt;li&gt;如果图片已经重新生成，浏览器是否仍在使用旧缓存。&lt;/li&gt;
&lt;/ul&gt;

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

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

&lt;p&gt;缓存方面，可以采用带版本的资源地址：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/uploads/test.webp?v=3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;如果使用了 CDN，还要确认它的缓存规则是否区分查询参数。&lt;/p&gt;

&lt;h2&gt;
  
  
  五、前台预览，也属于上传功能的一部分
&lt;/h2&gt;

&lt;p&gt;上传功能不能只在后台验证。&lt;/p&gt;

&lt;p&gt;对于文字密集的测速截图，前台缩略图能显示，并不代表内容能够看清楚。还需要考虑：&lt;/p&gt;

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

&lt;p&gt;在我做的 &lt;a href="https://jcrank.com/" rel="noopener noreferrer"&gt;jcrank&lt;/a&gt; 中，截图主要是供读者查看信息，因此比起统一裁切成漂亮的卡片，完整展示内容更重要。&lt;/p&gt;

&lt;p&gt;对这类图片，一列排列、保持比例，再提供放大查看，往往更符合实际阅读需求。&lt;/p&gt;

&lt;h2&gt;
  
  
  最后，按完整流程验证一次
&lt;/h2&gt;

&lt;p&gt;我现在更倾向于这样检查图片功能：&lt;/p&gt;

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

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

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

</description>
      <category>webdev</category>
      <category>php</category>
    </item>
  </channel>
</rss>
