Visão Geral da Stack Tecnológica
A construção de aplicações web modernas exige a combinação de frameworks que equilibrem produtividade no back-end e reatividade no front-end. A escolha tecnológica para este projeto de sistema de compras foca em três pilares principais:
Back-end com Spring Boot
O Spring Boot elimina a complexidade de configuração tradicional do ecossistema Spring. Ao embutir servidores web como Tomcat ou Undertow, permite o deploy imediato sem configurações externas de contêiner. Seu mecanismo de autoconfiguração analisa as dependências no classpath e prepara o ambiente automaticamente. Além disso, a integração nativa com módulos como Spring Data e Spring Cloud facilita a escalabilidade e a adoção de microsserviços, reduzindo significativamente o tempo de desenvolvimento.
Front-end com Vue.js
O Vue.js se destaca pelo uso de um DOM virtual, que minimiza operações custosas de manipulação direta da árvore de documentos. Sua arquitetura reativa sincroniza automaticamente o estado dos dados com a interface do usuário. A abordagem baseada em componentes permite a criação de interfaces modulares e reutilizáveis, garantindo que o desenvolvedor foque na lógica de negócio enquanto a camada de visualização se atualiza de forma otimizada e declarativa.
Persistência com MyBatis-Plus
Para a camada de acesso a dados, o MyBatis-Plus atua como uma extensão do MyBatis, reduzindo drasticamente a necessidade de escrever consultas SQL manuais. Oferece mapeamento objeto-relacional robusto, suporte a múltiplos SGBDs (MySQL, PostgreSQL, Oracle, entre outros) e geração automática de entidades, interfaces mapper e arquivos de mapeamento. Funcionalidades como paginação nativa, tratamento de locks otimistas e análise de desempenho tornam o desenvolvimento da camada de persistência mais ágil e menos propenso a erros.
Metodologia e Execução de Testes
A fase de validação do sistema tem como objetivo central confirmar que os módulos desenvolvidos atendem aos requisitos funcionais e não funcionais estipulados. Através de simulações controladas, busca-se identificar falhas lógicas, inconsistências de interface e gargalos de performance antes da entrega final.
Objetivos da Validação
Os testes funcionam como o mecanismo de controle de qualidade definitivo. Eles garantem que o comportamento do software esteja alinhado com as expectativas do usuário final, prevenindo interrupções durante o uso produtivo. A estratégia envolve a análise sob múltiplas perspectivas, simulando cenários reais de operação para verificar a robustez da lógica de negócio e a fluidez das transações. O foco permanece na experiência do usuário, descartando testes baseados em premissas irreais que possam distorcer os resultados.
Cenários de Teste Funcional (Caixa-Preta)
A validação foi conduzida via testes de caixa-preta, executando validações de borda, preenchimento obrigatório e verificação de fluxo. Abaixo, detalhamos os principais casos aplicados:
Validação de Acesso (Login)
| Parâmetros de Entrada | Comportamento Esperado | Resultado Observado | Análise |
|---|---|---|---|
| Admin: "gerente", Senha: "segura99", Captcha: válido | Redirecionamento para painel | Sessão iniciada com sucesso | Conforme especificado |
| Admin: "gerente", Senha: "errada", Captcha: válido | Bloqueio e mensagem de erro | Alerta de credenciais inválidas | Conforme especificado |
| Admin: "gerente", Senha: "segura99", Captcha: inválido | Impedimento de entrada | Pedido de revalidação do captcha | Conforme especificado |
| Campos em branco | Validação obrigatória | Mensagem de preenchimento necessário | Conforme especificado |
Gestão de Cadastros
| Parâmetros de Entrada | Comportamento Esperado | Resultado Observado | Análise |
|---|---|---|---|
| Preenchimento completo e único | Criação de registro | Novo item listado corretamente | Conforme especificado |
| Alteração de dados existentes | Atualização refletida | Campos modificados persistidos | Conforme especificado |
| Remoção com confirmação | Exclusão lógica/física | Registro removido da listagem | Conforme especificado |
| Duplicidade de identificador | Bloqueio de inserção | Aviso de duplicidade detectada | Conforme especificado |
Avaliação Geral dos Testes
A execução dos cenários confirmou que a arquitetura atende aos critérios iniciais de projeto. A lógica de navegação entre módulos mantém-se consistente, e as validações de entrada impedem comportamentos inesperados. O sistema demonstra estabilidade operacional, com fluxos intuitivos que não exigem curva de aprendizado acentuada. A validação baseada em requisitos garantiu que todas as interações críticas funcionem conforme o esperado, consolidando a prontidão da aplicação para ambientes de produção.
Referência de Implementação (Back-end)
Abaixo, apresenta-se um exemplo de como a autenticação baseada em token foi estruturada, substituindo a validação tradicional de sessão por um mecanismo stateless.
package com.sistema.compras.seguranca;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.stereotype.Service;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.method.HandlerMethod;
@RestController
@RequestMapping("/api/seguranca")
public class ControladorDeLogin {
private final ServicoAutenticacao servicoDeToken;
@Autowired
public ControladorDeLogin(ServicoAutenticacao servicoDeToken) {
this.servicoDeToken = servicoDeToken;
}
@PostMapping("/entrada")
public ResponseEntity<String> processarAcesso(@RequestBody CredenciaisAcesso credenciais) {
UsuarioValidacao usuarioValido = servicoDeToken.buscarPeloNome(credenciais.getIdentificador());
if (usuarioValido == null || !usuarioValido.getChaveSecreta().equals(credenciais.getChaveSecreta())) {
return ResponseEntity.badRequest().body("{\"erro\": \"Credenciais inválidas\"}");
}
String codigoAcesso = servicoDeToken.gerarIdentificadorSessao(
usuarioValido.getId(),
usuarioValido.getIdentificador(),
"tbl_usuarios",
usuarioValido.getNivelPermissao()
);
return ResponseEntity.ok("{\"token\": \"" + codigoAcesso + "\"}");
}
}
@Service
class ServicoAutenticacao {
public String gerarIdentificadorSessao(Long idEntidade, String nomeEntidade, String fonteDados, String perfilAcesso) {
String registroAtivo = repositorioDeSessoes.buscarPorUsuarioEPerfil(idEntidade, perfilAcesso);
String novoToken = java.util.UUID.randomUUID().toString().replace("-", "");
java.time.ZonedDateTime expiracao = java.time.ZonedDateTime.now().plusHours(1);
if (registroAtivo != null) {
repositorioDeSessoes.atualizarTokenERenovar(expiracao, novoToken, Long.parseLong(registroAtivo));
} else {
repositorioDeSessoes.salvar(new SessaoAtiva(idEntidade, nomeEntidade, fonteDados, perfilAcesso, novoToken, expiracao));
}
return novoToken;
}
}
@Component
public class FiltroInterceptacaoDeRequisicoes implements HandlerInterceptor {
private static final String CABECALHO_TOKEN = "X-Access-Token";
@Autowired
private RepositorioSessoes repositorioDeTokens;
@Override
public boolean preHandle(jakarta.servlet.http.HttpServletRequest requisicao,
jakarta.servlet.http.HttpServletResponse resposta,
Object manipulador) throws Exception {
resposta.setHeader("Access-Control-Allow-Origin", requisicao.getHeader("Origin"));
resposta.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
resposta.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Access-Token");
resposta.setHeader("Access-Control-Allow-Credentials", "true");
if ("OPTIONS".equalsIgnoreCase(requisicao.getMethod())) {
resposta.setStatus(HttpStatus.OK.value());
return false;
}
if (manipulador instanceof HandlerMethod metodo) {
if (metodo.getMethod().isAnnotationPresent(IgnorarSeguranca.class)) {
return true;
}
}
String tokenRecebido = requisicao.getHeader(CABECALHO_TOKEN);
if (tokenRecebido == null || tokenRecebido.trim().isEmpty()) {
enviarRespostaNegada(resposta);
return false;
}
var dadosSessao = repositorioDeTokens.localizarPorValor(tokenRecebido);
if (dadosSessao != null && dadosSessao.getTempoExpiracao().isAfter(java.time.ZonedDateTime.now())) {
requisicao.getSession().setAttribute("usuarioLogado", dadosSessao.getNomeEntidade());
requisicao.getSession().setAttribute("nivelAcesso", dadosSessao.getPerfilAcesso());
return true;
}
enviarRespostaNegada(resposta);
return false;
}
private void enviarRespostaNegada(jakarta.servlet.http.HttpServletResponse resposta) throws java.io.IOException {
resposta.setStatus(HttpStatus.UNAUTHORIZED.value());
resposta.setContentType("application/json");
resposta.getWriter().write("{\"status\": 401, \"mensagem\": \"Autenticação necessária\"}");
}
}
Modelagem de Dados
A estrutura relacional utilizada para gerenciar sessões ativas segue o padrão abaixo, garantindo controle de expiração e vinculação correta entre usuário e contexto de acesso.
CREATE TABLE IF NOT EXISTS sessoes_autenticacao (
id_sessao BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 'Identificador único da sessão',
id_entidade BIGINT NOT NULL COMMENT 'Referência ao ID do usuário',
nome_entidade VARCHAR(100) NOT NULL COMMENT 'Login ou identificador do usuário',
tabela_origem VARCHAR(50) COMMENT 'Origem dos dados (ex: clientes, administradores)',
perfil_acesso VARCHAR(50) COMMENT 'Nível de permissão concedido',
valor_token VARCHAR(255) NOT NULL UNIQUE COMMENT 'Token de acesso gerado',
data_criacao TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'Momento de geração do token',
data_expiracao TIMESTAMP NOT NULL COMMENT 'Limite de validade da sessão',
INDEX idx_token (valor_token),
INDEX idx_entidade (id_entidade)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Tabela de controle de sessões ativas';
INSERT INTO sessoes_autenticacao (id_entidade, nome_entidade, tabela_origem, perfil_acesso, valor_token, data_criacao, data_expiracao) VALUES
(101, 'comprador_01', 'tb_clientes', 'CLIENTE', 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6', '2023-10-15 08:30:00', '2023-10-15 09:30:00'),
(102, 'gestor_loja', 'tb_funcionarios', 'ADMIN', 'z9y8x7w6v5u4t3s2r1q0p9o8n7m6l5k4', '2023-10-15 09:15:00', '2023-10-15 10:15:00'),
(103, 'supervisor_2', 'tb_funcionarios', 'GERENTE', 'm3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8', '2023-10-15 10:00:00', '2023-10-15 11:00:00');