
Boa saúde, vendedores ambulantes! No processo de trabalhar em um projeto de site de namoro, tornou-se necessário organizar o armazenamento de fotos do usuário. De acordo com os termos de referência, o número de fotos por usuário é limitado a 10 arquivos. Mas pode haver dezenas de milhares de usuários. Especialmente considerando que o projeto em sua forma atual já existe desde o início do "zero". Ou seja, já existem milhares de usuários no banco de dados. Quase todo sistema de arquivos, até onde eu sei, reage muito negativamente a um grande número de nós filhos em uma pasta. Por experiência, posso dizer que os problemas começam após 1000-1500 arquivos / pastas na pasta pai.
Isenção de responsabilidade. Pesquisei no Google antes de escrever o artigo e encontrei várias soluções para o problema em discussão (por exemplo, aqui ou aqui ). Mas não encontrei uma solução única que corresponda exatamente à minha. Além disso, neste artigo, apenas compartilho minha própria experiência na solução do problema.Teoria
Além da tarefa de armazenamento, como tal, havia também uma condição na declaração de trabalho, segundo a qual era necessário deixar legendas e legendas para fotos. Claro, você não pode ficar sem um banco de dados. Ou seja, a primeira coisa que fazemos é criar uma tabela na qual escrevemos o mapeamento de metadados (assinaturas, títulos, etc.) com os arquivos no disco. Cada arquivo corresponde a uma linha no banco de dados. Assim, cada arquivo tem um identificador.
Uma pequena digressão. Vamos falar sobre o incremento automático. Um site de namoro pode ter uma dúzia ou dois mil usuários. A questão é quantos usuários geralmente passam pelo projeto durante todo o tempo de sua existência. Por exemplo, o público ativo do dating-ru é de várias centenas de milhares. No entanto, imagine quantos usuários restam durante a vida útil deste projeto; quantos usuários não estão ativados até o momento. E agora adicione nossa legislação, que nos obriga a armazenar informações sobre usuários por pelo menos seis meses ... Mais cedo ou mais tarde, 4 com um centavo de
UNSIGNED INT terminará. Portanto, é melhor
usar BIGINT para a chave primária.
Agora vamos tentar imaginar um número do tipo
BIGINT . Isso é 8 bytes. Cada byte é de 0 a 255. 255 nós filhos são bastante normais para qualquer sistema de arquivos. Ou seja, pegamos o identificador de arquivo na representação hexadecimal, dividimos em pedaços de dois caracteres. Usamos esses pedaços como nomes de pastas, os últimos como o nome do arquivo físico. LUCRO!
0f/65/84/10/67/68/19/ff.file
Elegante e simples. A extensão do arquivo não é importante aqui. De qualquer forma, o arquivo será fornecido por um script que fornecerá ao navegador um tipo MIME específico, que também armazenaremos no banco de dados. Além disso, o armazenamento de informações sobre o arquivo no banco de dados permite redefinir o caminho do navegador para ele. Digamos que o arquivo que temos esteja localizado em relação ao diretório do projeto no caminho
/content/files/0f/65/84/10/67/68/19/ff.file
. E no banco de dados, você pode escrever uma URL para ele, por exemplo,
/content/users/678/files/somefile
. SEOs provavelmente são muito sorridentes agora. Tudo isso nos permite não nos preocupar mais sobre onde colocar o arquivo fisicamente.
Tabela no banco de dados
Além do identificador, tipo MIME, URL e localização física, armazenaremos os arquivos md5 e sha1 na tabela para filtrar os mesmos arquivos, se necessário. Obviamente, também precisamos armazenar relacionamentos de entidades nesta tabela. Suponha o ID do usuário ao qual os arquivos pertencem. E se o projeto não for muito grande, no mesmo sistema podemos armazenar, digamos, fotografias de mercadorias. Portanto, também armazenaremos o nome da classe de entidade à qual o registro pertence.
Falando em pássaros. Se você fechar a pasta usando .htaccess para acesso externo, o arquivo poderá ser obtido apenas através do script. E no script será possível determinar o acesso ao arquivo. Olhando um pouco mais adiante, direi que no meu CMS (que o projeto mencionado está sendo visto agora) o acesso é determinado por grupos de usuários básicos, dos quais eu tenho 8 - convidados, usuários, gerentes, administradores, inativos, bloqueados, excluídos e superadministradores. O superadministrador pode fazer absolutamente tudo, por isso não participa da determinação do acesso. Se o usuário tiver um sinalizador de superadministrador, ele será um superadministrador. Tudo é simples. Ou seja, determinaremos o acesso aos sete grupos restantes. O acesso é simples - dê o arquivo ou não. Total, você pode pegar um campo do tipo
TINYINT .
E mais uma coisa. De acordo com nossa lei, teremos que armazenar fisicamente imagens personalizadas. Ou seja, precisamos marcar as imagens como excluídas, em vez de excluir fisicamente. É mais conveniente usar um campo de bits para esses fins. Nesses casos, costumo usar um campo do tipo
INT . Para reservar, por assim dizer. Além disso, eu já tenho uma tradição estabelecida de colocar a bandeira
DELETED no quinto bit a partir do final. Mas isso não importa novamente.
O que temos como resultado:
create table `files` ( `id` bigint not null auto_increment,
Classe de expedidor
Agora precisamos criar uma classe com a qual faremos o upload de arquivos. A classe deve fornecer a capacidade de criar arquivos, substituir / modificar arquivos, excluir arquivos. Além disso, vale considerar dois pontos. Primeiramente, o projeto pode ser transferido de servidor para servidor. Portanto, na classe, você precisa definir uma propriedade que contenha o diretório raiz dos arquivos. Em segundo lugar, será muito desagradável se alguém bater em uma tabela no banco de dados. Então, você precisa prever a possibilidade de recuperação de dados. Com o primeiro, tudo fica geralmente claro. Quanto ao backup de dados, reservamos apenas o que não pode ser restaurado.
ID - restaurado a partir do local físico do arquivo
entity_type - não restaurado
entidade - não restaurada
mime - restaurado usando a extensão finfo
md5 - é restaurado a partir do próprio arquivo
sha1 - restaurado a partir do próprio arquivo
file - restaurado a partir do local físico do arquivo
URL - não restaurado
meta - não restaurado
tamanho - restaurado a partir do próprio arquivo
criado - você pode obter informações de um arquivo
atualizado - você pode obter informações de um arquivo
acesso - não restaurado
sinalizadores - não restaurados
Você pode descartar imediatamente as informações meta. Não é crítico para o funcionamento do sistema. E para uma recuperação mais rápida, você ainda precisa salvar o tipo MIME. Total: tipo de entidade, ID da entidade, MIME, URL, acesso e sinalizadores. Para aumentar a confiabilidade do sistema, armazenaremos informações de backup para cada pasta de destino separadamente na própria pasta.
Código da classe <?php class BigFiles { const FLAG_DELETED = 0x08000000;
Considere alguns pontos:
-
realRoot - o caminho completo para a pasta com o sistema de arquivos terminando em uma barra.
-
webRoot - o caminho da raiz do site sem uma barra inicial (veja abaixo o porquê).
- Como DBMS, eu uso a extensão
MySQLi .
- De fato, as informações da matriz
$ _FILES são passadas como o primeiro argumento para o método de
upload .
- Se você chamar o método de
atualização para passar o ID de um arquivo existente, ele será substituído se a matriz de entrada em
tmp_name não estiver vazia.
- Você pode excluir e alterar os sinalizadores de arquivos vários de cada vez. Para fazer isso, em vez de passar o identificador de arquivo, você deve passar uma matriz com identificadores ou uma string com eles, separada por vírgulas.
Encaminhamento
Na verdade, ele se resume a algumas linhas no htaccess na raiz do site (pressupõe-se que o mod_rewrite esteja ativado):
RewriteCond %{REQUEST_URI} ^/content/(.*)$ RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.+)$ content/index.php?file=$1 [L,QSA]
"Conteúdo" é a pasta na raiz do site no meu caso. Claro que você pode nomear a pasta de maneira diferente. E, claro, o próprio index.php, armazenado no meu caso na pasta de conteúdo:
<?php $dbHost = '127.0.0.1'; $dbUser = 'user'; $dbPass = '****'; $dbName = 'database'; try { if (empty($_REQUEST['file'])) { header('HTTP/1.1 400 Bad Request'); exit; } $userG = 'anonimous';
Bem, por si só, fechamos o próprio sistema de arquivos do acesso externo. Coloque o arquivo
.htaccess
na raiz da pasta
content/files
com apenas uma linha:
Deny from all
Sumário
Esta solução permite evitar a perda de desempenho do sistema de arquivos devido a um aumento no número de arquivos. Pelo menos os problemas na forma de milhares de arquivos em uma pasta podem definitivamente ser evitados. E, ao mesmo tempo, podemos organizar e controlar o acesso a arquivos em endereços legíveis por humanos. Além disso, conformidade com nossa legislação sombria. Faça uma reserva imediatamente, esta solução NÃO é uma maneira completa de proteger o conteúdo. Lembre-se: se algo é reproduzido no navegador, ele pode ser baixado gratuitamente.