Как вы храните загруженные файлы в файловой системе? - PullRequest
0 голосов
/ 22 октября 2008

Я пытаюсь найти лучший способ хранения загруженных пользователем файлов в файловой системе. Файлы варьируются от личных файлов до файлов вики. Конечно, БД покажет те файлы, которые мне еще предстоит выяснить.

Основные требования:

  • Фея Приличная Безопасность, чтобы Люди не могли угадать имена файлов (Picture001.jpg, Picture002.jpg, Music001.mp3 большой нет нет)
  • Простое резервное копирование и зеркальное отображение (я предпочитаю способ, чтобы мне не нужно было копировать весь жесткий диск каждый раз, когда я хочу сделать резервную копию. Мне нравится идея резервного копирования только самых новых элементов, но я гибок варианты здесь.)
  • При необходимости масштабируется до миллионов файлов на нескольких серверах.

Ответы [ 5 ]

3 голосов
/ 22 октября 2008

Направляющие для имен файлов, автоматически расширяющие иерархию папок, содержащую не более пары тысяч файлов / папок в каждой папке. Резервное копирование новых файлов выполняется путем резервного копирования новых папок.

Вы не указали, какую среду и / или язык программирования вы используете, но вот пример C # / .net / Windows:

using System;
using System.IO;
using System.Xml.Serialization;

/// <summary>
/// Class for generating storage structure and file names for document storage.
/// Copyright (c) 2008, Huagati Systems Co.,Ltd. 
/// </summary>

public class DocumentStorage
{
    private static StorageDirectory _StorageDirectory = null;

    public static string GetNewUNCPath()
    {
        string storageDirectory = GetStorageDirectory();
        if (!storageDirectory.EndsWith("\\"))
        {
            storageDirectory += "\\";
        }
        return storageDirectory + GuidEx.NewSeqGuid().ToString() + ".data";
    }

    public static void SaveDocumentInfo(string documentPath, Document documentInfo)
    {
        //the filestream object don't like NTFS streams so this is disabled for now...
        return;

        //stores a document object in a separate "docinfo" stream attached to the file it belongs to
        //XmlSerializer ser = new XmlSerializer(typeof(Document));
        //string infoStream = documentPath + ":docinfo";
        //FileStream fs = new FileStream(infoStream, FileMode.Create);
        //ser.Serialize(fs, documentInfo);
        //fs.Flush();
        //fs.Close();
    }

    private static string GetStorageDirectory()
    {
        string storageRoot = ConfigSettings.DocumentStorageRoot;
        if (!storageRoot.EndsWith("\\"))
        {
            storageRoot += "\\";
        }

        //get storage directory if not set
        if (_StorageDirectory == null)
        {
            _StorageDirectory = new StorageDirectory();
            lock (_StorageDirectory)
            {
                string path = ConfigSettings.ReadSettingString("CurrentDocumentStoragePath");
                if (path == null)
                {
                    //no storage tree created yet, create first set of subfolders
                    path = CreateStorageDirectory(storageRoot, 1);
                    _StorageDirectory.FullPath = path.Substring(storageRoot.Length);
                    ConfigSettings.WriteSettingString("CurrentDocumentStoragePath", _StorageDirectory.FullPath);
                }
                else
                {
                    _StorageDirectory.FullPath = path;
                }
            }
        }

        int fileCount = (new DirectoryInfo(storageRoot + _StorageDirectory.FullPath)).GetFiles().Length;
        if (fileCount > ConfigSettings.FolderContentLimitFiles)
        {
            //if the directory has exceeded number of files per directory, create a new one...
            lock (_StorageDirectory)
            {
                string path = GetNewStorageFolder(storageRoot + _StorageDirectory.FullPath, ConfigSettings.DocumentStorageDepth);
                _StorageDirectory.FullPath = path.Substring(storageRoot.Length);
                ConfigSettings.WriteSettingString("CurrentDocumentStoragePath", _StorageDirectory.FullPath);
            }
        }

        return storageRoot + _StorageDirectory.FullPath;
    }

    private static string GetNewStorageFolder(string currentPath, int currentDepth)
    {
        string parentFolder = currentPath.Substring(0, currentPath.LastIndexOf("\\"));
        int parentFolderFolderCount = (new DirectoryInfo(parentFolder)).GetDirectories().Length;
        if (parentFolderFolderCount < ConfigSettings.FolderContentLimitFolders)
        {
            return CreateStorageDirectory(parentFolder, currentDepth);
        }
        else
        {
            return GetNewStorageFolder(parentFolder, currentDepth - 1);
        }
    }

    private static string CreateStorageDirectory(string currentDir, int currentDepth)
    {
        string storageDirectory = null;
        string directoryName = GuidEx.NewSeqGuid().ToString();
        if (!currentDir.EndsWith("\\"))
        {
            currentDir += "\\";
        }
        Directory.CreateDirectory(currentDir + directoryName);

        if (currentDepth < ConfigSettings.DocumentStorageDepth)
        {
            storageDirectory = CreateStorageDirectory(currentDir + directoryName, currentDepth + 1);
        }
        else
        {
            storageDirectory = currentDir + directoryName;
        }
        return storageDirectory;
    }

    private class StorageDirectory
    {
        public string DirectoryName { get; set; }
        public StorageDirectory ParentDirectory { get; set; }
        public string FullPath
        {
            get
            {
                if (ParentDirectory != null)
                {
                    return ParentDirectory.FullPath + "\\" + DirectoryName;
                }
                else
                {
                    return DirectoryName;
                }
            }
            set
            {
                if (value.Contains("\\"))
                {
                    DirectoryName = value.Substring(value.LastIndexOf("\\") + 1);
                    ParentDirectory = new StorageDirectory { FullPath = value.Substring(0, value.LastIndexOf("\\")) };
                }
                else
                {
                    DirectoryName = value;
                }
            }
        }
    }
}
3 голосов
/ 22 октября 2008

Один из методов - хранить данные в файлах, названных в честь хэша (SHA1) их содержимого. Это не легко догадаться, любая программа резервного копирования должна быть в состоянии справиться с этим, и она легко теряется (сохраняя хэши, начинающиеся с 0 на одной машине, хэши, начинающиеся с 1 на следующей, и т. Д.).

База данных будет содержать сопоставление назначенного имени пользователя и хэша содержимого SHA1.

1 голос
/ 22 октября 2008

SHA1-хэш имени файла + соль (или, если хотите, содержимого файла. Это облегчает обнаружение дубликатов файлов, но также создает ОЧЕНЬ большую нагрузку на сервер). Это может потребовать некоторой настройки, чтобы быть уникальным (то есть добавить Uploaded UserID или Timestamp), и соль заключается в том, чтобы сделать его неубедительным.

Структура папок затем по частям хэша.

Например, если хеш равен "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12", то папки могут быть:

/2
/2/2f/
/2/2f/2fd/
/2/2f/2fd/2fd4e1c67a2d28fced849ee1bb76e7391b93eb12

Это сделано для предотвращения больших папок (в некоторых операционных системах возникают проблемы с перечислением папок с миллионом файлов, что приводит к созданию нескольких подпапок для частей хэша. Сколько уровней? Это зависит от того, сколько файлов вы ожидаете, но 2 или 3 обычно разумно.

0 голосов
/ 22 октября 2008

Расширяя ответ Фила Сакре, еще один аспект безопасности заключается в использовании отдельного доменного имени для загружаемых файлов (например, Википедия использует upload.wikimedia.org) и убедитесь, что домен не может прочитать ни одного куки вашего сайта. Это не позволяет людям загружать HTML-файл со сценарием для кражи файлов cookie ваших пользователей (просто установить заголовок Content-Type недостаточно, поскольку некоторые браузеры , как известно, игнорируют его и предполагают, основываясь на содержимое файла, его также можно встраивать в другие типы файлов, поэтому нетрудно проверить HTML и запретить его).

0 голосов
/ 22 октября 2008

С точки зрения одного аспекта вашего вопроса (безопасность): лучший способ безопасного хранения выгруженных файлов в файловой системе - убедиться, что выгруженные файлы находятся вне корневого каталога (т.е. вы не можете получить к ним доступ напрямую через URL - вам нужно пройти через скрипт).

Это дает вам полный контроль над тем, что люди могут загрузить (безопасность), и позволяет выполнять такие вещи, как ведение журнала. Конечно, вы должны убедиться, что сам скрипт безопасен, но это означает, что только люди, которым вы разрешите, смогут загружать определенные файлы.

...