wifi-etl/app/extractor/ruijie.py
Rafael Lopes 331a021d9a
Some checks failed
Deploy WiFi-ETL Prod / deploy (push) Failing after 0s
FEAT: Implementado ETL completo para Ruijie e Wifeed
- Adicionado suporte para extração de dados do Ruijie e WiFeed, incluindo autenticação e tratamento de erros.
- Adicionado suporte para watermarking em ambas as fontes para extração incremental.
- Criado script de transformação para mesclagem de MAC addresses.
- Implementado Backfill para WiFeed, permitindo extração histórica com controle de taxa.
- Adicionado script de depuração para testes de transformação do WiFeed.
- Desenvolvido scripts de implantação e configurações do Docker para setup de produção.
- Criado script de inicialização do schema do PostgreSQL em infra/init.sql.
- Adicionado teste automatizado para lógica de transformação e carregamento em test_transform_load.py.
- Atualizado documentation para implantação e setup de produção.
2026-04-22 16:55:44 -03:00

110 lines
3.7 KiB
Python

import requests
import logging
from typing import Tuple, Optional
from app.core.config import (
RUIJIE_BASE_URL,
RUIJIE_APPID,
RUIJIE_SECRET,
RUIJIE_ACCESS_TOKEN,
RUIJIE_GROUP_ID,
)
logger = logging.getLogger(__name__)
BASE_URL = RUIJIE_BASE_URL
def get_access_token() -> str:
url = f"{BASE_URL}/service/api/oauth20/client/access_token?token=d63dss0a81e4415a889ac5b78fsc904a"
payload = {"appid": RUIJIE_APPID, "secret": RUIJIE_SECRET}
resp = requests.post(url, json=payload, timeout=15)
resp.raise_for_status()
token = resp.json().get("data", {}).get("access_token")
if not token:
raise ValueError(f"Token não retornado: {resp.json()}")
return token
def refresh_token(access_token: str) -> str:
url = f"{BASE_URL}/service/api/token/refresh?appid={RUIJIE_APPID}&secret={RUIJIE_SECRET}&access_token={access_token}"
resp = requests.get(url, timeout=15)
resp.raise_for_status()
token = resp.json().get("accessToken") or resp.json().get("data", {}).get("access_token")
if not token:
raise ValueError(f"Refresh falhou: {resp.json()}")
return token
def get_active_users(access_token: str, page_index: int = 1, page_size: int = 100) -> list[dict]:
url = f"{BASE_URL}/logbizagent/logbiz/api/sta/sta_users"
payload = {
"groupId": int(RUIJIE_GROUP_ID),
"pageSize": page_size,
"pageIndex": page_index,
"staType": "onofflineUserHistory",
}
headers = {"Content-Type": "application/json"}
params = {"access_token": access_token}
resp = requests.post(url, json=payload, headers=headers, params=params, timeout=15)
resp.raise_for_status()
return resp.json().get("list", [])
def extract_all_users(
access_token: str,
watermark_ms: Optional[int] = None,
page_size: int = 100
) -> Tuple[list[dict], int]:
"""
Extrai sessões Ruijie com suporte a watermark.
Args:
access_token: Token Ruijie
watermark_ms: onlineTime (epoch ms) da última sessão processada.
Se fornecido, ignora registros com onlineTime <= watermark.
page_size: Tamanho da página
Returns:
(lista_de_registros, novo_watermark)
- novo_watermark = maior onlineTime encontrado (ou watermark anterior se nada novo)
"""
all_records = []
page = 1
max_online_ms = watermark_ms or 0
stopped_early = False
while True:
records = get_active_users(access_token, page_index=page, page_size=page_size)
if not records:
break
# Filtra por watermark (se fornecido) ou adiciona tudo
if watermark_ms is not None:
filtered = [r for r in records if r.get('onlineTime', 0) > watermark_ms]
if len(filtered) < len(records):
# Parou de voltar no tempo — já viu tudo até o watermark
stopped_early = True
all_records.extend(filtered)
break
all_records.extend(filtered)
else:
# Sem watermark: adiciona todos os registros da página
all_records.extend(records)
# Atualiza max_online_ms
page_max = max((r.get('onlineTime', 0) for r in records), default=0)
if page_max > max_online_ms:
max_online_ms = page_max
logger.info(f"Ruijie: página {page}{len(records)} registros (watermark={watermark_ms})")
if len(records) < page_size or stopped_early:
break
page += 1
new_watermark = max_online_ms if max_online_ms > (watermark_ms or 0) else watermark_ms
logger.info(f"Ruijie: total extraído = {len(all_records)} registros (novo watermark={new_watermark})")
return all_records, new_watermark