1. Visão geral
Neste codelab, você criará um front-end da Web no Google App Engine para permitir que os usuários façam upload de fotos do aplicativo da Web, além de navegar pelas imagens enviadas e as miniaturas delas.
Esse aplicativo da Web usará um framework CSS chamado Bulma, por oferecer uma boa interface do usuário, e também o framework de front-end JavaScript Vue.JS para chamar a API do aplicativo que você criará.
Esse aplicativo consistirá em três guias:
- Uma página inicial com as miniaturas de todas as imagens enviadas com a lista de rótulos que as descrevem (aqueles detectados pela API Cloud Vision em um laboratório anterior).
- Uma página de colagem que mostra a colagem feita das quatro fotos mais recentes enviadas.
- Uma página de upload, em que os usuários podem fazer upload de novas fotos.
O front-end resultante será assim:
Essas três páginas são HTML simples:
- A página home (
index.html
) chama o código de back-end do Node App Engine para receber a lista de imagens em miniatura e os rótulos delas por uma chamada AJAX para o URL/api/pictures
. A página inicial está usando o Vue.js para buscar esses dados. - A página colagem (
collage.html
) aponta para a imagemcollage.png
que monta as quatro imagens mais recentes. - A página de upload (
upload.html
) oferece um formulário simples para fazer upload de uma imagem por uma solicitação POST para o URL/api/pictures
.
O que você vai aprender
- App Engine
- Cloud Storage
- Cloud Firestore
2. Configuração e requisitos
Configuração de ambiente autoguiada
- Faça login no Console do Google Cloud e crie um novo projeto ou reutilize um existente. Crie uma conta do Gmail ou do Google Workspace, se ainda não tiver uma.
- O Nome do projeto é o nome de exibição para os participantes do projeto. Ele é uma string de caracteres que não é usada pelas APIs do Google e pode ser atualizada a qualquer momento.
- O ID do projeto precisa ser exclusivo em todos os projetos do Google Cloud e não pode ser alterado após a definição. O Console do Cloud gera automaticamente uma string única, geralmente não importa o que seja. Na maioria dos codelabs, você precisará fazer referência ao ID do projeto, que geralmente é identificado como
PROJECT_ID
. Então, se você não gostar dele, gere outro ID aleatório ou crie um próprio e veja se ele está disponível. Em seguida, ele fica "congelado" depois que o projeto é criado. - Há um terceiro valor, um Número de projeto, que algumas APIs usam. Saiba mais sobre esses três valores na documentação.
- Em seguida, você precisará ativar o faturamento no Console do Cloud para usar os recursos/APIs do Cloud. A execução deste codelab não será muito cara, se tiver algum custo. Para encerrar os recursos e não gerar cobranças além deste tutorial, siga as instruções de "limpeza" encontradas no final do codelab. Novos usuários do Google Cloud estão qualificados para o programa de US$ 300 de avaliação sem custos.
Inicie o Cloud Shell
Embora o Google Cloud e o Spanner possam ser operados remotamente do seu laptop, neste codelab usaremos o Google Cloud Shell, um ambiente de linha de comando executado no Cloud.
No Console do Google Cloud, clique no ícone do Cloud Shell na barra de ferramentas superior à direita:
O provisionamento e a conexão com o ambiente levarão apenas alguns instantes para serem concluídos: Quando o processamento for concluído, você verá algo como:
Essa máquina virtual contém todas as ferramentas de desenvolvimento necessárias. Ela oferece um diretório principal persistente de 5 GB, além de ser executada no Google Cloud. Isso aprimora o desempenho e a autenticação da rede. Todo o trabalho neste laboratório pode ser feito apenas com um navegador.
3. Ativar APIs
O App Engine requer a API Compute Engine. Verifique se ele está ativado:
gcloud services enable compute.googleapis.com
A operação será concluída com êxito:
Operation "operations/acf.5c5ef4f6-f734-455d-b2f0-ee70b5a17322" finished successfully.
4. Clonar o código
Confira o código, caso ainda não tenha feito isso:
git clone https://github.com/GoogleCloudPlatform/serverless-photosharing-workshop
Acesse o diretório que contém o front-end:
cd serverless-photosharing-workshop/frontend
Você terá o seguinte layout de arquivo para o front-end:
frontend | ├── index.js ├── package.json ├── app.yaml | ├── public | ├── index.html ├── collage.html ├── upload.html | ├── app.js ├── script.js ├── style.css
Na raiz do nosso projeto, você tem três arquivos:
index.js
contém o código Node.js.package.json
define as dependências da biblioteca.app.yaml
é o arquivo de configuração do Google App Engine.
Uma pasta public
contém os recursos estáticos:
index.html
é a página que mostra todas as imagens de miniaturas e rótuloscollage.html
mostra a colagem das fotos recentesupload.html
contém um formulário para carregar novas fotos- O
app.js
está usando o Vue.js para preencher a página doindex.html
com os dados. - O
script.js
processa o menu de navegação e o "hambúrguer" dele. ícone em telas pequenas style.css
define algumas diretivas CSS
5. Explorar o código
Dependências
O arquivo package.json
define as dependências de biblioteca necessárias:
{
"name": "frontend",
"version": "0.0.1",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"@google-cloud/firestore": "^3.4.1",
"@google-cloud/storage": "^4.0.0",
"express": "^4.16.4",
"dayjs": "^1.8.22",
"bluebird": "^3.5.0",
"express-fileupload": "^1.1.6"
}
}
Nosso aplicativo depende de:
- firestore: para acessar o Cloud Firestore com nossos metadados de imagens,
- storage: para acessar o Google Cloud Storage onde as imagens estão armazenadas,
- express: o framework da Web para Node.js,
- dayjs: uma pequena biblioteca para mostrar datas de uma forma legível para humanos,
- bluebird: uma biblioteca de promessas de JavaScript,
- express-fileupload: uma biblioteca para processar uploads de arquivos facilmente.
Front-end do Express
No início do controlador index.js
, você vai precisar de todas as dependências definidas em package.json
anteriormente:
const express = require('express');
const fileUpload = require('express-fileupload');
const Firestore = require('@google-cloud/firestore');
const Promise = require("bluebird");
const {Storage} = require('@google-cloud/storage');
const storage = new Storage();
const path = require('path');
const dayjs = require('dayjs');
const relativeTime = require('dayjs/plugin/relativeTime')
dayjs.extend(relativeTime)
A instância do aplicativo Express é criada.
São usados dois middlewares do Express:
- A chamada
express.static()
indica que os recursos estáticos estarão disponíveis no subdiretóriopublic
. - E o
fileUpload()
configura o upload de arquivos para limitar o tamanho a 10 MB e fazer upload dos arquivos localmente no sistema de arquivos na memória do diretório/tmp
.
const app = express();
app.use(express.static('public'));
app.use(fileUpload({
limits: { fileSize: 10 * 1024 * 1024 },
useTempFiles : true,
tempFileDir : '/tmp/'
}))
Entre os recursos estáticos, estão os arquivos HTML da página inicial, da página de colagem e da página de upload. Essas páginas chamarão o back-end da API. Essa API terá os seguintes endpoints:
POST /api/pictures
Através do formulário em upload.html, as imagens serão carregadas por meio de uma solicitação POSTGET /api/pictures
Esse endpoint retorna um documento JSON contendo a lista de imagens e os rótulos delasGET /api/pictures/:name
Esse URL redireciona para o local de armazenamento em nuvem da imagem em tamanho originalGET /api/thumbnails/:name
Esse URL redireciona para o local de armazenamento em nuvem da imagem em miniaturaGET /api/collage
O último URL redireciona para o local de armazenamento em nuvem da colagem gerada
Upload de imagem
Antes de explorar o código Node.js de upload de imagens, dê uma olhada rápida em public/upload.html
.
...
<form method="POST" action="/api/pictures" enctype="multipart/form-data">
...
<input type="file" name="pictures">
<button>Submit</button>
...
</form>
...
O elemento de formulário aponta para o endpoint /api/pictures
, com um método POST HTTP e um formato de várias partes. Agora, o index.js
precisa responder a esse endpoint e método e extrair os arquivos:
app.post('/api/pictures', async (req, res) => {
if (!req.files || Object.keys(req.files).length === 0) {
console.log("No file uploaded");
return res.status(400).send('No file was uploaded.');
}
console.log(`Receiving files ${JSON.stringify(req.files.pictures)}`);
const pics = Array.isArray(req.files.pictures) ? req.files.pictures : [req.files.pictures];
pics.forEach(async (pic) => {
console.log('Storing file', pic.name);
const newPicture = path.resolve('/tmp', pic.name);
await pic.mv(newPicture);
const pictureBucket = storage.bucket(process.env.BUCKET_PICTURES);
await pictureBucket.upload(newPicture, { resumable: false });
});
res.redirect('/');
});
Primeiro, verifique se realmente há arquivos sendo enviados. Em seguida, faça o download dos arquivos localmente usando o método mv
do módulo Node de upload de arquivos. Agora que os arquivos estão disponíveis no sistema de arquivos local, faça upload das imagens para o bucket do Cloud Storage. Por fim, você redireciona o usuário de volta para a tela principal do aplicativo.
Listar as imagens
É hora de mostrar suas belas fotos!
No gerenciador /api/pictures
, analise a coleção pictures
do banco de dados do Firestore para recuperar todas as imagens com a miniatura gerada, ordenadas por data de criação decrescente.
Você envia cada imagem em uma matriz JavaScript, com o nome dela, os rótulos que a descrevem (da API Cloud Vision), a cor dominante e uma data de criação simples (com dayjs
, fazemos ajustes de horário como "3 days from now").
app.get('/api/pictures', async (req, res) => {
console.log('Retrieving list of pictures');
const thumbnails = [];
const pictureStore = new Firestore().collection('pictures');
const snapshot = await pictureStore
.where('thumbnail', '==', true)
.orderBy('created', 'desc').get();
if (snapshot.empty) {
console.log('No pictures found');
} else {
snapshot.forEach(doc => {
const pic = doc.data();
thumbnails.push({
name: doc.id,
labels: pic.labels,
color: pic.color,
created: dayjs(pic.created.toDate()).fromNow()
});
});
}
console.table(thumbnails);
res.send(thumbnails);
});
Esse controlador retorna resultados no seguinte formato:
[
{
"name": "IMG_20180423_163745.jpg",
"labels": [
"Dish",
"Food",
"Cuisine",
"Ingredient",
"Orange chicken",
"Produce",
"Meat",
"Staple food"
],
"color": "#e78012",
"created": "a day ago"
},
...
]
Essa estrutura de dados é consumida por um pequeno snippet do Vue.js da página index.html
. Veja uma versão simplificada da marcação dessa página:
<div id="app">
<div class="container" id="app">
<div id="picture-grid">
<div class="card" v-for="pic in pictures">
<div class="card-content">
<div class="content">
<div class="image-border" :style="{ 'border-color': pic.color }">
<a :href="'/api/pictures/' + pic.name">
<img :src="'/api/thumbnails/' + pic.name">
</a>
</div>
<a class="panel-block" v-for="label in pic.labels" :href="'/?q=' + label">
<span class="panel-icon">
<i class="fas fa-bookmark"></i>
</span>
{{ label }}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
O ID do div indicará que o Vue.js é a parte da marcação que será renderizada dinamicamente. As iterações são feitas graças às diretivas v-for
.
As imagens recebem uma bonita borda colorida correspondente à cor dominante na imagem, conforme encontrado pela API Cloud Vision. Nós apontamos as miniaturas e as imagens de largura total no link e nas fontes de imagem.
Por fim, listamos os rótulos que descrevem a imagem.
Este é o código JavaScript para o snippet Vue.js (no arquivo public/app.js
importado na parte inferior da página index.html
):
var app = new Vue({
el: '#app',
data() {
return { pictures: [] }
},
mounted() {
axios
.get('/api/pictures')
.then(response => { this.pictures = response.data })
}
})
O código do Vue usa a biblioteca Axios para fazer uma chamada AJAX para o endpoint /api/pictures
. Em seguida, os dados retornados são vinculados ao código de visualização na marcação que vimos anteriormente.
Visualização das imagens
Do index.html
, nossos usuários podem ver as miniaturas das imagens, clicar nelas para ver as imagens em tamanho real e, para collage.html
, os usuários visualizam a imagem collage.png
.
Na marcação HTML dessas páginas, a imagem src
e o link href
apontam para esses três endpoints, que redirecionam para os locais do Cloud Storage das imagens, miniaturas e colagem. Não é necessário codificar o caminho na marcação HTML.
app.get('/api/pictures/:name', async (req, res) => {
res.redirect(`https://storage.cloud.google.com/${process.env.BUCKET_PICTURES}/${req.params.name}`);
});
app.get('/api/thumbnails/:name', async (req, res) => {
res.redirect(`https://storage.cloud.google.com/${process.env.BUCKET_THUMBNAILS}/${req.params.name}`);
});
app.get('/api/collage', async (req, res) => {
res.redirect(`https://storage.cloud.google.com/${process.env.BUCKET_THUMBNAILS}/collage.png`);
});
Como executar o aplicativo Node
Com todos os endpoints definidos, seu aplicativo Node.js está pronto para ser iniciado. Por padrão, o aplicativo Express detecta atividade na porta 8080 e está pronto para atender às solicitações recebidas.
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`Started web frontend service on port ${PORT}`);
console.log(`- Pictures bucket = ${process.env.BUCKET_PICTURES}`);
console.log(`- Thumbnails bucket = ${process.env.BUCKET_THUMBNAILS}`);
});
6. Testar localmente
Teste o código localmente para garantir que ele funciona antes de implantá-lo na nuvem.
Você precisa exportar as duas variáveis de ambiente correspondentes aos dois buckets do Cloud Storage:
export BUCKET_THUMBNAILS=thumbnails-${GOOGLE_CLOUD_PROJECT} export BUCKET_PICTURES=uploaded-pictures-${GOOGLE_CLOUD_PROJECT}
Dentro da pasta frontend
, instale as dependências de npm e inicie o servidor:
npm install; npm start
Se tudo tiver dado certo, ele deverá iniciar o servidor na porta 8080:
Started web frontend service on port 8080 - Pictures bucket = uploaded-pictures-${GOOGLE_CLOUD_PROJECT} - Thumbnails bucket = thumbnails-${GOOGLE_CLOUD_PROJECT}
Os nomes reais dos buckets aparecem nesses registros, o que é útil para fins de depuração.
No Cloud Shell, é possível usar o recurso de visualização da Web para navegar pelo aplicativo em execução localmente:
Use CTRL-C
para sair.
7. Implantar no App Engine
Seu aplicativo está pronto para ser implantado.
Configurar o App Engine
Examine o arquivo de configuração app.yaml
para o App Engine:
runtime: nodejs16 env_variables: BUCKET_PICTURES: uploaded-pictures-GOOGLE_CLOUD_PROJECT BUCKET_THUMBNAILS: thumbnails-GOOGLE_CLOUD_PROJECT
A primeira linha declara que o ambiente de execução é baseado no Node.js 10. Duas variáveis de ambiente estão definidas para apontar para os dois buckets: para as imagens originais e para as miniaturas.
Para substituir GOOGLE_CLOUD_PROJECT
pelo ID do projeto real, execute o seguinte comando:
sed -i -e "s/GOOGLE_CLOUD_PROJECT/${GOOGLE_CLOUD_PROJECT}/" app.yaml
Implantar
Defina a região de sua preferência para o App Engine e use a mesma região nos laboratórios anteriores:
gcloud config set compute/region europe-west1
E implante:
gcloud app deploy
Após um ou dois minutos, você verá que o aplicativo está veiculando tráfego:
Beginning deployment of service [default]... ╔════════════════════════════════════════════════════════════╗ ╠═ Uploading 8 files to Google Cloud Storage ═╣ ╚════════════════════════════════════════════════════════════╝ File upload done. Updating service [default]...done. Setting traffic split for service [default]...done. Deployed service [default] to [https://GOOGLE_CLOUD_PROJECT.appspot.com] You can stream logs from the command line by running: $ gcloud app logs tail -s default To view your application in the web browser run: $ gcloud app browse
Você também pode acessar a seção "App Engine" do console do Cloud para ver se o app foi implantado e conhecer os recursos do App Engine, como controle de versões e divisão de tráfego:
8. Testar o app
Para testar, acesse o URL padrão do App Engine para o aplicativo (https://<YOUR_PROJECT_ID>.appspot.com/
) e você verá a interface de front-end funcionando.
9. Limpeza (opcional)
Se você não pretende manter o app, limpe os recursos para economizar custos e ser um bom cidadão da nuvem excluindo todo o projeto:
gcloud projects delete ${GOOGLE_CLOUD_PROJECT}
10. Parabéns!
Parabéns! Esse aplicativo da Web Node.js hospedado no App Engine une todos os seus serviços e permite que seus usuários façam upload e visualizem imagens.
O que vimos
- App Engine
- Cloud Storage
- Cloud Firestore