Arquitetura e Implementação de Plataforma de E-commerce para Viagens com Spring Boot, Vue e UniApp

Stack Tecnológica

Backend com Spring Boot

O framework Spring Boot otimiza o desenvolvimento de aplicações Java ao eliminar a necessidade de configurações XML extensas. Com servidores web embutidos como Tomcat, Jetty e Undertow, ele permite a execução imediata sem dependências externas. Seu mecanismo de autoconfiguração analisa o classpath e ajusta automaticamente os beans do Spring, acelerando a integração com módulos como Spring Data, Spring Security e Spring Cloud. Essa abordagem "convention over configuration" é fundamental para a construção de microsserviços e APIs RESTful escaláveis.

Frontend com Vue.js

No lado do cliente, o Vue.js utiliza uma arquitetura reativa e um DOM Virtual para otimizar a renderização da interface. Seu sistema de componentes e o enlace de dados bidirecional (two-way data binding) permitem que os desenvolvedores foquem na lógica de negócios e na manipulação de estado, enquanto o framework gerencia eficientemente as atualizações cirúrgicas no DOM real, garantindo alta performance e manutenibilidade em aplicações SPA (Single Page Applications).

Camada de Persistência com MyBatis-Plus

Para o aceso a dados, o MyBatis-Plus atua como uma extensão robusta do MyBatis tradicional. Ele fornece operações CRUD genéricas, paginação nativa, controle de concorrência (optimistic locking) e geradores de código. Isso reduz drasticamente a escrita de SQL repetitivo e otimiza o mapeamento objeto-relacional (ORM) para bancos de dados relacionais como MySQL, PostgreSQL e Oracle, elevando a produtividade na camada de repositório.

Engenharia de Testes e Validação

A garantia de qualidade do sistema é conduzida através de testes de caixa preta, focando em fluxos de usuário, valores limite, validação de campos obrigatórios e tratamento de exceções. O objetivo é assegurar que os módulos atendam rigorosamente aos requisitos funcionais e não funcionais antes da impalntação em produção.

Testes do Módulo de Autenticação

A validação do fluxo de login verifica a integridade das credenciais, a validação de captcha e o controle de permissões baseadas em roles.

Cenário de Teste Dados de Entrada Resultado Esperado Status
Login com credenciais válidas user: admin_travel | pass: SecurePass123 | captcha: correto Geração de Token e redirecionamento para o dashboard Aprovado
Login com senha incorreta user: admin_travel | pass: WrongPass | captcha: correto Bloqueio e mensagem de erro "Credenciais inválidas" Aprovado
Login com captcha inválido user: admin_travel | pass: SecurePass123 | captcha: incorreto Bloqueio e mensagem "Falha na verificação de segurança" Aprovado
Login com campo obrigatório vazio user: vazio | pass: SecurePass123 | captcha: correto Alerta de validação de formulário no frontend Aprovado

Testes do Módulo de Gestão de Usuários

Este módulo valida as operações de criação, leitura, atualização e exclusão (CRUD), garantindo a integridade referencial e a validação de regras de negócio.

Cenário de Teste Ação Executada Resultado Esperado Status
Cadastro de novo usuário Inserir dados válidos e únicos Registro salvo com sucesso e exibido na listagem Aprovado
Cadastro com identificador duplicado Inserir e-mail ou username já existente Falha no cadastro com alerta de violação de unicidade Aprovado
Atualização de perfil Modificar dados pessoais e salvar Persistência das alterações e atualização da UI Aprovado
Exclusão de conta Selecionar usuário e confirmar exclusão Remoção lógica/física e atualização da tabela de dados Aprovado

Implementação de Código

Controlador de Autenticação e Gerenciador de Tokens

@RestController
@RequestMapping("/api/v1/auth")
public class AuthenticationController {

    @Autowired
    private UserAccountService accountService;
    
    @Autowired
    private TokenManager tokenManager;
    
    @Autowired
    private PasswordEncoder passwordEncoder;

    @IgnoreAuthentication
    @PostMapping("/signin")
    public ApiResult<AuthResponse> authenticate(@RequestBody LoginRequest request, HttpServletRequest httpRequest) {
        UserAccount account = accountService.findByLoginId(request.getLoginId());
        
        if (account == null || !passwordEncoder.matches(request.getSecretKey(), account.getHashedPassword())) {
            return ApiResult.failure(401, "Credenciais inválidas ou usuário não encontrado.");
        }
        
        String accessToken = tokenManager.issueToken(account.getId(), request.getLoginId(), account.getRole());
        return ApiResult.success(new AuthResponse(accessToken));
    }
}

@Service
public class TokenManager {

    @Autowired
    private TokenRepository tokenRepository;

    public String issueToken(Long userId, String loginId, String role) {
        Optional<AuthToken> existingToken = tokenRepository.findByUserIdAndRole(userId, role);
        String newToken = UUID.randomUUID().toString().replace("-", "");
        LocalDateTime expiration = LocalDateTime.now().plusHours(2);

        if (existingToken.isPresent()) {
            AuthToken token = existingToken.get();
            token.setAccessToken(newToken);
            token.setExpiresAt(expiration);
            tokenRepository.save(token);
        } else {
            AuthToken token = new AuthToken(userId, loginId, role, newToken, expiration);
            tokenRepository.save(token);
        }
        return newToken;
    }
}

Interceptador de Segurança e CORS

@Component
public class SecurityInterceptor implements HandlerInterceptor {

    private static final String AUTH_HEADER = "Authorization";

    @Autowired
    private TokenRepository tokenRepository;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        configureCors(response, request);

        if (RequestMethod.OPTIONS.name().equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            return false;
        }

        if (handler instanceof HandlerMethod) {
            HandlerMethod method = (HandlerMethod) handler;
            if (method.hasMethodAnnotation(IgnoreAuthentication.class)) {
                return true;
            }
        }

        String headerToken = request.getHeader(AUTH_HEADER);
        if (StringUtils.hasText(headerToken)) {
            Optional<AuthToken> validToken = tokenRepository.findByAccessToken(headerToken);
            if (validToken.isPresent() && validToken.get().getExpiresAt().isAfter(LocalDateTime.now())) {
                request.setAttribute("currentUserId", validToken.get().getUserId());
                request.setAttribute("currentRole", validToken.get().getRole());
                return true;
            }
        }

        writeUnauthorizedResponse(response);
        return false;
    }

    private void configureCors(HttpServletResponse response, HttpServletRequest request) {
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Requested-With");
        response.setHeader("Access-Control-Allow-Credentials", "true");
    }

    private void writeUnauthorizedResponse(HttpServletResponse response) {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentType("application/json;charset=UTF-8");
        try (PrintWriter writer = response.getWriter()) {
            writer.write("{\"code\":401,\"message\":\"Sessão expirada ou não autenticada.\"}");
        } catch (IOException e) {
            throw new RuntimeException("Falha ao escrever resposta de erro", e);
        }
    }
}

Estrutura do Banco de Dados

Modelagem da tabela responsável pelo armazenamento e controle de expiração das sessões de acesso (tokans).

-- --------------------------------------------------------
-- Estrutura da tabela para gerenciamento de tokens de acesso
-- --------------------------------------------------------
DROP TABLE IF EXISTS `auth_tokens`;
CREATE TABLE `auth_tokens` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Identificador único',
  `user_id` bigint(20) NOT NULL COMMENT 'Referência ao usuário',
  `login_name` varchar(100) NOT NULL COMMENT 'Identificador de login',
  `role` varchar(50) DEFAULT NULL COMMENT 'Perfil de acesso',
  `access_token` varchar(255) NOT NULL COMMENT 'Hash do token de sessão',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Data de emissão',
  `expires_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT 'Data de expiração',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_access_token` (`access_token`),
  KEY `idx_user_role` (`user_id`, `role`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Tabela de controle de sessões';

-- --------------------------------------------------------
-- Dados iniciais para testes de integração
-- --------------------------------------------------------
INSERT INTO `auth_tokens` (`user_id`, `login_name`, `role`, `access_token`, `created_at`, `expires_at`) 
VALUES 
(101, 'admin_master', 'ADMIN', 'a8f5f167f44f4964e6c998dee827110c3b2a3f9f', '2023-10-01 08:00:00', '2023-10-01 10:00:00'),
(102, 'user_traveler', 'CUSTOMER', 'b9g6g278g55g5075f7d009eff938221d4c3b4g0g', '2023-10-01 09:15:00', '2023-10-01 11:15:00'),
(103, 'guide_silva', 'GUIDE', 'c0h7h389h66h6186g8e110fgg049332e5d4c5h1h', '2023-10-01 10:30:00', '2023-10-01 12:30:00');

Tags: SpringBoot vue MyBatisPlus uniapp java

Publicado em 7-7 05:04