Icono del sitio IT&ライフハックブログ|学びと実践のためのアイデア集

[Guía práctica completa] Subida y entrega de archivos en Laravel — Storage/S3, URLs prefirmadas, optimización de imágenes, PDFs/vídeo, escaneo de virus, autorización, caché y texto alternativo accesible

php elephant sticker

Photo by RealToughCandy.com on Pexels.com

[Guía práctica completa] Subida y entrega de archivos en Laravel — Storage/S3, URLs prefirmadas, optimización de imágenes, PDFs/vídeo, escaneo de virus, autorización, caché y texto alternativo accesible

Qué aprenderás en este artículo (puntos clave)

  • Los fundamentos de Laravel Storage (Flysystem) y cómo diseñar para local/S3/CDN
  • Seguridad en subidas (validación MIME, límites de tamaño, defensas contra suplantación de extensiones, eliminación de EXIF, escaneo de virus)
  • Entrega segura con URLs prefirmadas (caducidad, Content-Disposition, cuándo usar privado vs público)
  • Generación de variantes de imagen (redimensionado/miniaturas/WebP/AVIF), srcset, carga diferida y prevención de CLS
  • Política de entrega de PDF/Office (alternativas HTML, resúmenes, PDFs etiquetados)
  • Entrega de vídeo (cómo pensar en HLS/DASH, subtítulos/WebVTT, evitar autoplay)
  • Caché (ETag/Cache-Control) y optimización de costes de CDN
  • Accesibilidad de medios (texto alternativo, captions, subtítulos, transcripciones y flujo de trabajo en admin)

Lectores previstos (¿a quién beneficia?)

  • Ingenieros Laravel principiantes–intermedios: quieres manejar imágenes/archivos de forma segura protegiendo el rendimiento.
  • Tech leads en SaaS/medios/e-commerce: quieres estandarizar una plataforma de medios asumiendo S3/CDN.
  • Editores/diseñadores: quieres operacionalizar el texto alternativo y los subtítulos.
  • QA/responsables de accesibilidad: quieres garantizar “entendible para todos”, incluso en medios.

Nivel de accesibilidad: ★★★★★

Cubre texto alternativo, captions, subtítulos, transcripciones, operación con lector de pantalla y teclado, reducción de movimiento y señalización de estado no dependiente del color—tanto implementación como operaciones.


1. Introducción: las funciones de archivos están entre “conveniente” y “peligroso”

Subir imágenes, PDFs y vídeos mejora mucho la UX. Pero también es un área propensa a incidentes de seguridad (suplantación de extensiones, malware, fugas de datos) y problemas de rendimiento (imágenes pesadas, costes de transferencia, inconsistencia de caché). Los medios también exponen brechas de accesibilidad: sin texto alternativo o captions, la información simplemente no llega a algunas personas usuarias.

Laravel permite un enfoque de extremo a extremo—desde Storage hasta autorización, URLs firmadas, colas y respuestas HTTP. Esta guía organiza un “patrón” práctico para una plataforma de archivos que sea segura, rápida y entendible para todos.


2. Diseño de almacenamiento: separa público y privado

2.1 Política central

  • Por defecto, almacenamiento privado: todo lo que requiera control de acceso debe ser privado
  • Solo las “variantes seguras para publicar” deben ser públicas
  • Mantén los originales no públicos; distribuye vía URLs prefirmadas o a través de la app

En S3, es conveniente separar por bucket (o prefijo), por ejemplo:

  • private/ (no público)
  • public/ (entrega vía CDN)

3. Configuración de Storage: prepara S3 + CDN

FILESYSTEM_DISK=s3
AWS_DEFAULT_REGION=ap-northeast-1
AWS_BUCKET=example-bucket
AWS_URL=https://cdn.example.com
// Guardar (privado)
$path = Storage::disk('s3')->putFile('private/uploads', $request->file('file'), 'private');

// Distribución temporal (10 minutos)
$url = Storage::disk('s3')->temporaryUrl($path, now()->addMinutes(10), [
  'ResponseContentDisposition' => 'inline',
]);
  • Configurar AWS_URL con tu CDN hace que Storage::url() devuelva URLs del CDN.
  • Para distribución privada, temporaryUrl() es muy práctico.

4. Seguridad de subidas: combina validación con saneamiento

4.1 Validación (MIME + tamaño)

$request->validate([
  'file' => ['required','file','mimetypes:image/jpeg,image/png,image/webp,application/pdf','max:8192'],
]);
  • Valida MIME, no la extensión.
  • max está en KB.

4.2 Defensa contra suplantación de extensiones

  • Usa mimetypes más comprobaciones de “contenido real” vía fileinfo
  • Usa nombres de almacenamiento aleatorios tras la subida (no confíes en el nombre original)

4.3 Eliminación de EXIF (metadatos de ubicación)

Dejar datos de ubicación en fotos puede causar filtraciones. Es más seguro eliminar EXIF durante el procesamiento (a menudo junto al redimensionado).

4.4 Escaneo de virus

  • Encola el escaneo como un job inmediatamente tras la subida (asíncrono)
  • Si es malicioso, ponlo en cuarentena y notifica a usuarios con redacción segura
  • No lo hagas accesible externamente hasta que termine el escaneo (mejor práctica)

5. Gestiona metadatos en la BD: no “solo guardes archivos”

Guardar solo en el filesystem dificulta la operación—gestiona metadatos en la BD.

Ejemplo de campos de la tabla media (conceptual):

  • disk
  • path
  • mime
  • size
  • variants (miniaturas, etc.)
  • meta (alt/caption/idioma, etc.)
  • owner_id / tenant_id
  • visibility (private/public)

Esto habilita:

  • autorización
  • regeneración
  • texto alternativo multilingüe
  • auditoría

6. Optimización de imágenes: genera variantes en jobs

Los originales suelen ser grandes, así que genera variantes amigables para display:

  • anchos: 320/640/1280
  • formatos: AVIF/WebP/JPEG
  • miniaturas para listas
  • fija width/height para evitar CLS

Generar variantes es lento si se hace en síncrono—mándalo a colas.

dispatch(new GenerateImageVariants($mediaId))->onQueue('media');

7. Entrega responsive: picture + srcset

<picture>
  <source type="image/avif" srcset="{{ $v['avif'] }}">
  <source type="image/webp" srcset="{{ $v['webp'] }}">
  <img
    src="{{ $v['w640'] }}"
    srcset="{{ $v['w320'] }} 320w, {{ $v['w640'] }} 640w, {{ $v['w1280'] }} 1280w"
    sizes="(max-width: 640px) 90vw, 640px"
    alt="{{ $media->meta['alt'] ?? '' }}"
    width="640" height="400"
    loading="lazy" decoding="async"
  >
</picture>
  • alt es obligatorio (imágenes decorativas usan alt="")
  • Especificar width/height reduce CLS
  • loading="lazy" reduce coste de transferencia

8. URLs prefirmadas y descargas seguras

8.1 Rutas firmadas

Si se requiere control de acceso, la app debe mediar la distribución.

Route::get('/files/{media}', [FileController::class,'show'])
  ->middleware(['auth','signed'])
  ->name('files.show');
$url = URL::temporarySignedRoute('files.show', now()->addMinutes(10), ['media'=>$media->id]);

8.2 Content-Disposition

  • inline: renderizar en el navegador (imágenes/PDF)
  • attachment: descargar (archivos sensibles)

9. Caché: CDN + nombres con hash es la combinación más fuerte

Si las variantes públicas incluyen un hash de contenido en el nombre:

  • caché a largo plazo (1 año)
  • actualizaciones instantáneas cuando el contenido cambia (cambia la URL)

Ejemplo: thumb.5f2a9c.webp
Cache-Control: public, max-age=31536000, immutable

Para entrega dinámica, usa ETag/304.


10. PDFs y documentos: importan las alternativas y los resúmenes

  • No dependas solo del PDF; ofrece también la información clave en HTML
  • Usa PDFs etiquetados cuando sea posible (estructura de encabezados, texto alternativo)
  • Para PDFs largos, ofrece un resumen y una tabla de contenidos
  • Incluye tipo de archivo y tamaño en enlaces de descarga (p. ej., “PDF 2,3MB”)

La accesibilidad mejora mucho cuando hay una vía alternativa para personas que no pueden abrir o leer PDFs con facilidad.


11. Vídeo: haz estándar los captions (VTT) y transcripciones

Como el vídeo porta mucha información, no llegará a usuarios sin captions. Estandariza al menos:

  • <track kind="captions" src="...vtt" srclang="ja">
  • Una transcripción (versión en texto del audio)
  • Evitar autoplay; requerir acción del usuario
  • Respetar prefers-reduced-motion y suprimir vídeos “hero” cuando corresponda

12. Operación del texto alternativo: la calidad la determina el flujo editorial

No puedes llenar el texto alternativo solo con tecnología—usa un patrón operativo:

  • Mostrar siempre un campo de alt en el momento de subida
  • Permitir alt vacío solo cuando esté marcado explícitamente como “decorativo”
  • Para texto dentro de imagen, pon el mismo contenido en alt (si es largo, mejor en el cuerpo del texto)
  • En sitios multilingües, guarda alt por idioma como alt[ja] / alt[en]

13. Testing: protege subidas y autorización

  • Usa Storage Fake para validar subidas
  • Confirma que las URLs firmadas caducan y se rechazan
  • Confirma que otros usuarios no pueden acceder (autorización)
Storage::fake('s3');

$res = $this->post('/media', [
  'file' => UploadedFile::fake()->image('a.jpg', 800, 600),
]);
$res->assertCreated();

14. Errores comunes y cómo evitarlos

  • Poner originales en público
    • Solución: mantener originales privados; publicar solo variantes
  • Confiar en atributos accept
    • Solución: validar MIME y tamaño en servidor
  • Dejar alt vacío
    • Solución: exigir entrada (excepto decorativo)
  • Imágenes pesadas y páginas lentas
    • Solución: variantes + srcset + caché a largo plazo
  • Autoplay de vídeo con sonido
    • Solución: reproducción iniciada por usuario + captions + transcripciones
  • La cola se detiene y dejan de generarse variantes
    • Solución: monitorización con Horizon + notificación de trabajos fallidos
  • La caché no funciona y suben los costes
    • Solución: nombres con hash + immutable

15. Checklist (compartible)

Seguridad

  • [ ] Validar MIME + tamaño; prevenir suplantación de extensiones
  • [ ] Eliminar EXIF; escaneo de virus (async)
  • [ ] Mantener originales privados; distribuir vía URLs prefirmadas

Rendimiento

  • [ ] Generación de variantes en cola (resize/AVIF/WebP)
  • [ ] picture/srcset, width/height, lazy loading
  • [ ] CDN + nombres con hash + caché a largo plazo

Operaciones

  • [ ] Gestión de metadatos de medios (owner/tenant/variants/meta)
  • [ ] Monitorizar cola/scheduler; notificar fallos
  • [ ] Borrado y ciclo de vida (limpiar variantes antiguas)

Accesibilidad

  • [ ] Operación de alt (requerido; decorativo es vacío)
  • [ ] Captions, subtítulos (VTT), transcripciones
  • [ ] Alternativas para PDF (HTML/resumen/mostrar tamaño)

Testing

  • [ ] Validar subidas con Storage::fake
  • [ ] Caducidad y autorización de URL firmada
  • [ ] Denegar acceso a otros usuarios

16. Conclusión

Las funciones de archivos de Laravel se vuelven notablemente estables cuando se diseñan de manera consistente alrededor de Storage: almacenar, distribuir, optimizar, autorizar y operar como un único sistema. Mantén originales privados y publica solo variantes derivadas. Aplica validación y saneamiento estrictos en subidas, genera variantes asíncronamente con colas, y haz rápida la entrega con CDN + caché de largo plazo. Al mismo tiempo, gestiona información de accesibilidad como texto alternativo y captions como metadatos, y asegura calidad mediante un flujo editorial. Los archivos son convenientes—pero también propensos a incidentes. Precisamente por eso conviene construir el “patrón” temprano y crecer con seguridad.


Enlaces de referencia

Salir de la versión móvil