96 lines
39 KiB
JavaScript
96 lines
39 KiB
JavaScript
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 "7.2 上传配置详解""></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 "配置文件""></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;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">upload_dir</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =></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;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">FILE_UPLOAD_DIR</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">uploads</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</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;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">domain_url</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =></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;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">FILE_DOMAIN_URL</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> ''</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;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">temp_dir</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =></span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">temp</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</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;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">image_ext</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =></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;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">FILE_IMAGE_EXT</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">gif,jpg,jpeg,png,bmp,webp</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</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;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">file_ext</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =></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;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">FILE_EXT</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</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;">'</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;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">max_size</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =></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;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">FILE_MAX_SIZE</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</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 "配置项详解""></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 "upload_dir — 上传根目录""></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 "domain_url — 文件访问域名""></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 "temp_dir — 临时目录""></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;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">temp_dir</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =></span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">temp</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</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 "max_size — 文件大小限制""></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 "image_ext / file_ext — 后缀白名单""></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 ".env 配置示例""></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 "目录结构""></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 "目录命名规则""></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 "文件生命周期""></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: "测试", avatar: "temp/20261002/..." }</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 "与 Service 层的配合""></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;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">cover</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</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;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">article</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</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 "Nginx 配置""></a></h2><h3 id="静态文件直接由-Nginx-返回" tabindex="-1">静态文件直接由 Nginx 返回 <a class="header-anchor" href="#静态文件直接由-Nginx-返回" aria-label="Permalink to "静态文件直接由 Nginx 返回""></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 "禁止上传目录执行 PHP""></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 "禁止访问隐藏文件""></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 "常见问题""></a></h2><h3 id="问题-1-上传提示-目录不可写-" tabindex="-1">问题 1:上传提示"目录不可写" <a class="header-anchor" href="#问题-1-上传提示-目录不可写-" aria-label="Permalink to "问题 1:上传提示"目录不可写"""></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 "问题 2:上传后图片不显示""></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 "问题 3:文件大小限制不生效""></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 "问题 4:临时文件未清理""></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 "安全建议""></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};
|