You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel带隐私限制的文件上传、存储与下载开发问询

Hey there! Let's walk through building this file association system—this is a super common use case, so we can break it down into manageable, actionable steps:

Core Solution Breakdown

1. Database Table Design (Foundational Piece)

First, we need tables to track users, uploaded files, and the links between them. A multi-to-many relationship works here because one file can be linked to multiple users, and one user can have multiple files.

-- Users table (tracks admins and regular users)
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    role ENUM('admin', 'user') NOT NULL DEFAULT 'user',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Files table (stores metadata about uploaded files)
CREATE TABLE files (
    id INT PRIMARY KEY AUTO_INCREMENT,
    original_filename VARCHAR(255) NOT NULL,
    stored_filename VARCHAR(255) NOT NULL, -- Unique name to avoid conflicts
    file_path VARCHAR(255) NOT NULL, -- Server storage path (e.g., "uploads/20240520/unique-id.pdf")
    file_size INT NOT NULL, -- File size in bytes
    uploader_id INT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (uploader_id) REFERENCES users(id)
);

-- User-File Association table (links files to their assigned users)
CREATE TABLE user_file_associations (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    file_id INT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id),
    FOREIGN KEY (file_id) REFERENCES files(id),
    UNIQUE KEY unique_user_file (user_id, file_id) -- Prevent duplicate associations
);

2. Admin Restricted Area: Permission Control

You need to lock down the upload section so only admins can access it. This needs to be handled on both the backend (critical for security) and frontend (for a smooth UX).

Backend Middleware Example (Node.js/Express)

// Middleware to check if the user is an admin
function requireAdmin(req, res, next) {
    if (req.user && req.user.role === 'admin') {
        return next();
    }
    res.status(403).send('Access Denied: Admin-only area');
}

// Protected upload route using the middleware
app.post('/admin/upload', requireAdmin, upload.single('file'), (req, res) => {
    // Upload logic goes here
});

Frontend Route Guard Example (Vue.js)

router.beforeEach((to, from, next) => {
    const currentUser = JSON.parse(localStorage.getItem('user'));
    // Redirect non-admins trying to access admin routes
    if (to.path.startsWith('/admin') && (!currentUser || currentUser.role !== 'admin')) {
        next('/login');
    } else {
        next();
    }
});

3. File Upload & User Association Workflow

When an admin uploads a file, the process should:

  • Accept the file and store it securely on the server
  • Save file metadata to the database
  • Link the file to selected users via the association table

Backend Upload Logic (Node.js/Express with Multer)

const multer = require('multer');
const path = require('path');
const fs = require('fs');

// Configure file storage for uploads
const storage = multer.diskStorage({
    destination: (req, file, cb) => {
        const uploadDir = path.join(__dirname, 'private_uploads'); // Use a non-web-accessible folder
        if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir);
        cb(null, uploadDir);
    },
    filename: (req, file, cb) => {
        // Generate a unique filename to avoid overwrites
        const uniqueName = `${Date.now()}-${Math.round(Math.random() * 1E9)}${path.extname(file.originalname)}`;
        cb(null, uniqueName);
    }
});

const upload = multer({ 
    storage: storage,
    limits: { fileSize: 10 * 1024 * 1024 }, // Limit to 10MB files
    fileFilter: (req, file, cb) => {
        // Allow only safe file types
        const allowedTypes = ['.pdf', '.doc', '.docx', '.xls', '.xlsx'];
        if (allowedTypes.includes(path.extname(file.originalname).toLowerCase())) {
            cb(null, true);
        } else {
            cb(new Error('Only PDF, Word, and Excel files are allowed'));
        }
    }
});

// Upload endpoint
app.post('/admin/upload', requireAdmin, upload.single('file'), async (req, res) => {
    try {
        const { userIds } = req.body; // Array of user IDs selected by the admin
        const file = req.file;

        // Save file metadata to the database
        const fileResult = await db.query(
            'INSERT INTO files (original_filename, stored_filename, file_path, file_size, uploader_id) VALUES (?, ?, ?, ?, ?)',
            [file.originalname, file.filename, file.path, file.size, req.user.id]
        );
        const fileId = fileResult.insertId;

        // Batch create user-file associations
        const associationEntries = userIds.map(userId => [userId, fileId]);
        await db.query(
            'INSERT INTO user_file_associations (user_id, file_id) VALUES ?',
            [associationEntries]
        );

        res.status(200).json({ message: 'File uploaded and linked to users successfully!' });
    } catch (error) {
        console.error(error);
        res.status(500).json({ message: 'Upload failed. Please try again.' });
    }
});

4. User-Facing Documents Section

For regular users, we need to fetch only the files linked to them, and let them download those files securely.

Backend Fetch & Download Routes

// Middleware to check if user is authenticated
function requireAuth(req, res, next) {
    if (req.user) {
        return next();
    }
    res.status(401).send('Please log in first');
}

// Get user's associated files
app.get('/user/documents', requireAuth, async (req, res) => {
    try {
        const userId = req.user.id;
        const userDocuments = await db.query(`
            SELECT f.id, f.original_filename, f.created_at, u.username AS uploaded_by
            FROM files f
            JOIN user_file_associations ufa ON f.id = ufa.file_id
            JOIN users u ON f.uploader_id = u.id
            WHERE ufa.user_id = ?
            ORDER BY f.created_at DESC
        `, [userId]);

        res.status(200).json(userDocuments);
    } catch (error) {
        res.status(500).json({ message: 'Failed to load your documents' });
    }
});

// Secure file download route (verify user has access first)
app.get('/user/download/:fileId', requireAuth, async (req, res) => {
    try {
        const userId = req.user.id;
        const fileId = req.params.fileId;

        // Check if user is allowed to download this file
        const accessCheck = await db.query(
            'SELECT * FROM user_file_associations WHERE user_id = ? AND file_id = ?',
            [userId, fileId]
        );

        if (accessCheck.length === 0) {
            return res.status(403).send('You do not have permission to download this file');
        }

        // Fetch file details
        const file = await db.query('SELECT * FROM files WHERE id = ?', [fileId]);
        if (file.length === 0) {
            return res.status(404).send('File not found');
        }

        // Send file to user (backend proxies the download to keep files secure)
        res.download(file[0].file_path, file[0].original_filename);
    } catch (error) {
        res.status(500).json({ message: 'Download failed' });
    }
});

5. Critical Security & UX Notes

  • File Storage Safety: Never store uploads in a web-accessible directory—use a private folder and proxy downloads through the backend to prevent unauthorized access.
  • Double-Check Permissions: Don't rely solely on frontend guards; every sensitive endpoint must validate user roles/access on the backend.
  • Error Handling: Give clear, user-friendly messages for upload failures, access denials, or missing files.
  • Cleanup: Add logic to delete orphaned files (if a file's associations are removed or the file is deleted from the database).

内容的提问来源于stack exchange,提问作者api pota

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 04:11:16