# 7.2 上传配置详解 :::tip 概述 文件上传配置定义在 `config/file.php` 和 `.env` 中,控制上传目录、访问域名、大小限制、允许的文件类型等。所有值通过 `env()` 函数从 `.env` 读取,支持多环境切换;`config/file.php` 中的注释明确标注了每个配置的**读取方**。 ::: ## 配置文件 ```php // config/file.php — 文件上传配置(来自项目实际代码,节选) return [ // 文件上传根目录(相对 public),所有上传文件均存放于此 // 读取方:UploadController::uploadFile()、save_file()、save_remote_file() 'upload_dir' => env('FILE_UPLOAD_DIR', 'uploads'), // 文件访问域名,用于拼接完整 URL;为空时返回相对路径 // 读取方:get_file_url()、save_content()、processContentFieldsOnRead() 'domain_url' => env('FILE_DOMAIN_URL', ''), // 临时文件目录名(相对 upload_dir) // 读取方:UploadController::uploadFile()(上传落盘目录)、save_file()(识别 temp/ 开头的临时文件后迁移到正式目录) 'temp_dir' => 'temp', // 允许上传的图片后缀(逗号分隔) // 安全说明:svg 已移除,因其可内嵌 JavaScript 导致 XSS 风险 'image_ext' => env('FILE_IMAGE_EXT', 'gif,jpg,jpeg,png,bmp,webp'), // 允许上传的文件后缀(逗号分隔) 'file_ext' => env('FILE_EXT', 'gif,jpg,jpeg,png,bmp,webp,doc,docx,xls,xlsx,ppt,pptx,pdf,zip,rar,txt,csv'), // 单文件最大尺寸(MB),转换为字节存储 'max_size' => (int) env('FILE_MAX_SIZE', 10) * 1024 * 1024, ]; ``` ## 配置项详解 | 配置项 | .env 变量 | 默认值 | 说明 | |--------|----------|--------|------| | `upload_dir` | `FILE_UPLOAD_DIR` | `uploads` | 上传根目录(**相对 `public/`**),所有文件存储于此 | | `domain_url` | `FILE_DOMAIN_URL` | (空) | 文件访问域名,用于拼接完整 URL | | `temp_dir` | — | `temp` | 临时目录名(相对 upload_dir),上传接口的落盘目录,`save_file()` 据此识别待迁移文件 | | `image_ext` | `FILE_IMAGE_EXT` | `gif,jpg,jpeg,png,bmp,webp` | 图片允许后缀(**无 svg**) | | `file_ext` | `FILE_EXT` | 图片+文档+压缩包等 | 文件允许后缀 | | `max_size` | `FILE_MAX_SIZE` | `10` | 单文件最大大小(MB,配置内自动转字节) | ### upload_dir — 上传根目录 ```ini # .env FILE_UPLOAD_DIR=uploads ``` **要求:** - 本项目为**相对 `public/` 的目录**,Web 可直接访问其中的静态文件 - 目录必须存在且有写入权限(Docker 镜像已创建并授权 `public/uploads`) - 上传文件随项目目录持久化(Docker 下即宿主机目录,不依赖命名卷) **各环境示例:** | 环境 | 值 | 实际位置 | |------|------|---------| | 本地开发 | `uploads` | `项目/public/uploads/` | | Linux 生产 | `uploads`(或绝对路径) | Nginx root 下可直接访问 | | Docker | `uploads` | 挂载的项目目录内 | ### domain_url — 文件访问域名 ```ini # .env FILE_DOMAIN_URL=https://cdn.example.com ``` **要求:** - 不要以 `/` 结尾(读取方会 `rtrim` 处理,但配置时保持规范) - 必须是前端可访问的域名 - 本地开发可留空(接口返回相对路径,由同源/Vite 代理直接访问) **各环境示例:** | 环境 | 域名示例 | |------|---------| | 本地开发 | (留空,使用相对路径) | | 虚拟域名开发 | `http://file.laravel13.elevue` | | 测试环境 | `https://test-cdn.example.com` | | 生产环境 | `https://cdn.example.com` 或对象存储域名 | ### temp_dir — 临时目录 ```php // config/file.php 'temp_dir' => 'temp', ``` **说明:** - 上传接口 `uploadFile` 把文件写入 `{upload_dir}/temp/Ymd/`,不直接落正式目录 - `save_file()` / `save_content()` 依据 `temp/` 前缀把文件迁移到 Service 的 `fileSaveDir` 子目录 - 正式目录未被表单引用的临时文件可按周期清理(见下方「问题 4」) ### max_size — 文件大小限制 ```ini # .env(单位 MB,config 内 ×1024×1024 转字节) FILE_MAX_SIZE=10 ``` **建议值:** | 场景 | 建议值 | 说明 | |------|--------|------| | 头像上传 | 2 MB | 图片通常不会太大 | | 文章封面 | 5 MB | 压缩后的图片 | | 通用文件上传 | 10 MB | 默认值 | | 文档上传 | 20 MB | PDF/Word 等 | :::warning 双层限制 业务层限制生效前,PHP 层必须放行:`php.ini` 的 `upload_max_filesize` / `post_max_size` 需 **大于等于** `FILE_MAX_SIZE`(建议 50M)。 ::: ### image_ext / file_ext — 后缀白名单 ```ini # .env FILE_IMAGE_EXT=gif,jpg,jpeg,png,bmp,webp # uploadFile?type=image 校验 FILE_EXT=gif,jpg,jpeg,png,bmp,webp,doc,docx,xls,xlsx,ppt,pptx,pdf,zip,rar,txt,csv # uploadFile(默认 type=file)校验 ``` **说明:** - `image_ext` 用于 `type=image`,只允许图片格式;**svg 已移除**(内嵌 JS 的 XSS 风险) - `file_ext` 用于 `type=file`,允许图片、文档、压缩包等常见格式 - 白名单为空数组时跳过校验(防御性兜底),生产环境务必保留非空配置 ## .env 配置示例 ```ini # 开发环境 FILE_UPLOAD_DIR=uploads FILE_DOMAIN_URL= FILE_MAX_SIZE=10 # 生产环境(示例) FILE_UPLOAD_DIR=uploads FILE_DOMAIN_URL=https://cdn.example.com FILE_MAX_SIZE=10 FILE_IMAGE_EXT=gif,jpg,jpeg,png,bmp,webp FILE_EXT=gif,jpg,jpeg,png,bmp,webp,doc,docx,xls,xlsx,pdf,zip,rar ``` ## 目录结构 ```text public/ └── {upload_dir}/ # upload_dir(默认 uploads) ├── temp/ # 临时目录(UploadController 上传落盘) │ └── 20261002/ │ └── 123456_a1b2c3d4e5f6.jpg ├── article/ # 正式目录(由 Service 的 fileSaveDir 配置) │ └── 20261002/ │ └── 153052_9f8e7d6c5b4a.pdf ├── example/ ├── user/ │ └── avatar/ └── file_template/ ``` ### 目录命名规则 | 目录 | 说明 | 来源 | |------|------|------| | `temp/` | 临时文件目录(上传接口落盘) | `config/file.php → temp_dir` | | `{module}/` | 按模块的正式目录 | Service 的 `fileSaveDir`(未配置按模型名推导) | | `{Ymd}/` | 日期子目录 | 自动创建 | | `{His}_{random}.{ext}` | 时间+随机文件名 | 防冲突与可预测性 | ## 文件生命周期 ```text 1. 前端上传 → POST /api/upload/uploadFile 保存到: public/uploads/temp/20261002/123456_a1b2c3d4e5f6.jpg 返回: filePath + fileUrl 2. 前端提交表单 → 携带 filePath POST /api/example/add { name: "测试", avatar: "temp/20261002/..." } 3. ExampleService::add() → processFileFieldsOnSave → save_file() temp/ → 迁移到 fileSaveDir(example/)后,正式目录相对路径入库 (其它分支:去掉域名直接用;外部 URL → 下载保存) 4. ExampleService::detail() → processFileFieldsOnRead → get_file_url() 相对路径 → 拼接 FILE_DOMAIN_URL example/20261002/... → https://cdn.example.com/example/20261002/... ``` ## 与 Service 层的配合 ```php class ArticleService extends BaseTenantService { use HasFileProcessing, /* 其他 Trait */; protected array $fileFields = ['cover']; protected string $fileSaveDir = 'article'; } ``` **实际路径映射:** ```text 配置: FILE_UPLOAD_DIR = uploads FILE_DOMAIN_URL = https://cdn.example.com fileSaveDir = article 结果: 上传文件: public/uploads/temp/20261002/xxx.jpg Service 归一化后入库: article/20261002/xxx.jpg 前端显示: https://cdn.example.com/article/20261002/xxx.jpg ``` ## Nginx 配置 ### 静态文件直接由 Nginx 返回 ```nginx # 项目方案:public/uploads 位于站点根内,location / 由 try_files 直接返回静态文件 location / { try_files $uri $uri/ /index.php?$query_string; } ``` ### 禁止上传目录执行 PHP ```nginx # docker/nginx.conf 已内置:仅入口 index.php 可执行,其余 .php 一律 404 location ~ \.php$ { return 404; } ``` ### 禁止访问隐藏文件 ```nginx location ~ /\. { deny all; } ``` ## 常见问题 ### 问题 1:上传提示"目录不可写" **原因:** `public/uploads` 没有写入权限。 **解决:** ```bash # Linux chmod -R 755 public/uploads chown -R www-data:www-data public/uploads # Docker:镜像内已 chown(storage / bootstrap/cache / public/uploads) ``` ### 问题 2:上传后图片不显示 **原因:** `FILE_DOMAIN_URL` 配置错误或与前端不同源。 **解决:** 1. 检查 `.env` 中 `FILE_DOMAIN_URL` 是否为前端可访问的地址(改后 `php artisan config:clear`) 2. 留空时用相对路径——确认前端与后端同源或经代理 3. 浏览器直接打开图片 URL 验证 ### 问题 3:文件大小限制不生效 **原因:** PHP 层限制小于业务层。 **解决:** ```ini ; php.ini(修改后重启服务) upload_max_filesize = 50M post_max_size = 50M max_execution_time = 300 ``` ### 问题 4:临时文件未清理 **原因:** 上传后未提交表单(或表单未引用)的文件停留在 `temp/`,不会被迁移。 **解决:** 定期清理 `temp/` 中超过 1 天的文件: ```bash # 定时任务:每天凌晨清理 1 天前的临时文件 find public/uploads/temp/ -type f -mtime +1 -delete ``` ## 安全建议 | 建议 | 说明 | |------|------| | 扩展名白名单 | 只允许已知安全的文件类型(`FILE_IMAGE_EXT`/`FILE_EXT`) | | 移除高危后缀 | svg 已移除;生产白名单去掉 `sql/js/css` 等可执行类 | | MIME 类型检查 | 导入等场景配合 `finfo` 检测真实 MIME(见 [7.4 数据导入导出](/zh/tools/excel.html)) | | 随机文件名 | 服务端生成(时间+随机串),防止路径遍历和文件名注入 | | 文件大小限制 | `FILE_MAX_SIZE` 防止大文件 DoS | | Nginx 禁止执行 PHP | 仅入口 `index.php` 可执行(Docker 配置已内置) | | 定期清理临时文件 | `temp/` 内未被引用的文件按周期清理,避免磁盘空间被占满 |