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.