Visão Geral do Projeto
Com o aumento constante do consumo, a geração de itens excedentes tornou-se uma realidade comum em ambientes acadêmicos. Para mitigar o desperdício e promover a economia circular dentro dos campi, foi desenvolvido este sistema de negociação de itens de segunda mão. A plataforma permite que estudantes e funcionários anunciem e adquiram produtos de forma eficiente e segura.
A solução utiliza uma arquitetura moderna baseada em microserviços simplificados, empregando Spring Boot no ecossistema de backend e Vue.js para a interface do usuário. A persistência de dados é gerenciada pelo MySQL através do framework MyBatis, garantindo alta performance em operações de consulta e escrita.
Especificações Técnicas
- Interface (Frontend): Vue.js 2.7 com biblioteca de componentes View UI.
- Lógica de Negócio (Backend): Spring Boot 3.1.
- Banco de Dados: MySQL 8.0.
- Arquitetura: B/S (Browser/Server) com separação total entre cliente e servidor.
Estrutura Funcional do Sistema
O ecossistema está dividido em seis módulos principais que compõem o núcleo da operação administrativa e do usuário:
1. Módulo de Administração de Dados
Gerencia as configurações fundamentais da plataforma, incluindo o controle de acesso de usuários, hierarquias organizacionais, permissões de menus baseadas em perfis (RBAC), logs de auditoria e o repositório de arquivos em nuvem para fotos de produtos e documentos.
2. Gerenciamento de Catálogo de Itens
Responsável pelo ciclo de vida dos produtos anunciados. Permite que administradores e vendedores realizem operações de inclusão, remoção, atualização e busca detalhada de mercadorias.
3. Sistema de Agendamento de Visitas
Facilita o contato inicial entre interessados. O comprador pode solicitar uma inspeção física do item. O sistema notifica o vendedor para que os detalhes do encontro offline sejam ajustados.
4. Reserva e Finalização de Pedidos
Este módulo processa a intenção de compra. O interessado reserva o item informando um valor proposto. O vendedor tem a prerrogativa de aceitar ou declinar a oferta, formalizando a transação em caso de aceite.
5. Mural de Interação e Suporte
Um canal dedicado para comunicação direta. Serve para esclarecer dúvidas sobre produtos ou mediar possíveis divergências entre as partes envolvidas na negociação.
6. Painel de Conteúdo e Notícias
Utilizado para a publicação de informes institucionais, guias de segurança para evitar fraudes e novidades do marketplace universitário.
Arquitetura de Dados e Entidades
A modelagem do banco de dados foi estruturada para suportar a escalabilidade do sistema:
- Usuário: Armazena credenciais, contatos, CPF e nível de acesso.
- Produto: Contém especificações técnicas, preço, fotos e identificação do proprietário.
- Reserva: Registra o vínculo entre comprador e objeto, incluindo data e status da oferta.
- Comentário: Tabela relacional para mensagens, respostas e registros temporais de conversas.
Implementação de Funcionalidades Core
Autenticação de Usuários
@GetMapping("/auth/login")
@ApiOperation(value = "Acesso ao Portal")
public Result<String> realizarLogin(@RequestParam String login, @RequestParam String senha) {
QueryWrapper<User> query = new QueryWrapper<>();
query.eq("username", login);
User usuarioLocalizado = userService.getOne(query);
if (usuarioLocalizado == null) {
return ResultUtil.error("Credenciais inválidas ou usuário não encontrado");
}
BCryptPasswordEncoder validador = new BCryptPasswordEncoder();
if (!validador.matches(senha, usuarioLocalizado.getPassword())) {
return ResultUtil.error("Senha incorreta");
}
String tokenAcesso = tokenProvider.createToken(usuarioLocalizado.getUsername(), true);
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
new SecurityUserDetails(usuarioLocalizado), null, null
);
SecurityContextHolder.getContext().setAuthentication(auth);
return new ResultUtil<String>().setData(tokenAcesso);
}
Cadastro de Novos Membros
@GetMapping("/auth/register")
@ApiOperation(value = "Registro de Usuário")
public Result<String> registrarUsuario(@RequestParam String nome, @RequestParam String tel, @RequestParam String pass) {
QueryWrapper<User> buscaExistente = new QueryWrapper<>();
buscaExistente.and(i -> i.eq("username", nome).or().eq("mobile", tel));
if (userService.count(buscaExistente) > 0) {
return ResultUtil.error("Nome de usuário ou telefone já vinculados a uma conta");
}
User novoUser = new User();
novoUser.setUsername(nome);
novoUser.setMobile(tel);
novoUser.setPassword(new BCryptPasswordEncoder().encode(pass));
userService.save(novoUser);
// Atribuição de perfil padrão
Role perfilPadrao = roleService.getOne(new QueryWrapper<Role>().eq("is_default", true));
if (perfilPadrao != null) {
userRoleService.save(new UserRole(novoUser.getId(), perfilPadrao.getId()));
}
return new ResultUtil<String>().setData("Sucesso");
}
Processamento de Reserva de Produto
@PostMapping("/api/pedidos/reservar")
@ApiOperation(value = "Solicitar Reserva")
public Result<Object> solicitarReserva(@RequestParam String idProduto) {
Product item = productService.getById(idProduto);
if (item == null) return ResultUtil.error("Item indisponível");
User comprador = authContext.getUsuarioAtual();
long duplicados = pedidoService.count(new QueryWrapper<Pedido>()
.eq("item_id", idProduto)
.eq("comprador_id", comprador.getId()));
if (duplicados > 0) return ResultUtil.error("Você já possui uma reserva para este item");
Pedido novoPedido = new Pedido();
novoPedido.setItemId(idProduto);
novoPedido.setPrecoAcordado(item.getPreco());
novoPedido.setVendedorId(item.getOwnerId());
novoPedido.setCompradorId(comprador.getId());
novoPedido.setDataCriacao(LocalDateTime.now());
pedidoService.save(novoPedido);
return ResultUtil.success("Reserva efetuada com sucesso");
}
Consulta Paginada de Interações
@GetMapping("/api/forum/mensagens")
@ApiOperation(value = "Listar Mensagens")
public Result<IPage<Mensagem>> buscarMensagens(@ModelAttribute Mensagem filtro, @ModelAttribute PageVo pagina) {
QueryWrapper<Mensagem> criteria = new QueryWrapper<>();
if (filtro.getDataReferencia() != null) {
criteria.eq("created_at", filtro.getDataReferencia());
}
if (filtro.getParentId() == null || filtro.getParentId().isEmpty()) {
criteria.isNull("parent_id");
} else {
criteria.eq("parent_id", filtro.getParentId());
}
IPage<Mensagem> dados = msgService.page(PageUtil.format(pagina), criteria);
return new ResultUtil<IPage<Mensagem>>().setData(dados);
}