Lembre-se da série de tutoriais que escrevi com relação a Criação de um armazenamento de perfil de usuário com Node.js e NoSQL? Esse tutorial cobriu uma série de assuntos, desde a criação de uma API RESTful com Node.js, o tratamento de sessões de usuários, a modelagem de dados e, é claro, o armazenamento de dados associados aos usuários.
E se quiséssemos usar os mesmos conceitos e aplicá-los com Golang em vez de JavaScript com Node.js?
Veremos como desenvolver um armazenamento de perfil de usuário com Golang e Servidor Couchbase que atua como um substituto modular para a alternativa do Node.js.
Daqui para frente, vamos supor que você tenha o Go instalado e configurado, bem como o Couchbase Server. Não é importante se você já viu a instrução Versão Node.js deste tutorial porque vamos revisar tudo.
Caso você não saiba o que é um armazenamento de perfil de usuário ou o que ele faz, trata-se simplesmente de uma solução para armazenar informações sobre usuários e informações associadas a eles. Veja, por exemplo, um blog. Um blog pode ter vários autores, que são tecnicamente usuários. Cada autor escreverá conteúdo e esse conteúdo será associado ao usuário específico que o escreveu. Cada autor também terá seu próprio método de login no blog.
Como os modelos de dados do usuário podem mudar com muita frequência, o uso de um banco de dados NoSQL com um modelo de armazenamento flexível costuma ser mais eficaz do que uma alternativa RDBMS. Mais informações sobre o aspecto da modelagem de dados podem ser encontradas neste artigo, Armazenamento de perfil de usuário: Modelagem avançada de dados, escrito por Kirk Kirkconnell.
Gerenciamento de usuários Golang para criar um novo projeto
Vamos passar todo o tempo em um único arquivo Go. Em algum lugar em seu $GOPATH, crie um arquivo chamado main.go.
Também precisaremos de algumas dependências, tanto para o Couchbase quanto para outros pacotes. Na linha de comando, execute o seguinte:
1 2 3 4 5 |
ir obter github.com/couchbase/gocb ir obter github.com/gorila/contexto ir obter github.com/gorila/manipuladores ir obter github.com/gorila/mux ir obter github.com/satori/ir.uuid |
As dependências acima permitirão que nos comuniquemos com o Couchbase Server, geremos valores de UUID e criemos uma API RESTful com tratamento de compartilhamento de recursos entre origens (CORS).
A próxima etapa é criar um código padrão para o nosso projeto. Abra a seção main.go e inclua o seguinte:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
pacote principal importação ( "encoding/json" "fmt" "log" "net/http" "strings" "tempo" "golang.org/x/crypto/bcrypt" "github.com/couchbase/gocb" "github.com/gorilla/context" "github.com/gorilla/handlers" "github.com/gorilla/mux" uuid "github.com/satori/go.uuid" ) tipo Conta estrutura { Tipo string `json:"type,omitempty"` Pid string `json:"pid,omitempty"` E-mail string `json:"email,omitempty"` Senha string `json:"password,omitempty"` } tipo Perfil estrutura { Tipo string `json:"type,omitempty"` Nome próprio string `json:"firstname,omitempty"` Sobrenome string `json:"lastname,omitempty"` } tipo Sessão estrutura { Tipo string `json:"type,omitempty"` Pid string `json:"pid,omitempty"` } tipo Blog estrutura { Tipo string `json:"type,omitempty"` Pid string `json:"pid,omitempty"` Título string `json:"title,omitempty"` Conteúdo string `json:"content,omitempty"` Carimbo de data/hora int `json:"timestamp,omitempty"` } var balde *gocb.Balde func Validar(próxima http.HandlerFunc) http.HandlerFunc {} func RegisterEndpoint(w http.Escritor de respostas, req *http.Solicitação) {} func LoginEndpoint(w http.Escritor de respostas, req *http.Solicitação) {} func Ponto de extremidade da conta(w http.Escritor de respostas, req *http.Solicitação) {} func BlogsEndpoint(w http.Escritor de respostas, req *http.Solicitação) {} func BlogEndpoint(w http.Escritor de respostas, req *http.Solicitação) {} func principal() { fmt.Println("Iniciando o servidor Go...") roteador := mux.NewRouter() agrupamento, _ := gocb.Conectar("couchbase://localhost") balde, _ = agrupamento.OpenBucket("default", "") roteador.HandleFunc("/account", RegisterEndpoint).Métodos("POST") roteador.HandleFunc("/login", LoginEndpoint).Métodos("POST") roteador.HandleFunc("/account", Validar(Ponto de extremidade da conta)).Métodos("GET") roteador.HandleFunc("/blogs", Validar(BlogsEndpoint)).Métodos("GET") roteador.HandleFunc("/blog", Validar(BlogEndpoint)).Métodos("POST") registro.Fatal(http.Ouvir e servir(":3000", manipuladores.CORS(manipuladores.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Authorization" (Autorização)}), manipuladores.AllowedMethods([]string{"GET", "POST", "PUT", "CABEÇA", "OPTIONS"}), manipuladores.Origens permitidas([]string{"*"}))(roteador))) } |
No exemplo acima, você notará que criamos algumas estruturas de dados para representar nossos dados. Estamos adotando a ideia de uma plataforma de blog.
O aplicativo terá cinco pontos de extremidade da API RESTful, um método validador para nossas sessões de usuário e uma variável global do Couchbase que nos permitirá acessar nossa instância aberta em qualquer lugar do aplicativo.
1 2 3 |
roteador.HandleFunc("/account", Validar(Ponto de extremidade da conta)).Métodos("GET") roteador.HandleFunc("/blogs", Validar(BlogsEndpoint)).Métodos("GET") roteador.HandleFunc("/blog", Validar(BlogEndpoint)).Métodos("POST") |
Observe que os três pontos de extremidade acima têm o Validar
anexada a eles. Isso significa que o usuário deve ter se autenticado e estar fornecendo uma sessão válida para progredir. Nesse sentido, o Validar
atua como um middleware.
Como planejamos consultar os dados, mais especificamente, os artigos do blog para um determinado usuário, precisamos criar um índice. Usando o painel da Web, a CLI do Couchbase ou o aplicativo Go, execute o seguinte:
1 |
CRIAR ÍNDICE `usuário do blog` ON `padrão`(tipo, pid); |
O índice acima nos permitirá consultar por um tipo
bem como uma propriedade pid
propriedade.
Agora podemos começar a preencher as lacunas de cada um de nossos endpoints de API.
Permitir que os usuários registrem novas informações no Profile Store
Como não temos usuários no armazenamento de perfis até o momento, faria sentido criar um ponto de extremidade que ofereça suporte à criação de novos usuários.
É uma boa prática nunca armazenar informações de credenciais do tipo nome de usuário e senha com informações reais do usuário. Por esse motivo, criar um novo usuário significa criar um perfil bem como um documento conta documento. O conta fará referência ao documento perfil document. Ambos serão modelados de acordo com nossas estruturas de dados Go que vimos no código padrão.
No main.go adicione o seguinte:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
func RegisterEndpoint(w http.Escritor de respostas, req *http.Solicitação) { var dados mapa[string]interface{} _ = json.NewDecoder(req.Corpo).Decodificar(&dados) id := uuid.NovoV4().Cordas() passwordHash, _ := bcrypt.GenerateFromPassword([]byte(dados["senha"].(string)), 10) conta := Conta{ Tipo: "account" (conta), Pid: id, E-mail: dados["email"].(string), Senha: string(passwordHash), } perfil := Perfil{ Tipo: "profile" (perfil), Nome próprio: dados["firstname"].(string), Sobrenome: dados["lastname" (sobrenome)].(string), } _, erro := balde.Inserir(id, perfil, 0) se erro != nulo { w.WriteHeader(401) w.Escrever([]byte(erro.Erro())) retorno } _, erro = balde.Inserir(dados["email"].(string), conta, 0) se erro != nulo { w.WriteHeader(401) w.Escrever([]byte(erro.Erro())) retorno } json.Novo codificador(w).Codificar(conta) } |
Há algumas coisas importantes acontecendo no código do endpoint acima.
Primeiro, estamos aceitando os dados JSON que foram enviados com o corpo POST da solicitação do cliente. Estamos gerando um ID exclusivo para o perfil documento e fazer o hash da senha para mantê-la em segurança com o BCrypt.
Quando se trata de realmente salvar os dados, o perfil do usuário receberá uma identificação exclusiva, enquanto a conta receberá um endereço de e-mail como identificação e uma referência à identificação do perfil no documento.
Ao seguir essa abordagem, a conta pode ser facilmente ampliado para outras formas de credenciais. Por exemplo, o documento da conta poderia ser renomeado como basicauthe poderíamos ter o Facebook, o Twitter, etc., que fazem referência às informações do perfil.
Implementação de um token de sessão para usuários
Fazer login no aplicativo por meio de nossos dois documentos é um pouco diferente. Nunca é uma boa ideia divulgar o nome de usuário e a senha mais do que o absolutamente necessário.
Por esse motivo, é uma boa ideia usar um token de sessão que represente o usuário. Esse token pode expirar e não contém informações confidenciais.
Use o seguinte código de login para o main.go file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
func LoginEndpoint(w http.Escritor de respostas, req *http.Solicitação) { var dados Conta var conta Conta _ = json.NewDecoder(req.Corpo).Decodificar(&dados) _, erro := balde.Obter(dados.E-mail, &conta) se erro != nulo { w.WriteHeader(401) w.Escrever([]byte(erro.Erro())) retorno } erro = bcrypt.CompareHashAndPassword([]byte(conta.Senha), []byte(dados.Senha)) se erro != nulo { w.WriteHeader(401) w.Escrever([]byte(erro.Erro())) retorno } sessão := Sessão{ Tipo: "sessão", Pid: conta.Pid, } var resultado mapa[string]interface{} resultado = fazer(mapa[string]interface{}) resultado["sid"] = uuid.NovoV4().Cordas() _, erro = balde.Inserir(resultado["sid"].(string), &sessão, 3600) se erro != nulo { w.WriteHeader(401) w.Escrever([]byte(erro.Erro())) retorno } json.Novo codificador(w).Codificar(resultado) } |
Quando o e-mail e a senha são passados para esse ponto de extremidade, o conta é recuperado com base no e-mail que foi fornecido. A senha com hash dentro desse documento é então comparada com a senha sem hash.
Se as credenciais forem válidas, um sessão é criado. Esse documento de sessão tem uma chave exclusiva, mas faz referência à chave do perfil documento. Um tempo de expiração também é adicionado ao documento. Quando o tempo de expiração passar, o documento será automaticamente removido do Couchbase sem qualquer intervenção do aplicativo ou do usuário. Isso ajuda a proteger a conta.
Com as contas funcionais, precisamos nos preocupar em associar informações aos usuários.
Gerenciamento de informações do usuário no Profile Store por meio de um token de sessão
Quando um usuário tenta fazer algo específico para si mesmo, precisamos validar se ele é quem deveria ser e se as informações que está alterando são aplicadas à pessoa correta.
É nesse ponto que o middleware de validação de token de sessão entra em ação.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
func Validar(próxima http.HandlerFunc) http.HandlerFunc { retorno http.HandlerFunc(func(w http.Escritor de respostas, req *http.Solicitação) { authorizationHeader := req.Cabeçalho.Obter("autorização") se authorizationHeader != "" { bearerToken := cadeias de caracteres.Dividir(authorizationHeader, " ") se len(bearerToken) == 2 { var sessão Sessão _, erro := balde.Obter(bearerToken[1], &sessão) se erro != nulo { w.WriteHeader(401) w.Escrever([]byte(erro.Erro())) retorno } contexto.Conjunto(req, "pid", sessão.Pid) balde.Toque(bearerToken[1], 0, 3600) próxima(w, req) } } mais { w.WriteHeader(401) w.Escrever([]byte("É necessário um cabeçalho de autorização")) retorno } }) } |
Toda solicitação a um de nossos três pontos de extremidade especiais exigirá um cabeçalho de autorização que contenha um token de portador com o ID da sessão. Sem token de portador significa que a solicitação falhará. Um token de portador incorreto ou expirado significa que a solicitação falhará.
A validação trocará o ID da sessão por um ID do perfil a ser usado na próxima etapa da solicitação.
Começando de forma simples, digamos que queremos retornar as informações de perfil de um determinado usuário. Nosso endpoint pode ser parecido com o seguinte:
1 2 3 4 5 6 7 8 9 10 11 |
func Ponto de extremidade da conta(w http.Escritor de respostas, req *http.Solicitação) { pid := contexto.Obter(req, "pid").(string) var perfil Perfil _, erro := balde.Obter(pid, &perfil) se erro != nulo { w.WriteHeader(401) w.Escrever([]byte(erro.Erro())) retorno } json.Novo codificador(w).Codificar(perfil) } |
O pid
é passado pelo middleware de validação e uma pesquisa é feita com o ID do perfil.
Até agora não foi tão difícil, certo?
Vamos dar um passo adiante e introduzir algumas consultas N1QL em nosso projeto. Digamos que queiramos obter todas as publicações de blog de um determinado usuário. Isso usará o nosso índice e as consultas do tipo SQL.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
func BlogsEndpoint(w http.Escritor de respostas, req *http.Solicitação) { var n1qlParams []interface{} n1qlParams = anexar(n1qlParams, contexto.Obter(req, "pid").(string)) consulta := gocb.NewN1qlQuery("SELECT `" + balde.Nome() + "`.* FROM `" + balde.Nome() + "` WHERE type = 'blog' AND pid = $1") consulta.Consistência(gocb.RequestPlus) linhas, erro := balde.ExecutarN1qlQuery(consulta, n1qlParams) se erro != nulo { w.WriteHeader(401) w.Escrever([]byte(erro.Erro())) retorno } var fila Blog var resultado []Blog para linhas.Próximo(&fila) { resultado = anexar(resultado, fila) fila = Blog{} } linhas.Fechar() se resultado == nulo { resultado = fazer([]Blog, 0) } json.Novo codificador(w).Codificar(resultado) } |
No código do ponto de extremidade acima, pegamos o pid
do middleware de validação e adicioná-lo como um parâmetro para nossa consulta parametrizada.
Vamos iterar através do Resultados da consulta
retornados da consulta e os adiciona a um []Blog
variável. Se nenhum resultado for encontrado, podemos simplesmente retornar uma fatia vazia.
Fazer pesquisas diretas com base na chave sempre será mais rápido do que as consultas N1QL, mas as consultas N1QL são muito úteis quando você precisa consultar por informações de propriedade.
O ponto de chegada final não é diferente do que já vimos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
func BlogEndpoint(w http.Escritor de respostas, req *http.Solicitação) { var blog Blog _ = json.NewDecoder(req.Corpo).Decodificar(&blog) blog.Tipo = "blog" blog.Pid = contexto.Obter(req, "pid").(string) blog.Carimbo de data/hora = int(tempo.Agora().Unix()) _, erro := balde.Inserir(uuid.NovoV4().Cordas(), blog, 0) se erro != nulo { w.WriteHeader(401) w.Escrever([]byte(erro.Erro())) retorno } json.Novo codificador(w).Codificar(blog) } |
No código acima, aceitamos um corpo POST do cliente, bem como o pid
do middleware de validação. Essas informações são então salvas no Couchbase.
Conclusão
Você acabou de ver como criar um armazenamento básico de perfil de usuário ou, neste caso, uma plataforma de blog, usando a linguagem de programação Go e Servidor Couchbase. Este é um tutorial alternativo para um tutorial anterior que eu havia escrito sobre o mesmo assunto, mas com o Node.js.
Deseja levar este tutorial para o próximo nível? Veja como criar um front-end de cliente da Web com Angular ou um front-end de cliente móvel com NativeScript.
Para obter mais informações sobre como usar o Couchbase com a Golang, consulte a seção Portal do desenvolvedor do Couchbase.