Desenvolvimento de um Sistema de Comércio de Itens Usados para Ambientes Universitários com Java e Spring Boot

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);
}

Tags: java SpringBoot Vue.js MySQL MyBatis

Publicado em 7-3 01:20