Parte 4: Upload de imagens
Até agora, a Parte 0 e a Parte 1 abordaram o modelo de dados e o documento do usuário usados no aplicativo, seguidos pela Parte 2, em que verificamos as contas por e-mail usando o Nodemailer e o Sendgrid. Em seguida, tivemos a Parte 3, que abordou a autenticação de usuário baseada em sessão. Agora que podemos criar um usuário e permitir que ele faça login, seria ideal associar uma foto de perfil a esse usuário. Para isso, precisamos fazer upload de imagens e cortá-las de modo que todas tenham um formato uniforme. Vamos começar!
Materiais necessários:
- Node.js
- Expresso
- Biblioteca "fs" do Node.js
- Binários do GraphicsMagick
Módulos de nó usados:
- Couchbase Node.js SDK/N1QL
- analisador de corpo
- uuid
- Multer
- GraphicsMagick
Resumo:
Uma das partes mais difíceis do desenvolvimento do Touchbase foi criar uma maneira de armazenar imagens. Isso foi um desafio porque há muitas partes diferentes (front-end e back-end) que precisam se unir para esse processo. Havia a necessidade de recortar, para que os usuários tivessem o máximo de controle sobre suas imagens. Isso exigia um módulo de corte para o Angular.js, ng-cropperbem como uma maneira de redimensionar a imagem para as novas dimensões encontradas no back-end fornecido por graphicsMagick. Havia também a necessidade de escolher em que formato armazenar essas imagens (base64), entre várias abordagens diferentes. Esta postagem do blog aborda o processo de passar a imagem do front-end para o back-end, incluindo o corte e a formatação.
Instalação do Multer e do GraphicsMagick
A configuração do módulo Multer é uma parte importante desse processo. O Multer é um módulo de nó que usamos para armazenar as imagens publicadas no front-end em uma pasta em nosso servidor. Para fazer download do módulo, execute o comando abaixo.
1 |
$ npm instalar multer --salvar |
Outro módulo importante de que precisamos é o GraphicsMagick. O download do GraphicsMagick deve ser feito em duas partes: instalar os binários do GraphicsMagick em seu sistema e fazer o download do módulo de nó do GraphicsMagick. Essa combinação nos permite alterar imagens em um caminho de arquivo especificado em nosso aplicativo Node.js. As instruções para baixar os binários do GraphicsMagick para os sistemas Linux, OSX e Windows podem ser encontradas no site Repositório do Github do Touchbase. Após concluir essa etapa, faça o download do módulo node para o GraphicsMagick com o comando abaixo.
1 |
$ npm instalar gm --salvar |
Configuração do Multer
A configuração do módulo Multer é feita na seção app.js e ajudará a indicar o estágio em que a imagem se encontra no processo de upload. A parte que mais nos interessa é quando o upload de uma imagem é concluído, o que é indicado por uma variável chamada 'feito'. Quando isso ocorrer, poderemos acessar o arquivo com segurança e fazer o corte e a redução de tamanho necessários da imagem.
Configuração do Multer app.js
Há um ponto de extremidade da API que lida com o corte e a redução da imagem (usando a configuração que fizemos anteriormente), e a maior parte de seu trabalho ocorre no 'Picture.attempt' função. Essa função só é executada depois que o upload da imagem é concluído (quando feito é verdadeiro), e Multer o armazenou no arquivo uploads especificada na pasta app.js configuração. A rota em si só pode recuperar alguns atributos da imagem antes que o upload da imagem seja concluído. Esses metadados que o Multer recebe serão cruciais durante o processo de corte.
O próprio Multer simplesmente faz o upload de qualquer tipo de formulário do tipo <enctype=’multipart/form-data’>. A imagem é simplesmente um tipo de entrada nesse formulário e, portanto, também podemos adicionar outros atributos ao formulário. Por exemplo, para obter os dados de corte do ng-cropper, eu crio um atributo oculto do formulário que é um objeto que contém todos os dados de corte. Dessa forma, a imagem e os metadados do front-end são enviados juntos no HTML POST executado quando o formulário é enviado. Durante esse processo, eu também acesso o localStorage do navegador, que contém o sessionID (discutido na parte 3) para enviar o ID, de modo que a rota possa ser autenticada. O 'Session.auth' é ajustada para lidar com essa situação do sessionID que vem no 'req.body' (o que acontece quando o sessionID é enviado como metadados do Multer). Para ver como isso é feito, a implementação do Angular.js do ng-cropper, bem como o formulário HTML que carrega a imagem com os dados de corte, são mostrados abaixo.
Formulário HTML de filtro com POST em public/html/picture-partial.html
Uso de "ng-cropper" em pictureController em public/js/touchbase.js
O Multer divide os dados do formulário em duas partes diferentes, quando são enviados para o back-end. Todos os metadados sobre qualquer arquivo carregado são especificados em 'req.files'. Isso nos informa coisas básicas como o caminho do arquivo para a imagem, o tamanho da imagem, o formato da imagem, etc. Diferentes aspectos disso são usados em nosso 'Picture.attempt' função. O restante dos dados do front-end, qualquer coisa que não seja um arquivo, é encontrado na função 'req.body' attribute. A partir daqui, podemos acessar os dados de corte que carregamos usando o atributo do formulário HTML. A maneira de acessar esses diferentes parâmetros é usando os atributos das diferentes partes do formulário. Por exemplo, ao acessar os uploads da imagem, no formulário de front-end, tínhamos a entrada de arquivo com um nome de 'userPhoto'. Para acessar os metadados para isso, olharíamos para 'req.files.userPhoto'. Para obter os dados de cultivo, devemos consultar seu nome, 'cropDim'e acessá-lo usando 'req.body.cropDim'. Esses dados são acessados no '/api/uploadAttempt' que pode ser vista abaixo.
API '/api/uploadAttempt
Agora que podemos acessar todos esses dados, precisamos usá-los. Eles entram em uso no 'Picture.attempt' função em models/picturemodel.js onde podemos usá-lo para realmente alterar a imagem. Primeiro, precisamos verificar se essa imagem é algo que podemos armazenar adequadamente. Em nossa configuração do Multer em app.js permitimos o upload de imagens de até 20 MB. Fazemos isso para fins de tratamento de erros, permitindo muito mais do que a maioria precisaria. Se um arquivo for carregado com um tamanho maior do que o nosso limite, o Multer falhará silenciosamente, portanto, apenas definimos um limite arbitrariamente alto. Em 'Picture.attempt'No primeiro nível, verificamos se a imagem tem mais de 7,5 MB e, se tiver, a excluímos. Nesse segundo nível de validação, informaríamos ao usuário que o tamanho máximo do arquivo é de 7,5 MB e que a imagem não pôde ser carregada, com orientações sobre como corrigir o problema. A 'fs.unlink' exclui o arquivo usando o caminho do arquivo que Multer nos forneceu no comando 'req.files' atributo. Em seguida, a função chama 'Picture.attempt'com o caminho de arquivo mencionado acima e manipula a imagem com o comando 'fs' e GraphicsMagick.
Função 'Picture.attempt
As etapas finais de validação da imagem vêm do GraphicsMagick. O motivo pelo qual usamos o GraphicsMagick para verificar o tipo de arquivo em vez do Multer é que o Multer verifica apenas a extensão do arquivo, o que não representa necessariamente o conteúdo. O GraphicsMagick faz sua própria análise do arquivo. Se o tipo de arquivo não for um dos formatos aceitáveis, o upload falhará e o usuário será notificado do erro. Passando por essa validação, precisamos apenas alterar o arquivo para cortar e reduzir o tamanho.
Usando o GraphicsMagick, a primeira etapa é 'autoOrient()' que usará dados EXIF (um padrão de formatação de arquivos de imagem) para orientar corretamente a imagem para a orientação em que foi tirada. Isso é importante para uploads de celulares, pois muitos deles podem ser girados em 90 graus, resultando em uma imagem estranha.
A imagem é então cortada usando os dados de corte que obtivemos do front-end por meio de 'req.body.cropDim'. Isso é então passado para a função de recorte do GraphicsMagick, que requer um deslocamento X e Y, bem como a altura e a largura da imagem. O ng-cropper espera esse método de recorte e nos fornece esses atributos quando a imagem é recortada, simplificando a passagem desses atributos diretamente para o GraphicsMagick.
Por fim, fazemos um downsizing básico da imagem para que o arquivo fique menor. Na minha opinião, 7,5 MB, ou mesmo 2 MB, é um pouco grande para ser armazenado e recuperado toda vez que o site é usado e um perfil de usuário é visualizado. Embora isso não seja necessariamente um grande esforço para o aplicativo, pode causar uma experiência ruim para os usuários, especialmente os usuários móveis. Depois de alguns testes, descobri que reduzir a escala para 200px e definir a qualidade da imagem para 50% trazia o máximo benefício em termos de qualidade de imagem e tamanho de arquivo. Em minha implementação atual, as imagens são quadrados de 150px. Tenha isso em mente, pois talvez você queira fazer a redução de forma diferente para o seu aplicativo se optar por exibir as imagens maiores ou menores. Após todas essas alterações, essa imagem alterada é gravada sobre seu local anterior.
Depois que a imagem for devidamente alterada, queremos lê-la novamente para transformar o binário da imagem em uma string base64 usando um Javascript 'Buffer' function. Fazemos isso porque o HTML pode ler a base64 em suas tags de imagem, o que simplificará a renderização das imagens no front-end quando quisermos exibi-las.
Por fim, carregamos essa cadeia de caracteres base64 usando uma operação de inserção de chave-valor. Usando o 'Session.auth' e o sessionID enviado nos dados do formulário Multer, sabemos o ID do usuário que fez o upload da imagem. Então, em 'req.userID' Como podemos ver, o '/api/uploadAttempt' API. Esse ID de usuário, com '_picMulterNode' anexado, é usado como o ID do documento para a imagem, de modo que possamos vincular facilmente o usuário a essa imagem no futuro, quando precisarmos encontrar o perfil completo de um usuário.
O Couchbase é inteligente e reconhece que essa cadeia de caracteres base64 que foi carregada não é JSON e a armazena como tipo binário. Após esse ponto, usamos o 'fs.unlink' para excluir a imagem do arquivo uploads pasta. Isso basicamente torna a pasta uploads como um buffer e, em seguida, insere o conteúdo base64 da imagem no banco de dados, antes de excluí-la da pasta uploads pasta. Por fim, uma consulta N1QL é executada para alterar o atributo booleano 'login.hasPicture' do usuário em questão para true, em vez do padrão false. Isso garantirá que o perfil do usuário não receba a foto de estoque aplicada a ele e, em vez disso, será executada uma pesquisa da foto de perfil carregada pelo usuário.
Função 'Picture.receive
Ao final de todo esse upload de imagens, também deve haver uma maneira de receber a imagem de perfil de cada usuário. Para isso, usamos a função 'Picture.receive' função. Ela é chamada na função '/api/advancedSearch' API em rotas/routes.js porque esse endpoint precisa encontrar cada usuário e sua foto de perfil para exibir no front-end. O 'Picture.receive' recebe o documentID de um usuário como entrada e retorna a cadeia de caracteres base64 de sua imagem. Primeiro, ela verifica se o booleano 'login.hasPicture' é verdadeiro. Caso contrário, ela simplesmente aplica a imagem padrão ao usuário. Se o atributo for verdadeiro, ele pega o atributo 'uuid' do usuário e anexa '_picMulterNode' a ele e, em seguida, faz um get KEY/VALUE padrão para recuperar a imagem do usuário, que foi armazenada como uma cadeia de caracteres base64. O código para essa função pode ser visto acima.
Em resumo, usamos o Multer para lidar com uploads de arquivos de <enctype=’multipart/form-data’> e os armazenou em um uploads em nosso servidor. Também usamos atributos ocultos nesse formulário para armazenar os dados de corte do ng-cropper e os adicionamos ao corpo dos dados enviados no Multer. No back-end, usamos o GraphicsMagick para primeiro cortar a imagem com os dados de corte do ng-cropper e reduzimos o tamanho da imagem para melhorar a experiência do usuário. Convertemos a imagem para base64, a armazenamos no Couchbase e a mantivemos com um documentID relacionado ao documentID do usuário. Por fim, escrevemos uma função para recuperar a string de imagem base64 de um indivíduo, de modo que ela possa ser usada com facilidade dentro de um atributo de imagem HTML no front-end.
Isso conclui a parte de carregamento de imagens da série de tutoriais do Touchbase. Deixe um comentário abaixo com seus comentários e obrigado pela leitura!