Files
rxthinkcmf-wiki/RXThinkCMF_Laravel13_EleVue/.vitepress/dist/assets/zh_tools_upload-config.md.NHEBbUek.js
T
2026-10-25 18:52:36 +08:00

96 lines
39 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
import{_ as i,o as a,c as n,a2 as t}from"./chunks/framework.B-QBKwc1.js";const c=JSON.parse('{"title":"上传配置详解","description":"文件上传配置定义在 config/file.php 和 .env 中,控制上传目录、访问域名、大小限制、允许的文件类型等。","frontmatter":{},"headers":[],"relativePath":"zh/tools/upload-config.md","filePath":"zh/tools/upload-config.md"}'),e={name:"zh/tools/upload-config.md"};function l(p,s,h,d,k,r){return a(),n("div",null,[...s[0]||(s[0]=[t(`<h1 id="_7-2-上传配置详解" tabindex="-1">7.2 上传配置详解 <a class="header-anchor" href="#_7-2-上传配置详解" aria-label="Permalink to &quot;7.2 上传配置详解&quot;">​</a></h1><div class="tip custom-block"><p class="custom-block-title">概述</p><p>文件上传配置定义在 <code>config/file.php</code> 和 <code>.env</code> 中,控制上传目录、访问域名、大小限制、允许的文件类型等。所有值通过 <code>env()</code> 函数从 <code>.env</code> 读取,支持多环境切换;<code>config/file.php</code> 中的注释明确标注了每个配置的<strong>读取方</strong>。</p></div><h2 id="配置文件" tabindex="-1">配置文件 <a class="header-anchor" href="#配置文件" aria-label="Permalink to &quot;配置文件&quot;">​</a></h2><div class="language-php vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">php</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">// config/file.php — 文件上传配置(来自项目实际代码,节选)</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">return</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 文件上传根目录(相对 public),所有上传文件均存放于此</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 读取方:UploadController::uploadFile()、save_file()、save_remote_file()</span></span>
<span class="line"><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">upload_dir</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =&gt;</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> env</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">FILE_UPLOAD_DIR</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">uploads</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">),</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 文件访问域名,用于拼接完整 URL;为空时返回相对路径</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 读取方:get_file_url()、save_content()、processContentFieldsOnRead()</span></span>
<span class="line"><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">domain_url</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =&gt;</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> env</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">FILE_DOMAIN_URL</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">),</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 临时文件目录名(相对 upload_dir)</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 读取方:UploadController::uploadFile()(上传落盘目录)、save_file()(识别 temp/ 开头的临时文件后迁移到正式目录)</span></span>
<span class="line"><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">temp_dir</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =&gt;</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">temp</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 允许上传的图片后缀(逗号分隔)</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 安全说明:svg 已移除,因其可内嵌 JavaScript 导致 XSS 风险</span></span>
<span class="line"><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">image_ext</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =&gt;</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> env</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">FILE_IMAGE_EXT</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">gif,jpg,jpeg,png,bmp,webp</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">),</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 允许上传的文件后缀(逗号分隔)</span></span>
<span class="line"><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">file_ext</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =&gt;</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> env</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">FILE_EXT</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">gif,jpg,jpeg,png,bmp,webp,doc,docx,xls,xlsx,ppt,pptx,pdf,zip,rar,txt,csv</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">),</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 单文件最大尺寸(MB),转换为字节存储</span></span>
<span class="line"><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">max_size</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =&gt;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> (</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#AB5959;--shiki-light-font-style:inherit;">int</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">)</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> env</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">FILE_MAX_SIZE</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 10</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">)</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> *</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 1024</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> *</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 1024</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span></code></pre></div><h2 id="配置项详解" tabindex="-1">配置项详解 <a class="header-anchor" href="#配置项详解" aria-label="Permalink to &quot;配置项详解&quot;">​</a></h2><table tabindex="0"><thead><tr><th>配置项</th><th>.env 变量</th><th>默认值</th><th>说明</th></tr></thead><tbody><tr><td><code>upload_dir</code></td><td><code>FILE_UPLOAD_DIR</code></td><td><code>uploads</code></td><td>上传根目录(<strong>相对 <code>public/</code></strong>),所有文件存储于此</td></tr><tr><td><code>domain_url</code></td><td><code>FILE_DOMAIN_URL</code></td><td>(空)</td><td>文件访问域名,用于拼接完整 URL</td></tr><tr><td><code>temp_dir</code></td><td>—</td><td><code>temp</code></td><td>临时目录名(相对 upload_dir),上传接口的落盘目录,<code>save_file()</code> 据此识别待迁移文件</td></tr><tr><td><code>image_ext</code></td><td><code>FILE_IMAGE_EXT</code></td><td><code>gif,jpg,jpeg,png,bmp,webp</code></td><td>图片允许后缀(<strong>无 svg</strong>)</td></tr><tr><td><code>file_ext</code></td><td><code>FILE_EXT</code></td><td>图片+文档+压缩包等</td><td>文件允许后缀</td></tr><tr><td><code>max_size</code></td><td><code>FILE_MAX_SIZE</code></td><td><code>10</code></td><td>单文件最大大小(MB,配置内自动转字节)</td></tr></tbody></table><h3 id="upload-dir-—-上传根目录" tabindex="-1">upload_dir — 上传根目录 <a class="header-anchor" href="#upload-dir-—-上传根目录" aria-label="Permalink to &quot;upload_dir — 上传根目录&quot;">​</a></h3><div class="language-ini vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ini</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># .env</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">FILE_UPLOAD_DIR</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">uploads</span></span></code></pre></div><p><strong>要求:</strong></p><ul><li>本项目为<strong>相对 <code>public/</code> 的目录</strong>,Web 可直接访问其中的静态文件</li><li>目录必须存在且有写入权限(Docker 镜像已创建并授权 <code>public/uploads</code>)</li><li>上传文件随项目目录持久化(Docker 下即宿主机目录,不依赖命名卷)</li></ul><p><strong>各环境示例:</strong></p><table tabindex="0"><thead><tr><th>环境</th><th>值</th><th>实际位置</th></tr></thead><tbody><tr><td>本地开发</td><td><code>uploads</code></td><td><code>项目/public/uploads/</code></td></tr><tr><td>Linux 生产</td><td><code>uploads</code>(或绝对路径)</td><td>Nginx root 下可直接访问</td></tr><tr><td>Docker</td><td><code>uploads</code></td><td>挂载的项目目录内</td></tr></tbody></table><h3 id="domain-url-—-文件访问域名" tabindex="-1">domain_url — 文件访问域名 <a class="header-anchor" href="#domain-url-—-文件访问域名" aria-label="Permalink to &quot;domain_url — 文件访问域名&quot;">​</a></h3><div class="language-ini vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ini</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># .env</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">FILE_DOMAIN_URL</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">https://cdn.example.com</span></span></code></pre></div><p><strong>要求:</strong></p><ul><li>不要以 <code>/</code> 结尾(读取方会 <code>rtrim</code> 处理,但配置时保持规范)</li><li>必须是前端可访问的域名</li><li>本地开发可留空(接口返回相对路径,由同源/Vite 代理直接访问)</li></ul><p><strong>各环境示例:</strong></p><table tabindex="0"><thead><tr><th>环境</th><th>域名示例</th></tr></thead><tbody><tr><td>本地开发</td><td>(留空,使用相对路径)</td></tr><tr><td>虚拟域名开发</td><td><code>http://file.laravel13.elevue</code></td></tr><tr><td>测试环境</td><td><code>https://test-cdn.example.com</code></td></tr><tr><td>生产环境</td><td><code>https://cdn.example.com</code> 或对象存储域名</td></tr></tbody></table><h3 id="temp-dir-—-临时目录" tabindex="-1">temp_dir — 临时目录 <a class="header-anchor" href="#temp-dir-—-临时目录" aria-label="Permalink to &quot;temp_dir — 临时目录&quot;">​</a></h3><div class="language-php vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">php</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">// config/file.php</span></span>
<span class="line"><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">temp_dir</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =&gt;</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">temp</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span></code></pre></div><p><strong>说明:</strong></p><ul><li>上传接口 <code>uploadFile</code> 把文件写入 <code>{upload_dir}/temp/Ymd/</code>,不直接落正式目录</li><li><code>save_file()</code> / <code>save_content()</code> 依据 <code>temp/</code> 前缀把文件迁移到 Service 的 <code>fileSaveDir</code> 子目录</li><li>正式目录未被表单引用的临时文件可按周期清理(见下方「问题 4」)</li></ul><h3 id="max-size-—-文件大小限制" tabindex="-1">max_size — 文件大小限制 <a class="header-anchor" href="#max-size-—-文件大小限制" aria-label="Permalink to &quot;max_size — 文件大小限制&quot;">​</a></h3><div class="language-ini vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ini</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># .env(单位 MB,config 内 ×1024×1024 转字节)</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">FILE_MAX_SIZE</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">10</span></span></code></pre></div><p><strong>建议值:</strong></p><table tabindex="0"><thead><tr><th>场景</th><th>建议值</th><th>说明</th></tr></thead><tbody><tr><td>头像上传</td><td>2 MB</td><td>图片通常不会太大</td></tr><tr><td>文章封面</td><td>5 MB</td><td>压缩后的图片</td></tr><tr><td>通用文件上传</td><td>10 MB</td><td>默认值</td></tr><tr><td>文档上传</td><td>20 MB</td><td>PDF/Word 等</td></tr></tbody></table><div class="warning custom-block"><p class="custom-block-title">双层限制</p><p>业务层限制生效前,PHP 层必须放行:<code>php.ini</code> 的 <code>upload_max_filesize</code> / <code>post_max_size</code> 需 <strong>大于等于</strong> <code>FILE_MAX_SIZE</code>(建议 50M)。</p></div><h3 id="image-ext-file-ext-—-后缀白名单" tabindex="-1">image_ext / file_ext — 后缀白名单 <a class="header-anchor" href="#image-ext-file-ext-—-后缀白名单" aria-label="Permalink to &quot;image_ext / file_ext — 后缀白名单&quot;">​</a></h3><div class="language-ini vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ini</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># .env</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">FILE_IMAGE_EXT</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">gif,jpg,jpeg,png,bmp,webp </span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># uploadFile?type=image 校验</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">FILE_EXT</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">gif,jpg,jpeg,png,bmp,webp,doc,docx,xls,xlsx,ppt,pptx,pdf,zip,rar,txt,csv </span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># uploadFile(默认 type=file)校验</span></span></code></pre></div><p><strong>说明:</strong></p><ul><li><code>image_ext</code> 用于 <code>type=image</code>,只允许图片格式;<strong>svg 已移除</strong>(内嵌 JS 的 XSS 风险)</li><li><code>file_ext</code> 用于 <code>type=file</code>,允许图片、文档、压缩包等常见格式</li><li>白名单为空数组时跳过校验(防御性兜底),生产环境务必保留非空配置</li></ul><h2 id="-env-配置示例" tabindex="-1">.env 配置示例 <a class="header-anchor" href="#-env-配置示例" aria-label="Permalink to &quot;.env 配置示例&quot;">​</a></h2><div class="language-ini vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ini</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 开发环境</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">FILE_UPLOAD_DIR</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">uploads</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">FILE_DOMAIN_URL</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">FILE_MAX_SIZE</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">10</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 生产环境(示例)</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">FILE_UPLOAD_DIR</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">uploads</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">FILE_DOMAIN_URL</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">https://cdn.example.com</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">FILE_MAX_SIZE</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">10</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">FILE_IMAGE_EXT</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">gif,jpg,jpeg,png,bmp,webp</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">FILE_EXT</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">gif,jpg,jpeg,png,bmp,webp,doc,docx,xls,xlsx,pdf,zip,rar</span></span></code></pre></div><h2 id="目录结构" tabindex="-1">目录结构 <a class="header-anchor" href="#目录结构" aria-label="Permalink to &quot;目录结构&quot;">​</a></h2><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>public/</span></span>
<span class="line"><span>└── {upload_dir}/ # upload_dir(默认 uploads)</span></span>
<span class="line"><span> ├── temp/ # 临时目录(UploadController 上传落盘)</span></span>
<span class="line"><span> │ └── 20261002/</span></span>
<span class="line"><span> │ └── 123456_a1b2c3d4e5f6.jpg</span></span>
<span class="line"><span> ├── article/ # 正式目录(由 Service 的 fileSaveDir 配置)</span></span>
<span class="line"><span> │ └── 20261002/</span></span>
<span class="line"><span> │ └── 153052_9f8e7d6c5b4a.pdf</span></span>
<span class="line"><span> ├── example/</span></span>
<span class="line"><span> ├── user/</span></span>
<span class="line"><span> │ └── avatar/</span></span>
<span class="line"><span> └── file_template/</span></span></code></pre></div><h3 id="目录命名规则" tabindex="-1">目录命名规则 <a class="header-anchor" href="#目录命名规则" aria-label="Permalink to &quot;目录命名规则&quot;">​</a></h3><table tabindex="0"><thead><tr><th>目录</th><th>说明</th><th>来源</th></tr></thead><tbody><tr><td><code>temp/</code></td><td>临时文件目录(上传接口落盘)</td><td><code>config/file.php → temp_dir</code></td></tr><tr><td><code>{module}/</code></td><td>按模块的正式目录</td><td>Service 的 <code>fileSaveDir</code>(未配置按模型名推导)</td></tr><tr><td><code>{Ymd}/</code></td><td>日期子目录</td><td>自动创建</td></tr><tr><td><code>{His}_{random}.{ext}</code></td><td>时间+随机文件名</td><td>防冲突与可预测性</td></tr></tbody></table><h2 id="文件生命周期" tabindex="-1">文件生命周期 <a class="header-anchor" href="#文件生命周期" aria-label="Permalink to &quot;文件生命周期&quot;">​</a></h2><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>1. 前端上传 → POST /api/upload/uploadFile</span></span>
<span class="line"><span> 保存到: public/uploads/temp/20261002/123456_a1b2c3d4e5f6.jpg</span></span>
<span class="line"><span> 返回: filePath + fileUrl</span></span>
<span class="line"><span></span></span>
<span class="line"><span>2. 前端提交表单 → 携带 filePath</span></span>
<span class="line"><span> POST /api/example/add { name: &quot;测试&quot;, avatar: &quot;temp/20261002/...&quot; }</span></span>
<span class="line"><span></span></span>
<span class="line"><span>3. ExampleService::add() → processFileFieldsOnSave → save_file()</span></span>
<span class="line"><span> temp/ → 迁移到 fileSaveDir(example/)后,正式目录相对路径入库</span></span>
<span class="line"><span> (其它分支:去掉域名直接用;外部 URL → 下载保存)</span></span>
<span class="line"><span></span></span>
<span class="line"><span>4. ExampleService::detail() → processFileFieldsOnRead → get_file_url()</span></span>
<span class="line"><span> 相对路径 → 拼接 FILE_DOMAIN_URL</span></span>
<span class="line"><span> example/20261002/...</span></span>
<span class="line"><span> → https://cdn.example.com/example/20261002/...</span></span></code></pre></div><h2 id="与-Service-层的配合" tabindex="-1">与 Service 层的配合 <a class="header-anchor" href="#与-Service-层的配合" aria-label="Permalink to &quot;与 Service 层的配合&quot;">​</a></h2><div class="language-php vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">php</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">class</span><span style="--shiki-dark:#97E1F1;--shiki-light:#2E8F82;"> ArticleService</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> extends</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#59873A;--shiki-light-font-style:inherit;"> BaseTenantService</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">{</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> use</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> HasFileProcessing</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> /* 其他 Trait */</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">fileFields</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">cover</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> string</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">fileSaveDir</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">article</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">}</span></span></code></pre></div><p><strong>实际路径映射:</strong></p><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>配置:</span></span>
<span class="line"><span> FILE_UPLOAD_DIR = uploads</span></span>
<span class="line"><span> FILE_DOMAIN_URL = https://cdn.example.com</span></span>
<span class="line"><span> fileSaveDir = article</span></span>
<span class="line"><span></span></span>
<span class="line"><span>结果:</span></span>
<span class="line"><span> 上传文件: public/uploads/temp/20261002/xxx.jpg</span></span>
<span class="line"><span> Service 归一化后入库: article/20261002/xxx.jpg</span></span>
<span class="line"><span> 前端显示: https://cdn.example.com/article/20261002/xxx.jpg</span></span></code></pre></div><h2 id="Nginx-配置" tabindex="-1">Nginx 配置 <a class="header-anchor" href="#Nginx-配置" aria-label="Permalink to &quot;Nginx 配置&quot;">​</a></h2><h3 id="静态文件直接由-Nginx-返回" tabindex="-1">静态文件直接由 Nginx 返回 <a class="header-anchor" href="#静态文件直接由-Nginx-返回" aria-label="Permalink to &quot;静态文件直接由 Nginx 返回&quot;">​</a></h3><div class="language-nginx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">nginx</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 项目方案:public/uploads 位于站点根内,location / 由 try_files 直接返回静态文件</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">location</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#59873A;"> / </span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">{</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> try_files </span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">$</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">uri</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">uri</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">/ /index.php?</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">$</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">query_string</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">}</span></span></code></pre></div><h3 id="禁止上传目录执行-PHP" tabindex="-1">禁止上传目录执行 PHP <a class="header-anchor" href="#禁止上传目录执行-PHP" aria-label="Permalink to &quot;禁止上传目录执行 PHP&quot;">​</a></h3><div class="language-nginx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">nginx</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># docker/nginx.conf 已内置:仅入口 index.php 可执行,其余 .php 一律 404</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">location</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> ~</span><span style="--shiki-dark:#E7EE98;--shiki-light:#AB5E3F;"> \\.php$ </span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">{</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> return</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 404</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">;</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">}</span></span></code></pre></div><h3 id="禁止访问隐藏文件" tabindex="-1">禁止访问隐藏文件 <a class="header-anchor" href="#禁止访问隐藏文件" aria-label="Permalink to &quot;禁止访问隐藏文件&quot;">​</a></h3><div class="language-nginx vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">nginx</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">location</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> ~</span><span style="--shiki-dark:#E7EE98;--shiki-light:#AB5E3F;"> /\\. </span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">{</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> deny </span><span style="--shiki-dark:#BF9EEE;--shiki-light:#1E754F;">all</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">}</span></span></code></pre></div><h2 id="常见问题" tabindex="-1">常见问题 <a class="header-anchor" href="#常见问题" aria-label="Permalink to &quot;常见问题&quot;">​</a></h2><h3 id="问题-1-上传提示-目录不可写-" tabindex="-1">问题 1:上传提示&quot;目录不可写&quot; <a class="header-anchor" href="#问题-1-上传提示-目录不可写-" aria-label="Permalink to &quot;问题 1:上传提示&quot;目录不可写&quot;&quot;">​</a></h3><p><strong>原因:</strong> <code>public/uploads</code> 没有写入权限。</p><p><strong>解决:</strong></p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># Linux</span></span>
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">chmod</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#A65E2B;"> -R</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 755</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> public/uploads</span></span>
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">chown</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#A65E2B;"> -R</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> www-data:www-data</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> public/uploads</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># Docker:镜像内已 chown(storage / bootstrap/cache / public/uploads)</span></span></code></pre></div><h3 id="问题-2-上传后图片不显示" tabindex="-1">问题 2:上传后图片不显示 <a class="header-anchor" href="#问题-2-上传后图片不显示" aria-label="Permalink to &quot;问题 2:上传后图片不显示&quot;">​</a></h3><p><strong>原因:</strong> <code>FILE_DOMAIN_URL</code> 配置错误或与前端不同源。</p><p><strong>解决:</strong></p><ol><li>检查 <code>.env</code> 中 <code>FILE_DOMAIN_URL</code> 是否为前端可访问的地址(改后 <code>php artisan config:clear</code>)</li><li>留空时用相对路径——确认前端与后端同源或经代理</li><li>浏览器直接打开图片 URL 验证</li></ol><h3 id="问题-3-文件大小限制不生效" tabindex="-1">问题 3:文件大小限制不生效 <a class="header-anchor" href="#问题-3-文件大小限制不生效" aria-label="Permalink to &quot;问题 3:文件大小限制不生效&quot;">​</a></h3><p><strong>原因:</strong> PHP 层限制小于业务层。</p><p><strong>解决:</strong></p><div class="language-ini vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ini</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">; php.ini(修改后重启服务)</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">upload_max_filesize</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;"> 50M</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">post_max_size</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;"> 50M</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">max_execution_time</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;"> 300</span></span></code></pre></div><h3 id="问题-4-临时文件未清理" tabindex="-1">问题 4:临时文件未清理 <a class="header-anchor" href="#问题-4-临时文件未清理" aria-label="Permalink to &quot;问题 4:临时文件未清理&quot;">​</a></h3><p><strong>原因:</strong> 上传后未提交表单(或表单未引用)的文件停留在 <code>temp/</code>,不会被迁移。</p><p><strong>解决:</strong> 定期清理 <code>temp/</code> 中超过 1 天的文件:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 定时任务:每天凌晨清理 1 天前的临时文件</span></span>
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">find</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> public/uploads/temp/</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#A65E2B;"> -type</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> f</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#A65E2B;"> -mtime</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> +1</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#A65E2B;"> -delete</span></span></code></pre></div><h2 id="安全建议" tabindex="-1">安全建议 <a class="header-anchor" href="#安全建议" aria-label="Permalink to &quot;安全建议&quot;">​</a></h2><table tabindex="0"><thead><tr><th>建议</th><th>说明</th></tr></thead><tbody><tr><td>扩展名白名单</td><td>只允许已知安全的文件类型(<code>FILE_IMAGE_EXT</code>/<code>FILE_EXT</code>)</td></tr><tr><td>移除高危后缀</td><td>svg 已移除;生产白名单去掉 <code>sql/js/css</code> 等可执行类</td></tr><tr><td>MIME 类型检查</td><td>导入等场景配合 <code>finfo</code> 检测真实 MIME(见 <a href="/zh/tools/excel.html">7.4 数据导入导出</a>)</td></tr><tr><td>随机文件名</td><td>服务端生成(时间+随机串),防止路径遍历和文件名注入</td></tr><tr><td>文件大小限制</td><td><code>FILE_MAX_SIZE</code> 防止大文件 DoS</td></tr><tr><td>Nginx 禁止执行 PHP</td><td>仅入口 <code>index.php</code> 可执行(Docker 配置已内置)</td></tr><tr><td>定期清理临时文件</td><td><code>temp/</code> 内未被引用的文件按周期清理,避免磁盘空间被占满</td></tr></tbody></table>`,69)])])}const g=i(e,[["render",l]]);export{c as __pageData,g as default};