Arquitetura e Implementação de um Sistema de Compras Móvel com Spring Boot e Vue.js

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');

Tags: SpringBoot Vue.js MyBatisPlus java SpringInterceptor

Publicado em 9-7 01:03