import{_ as i,o as a,c as n,a2 as t}from"./chunks/framework.BbmSSMjL.js";const c=JSON.parse('{"title":"7.2 上传配置详解","description":"","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(`

7.2 上传配置详解 ​

概述

文件上传配置定义在 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_dirFILE_UPLOAD_DIRuploads上传根目录(相对 public/),所有文件存储于此
domain_urlFILE_DOMAIN_URL(空)文件访问域名,用于拼接完整 URL
temp_dir—temp临时目录名(相对 upload_dir),上传接口的落盘目录,save_file() 据此识别待迁移文件
image_extFILE_IMAGE_EXTgif,jpg,jpeg,png,bmp,webp图片允许后缀(无 svg)
file_extFILE_EXT图片+文档+压缩包等文件允许后缀
max_sizeFILE_MAX_SIZE10单文件最大大小(MB,配置内自动转字节)

upload_dir — 上传根目录 ​

ini
# .env
FILE_UPLOAD_DIR=uploads

要求:

各环境示例:

环境值实际位置
本地开发uploads项目/public/uploads/
Linux 生产uploads(或绝对路径)Nginx root 下可直接访问
Dockeruploads挂载的项目目录内

domain_url — 文件访问域名 ​

ini
# .env
FILE_DOMAIN_URL=https://cdn.example.com

要求:

各环境示例:

环境域名示例
本地开发(留空,使用相对路径)
虚拟域名开发http://file.laravel13.naivevue
测试环境https://test-cdn.example.com
生产环境https://cdn.example.com 或对象存储域名

temp_dir — 临时目录 ​

php
// config/file.php
'temp_dir' => 'temp',

说明:

max_size — 文件大小限制 ​

ini
# .env(单位 MB,config 内 ×1024×1024 转字节)
FILE_MAX_SIZE=10

建议值:

场景建议值说明
头像上传2 MB图片通常不会太大
文章封面5 MB压缩后的图片
通用文件上传10 MB默认值
文档上传20 MBPDF/Word 等

双层限制

业务层限制生效前,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)校验

说明:

.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 数据导入导出)
随机文件名服务端生成(时间+随机串),防止路径遍历和文件名注入
文件大小限制FILE_MAX_SIZE 防止大文件 DoS
Nginx 禁止执行 PHP仅入口 index.php 可执行(Docker 配置已内置)
定期清理临时文件temp/ 内未被引用的文件按周期清理,避免磁盘空间被占满
`,69)])])}const g=i(e,[["render",l]]);export{c as __pageData,g as default};