O projeto consiste em uma plataforma completa para gestão de submissão e avaliação de trabalhos acadêmicos, dividida em três camadas principais: uma API REST construída com Spring Boot, um painel administrativo desenvolvido em Vue.js e um aplicativo móvel/multiplataforma baseado em uni-app. O objetivo é oferecer fluxo contínuo entre autores, revisores e administradores, garantindo rastreabilidade do estado de cada artigo.
Arquitetura e stack tecnológica
A solução adota arquitetura em camadas com separação clara entre interface, lógica de negócio e persistência. A comunicação entre frontend e backend ocorre exclusivamente via APIs REST protegidas por tokens de sessão.
- Backend: Spring Boot 2.x/3.x, Maven, Spring MVC, MyBatis-Plus.
- Painel web: Vue.js 2.x/3.x, Vue Router, Axios, Element Plus/Vuetify.
- Aplicativo: uni-app (Vue-based), compilado para H5, iOS e Android.
- Banco de dados: MySQL 8.0, com pool de conexões gerenciado pelo HikariCP.
Backend com Spring Boot
O backend é responsável por expor endpoints REST, aplicar regras de negócio, gerenciar autenticação e persistir dados. O Spring Boot simplifica a configuração por meio de starters e autoconfiguração, reduzindo a necessidade de XMLs manuais. A aplicação é empacotada como um JAR executável com servidor Tomcat embarcado.
Principais vantagens observadas:
- Configuração automática de datasource, segurança e validação a partir das dependências declaradas.
- Tomcat/Jetty/Undertow embarcados, dispensando deploy em servidor externo durante o desenvolvimento.
- Integração simplificada com Spring Data, Spring Security e serviços em nuvem.
Frontend web com Vue.js
O painel administrativo utiliza Vue.js para construir interfaces reativas e modulares. O padrão de componentes permite reutilizar elementos como tabelas de submissões, formulários de avaliação e gráficos de status. A reatividade do Vue garante que a interface seja atualizada automaticamente quando os dados da API são modificados, sem manipulação direta do DOM.
Aplicativo móvel com uni-app
O uni-app foi escolhido para unificar o código entre H5 e aplicativos nativos. A mesma base em Vue é compilada para diferentes plataformas, mantendo a lógica de submissão, notificações e consulta de pareceres. A comunicação com a API utiliza uni.request, equivalente ao Axios, com interceptadores centralizados para inclusão do token e tratamento de erros.
Persistência com MyBatis-Plus
MyBatis-Plus atua como camada de persistência, reduzindo a necessidade de SQL manual para operações CRUD. Ele oferece:
- Mapper genérico com métodos prontos como
insert,selectById,updateByIdedeleteById. - Wrapper para construção de consultas dinâmicas.
- Paginação automática e otimizada para o MySQL.
- Geração de código a partir das tabelas do banco.
Implementação da autenticação baseada em token
A autenticação segue o modelo de sessão stateless por token. Após o login bem-sucedido, o servidor gera um token aleatório, armazena metadados associados e devolve o token ao cliente. Em cada requisição subsequente, o token é enviado no cabeçalho Token e validado por um interceptor.
Exemplo do endpoint de login:
@IgnoreAuth
@PostMapping("/login")
public R login(String username, String password, String captcha, HttpServletRequest request) {
UsuarioEntity usuario = usuarioService.findOne(
new QueryWrapper<UsuarioEntity>().eq("username", username)
);
if (usuario == null || !usuario.getPassword().equals(password)) {
return R.error("Usuário ou senha inválidos");
}
String token = sessaoService.createToken(usuario.getId(), username, "usuarios", usuario.getRole());
return R.ok().put("token", token);
}
Serviço de geração e renovação do token:
@Override
public String createToken(Long userId, String username, String tableName, String role) {
SessaoEntity sessao = this.findOne(
new QueryWrapper<SessaoEntity>()
.eq("user_id", userId)
.eq("role", role)
);
String token = UUID.randomUUID().toString().replace("-", "");
Date expiration = Date.from(LocalDateTime.now().plusHours(1).atZone(ZoneId.systemDefault()).toInstant());
if (sessao != null) {
sessao.setToken(token);
sessao.setExpirationTime(expiration);
this.updateById(sessao);
} else {
this.insert(new SessaoEntity(userId, username, tableName, role, token, expiration));
}
return token;
}
Interceptor de validação do token e configuração de CORS:
@Component
public class AuthInterceptor implements HandlerInterceptor {
public static final String HEADER_TOKEN = "Token";
@Autowired
private SessaoService sessaoService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers",
"x-requested-with,request-source,Token,Origin,Content-Type,cache-control,Accept,Authorization");
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
if (RequestMethod.OPTIONS.name().equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpStatus.OK.value());
return false;
}
if (!(handler instanceof HandlerMethod)) {
return true;
}
IgnoreAuth ignoreAuth = ((HandlerMethod) handler).getMethodAnnotation(IgnoreAuth.class);
if (ignoreAuth != null) {
return true;
}
String token = request.getHeader(HEADER_TOKEN);
SessaoEntity sessao = null;
if (StringUtils.isNotBlank(token)) {
sessao = sessaoService.findByToken(token);
}
if (sessao != null && sessao.getExpirationTime().after(new Date())) {
request.getSession().setAttribute("userId", sessao.getUserId());
request.getSession().setAttribute("role", sessao.getRole());
request.getSession().setAttribute("tableName", sessao.getTableName());
request.getSession().setAttribute("username", sessao.getUsername());
return true;
}
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
try (PrintWriter writer = response.getWriter()) {
writer.print(JSONObject.toJSONString(R.error(401, "Sessão inválida ou expirada")));
}
return false;
}
}
Modelo de dados para controle de sessão
A tabela de sessões armazena o token gerado, o identificador do usuário, a role e a data de expiração. Abaixo está um exemplo de DDL e registros iniciais:
DROP TABLE IF EXISTS `sessao`;
CREATE TABLE `sessao` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Identificador único',
`user_id` bigint(20) NOT NULL COMMENT 'Identificador do usuário',
`username` varchar(100) NOT NULL COMMENT 'Nome de usuário',
`table_name` varchar(100) DEFAULT NULL COMMENT 'Tabela de origem do usuário',
`role` varchar(100) DEFAULT NULL COMMENT 'Papel/perfil do usuário',
`token` varchar(200) NOT NULL COMMENT 'Token de sessão',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Criado em',
`expiration_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT 'Expira em',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_token` (`token`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='Controle de sessões';
INSERT INTO `sessao` VALUES
(1, 23, 'cd01', 'estudante', 'ESTUDANTE', 'al6svx5qkei1wljry5o1npswhdpqcpcg', '2023-02-23 21:46:45', '2023-03-15 14:01:36'),
(2, 11, 'xh01', 'estudante', 'ESTUDANTE', 'fahmrd9bkhqy04sq0fzrl4h9m86cu6kx', '2023-02-27 18:33:52', '2023-03-17 18:27:42'),
(3, 1, 'admin', 'usuarios', 'ADMIN', 'h1pqzsb9bldh93m92j9m2sljy9bt1wdh', '2023-02-27 19:37:01', '2023-03-17 18:23:02');
Testes do sistema
Os testes foram conduzidos com abordagem de caixa-preta, simulando o comportamento do usuário final. O objetivo é detectar falhas de validação, permissões e fluxo antes da entrega.
Testes de login
| Dados de entrada | Resultado esperado |
|---|---|
| Usuário e senha corretos, captcha válido | Acesso ao sistema com token retornado |
| Senha incorreta | Mensagem de erro e bloqueio do login |
| Captcha incorreto | Erro de validação do captcha |
| Campo de usuário vazio | Notificação de campo obrigatório |
Testes de gestão de usuários
| Cenário | Resultado esperado |
|---|---|
| Cadastro com dados válidos | Usuário aparece na listagem |
| Edição de informações | Dados atualizados na interface |
| Exclusão confirmada | Registro removido do banco |
| Cadastro com usuário duplicado | Erro informando duplicidade |
Após a execução dos testes, o sistema apresnetou comportamento conforme o especificado, com fluxo de autenticação, controle de permissões e operações CRUD funcionando de maneira estável.