定制 wramirez83/fluxupload 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

wramirez83/fluxupload

最新稳定版本:3.0.0

Composer 安装命令:

composer require wramirez83/fluxupload

包简介

Laravel package for large file uploads using chunking with automatic resumption

README 文档

README

FluxUpload es un paquete Laravel que permite la subida de archivos grandes (superiores a 1GB) mediante chunking (división en partes) con capacidad de reanudación automática.

📋 Características

  • ✅ Subida de archivos grandes (hasta 25GB)
  • ✅ Chunking automático (división en partes)
  • ✅ Reanudación automática de cargas interrumpidas
  • ✅ Validación de integridad mediante hash (opcional)
  • ✅ Soporte para múltiples discos de almacenamiento (Local, S3, MinIO, Azure Blob)
  • ✅ API REST completa
  • ✅ Cliente JavaScript incluido
  • ✅ Eventos para integración
  • ✅ Limpieza automática de sesiones expiradas
  • ✅ Compatible con Laravel 12

📦 Instalación

Requisitos

  • PHP 8.1 o superior
  • Laravel 12
  • Extensiones PHP: fileinfo, mbstring, openssl, json

Instalación vía Composer

composer require wramirez83/fluxupload

Versión actual: 3.0.0

Publicar configuración y migraciones

php artisan vendor:publish --tag=fluxupload-config
php artisan vendor:publish --tag=fluxupload-migrations

Ejecutar migraciones

php artisan migrate

Publicar assets JavaScript (opcional)

php artisan vendor:publish --tag=fluxupload-assets

⚙️ Configuración

El archivo de configuración se encuentra en config/fluxupload.php. Puedes configurar:

// Tamaño de chunk por defecto (2MB en v3.0.0)
'chunk_size' => env('FLUXUPLOAD_CHUNK_SIZE', 2097152),

// Tamaño máximo de archivo (25GB en v3.0.0)
'max_file_size' => env('FLUXUPLOAD_MAX_FILE_SIZE', 26843545600),

// Expiración de sesiones (24 horas)
'session_expiration_hours' => env('FLUXUPLOAD_SESSION_EXPIRATION', 24),

// Disco de almacenamiento
'storage_disk' => env('FLUXUPLOAD_STORAGE_DISK', 'local'),

// Ruta de almacenamiento
'storage_path' => env('FLUXUPLOAD_STORAGE_PATH', 'fluxupload'),

// Extensiones permitidas (vacío = todas)
'allowed_extensions' => env('FLUXUPLOAD_ALLOWED_EXTENSIONS') 
    ? explode(',', env('FLUXUPLOAD_ALLOWED_EXTENSIONS'))
    : [],

// Validar hash
'validate_hash' => env('FLUXUPLOAD_VALIDATE_HASH', false),
'hash_algorithm' => env('FLUXUPLOAD_HASH_ALGORITHM', 'sha256'),

Variables de entorno

Puedes configurar el paquete mediante variables de entorno en tu archivo .env:

FLUXUPLOAD_CHUNK_SIZE=2097152
FLUXUPLOAD_MAX_FILE_SIZE=26843545600
FLUXUPLOAD_SESSION_EXPIRATION=24
FLUXUPLOAD_STORAGE_DISK=local
FLUXUPLOAD_STORAGE_PATH=fluxupload
FLUXUPLOAD_ALLOWED_EXTENSIONS=pdf,doc,docx,zip
FLUXUPLOAD_VALIDATE_HASH=true
FLUXUPLOAD_HASH_ALGORITHM=sha256
FLUXUPLOAD_ROUTE_PREFIX=fluxupload
FLUXUPLOAD_MIDDLEWARE=api

🚀 Uso Básico

API REST

1. Inicializar sesión de subida

POST /fluxupload/init
Content-Type: application/json

{
    "filename": "documento.pdf",
    "total_size": 104857600,
    "chunk_size": 2097152,
    "mime_type": "application/pdf",
    "hash": "sha256_hash_here" // opcional
}

Respuesta:

{
    "success": true,
    "session_id": "abc123...",
    "total_chunks": 50,
    "chunk_size": 2097152,
    "uploaded_chunks": 0,
    "missing_chunks": [0, 1, 2, ..., 19],
    "progress": 0
}

2. Subir chunk

POST /fluxupload/chunk
Content-Type: multipart/form-data

session_id: abc123...
chunk_index: 0
chunk: [archivo binario]

Respuesta:

{
    "success": true,
    "message": "Chunk uploaded successfully",
    "session_id": "abc123...",
    "chunk_index": 0,
    "uploaded_chunks": 1,
    "total_chunks": 50,
    "progress": 5.0,
    "status": "uploading"
}

3. Consultar estado

GET /fluxupload/status/{session_id}

Respuesta:

{
    "success": true,
    "session_id": "abc123...",
    "filename": "documento.pdf",
    "status": "uploading",
    "uploaded_chunks": 25,
    "total_chunks": 50,
    "total_size": 104857600,
    "progress": 50.0,
    "missing_chunks": [25, 26, 27, ..., 49],
    "storage_path": null,
    "error_message": null,
    "expires_at": "2024-01-01T12:00:00Z"
}

Cliente JavaScript

Uso básico

<script src="/vendor/fluxupload/fluxupload.js"></script>
<script>
const uploader = new FluxUpload({
    baseUrl: '/fluxupload',
    chunkSize: 2 * 1024 * 1024, // 2MB (v3.0.0)
    parallelUploads: 3,
    onProgress: (progress) => {
        console.log(`Progress: ${progress.progress}%`);
    },
    onComplete: (status) => {
        console.log('Upload completed!', status);
    },
    onError: (error) => {
        console.error('Upload error:', error);
    }
});

// Subir archivo
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', async (e) => {
    const file = e.target.files[0];
    try {
        await uploader.upload(file);
    } catch (error) {
        console.error('Upload failed:', error);
    }
});
</script>

Reanudar subida

// Si tienes un session_id previo
uploader.sessionId = 'previous-session-id';
await uploader.resume();

Pausar/Cancelar

// Pausar
uploader.pause();

// Cancelar
uploader.cancel();

Uso en PHP (Backend)

use Wramirez83\FluxUpload\Facades\FluxUpload;

// Obtener servicios
$uploadService = FluxUpload::getUploadService();
$sessionService = FluxUpload::getSessionService();
$chunkService = FluxUpload::getChunkService();

// Crear sesión manualmente
$session = $sessionService->createSession([
    'original_filename' => 'archivo.pdf',
    'total_size' => 104857600,
    'total_chunks' => 50,
    'chunk_size' => 2097152,
]);

// Obtener chunks faltantes
$missingChunks = $sessionService->getMissingChunks($session->session_id);

// Ensamblar archivo manualmente
$uploadService->assembleFile($session);

📡 Eventos

FluxUpload emite eventos que puedes escuchar:

FluxUploadCompleted

Se emite cuando una subida se completa exitosamente.

use Wramirez83\FluxUpload\Events\FluxUploadCompleted;
use Illuminate\Support\Facades\Event;

Event::listen(FluxUploadCompleted::class, function (FluxUploadCompleted $event) {
    $session = $event->session;
    
    // Procesar archivo completado
    // $session->storage_path contiene la ruta del archivo final
    // $session->storage_disk contiene el disco donde se guardó
});

FluxUploadFailed

Se emite cuando una subida falla.

use Wramirez83\FluxUpload\Events\FluxUploadFailed;

Event::listen(FluxUploadFailed::class, function (FluxUploadFailed $event) {
    $session = $event->session;
    $error = $event->errorMessage;
    
    // Manejar error
    logger()->error("Upload failed: {$error}", [
        'session_id' => $session->session_id,
    ]);
});

FluxUploadChunkReceived

Se emite cada vez que se recibe un chunk (opcional).

use Wramirez83\FluxUpload\Events\FluxUploadChunkReceived;

Event::listen(FluxUploadChunkReceived::class, function (FluxUploadChunkReceived $event) {
    $session = $event->session;
    $chunk = $event->chunk;
    
    // Procesar chunk recibido
});

🧹 Limpieza de Sesiones

Comando Artisan

Para limpiar sesiones expiradas manualmente:

php artisan fluxupload:clean

Modo dry-run

Para ver qué se eliminaría sin eliminar realmente:

php artisan fluxupload:clean --dry-run

Programar limpieza automática

Agrega al app/Console/Kernel.php:

protected function schedule(Schedule $schedule)
{
    $schedule->command('fluxupload:clean')
        ->daily()
        ->at('02:00');
}

🔒 Seguridad

Autenticación

Puedes proteger las rutas mediante middleware. En config/fluxupload.php:

'middleware' => ['api', 'auth:sanctum'],

O usando middleware personalizado:

'middleware' => ['api', 'throttle:60,1'],

Validación de extensiones

'allowed_extensions' => ['pdf', 'doc', 'docx', 'zip'],

Validación de hash

Para validar la integridad del archivo:

'validate_hash' => true,
'hash_algorithm' => 'sha256', // o 'md5'

🧪 Pruebas

Ejecutar todas las pruebas:

composer test

O con PHPUnit directamente:

./vendor/bin/phpunit

Estructura de pruebas

  • tests/Unit/ - Pruebas unitarias de servicios y modelos
  • tests/Feature/ - Pruebas de integración de la API

📚 Ejemplos

Ejemplo completo con JavaScript

<!DOCTYPE html>
<html>
<head>
    <title>FluxUpload Example</title>
</head>
<body>
    <input type="file" id="fileInput">
    <button id="uploadBtn">Upload</button>
    <button id="pauseBtn">Pause</button>
    <button id="resumeBtn">Resume</button>
    <div id="progress"></div>

    <script src="/vendor/fluxupload/fluxupload.js"></script>
    <script>
        const uploader = new FluxUpload({
            baseUrl: '/fluxupload',
            chunkSize: 2 * 1024 * 1024, // 2MB (v3.0.0)
            parallelUploads: 3,
            onProgress: (progress) => {
                document.getElementById('progress').textContent = 
                    `Progress: ${progress.progress}% (${progress.uploaded}/${progress.total})`;
            },
            onComplete: (status) => {
                alert('Upload completed!');
                console.log('File saved at:', status.storage_path);
            },
            onError: (error) => {
                alert('Upload failed: ' + error.message);
            }
        });

        document.getElementById('uploadBtn').addEventListener('click', async () => {
            const fileInput = document.getElementById('fileInput');
            if (fileInput.files.length > 0) {
                try {
                    await uploader.upload(fileInput.files[0]);
                } catch (error) {
                    console.error(error);
                }
            }
        });

        document.getElementById('pauseBtn').addEventListener('click', () => {
            uploader.pause();
        });

        document.getElementById('resumeBtn').addEventListener('click', async () => {
            try {
                await uploader.resume();
            } catch (error) {
                console.error(error);
            }
        });
    </script>
</body>
</html>

Ejemplo con React

import { useState } from 'react';
import FluxUpload from './fluxupload';

function FileUploader() {
    const [progress, setProgress] = useState(0);
    const [uploader] = useState(() => new FluxUpload({
        baseUrl: '/fluxupload',
        onProgress: (p) => setProgress(p.progress),
        onComplete: (status) => {
            console.log('Completed:', status);
            setProgress(100);
        },
        onError: (error) => {
            console.error('Error:', error);
        }
    }));

    const handleFileChange = async (e) => {
        const file = e.target.files[0];
        if (file) {
            await uploader.upload(file);
        }
    };

    return (
        <div>
            <input type="file" onChange={handleFileChange} />
            <div>Progress: {progress}%</div>
        </div>
    );
}

🐛 Solución de Problemas

Error: "Session not found"

  • Verifica que el session_id sea correcto
  • Verifica que la sesión no haya expirado
  • Revisa los logs de Laravel

Error: "Chunk size validation failed"

  • Verifica que el tamaño del chunk coincida con el configurado
  • El último chunk puede ser más pequeño que el tamaño configurado

Archivo no se ensambla correctamente

  • Verifica que todos los chunks se hayan subido
  • Revisa los permisos de escritura en el directorio de chunks
  • Verifica el espacio en disco disponible

📝 Changelog

Ver CHANGELOG.md para más detalles.

🤝 Contribuir

Las contribuciones son bienvenidas. Por favor:

  1. Fork el proyecto
  2. Crea una rama para tu feature (git checkout -b feature/AmazingFeature)
  3. Commit tus cambios (git commit -m 'Add some AmazingFeature')
  4. Push a la rama (git push origin feature/AmazingFeature)
  5. Abre un Pull Request

📄 Licencia

Este paquete es de código abierto bajo la licencia MIT.

👥 Autores

  • Medusa - Desarrollo inicial

🙏 Agradecimientos

  • Laravel Framework
  • Comunidad de desarrolladores PHP

¿Necesitas ayuda? Abre un issue en GitHub o contacta al equipo de desarrollo.

统计信息

  • 总下载量: 6
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 2
  • 点击次数: 0
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 1
  • Watchers: 0
  • Forks: 0
  • 开发语言: HTML

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-11-16