| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
API backend desenvolvida em Java com Spring Boot para processamento de arquivos CSV/XLSX e geração de relatórios analíticos de vendas.
Este projeto faz parte do Projeto Hanami, uma iniciativa de impacto social voltada ao uso de tecnologia para análise de dados.
Desenvolver uma API robusta capaz de:
| Foco Principal | Entregas |
|---|---|
| Setup do projeto e arquitetura base | Estrutura inicial do projeto |
| Configuração de ambiente | Perfis dev e prod |
| Início do backend | Parser de dados e endpoint de upload |
| Persistência | Entidades e repositórios iniciais |
| Foco Principal | Entregas |
|---|---|
| Finalização da lógica de análise | Algoritmos completos |
| Relatórios | Geração de relatórios PDF |
| Documentação | README final e instruções de uso |
| Docker | Ambiente produtivo |
src/main/java/com/hanami/iurydev/apiHanami
├── controller # Camada de controle (endpoints REST)
├── dto # Objetos de transferência de dados
├── entity
│ ├── embeddable # Objetos incorporáveis
│ ├── enums # Enumerações do domínio
│ └── Venda # Entidade principal de vendas
├── repository # Interfaces JPA
├── service # Regras de negócio
└── ApiHanamiApplication
spring.datasource.url=jdbc:mysql://localhost:3306/hanamiapidb
spring.datasource.username=seu-login-mysql
spring.datasource.password=sua-senha-mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
# Aumenta o limite de tamanho do arquivo individual
spring.servlet.multipart.max-file-size=50MB
# Aumenta o limite total da requisição (arquivo + dados extras)
spring.servlet.multipart.max-request-size=50MB
spring.jackson.date-format=yyyy-MM-dd
spring.jackson.time-zone=America/Sao_Paulo spring.datasource.url=jdbc:mysql://${MYSQLHOST}:${MYSQLPORT}/${MYSQLDATABASE}
spring.datasource.username=${MYSQLUSER}
spring.datasource.password=${MYSQLPASSWORD}
spring.jpa.hibernate.ddl-auto=update
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=true
spring.jackson.date-format=yyyy-MM-dd
spring.jackson.time-zone=America/Sao_Paulo spring.profiles.active=devPasso a passo
git clone https://github.com/IuryDevJava/api-hanami.git cd api-hanami ./mvnw spring-boot:run <!-- Leitura de arquivos XLSX -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
<!-- Leitura de arquivos CSV -->
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.5.2</version>
</dependency>Endpoint responsável por receber arquivos CSV ou XLSX, validar os dados, processar as vendas e persistir no banco de dados
Post /vendas/upload
| Nome | Tipo | descrição |
|---|---|---|
| file | File | Arquivo CSV ou XLSX com os dados de vendas |
Body
{
"Status": "sucesso",
"Linhas_processadas": 10000
} {
"Status": "Aviso: Nenhuma nova linha processada",
"Linhas_processadas": 0
} {
"Status": "erro",
"Linhas_processadas": 0
} {
"Status": "Coluna obrigatória ausente: id_transacao",
"Linhas_processadas": 0
} {
"Receita_liquida": 5243176617.89,
"Lucro_bruto": 3099751358.11,
"Total_vendas": 8342927976.00,
"Media_por_transacao": 928849.70,
"Custo_total": 5243176617.89,
"Numero_transacoes": 8982
} [
{
"Nome_produto": "Carregador Wireless",
"Quatidade_vendida": 1006,
"Total_arrecadado": 288235871.00
},
{
"Nome_produto": "iPhone 15",
"Quatidade_vendida": 787,
"Total_arrecadado": 246201201.00
},
{
"Nome_produto": "Apple Watch",
"Quatidade_vendida": 858,
"Total_arrecadado": 249837283.00
}
] [
{
"Nome_produto": "Cabo USB-C",
"Quatidade_vendida": 1061,
"Total_arrecadado": 339386041.00
},
{
"Nome_produto": "Webcam HD",
"Quatidade_vendida": 1026,
"Total_arrecadado": 319378902.00
},
{
"Nome_produto": "Carregador Wireless",
"Quatidade_vendida": 1006,
"Total_arrecadado": 288235871.00
}
] [
{
"Nome_produto": "Cabo USB-C",
"Quatidade_vendida": 1061,
"Total_arrecadado": 339386041.00
},
{
"Nome_produto": "Webcam HD",
"Quatidade_vendida": 1026,
"Total_arrecadado": 319378902.00
},
{
"Nome_produto": "Chromecast",
"Quatidade_vendida": 934,
"Total_arrecadado": 294529780.00
}
] {
"Receita_liquida": 5243176617.89,
"Lucro_bruto": 3099751358.11,
"Custo_total": 5243176617.89
} CREATE DATABASE hanamiapidb;
USE hanamiapidb; SHOW TABLES; SELECT COUNT(*) FROM vendas; SELECT * FROM vendas LIMIT 10; SELECT id_transacao, observacao_validada
FROM vendas
WHERE processado_sucesso = false; SELECT processado_sucesso, COUNT(*)
FROM vendas
GROUP BY processado_sucesso; DROP TABLE hanamiapidb.vendas; SELECT
SUM(valor_final) AS total_vendas,
SUM(valor_final * (margem_lucro / 100)) AS lucro_bruto,
SUM(valor_final) - SUM(valor_final * (margem_lucro / 100)) AS receita_liquida,
SUM(valor_final - (valor_final * (margem_lucro / 100))) AS custo_total,
AVG(valor_final) AS media_por_transacao,
COUNT(*) AS numero_transacoes
FROM vendas
WHERE processado_sucesso = 1; SELECT
nome_produto,
COUNT(*) AS quantidade_vendida,
SUM(valor_final) AS total_arrecadado
FROM vendas
WHERE processado_sucesso = 1
GROUP BY nome_produto
ORDER BY total_arrecadado DESC; SELECT
SUM(valor_final * (margem_lucro / 100)) AS lucro_bruto,
SUM(valor_final) - SUM(valor_final * (margem_lucro / 100)) AS receita_liquida,
SUM(valor_final - (valor_final * (margem_lucro / 100))) AS custo_total
FROM vendas
WHERE processado_sucesso = 1; @Slf4j
@RestController
@RequestMapping("/vendas")
public class VendaController {
} 200 OK. Arquivo 'vendas_ficticias_10000_linhas.csv' foi processado com sucesso. Total: 10000 linhas
Erro 400. Ao tentar fazer o upload sem arquivo foi retornado um erro
Erro 422. Arquivo enviado não contém uma ou mais colunas obrigatórias Coluna obrigatória ausente: id_transacao
Erro crítico durante o processamento de upload
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency> springdoc.swagger-ui.path=/docs @Operation(
summary = "Upload",
description = "Faz o upload de arquivo CSV/XLSX"
)
@Parameter(description = "Critério de ordenação", example = "nome", schema = @Schema(allowableValues = {"nome", "preco", "quantidade"}))
@Parameter(description = "Formato do arquivo", required = true, schema = @Schema(allowableValues = {"json", "pdf"}))
Os filtros abaixo utilizam Query Methods do Spring Data JPA, onde as consultas são derivadas automaticamente a partir do nome do método.
List<Venda> findByProcessadoSucessoTrueAndCliente_Estado(String estado); @Operation(
summary = "Clientes e Região",
description = "Retorna métricas agrupadas por região, com filtro opcional por estado."
)
@GetMapping("/reports/regional-performance")
public ResponseEntity<Map<String, MetricasRegiaoDTO>> getRegionalPerformance(
@RequestParam(required = false) String estado) {
List<Venda> vendas;
if(estado != null && !estado.trim().isEmpty()) {
vendas = vendaRepository.findByProcessadoSucessoTrueAndCliente_Estado(estado.toUpperCase());
} else {
vendas = vendaRepository.findByProcessadoSucessoTrue();
}
List<MetricasRegiaoDTO> lista = vendaCalcularService.calcularMetricasPorRegiao(vendas);
// FILTRO ADICIONAL: Se o usuário pediu um estado, garantimos que o mapa
// contenha apenas a região daquele estado.
Map<String, MetricasRegiaoDTO> mapa = lista.stream()
.collect(Collectors.toMap(
MetricasRegiaoDTO::getRegiao,
dto -> dto
));
// Se houver filtro de estado, podemos filtrar o mapa final também
if (estado != null && !estado.trim().isEmpty() && !vendas.isEmpty()) {
// Pega a região do primeiro item da lista filtrada (já que todos são do mesmo estado)
String regiaoFiltrada = vendas.get(0).getLogistica().getRegiao().toString();
return ResponseEntity.ok(Map.of(regiaoFiltrada, mapa.get(regiaoFiltrada)));
}
return ResponseEntity.ok(mapa);
} List<Venda> findByDataVendaBetween(LocalDate startDate, LocalDate endDate); @Operation(
summary = "Relatório de Vendas",
description = "Retorna o resumo financeiro consolidado, com filtro opcional por período."
)
@GetMapping("/reports/sales-summary")
public ResponseEntity<RelatorioFinanceiroDTO> getSalesSumary(
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate) {
List<Venda> vendas;
if (startDate != null && endDate != null) {
// Conversão obrigatória: String -> LocalDate
LocalDate dataInicio = LocalDate.parse(startDate);
LocalDate dataFim = LocalDate.parse(endDate);
vendas = vendaRepository.findByDataVendaBetween(dataInicio, dataFim);
} else {
vendas = vendaRepository.findAll();
}
return ResponseEntity.ok(vendaCalcularService.calculaFinanceiro(vendas));
}Limpa o projeto
mvn cleanLimpa o projeto e verifica se o código compila e se não tem erros de sintaxe
mvn clean compile**Limpa, compila e empacota a aplicação dentro de um arquivo .jar
mvn clean package -DskipTestsFaça um teste e observe os logs para ver se está tudo certo
mvn test FROM maven:3.9.6-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn clean package -DskipTests
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
services:
db:
image: mysql:8.0
container_name: hanami-db
restart: always
environment:
MYSQL_DATABASE: hanamiapidb
MYSQL_ROOT_PASSWORD: root
ports:
- "3307:3306"
app:
build: .
container_name: hanami-app
ports:
- "8080:8080"
depends_on:
- db
environment:
- SPRING_DATASOURCE_URL=jdbc:mysql://db:3306/hanamiapidb?createDatabaseIfNotExist=true
- SPRING_DATASOURCE_USERNAME=root
- SPRING_DATASOURCE_PASSWORD=root
- SPRING_JPA_HIBERNATE_DDL_AUTO=updateObservação: Se quiser acessar o banco via Workbench/DBeaver enquanto o Docker roda, use a porta 3307
docker build -t projeto-apihanami . docker compose up -d http://localhost:8080/docs
docker-compose down -v[X] Upload: Os arquivos com a extensão .CSV e .XLSX estão sendo aceitos corretamente? (Sim)
[X] Código: O projeto compila sem erros? (Sim)
[X] Testes: Os endpoints no Postman e Swagger batem com os resultados do SQL? (Sim)
[X] Documentação: O README reflete a realidade do código? (Sim)
[X] Swagger/OpenAPI: Os endpoints estão documentados? (Sim)
[X] Docker: A aplicação roda corretamente via Docker? (Sim)
[X] Logs: Mensagens de sucesso e de erro foram registradas? (Sim)
[X] Tratamento de Exceções: Se eu enviar um CSV corrompido ou com colunas erradas, a API retorna um erro claro (ex: 400 Bad Request) (Sim)
[X] Validação na lógica de negócio: Filtros de data e ordenação funcionando via Query Param
| Back | FazBrowse Home | New Git URL |