Validação de Formulários no Servidor com JSP e Interatividade Dinâmica

Este artigo descreve a implementação de um sistema de validação de formulários no lado do servidor, utilizando objetos integrados JSP. A solução proposta vai além dos requisitos básicos de validação de entrada (verificando se o campo não está vazio e se tem um comprimento específico), incorporando um fundo dinâmico de partículas interativas gerado com Canvas e uma arquitetura de código altamente modular. Detalharemos a estrutura do projeto, os componentes principais e o processo de implantação.

Requisitos de Validação

O objetivo principal é desenvolver um formulário HTML com um campo de entrada de texto simples. Ao submeter a entrada, o servidor deve realizar as seguintes verificações:

  • A entrada não pode ser nula ou vazia.
  • O comprimento da entrada (após remover espaços em branco iniciais/finais) deve ser exatamente 5 caracteres.

Se a validação falhar, o servidor deve retornar uma mensagem de erro específica, limpar o campo de entrada e focar o cursor no campo. Se a validação for bem-sucedida, uma mensagem de sucesso deve ser exibida. A validação deve ser realizada exclusivamente no lado do servidor, utilizando os objetos integrados do JSP.

Estrutura do Projeto e Princípios de Design

O diretório raiz do projeto é nomeado formValidation. Ele contém os arquivos JSP para a interface do usuário e lógica de validação do servidor, pastas dedicadas para estilos CSS e scripts JavaScript, e um fragmento JSP reutilizável para exibir o histórico. Todos os arquivos JSP utilizam codificação UTF-8 e dependem de objetos JSP como request e session para validação e comunicação de dados. Os estilos e scripts são divididos em múltiplos arquivos pequenos, cada um com uma responsabilidade única para promover alta coesão e baixo acoplamento. A estrutura do diretório formValidation é a seguinte:

formValidation/
├── mainPage.jsp             # Interface principal do formulário
├── processInput.jsp         # Lógica de validação do lado do servidor
├── entryLog.jsp             # Fragmento para exibir registros de entrada
├── assets/
│   ├── css/
│   │   ├── base.css         # Estilos globais e de layout
│   │   ├── glassPanel.css   # Estilos para o painel com efeito de vidro
│   │   ├── formElements.css # Estilos para elementos do formulário
│   │   └── messages.css     # Estilos para mensagens de feedback e histórico
│   └── js/
│       ├── canvasInit.js    # Inicialização e responsividade do Canvas
│       ├── mouseControl.js  # Rastreamento da posição do cursor
│       ├── particleSystem.js# Definição da classe Partícula
│       ├── physics.js       # Detecção e resposta a colisões entre partículas
│       ├── animationLoop.js # Ciclo principal de animação e renderização
│       ├── charCounter.js   # Contagem e feedback em tempo real de caracteres
│       └── uiActions.js     # Inicialização da página e gerenciamento de eventos

O fundo dinâmico do tipo "partículas em colisão" com interatividade do cursor é implementado usando a API Canvas, com um gradiente de fundo escuro para garantir que o formulário seja claramente visível. A validação do lado do servidor, conforme os requisitos, utiliza o objeto request para obter o parâmetro de entrada, verificando se não é nulo/vazio e se o comprimento é exatamente 5. Os resultados da validação são encaminhados de volta para mainPage.jsp via RequestDispatcher. Em caso de falha, o campo é limpo e focado. Um contador de caracteres no lado do cliente fornece feedback visual instantâneo (muito curto, muito longo, exatos 5). Entradas válidas são armazenadas na session e exibidas como um histórico de até 5 entradas recentes, com um botão para limpar o histórico. A arquitetura modular distribui o código por 13 arquivos distintos, aumentando a legibilidade e a manutenibilidade.

Design de Estilos Globais (CSS)

A seção de estilos é dividida em quatro arquivos CSS para gerenciar a base do layout, o estilo do contêiner, os componentes do formulário e as mensagens de feedback.

base.css – Redefinição e Centralização

base.css redefine os estilos padrão do navegador, configura html e body para preencher a janela sem barras de rolagem, aplica um gradiente de fundo escuro e centraliza o conteúdo globalmente usando Flexbox. O Canvas de partículas é posicionado na camada mais baixa como fundo.

/* === Reset Global: Remove espaçamentos padrão de todos os elementos === */
*, *::before, *::after {
    margin: 0;
    padding: 0;
    box-sizing: border-box; /* Garante que padding e border estejam incluídos na largura/altura */
}

/* === Contêiner Base: Ocupa toda a janela e previne rolagem === */
html, body {
    width: 100%;
    height: 100%;
    overflow: hidden; /* Oculta conteúdo que excede a janela, sem barras de rolagem */
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; /* Fontes preferenciais */
}

/* === Corpo da Página: Centraliza o conteúdo na tela === */
body {
    display: flex;
    justify-content: center; /* Centralização horizontal */
    align-items: center;     /* Centralização vertical */
    min-height: 100vh;       /* Garante centralização vertical mesmo com pouco conteúdo */
    background: linear-gradient(150deg, #1a1a2e, #16213e, #0f3460); /* Gradiente escuro e suave */
    color: #e0e0e0;          /* Cor de texto padrão clara */
}

/* === Canvas de Partículas: Fundo decorativo fixo === */
#particleCanvas {
    position: fixed; /* Posição fixa para não rolar com o conteúdo */
    top: 0;
    left: 0;
    z-index: 0; /* Garante que outros elementos fiquem acima */
    pointer-events: none; /* Ignora eventos do mouse */
}

glassPanel.css – Contêiner com Efeito de Vidro

glassPanel.css define o estilo semitransparente com efeito de vidro para .glass-container, incluindo bordas arredondadas, destaques de borda e sombras. O título interno utiliza text-shadow para um efeito de brilho.

/* === Contêiner com Efeito de Vidro e Título === */
.glass-container {
    position: relative;
    z-index: 10; /* Eleva o contêiner acima do fundo de canvas */
    width: 90vw;
    max-width: 550px; /* Largura máxima para telas grandes */
    background: rgba(255, 255, 255, 0.05); /* Fundo branco semitransparente */
    backdrop-filter: blur(14px); /* Efeito de vidro borrado */
    -webkit-backdrop-filter: blur(14px); /* Compatibilidade Safari */
    border-radius: 18px;
    border: 1px solid rgba(255, 255, 255, 0.15); /* Borda sutil */
    box-shadow: 0 15px 40px rgba(0,0,0,0.6); /* Sombra para dar profundidade */
    padding: 30px 25px;
    display: flex;
    flex-direction: column;
    gap: 15px; /* Espaçamento entre elementos internos */
}

.glass-container h2 {
    font-size: clamp(1.4rem, 5vw, 2.2rem); /* Tamanho responsivo do título */
    text-align: center;
    margin-bottom: 20px;
    font-weight: 600;
    color: #fff;
    text-shadow: 0 0 12px rgba(150, 200, 255, 0.8); /* Sombra de texto azulada */
}

formElements.css – Elementos do Formulário

formElements.css lida com a aparência do campo de entrada, do contador de caracteres e dos botões. O campo de entrada tem um fundo semitransparente com um brilho roxo ao focar. Os botões de submissão e reset usam cores contrastantes para se destacarem contra o fundo escuro.

/* === Estilos para Elementos de Formulário === */
input[type="text"] {
    width: 100%;
    padding: 13px 16px;
    font-size: 1.05rem;
    color: #e0e0e0;
    background: rgba(255,255,255,0.08); /* Fundo semitransparente */
    border: 1px solid rgba(255,255,255,0.2);
    border-radius: 10px;
    outline: none; /* Remove o contorno de foco padrão */
    backdrop-filter: blur(4px);
    transition: all 0.25s ease-out; /* Transição suave */
}

input[type="text"]:focus {
    border-color: #8b5cf6; /* Borda roxa ao focar */
    box-shadow: 0 0 15px rgba(139,92,246,0.35); /* Brilho suave */
}

.char-feedback {
    display: flex;
    justify-content: flex-end; /* Alinha o contador à direita */
    font-size: 0.8rem;
    color: rgba(255,255,255,0.5);
    margin-top: 5px;
    margin-bottom: 15px;
}
.char-feedback span {
    margin-left: 8px;
}

.action-buttons {
    display: flex;
    gap: 10px; /* Espaçamento entre botões */
    margin-bottom: 18px;
}

.action-btn {
    flex: 1; /* Distribui a largura igualmente */
    padding: 11px;
    font-weight: 500;
    border: none;
    border-radius: 10px;
    cursor: pointer;
    color: #fff;
    text-transform: uppercase;
    transition: 0.25s ease-out;
    letter-spacing: 0.8px;
}

.btn-submit {
    background: #6d28d9; /* Roxo vibrante */
}

.btn-submit:hover {
    background: #7c3aed; /* Roxo mais claro ao passar o mouse */
    transform: translateY(-1px); /* Pequeno efeito de elevação */
}

.btn-reset-history {
    background: rgba(255,255,255,0.15);
    border: 1px solid rgba(255,255,255,0.3);
}

.btn-reset-history:hover {
    background: rgba(255,255,255,0.25);
}

messages.css – Mensagens e Histórico

messages.css gerencia a exibição de mensagens de erro e sucesso, com bordas coloridas à esquerda e animações. Também estiliza a área de histórico na parte inferior. As unidades relativas e clamp() são usadas para garantir a responsividade.

/* === Estilos para Mensagens de Feedback e Histórico === */
.feedback-message {
    padding: 11px 16px;
    border-radius: 10px;
    margin-bottom: 14px;
    background: rgba(0,0,0,0.25);
    backdrop-filter: blur(7px);
    animation: fadeInSlide 0.35s ease-out; /* Animação de entrada */
    font-size: 0.95rem;
}

.msg-error {
    border-left: 3px solid #ef4444; /* Borda vermelha para erro */
    color: #fca5a5;
}

.msg-success {
    border-left: 3px solid #22c55e; /* Borda verde para sucesso */
    color: #86efac;
}

@keyframes fadeInSlide {
    from {
        opacity: 0;
        transform: translateY(-10px); /* Começa ligeiramente acima e transparente */
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.history-panel {
    margin-top: 18px;
    padding: 14px;
    background: rgba(0,0,0,0.25);
    border-radius: 10px;
    border: 1px solid rgba(255,255,255,0.1);
}

.history-panel h4 {
    margin-bottom: 9px;
    color: #a78bfa; /* Título roxo claro */
    border-bottom: 1px solid rgba(255,255,255,0.15);
    padding-bottom: 4px;
    font-size: 1rem;
}

.history-list-entries {
    list-style: none; /* Remove marcadores de lista */
    max-height: 110px;
    overflow-y: auto; /* Adiciona scroll se exceder a altura */
    font-size: 0.85rem;
}

.history-list-entries li {
    padding: 3px 0;
    border-bottom: 1px dashed rgba(255,255,255,0.08); /* Linhas tracejadas sutis */
    color: #c0c0c0;
}

Design de Scripts de Interação Frontend (JavaScript)

A parte JavaScript é dividida em sete arquivos, importados em mainPage.jsp na ordem de suas dependências.

canvasInit.js – Inicialização do Canvas

canvasInit.js obtém o elemento Canvas e seu contexto 2D, anexando-os a um objeto global pageState. Ele também configura um ouvinte para redimensionar o Canvas quando a janela mudar de tamanho.

// Cria ou reutiliza o objeto global 'pageState'
window.pageState = window.pageState || {};

// Obtém o elemento Canvas e o contexto 2D
pageState.canvasElement = document.getElementById('particleCanvas');
pageState.drawingContext = pageState.canvasElement.getContext('2d');

// Função para ajustar o tamanho do Canvas à janela
function adjustCanvasToWindow() {
    pageState.canvasElement.width = window.innerWidth;
    pageState.canvasElement.height = window.innerHeight;
}

// Ouve o evento de redimensionamento da janela
window.addEventListener('resize', adjustCanvasToWindow);

// Chama a função uma vez ao carregar para definir o tamanho inicial
adjustCanvasToWindow();

mouseControl.js – Rastreamento do Cursor

mouseControl.js rastreia as coordendaas do cursor na página e as armazena em pageState.pointer, que é usado para o efeito de repulsão das partículas.

// Inicializa o objeto 'pointer' no 'pageState' para rastrear o cursor
pageState.pointer = { x: null, y: null, repulsionRadius: 100 };

// Adiciona um ouvinte para o movimento do mouse na janela
window.addEventListener('mousemove', (event) => {
    pageState.pointer.x = event.clientX;
    pageState.pointer.y = event.clientY;
});

// Adiciona um ouvinte para quando o mouse sai da janela
window.addEventListener('mouseout', () => {
    pageState.pointer.x = null; // Reseta as coordenadas do mouse
    pageState.pointer.y = null;
});

particleSystem.js – Definição da Classe Partícula

particleSystem.js define a classe Particle, que inclui posição, velocidade, raio e cor da partícula. Ela implementa movimento autônomo, colisão com as bordas do Canvas e desenho com um efeito de brilho.

// Define a classe Particle no escopo global
window.Particle = class Particle {
    constructor() {
        this.initializeParticle();
    }

    initializeParticle() {
        // Posição inicial aleatória dentro do Canvas
        this.x = Math.random() * pageState.canvasElement.width;
        this.y = Math.random() * pageState.canvasElement.height;

        // Velocidade e direção aleatórias
        const directionAngle = Math.random() * Math.PI * 2; // Ângulo de 0 a 360 graus
        const speedMagnitude = 1.2 + Math.random() * 0.8; // Velocidade entre 1.2 e 2.0
        this.velocityX = Math.cos(directionAngle) * speedMagnitude;
        this.velocityY = Math.sin(directionAngle) * speedMagnitude;

        // Raio e cor aleatórios
        this.radius = 2 + Math.random() * 4; // Raio entre 2 e 6 pixels
        this.color = `hsla(${Math.random() * 50 + 200}, 60%, 55%, 0.7)`; // Tons de azul/ciano
    }

    // Atualiza a posição da partícula e trata colisões com as bordas
    updatePosition() {
        // Colisão com borda horizontal
        if (this.x - this.radius < 0 || this.x + this.radius > pageState.canvasElement.width) {
            this.velocityX *= -1; // Inverte a velocidade X
            // Ajusta a posição para não ficar fora da tela
            if (this.x - this.radius < 0) this.x = this.radius;
            else this.x = pageState.canvasElement.width - this.radius;
        }

        // Colisão com borda vertical
        if (this.y - this.radius < 0 || this.y + this.radius > pageState.canvasElement.height) {
            this.velocityY *= -1; // Inverte a velocidade Y
            // Ajusta a posição
            if (this.y - this.radius < 0) this.y = this.radius;
            else this.y = pageState.canvasElement.height - this.radius;
        }

        // Move a partícula
        this.x += this.velocityX;
        this.y += this.velocityY;
    }

    // Desenha a partícula no Canvas
    render() {
        const ctx = pageState.drawingContext;
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
        ctx.fillStyle = this.color;
        ctx.fill();

        // Adiciona um efeito de brilho sutil
        ctx.shadowColor = 'rgba(160, 220, 255, 0.4)';
        ctx.shadowBlur = this.radius * 2;
        ctx.fill(); // Preenche novamente para aplicar o brilho
        ctx.shadowBlur = 0; // Reseta o blur para não afetar outros desenhos
    }
};

physics.js – Detecção de Colisões

physics.js fornece a função handleParticleCollisions, que detecta colisões elásticas perfeitas entre pares de partículas, trocando suas velocidades normais e corrigindo qualquer sobreposição de posição.

// Função global para gerenciar colisões entre partículas
function handleParticleCollisions(particleArray) {
    for (let i = 0; i < particleArray.length; i++) {
        for (let j = i + 1; j < particleArray.length; j++) {
            const particleA = particleArray[i];
            const particleB = particleArray[j];

            // Vetor de distância entre as partículas
            const deltaX = particleB.x - particleA.x;
            const deltaY = particleB.y - particleA.y;
            const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);

            const minSeparation = particleA.radius + particleB.radius;

            // Se as partículas estão se sobrepondo (colidindo)
            if (distance < minSeparation) {
                // Vetor normal de colisão
                const normalX = deltaX / distance;
                const normalY = deltaY / distance;

                // Diferença de velocidade
                const relativeVelocityX = particleA.velocityX - particleB.velocityX;
                const relativeVelocityY = particleA.velocityY - particleB.velocityY;

                // Componente da velocidade relativa ao longo do vetor normal
                const normalVelocityComponent = relativeVelocityX * normalX + relativeVelocityY * normalY;

                // Se as partículas já estão se afastando, não há necessidade de resolver a colisão
                if (normalVelocityComponent > 0) continue;

                // Aplica a troca de velocidades para colisão elástica (massas iguais)
                particleA.velocityX -= normalVelocityComponent * normalX;
                particleA.velocityY -= normalVelocityComponent * normalY;
                particleB.velocityX += normalVelocityComponent * normalX;
                particleB.velocityY += normalVelocityComponent * normalY;

                // Resolve a sobreposição para evitar que as partículas "grudem"
                const overlapAmount = (minSeparation - distance) * 0.5; // Metade da sobreposição
                particleA.x -= overlapAmount * normalX;
                particleA.y -= overlapAmount * normalY;
                particleB.x += overlapAmount * normalX;
                particleB.y += overlapAmount * normalY;
            }
        }
    }
}

animationLoop.js – Ciclo Principal de Animação

animationLoop.js inicializa um array de partículas. No ciclo de animação, ele aplica uma força de repulsão do cursor a cada partícula, atualiza suas posições e detecta colisões, e finalmente desenha todas as partículas e linhas de conexão semitransparentes para criar o fundo dinâmico.

// Configurações para o sistema de partículas
const totalParticles = 60; // Número de partículas no fundo
const particlesArray = [];

// Inicializa o array de partículas
for (let i = 0; i < totalParticles; i++) {
    particlesArray.push(new Particle());
}

// Aplica força de repulsão do mouse
function applyMouseRepulsion(p) {
    if (pageState.pointer.x !== null && pageState.pointer.y !== null) {
        const deltaX = p.x - pageState.pointer.x;
        const deltaY = p.y - pageState.pointer.y;
        const dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);

        if (dist < pageState.pointer.repulsionRadius) {
            const repulsionForce = (pageState.pointer.repulsionRadius - dist) / pageState.pointer.repulsionRadius;
            const angle = Math.atan2(deltaY, deltaX);
            p.velocityX += Math.cos(angle) * repulsionForce * 0.7; // Fator de força
            p.velocityY += Math.sin(angle) * repulsionForce * 0.7;
        }
    }
}

// Desenha linhas entre partículas próximas
function renderConnectionLines() {
    const ctx = pageState.drawingContext;
    for (let i = 0; i < particlesArray.length; i++) {
        for (let j = i + 1; j < particlesArray.length; j++) {
            const p1 = particlesArray[i];
            const p2 = particlesArray[j];
            const deltaX = p1.x - p2.x;
            const deltaY = p1.y - p2.y;
            const dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);

            if (dist < 110) { // Distância máxima para desenhar a linha
                ctx.beginPath();
                ctx.moveTo(p1.x, p1.y);
                ctx.lineTo(p2.x, p2.y);
                // Opacidade da linha diminui com a distância
                ctx.strokeStyle = `rgba(255,255,255,${0.04 * (1 - dist / 110)})`;
                ctx.lineWidth = 0.5;
                ctx.stroke();
            }
        }
    }
}

// O loop principal de animação
function startAnimation() {
    pageState.drawingContext.clearRect(0, 0, pageState.canvasElement.width, pageState.canvasElement.height); // Limpa o Canvas

    // 1. Aplica força do mouse
    for (const p of particlesArray) applyMouseRepulsion(p);

    // 2. Atualiza posição e verifica bordas
    for (const p of particlesArray) p.updatePosition();

    // 3. Resolve colisões entre partículas
    handleParticleCollisions(particlesArray);

    // 4. Desenha as partículas
    for (const p of particlesArray) p.render();

    // 5. Desenha as linhas de conexão
    renderConnectionLines();

    requestAnimationFrame(startAnimation); // Solicita o próximo frame de animação
}

startAnimation(); // Inicia o loop

charCounter.js – Contador de Caracteres em Tempo Real

charCounter.js ouve o evento input do campo de texto, atualizando a exibição do contador de caracteres em tempo real. Ele também ajusta a mensagem de feedback e a cor (muito curto/longo, exato 5 caracteres).

// Referências para elementos do DOM
const inputField = document.getElementById('userInput');
const charCountDisplay = document.getElementById('charCountValue');
const lengthHint = document.getElementById('charLengthHint');

// Ouve eventos de entrada no campo de texto
inputField.addEventListener('input', () => {
    const currentLength = inputField.value.length;
    charCountDisplay.textContent = currentLength;

    if (currentLength < 5) {
        lengthHint.textContent = 'Muito Curto';
        lengthHint.style.color = '#f87171'; // Vermelho
    } else if (currentLength > 5) {
        lengthHint.textContent = 'Muito Longo';
        lengthHint.style.color = '#f87171'; // Vermelho
    } else {
        lengthHint.textContent = 'Exato: 5 caracteres!';
        lengthHint.style.color = '#4ade80'; // Verde
    }
});

uiActions.js – Inicialização da Página e Ações da UI

uiActions.js, ao carregar a página, verifica o tipo de mensagem (erro ou sucesso) retornado pelo servidor. Se for um erro, ele limpa o campo de entrada e foca o cursor nele. Ele também anexa um ouvinte de clique ao botão "Limpar Histórico", que recarrega a página com um parâmetro para limpar o log da sessão.

// Executa após o DOM ser completamente carregado
window.addEventListener('DOMContentLoaded', () => {
    // Obtém o tipo de mensagem do campo oculto (definido pelo JSP)
    const serverMessageType = document.getElementById('hiddenMsgType').value;
    const inputElement = document.getElementById('userInput');
    const displayCount = document.getElementById('charCountValue');
    const hintText = document.getElementById('charLengthHint');

    // Função auxiliar para atualizar o contador de caracteres
    function updateCharStatus(length) {
        displayCount.textContent = length;
        if (length < 5) {
            hintText.textContent = 'Muito Curto';
            hintText.style.color = '#f87171';
        } else if (length > 5) {
            hintText.textContent = 'Muito Longo';
            hintText.style.color = '#f87171';
        } else {
            hintText.textContent = 'Exato: 5 caracteres!';
            hintText.style.color = '#4ade80';
        }
    }

    // Se o servidor indicou um erro, limpa o campo e atualiza o contador
    if (serverMessageType === 'error') {
        inputElement.value = '';
        updateCharStatus(0);
    } else {
        // Caso contrário, atualiza o contador com o valor existente
        updateCharStatus(inputElement.value.length);
    }

    // Foca o campo de entrada automaticamente para o usuário
    inputElement.focus();
});

// Adiciona ouvinte de clique ao botão de reset do histórico
document.getElementById('resetHistoryBtn').addEventListener('click', () => {
    if (confirm('Tem certeza que deseja limpar todos os registros de envio bem-sucedidos?')) {
        window.location.href = 'mainPage.jsp?command=clearHistory'; // Redireciona para limpar o histórico
    }
});

Design de Páginas JSP

mainPage.jsp – Interface Principal do Formulário

mainPage.jsp é o ponto central da interação. No topo da página, um scriptlet JSP recupera feedbackMsg e msgCategory do escopo da request para renderizar mensagens de erro ou sucesso. O formulário aponta para processInput.jsp usando method="post". O campo de entrada não tem um valor pré-definido, aparecendo vazio a cada carregamento, conforme o requisito. Abaixo do campo de entrada, um contador de caracteres exibe "X / 5" com feedback textual. O rodapé inclui o fragmento de histórico via <jsp:include page="entryLog.jsp" />. Um campo de entrada oculto passa msgCategory para o JavaScript de frontend para limpar e focar o campo na carga da página.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.util.*"%>

<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Validação de Entrada - Sistema Modular</title>
    <link rel="stylesheet" href="assets/css/base.css">
    <link rel="stylesheet" href="assets/css/glassPanel.css">
    <link rel="stylesheet" href="assets/css/formElements.css">
    <link rel="stylesheet" href="assets/css/messages.css">
</head>
<body>
    <!-- Scriptlets JSP para obter dados do request/session -->
    <%
        String feedbackMsg = (String) request.getAttribute("feedbackMsg");
        String msgCategory = (String) request.getAttribute("msgCategory");
        if (feedbackMsg == null) feedbackMsg = "";
        if (msgCategory == null) msgCategory = "";

        List<String> currentHistory = (List<String>) session.getAttribute("recordLog");
        if (currentHistory == null) currentHistory = new ArrayList<>();
        request.setAttribute("recentEntries", currentHistory); // Passa para entryLog.jsp

        // Lógica para limpar o histórico via parâmetro de URL
        if ("clearHistory".equals(request.getParameter("command"))) {
            session.removeAttribute("recordLog");
            currentHistory.clear();
            request.setAttribute("recentEntries", currentHistory);
            // Redireciona para remover o parâmetro da URL, evitando múltiplas limpezas
            response.sendRedirect("mainPage.jsp");
            return; // Termina a execução do scriptlet
        }
    %>

    <canvas id="particleCanvas"></canvas>

    <div class="glass-container">
        <h2>Validação de Entrada</h2>

        <!-- Exibe mensagens de feedback do servidor -->
        <% if (!feedbackMsg.isEmpty()) { %>
            <div class="feedback-message <%= msgCategory.equals("error") ? "msg-error" : "msg-success" %>">
                <%= feedbackMsg %>
            </div>
        <% } %>

        <form action="processInput.jsp" method="post">
            <input type="text" id="userInput" name="userInput" placeholder="Digite exatamente 5 caracteres..." maxlength="20">
            
            <div class="char-feedback">
                <span>Caracteres: <span id="charCountValue">0</span> / 5</span>
                <span id="charLengthHint">Muito Curto</span>
            </div>

            <div class="action-buttons">
                <button type="submit" class="action-btn btn-submit">Validar</button>
                <button type="button" id="resetHistoryBtn" class="action-btn btn-reset-history">Limpar Histórico</button>
            </div>

            <!-- Campo oculto para passar o tipo de mensagem ao JavaScript -->
            <input type="hidden" id="hiddenMsgType" value="<%= msgCategory %>">
        </form>

        <!-- Inclui o fragmento JSP para o histórico -->
        <jsp:include page="entryLog.jsp" />
    </div>

    <!-- Scripts JS -->
    <script src="assets/js/canvasInit.js"></script>
    <script src="assets/js/mouseControl.js"></script>
    <script src="assets/js/particleSystem.js"></script>
    <script src="assets/js/physics.js"></script>
    <script src="assets/js/animationLoop.js"></script>
    <script src="assets/js/charCounter.js"></script>
    <script src="assets/js/uiActions.js"></script>
</body>
</html>

processInput.jsp – Validação do Lado do Servidor

processInput.jsp contém apenas a lógica do lado do servidor e não gera nenhuma saída HTML. Primeiro, ele define a codificação da requisição como UTF-8 e obtém o parâmetro userInput. Em seguida, ele realiza as seguintes verificações: se null ou vazio após trim(), retorna "Entrada não pode ser vazia"; caso contrário, se trim().length() não for igual a 5, retorna "Deve ter 5 caracteres...". Se ambas as condições forem falsas, a validação é bem-sucedida, e uma mensagem de "Formato correto" é retornada. Em caso de sucesso, a entrada também é salva em uma lista de histórico na session (mantendo as 5 entradas mais recentes). Finalmente, feedbackMsg e msgCategory são definidos como atributos da request e a requisição é encaminhada de volta para mainPage.jsp, mantendo a URL no navegador inalterada.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.util.*"%>
<%
    // Garante que os caracteres recebidos sejam interpretados corretamente
    request.setCharacterEncoding("UTF-8");

    // Obtém o valor do campo "userInput" do formulário
    String userEntry = request.getParameter("userInput");
    String validationMessage; // Mensagem de feedback para o usuário
    String messageCategory;   // Categoria da mensagem (e.g., "success" ou "error")

    // --- Lógica de Validação ---
    // 1. Verifica se a entrada é nula ou vazia (após remover espaços)
    if (userEntry == null || userEntry.trim().isEmpty()) {
        validationMessage = "⚠️ Erro: A entrada não pode ser vazia.";
        messageCategory = "error";
    }
    // 2. Verifica se o comprimento da entrada (após remover espaços) é exatamente 5
    else if (userEntry.trim().length() != 5) {
        validationMessage = "⚠️ Erro: Deve inserir exatamente 5 caracteres. Atual: " + userEntry.trim().length();
        messageCategory = "error";
    }
    // 3. Se passou por todas as validações, a entrada é válida
    else {
        validationMessage = "🎉 Sucesso! Sua entrada: " + userEntry;
        messageCategory = "success";

        // --- Gerenciamento do Histórico na Sessão ---
        // Recupera o log de registros da sessão
        List<String> recordLog = (List<String>) session.getAttribute("recordLog");
        if (recordLog == null) {
            recordLog = new ArrayList<>(); // Cria um novo log se não existir
        }

        recordLog.add(userEntry); // Adiciona a entrada bem-sucedida ao log

        // Mantém apenas os 5 registros mais recentes
        if (recordLog.size() > 5) {
            recordLog = recordLog.subList(recordLog.size() - 5, recordLog.size());
        }
        session.setAttribute("recordLog", recordLog); // Atualiza o log na sessão
    }

    // Define atributos na requisição para que mainPage.jsp possa exibi-los
    request.setAttribute("feedbackMsg", validationMessage);
    request.setAttribute("msgCategory", messageCategory);

    // Encaminha a requisição de volta para a página principal para exibir o resultado
    // A URL no navegador não muda, é uma transição interna no servidor
    request.getRequestDispatcher("mainPage.jsp").forward(request, response);
%>

entryLog.jsp – Fragmento de Histórico

entryLog.jsp é um fragmento JSP reutilizável que obtém a lista de histórico do escopo da request e a renderiza em uma lista <ul> em ordem inversa. Se não houver registros, ele exibe "Nenhum registro disponível".

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.util.*"%>
<%
    // Obtém a lista de registros recentes do escopo da requisição
    List<String> recentEntries = (List<String>) request.getAttribute("recentEntries");
    // Garante que a lista não seja nula para evitar NullPointerException
    if (recentEntries == null) recentEntries = new ArrayList<>();
%>

<div class="history-panel">
    <h4>📋 Registros Recentes</h4>
    <% if (recentEntries.isEmpty()) { %>
        <p style="color:rgba(255,255,255,0.4); font-size:0.8rem;">Nenhum registro disponível.</p>
    <% } else { %>
        <ul class="history-list-entries">
            <!-- Loop reverso para mostrar os registros mais recentes primeiro -->
            <% for (int i = recentEntries.size() - 1; i >= 0; i--) { %>
                <li>✅ <%= recentEntries.get(i) %></li>
            <% } %>
        </ul>
    <% } %>
</div>

Este fragmento JSP é incluído dinamicamente via <jsp:include>. A lista é exibida em ordem inversa para que a entrada mais recente esteja sempre no topo. As bordas tracejadas e semitransparentes garantem um design limpo e consistente.

Implantação e Execução Local com Eclipse e Tomcat

Para garantir que todos os recursos (estilos e scripts) sejam carregados corretamente, o projeto deve ser implantado em um servidor web.

Passo 1: Criar um Projeto Web Dinâmico
No Eclipse JEE, selecione File → New → Dynamic Web Project. Nomeie o projeto como formValidation e selecione seu Apache Tomcat v9.0 configurado como Target Runtime. Marque Generate web.xml deployment descriptor e clique em Finish.

Passo 2: Configurar Estrutura de Diretórios e Importar Arquivos
Dentro do diretório WebContent, crie as pastas assets/css e assets/js. Copie todos os arquivos CSS e JS para suas respectivas pastas. Posicione mainPage.jsp, processInput.jsp e entryLog.jsp diretamente no diretório raiz WebContent.

Passo 3: Implantar e Iniciar o Tomcat
Na view Servers, clique com o botão direito em Tomcat v9.0 Server → Add and Remove → adicione o projeto formValidation ao painel Configured → Finish. Inicie o servidor Tomcat clicando com o botão direito e selecionando Start. Aguarde a mensagem Server startup in xxx ms no Console.

Passo 4: Verificação no Navegador Local
Abra o navegador e navegue para http://localhost:8080/formValidation/mainPage.jsp. A página deve exibir o fundo dinâmico de partículas e o formulário de vidro centralizado. Teste os seguintes cenários:

  • Não digite nada e clique em "Validar": Uma mensagem de erro deve aparecer, o campo limpo e focado.
  • Digite menos ou mais de 5 caracteres: Uma mensagem de erro "Deve inserir exatamente 5 caracteres..." deve ser exibida.
  • Digite exatamente 5 caracteres (e.g., "Hello"): Uma mensagem de sucesso verde deve aparecer, e a entrada deve ser adicionada à área de "Registros Recentes".
  • Teste o botão "Limpar Histórico" para verificar se os registros da sessão são removidos.

Implantação em Servidor Cloud ECS

Após a verificação local, o projeto pode ser implantado em um servidor cloud, como uma instância Alibaba Cloud ECS, para acesso público.

Passo 1: Exportar o Arquivo WAR
No Eclipse, clique com o botão direito no projeto formValidation → Export → WAR file. Escolha o caminho de exportação e nomeie o arquivo como formValidation.war, garantindo que todos os arquivos de origem estejam incluídos.

Passo 2: Upload do Arquivo WAR para o ECS
Utilize uma ferramenta FTP (e.g., FileZilla) para conectar-se à sua instância ECS. Faça o upload de formValidation.war para o diretório webapps do Tomcat no servidor cloud (e.g., /opt/tomcat/webapps no Linux ou C:\Tomcat\webapps no Windows). O Tomcat descompactará automaticamente o arquivo WAR.

Passo 3: Configurar Grupo de Segurança e Firewall
Acesse o console da Alibaba Cloud. Para a instância ECS, navegue até as regras do "Grupo de Segurança" e adicione uma regra de entrada: Protocolo TCP, Intervalo de Portas 8080, Origem 0.0.0.0/0. Além disso, verifique o firewall do sistema operacional do seu servidor ECS (e.g., firewall do Windows ou iptables/firewalld no Linux) para garantir que a porta 8080 esteja aberta para conexões de entrada.

Passo 4: Iniciar o Tomcat no Cloud e Verificar
Conecte-se ao seu servidor ECS (via SSH ou RDP). Navegue até o diretório bin do Tomcat e execute o script de inicialização (startup.sh no Linux, startup.bat no Windows). Em um navegador local, acesse http://[SEU_IP_PUBLICO_ECS]:8080/formValidation/mainPage.jsp. A interface com o fundo dinâmico e o formulário deve ser exibida e funcionar exatamente como no ambiente local. Verifique todos os cenários de validação, o contador de caracteres, o histórico e a funcionalidade de reset para confirmar que o解析, gerenciamento de sessão e interatividade JavaScript estão funcionando correatmente no ambiente público.

Tags: jsp Server-Side Validation Form Validation javascript Canvas API

Publicado em 9-20 08:30