Arquitetura de Plataforma de Automação Social em PHP: Configuração, API e Integração OAuth

Visão Geral da Arquitetura

Uma plataforma de automação de interações sociais construída em PHP adota uma arquitetura modular, na qual cada arquivo assume uma responsabilidade bem definida. O objetivo é criar um ponto central de processamento de requisições, oferecer endpoints para ações automatizadas e integrar autenticação via provedores sociais sem armazenar senhas dos usuários. A estrutura típica inclui roteamento via Apache, um controlador frontal em index.php, serviços de API em api.php, configurações centralizadas em config.php e módulos específicos para OAuth, como social.php e qqauth.php.

  1. Roteamento Amigável com Apache e .htaccess

O Apache processa as requisições HTTP e, por meio do módulo mod_rewrite, converte URLs semânticas em parâmetros internos. Essa abordagem oculta o arquivo de entrada, melhora a indexação por mecanismos de busca e protege a estrutura interna da aplicação.

1.1 Regras Básicas de Reescrita

O arquivo .htaccess na raiz do projeto deve ativar o mecanismo de reescrita e redirecionar todas as requisições dinâmicas para index.php, preservando recursos estáticos como imagens, CSS e JavaScript.

RewriteEngine On
RewriteBase /

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?rota=$1 [QSA,L]

Na prática, uma requisição como /tarefa/criar é transformada internamente em index.php?rota=tarefa/criar, permitindo que o PHP realize o roteamento programático. O parâmetro QSA assegura que parâmetros de consulta existentes sejam preservados, enquanto L indica que aquela é a última regra aplicável.

1.2 Restrições de Acesso e Proteção de Arquivos

Arquivos sensíveis, como configurações e scripts auxiliares, devem ser bloqueados para acesso externo.

<FilesMatch "\.(sql|log|bak|config)$">
    Require all denied
</FilesMatch>

<Files "config.php">
    Require all denied
</Files>

Além disso, é recomendável desabilitar a execução de scripts em diretórios de upload e forçar conexões HTTPS para endpoints administrativos:

RewriteCond %{HTTPS} off
RewriteRule ^(admin|painel)/ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

1.3 Compressão e Cache

Para melhorar a performance, o servidor pode comprimir respostas de texto e definir cabeçalhos de cache para recursos estáticos.

<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/html text/css application/javascript
</IfModule>

<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType image/png "access plus 1 month"
    ExpiresByType text/css "access plus 1 week"
</IfModule>
  1. Controlador Frontal em index.php

O arquivo index.php atua como controlador frontal. Sua função é analisar a rota presente na URL, iniciar a sessão, verificar autenticação e delegar o processamento aos controladores específicos.

<?php
require_once 'config.php';

session_start();

$rota = isset($_GET['rota']) ? trim($_GET['rota'], '/') : 'inicio';
$segmentos = explode('/', filter_var($rota, FILTER_SANITIZE_URL));

$controlador = $segmentos[0] ?: 'inicio';
$acao = $segmentos[1] ?? 'index';

$rotasPublicas = ['inicio', 'login', 'registrar', 'oauth'];
$rotasProtegidas = ['painel', 'tarefas', 'perfil'];

if (in_array($controlador, $rotasProtegidas) && empty($_SESSION['usuario_id'])) {
    header('Location: /login');
    exit;
}

$arquivoControlador = "controllers/{$controlador}.php";

if (file_exists($arquivoControlador)) {
    require_once $arquivoControlador;
} else {
    http_response_code(404);
    require_once 'views/erros/404.php';
}

Esse padrão mantém a lógica de roteamento concentrada e facilita a adição de novas funcionalidades sem modificar o servidor web.

  1. API em api.php

O arquivo api.php expõe uma interface programática para ações da plataforma. Ele recebe requisições HTTP, valida parâmetros, executa regras de negócio e retorna respostas JSON uniformes.

3.1 Estrutura de Resposta Padronizada

Todas as respostas seguem um formato comum, facilitando o consumo por clientes web, mobile ou scripts externos.

<?php
require_once 'config.php';

header('Content-Type: application/json; charset=utf-8');

$resposta = [
    'status' => 'sucesso',
    'codigo' => 200,
    'dados' => null,
    'mensagem' => ''
];

try {
    $metodo = $_SERVER['REQUEST_METHOD'];
    $entrada = json_decode(file_get_contents('php://input'), true) ?? [];

    if ($metodo !== 'POST') {
        throw new Exception('Método não permitido', 405);
    }

    $acao = $entrada['acao'] ?? '';

    switch ($acao) {
        case 'criarTarefa':
            $resposta['dados'] = criarTarefa($entrada);
            break;
        case 'consultarStatus':
            $resposta['dados'] = consultarStatus($entrada);
            break;
        default:
            throw new Exception('Ação não encontrada', 404);
    }
} catch (Exception $e) {
    $resposta['status'] = 'erro';
    $resposta['codigo'] = $e->getCode() ?: 500;
    $resposta['mensagem'] = $e->getMessage();
    http_response_code($resposta['codigo']);
}

echo json_encode($resposta, JSON_UNESCAPED_UNICODE);

3.2 Validação de Dados de Entrada

A criação de uma tarefa exige validações rigorosas para evitar injeção de dados, abuso de recursos e inconsistências.

function criarTarefa(array $payload): array
{
    $camposObrigatorios = ['plataforma', 'destino', 'quantidade'];

    foreach ($camposObrigatorios as $campo) {
        if (empty($payload[$campo])) {
            throw new Exception("Campo obrigatório ausente: {$campo}", 400);
        }
    }

    $plataformasPermitidas = ['qq', 'wechat', 'weibo'];

    if (!in_array($payload['plataforma'], $plataformasPermitidas, true)) {
        throw new Exception('Plataforma não suportada', 400);
    }

    if (!filter_var($payload['destino'], FILTER_VALIDATE_URL)) {
        throw new Exception('URL de destino inválida', 400);
    }

    $quantidade = filter_var($payload['quantidade'], FILTER_VALIDATE_INT);

    if ($quantidade < 1 || $quantidade > 500) {
        throw new Exception('Quantidade fora do intervalo permitido', 400);
    }

    $pdo = conectarBanco();
    $stmt = $pdo->prepare(
        "INSERT INTO tarefas (plataforma, destino, quantidade, criado_em)
         VALUES (:plataforma, :destino, :quantidade, NOW())"
    );
    $stmt->execute([
        ':plataforma' => $payload['plataforma'],
        ':destino' => $payload['destino'],
        ':quantidade' => $quantidade
    ]);

    return ['tarefa_id' => $pdo->lastInsertId()];
}

3.3 Comunicação Segura com Serviços Externos

Quando a plataforma precisa consumir APIs de redes sociais, o cURL é configurado para forçar validação de certificado e limitar o tempo de espera.

function requisitarApiExterna(string $url, array $dados = [], array $cabecalhos = [])
{
    $ch = curl_init();

    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 15);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);

    if (!empty($dados)) {
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($dados));
    }

    if (!empty($cabecalhos)) {
        curl_setopt($ch, CURLOPT_HTTPHEADER, $cabecalhos);
    }

    $resultado = curl_exec($ch);

    if (curl_errno($ch)) {
        $erro = curl_error($ch);
        curl_close($ch);
        throw new Exception("Falha na requisição: {$erro}", 502);
    }

    $codigoHttp = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    return ['codigo' => $codigoHttp, 'corpo' => $resultado];
}
  1. Integração OAuth2.0 com Redes Sociais

A autenticação por meio de provedores sociais permite que o usuário acesse a plataforma sem criar uma nova senha. O fluxo segue o padrão OAuth2.0 com o modo authorization code.

4.1 Geração da URL de Autorização

Antes de redirecionar o usuário, o sistema gera um valor aleatório state e o armazena na sessão para prevenir ataques CSRF.

<?php
session_start();

$appId = getenv('QQ_APP_ID');
$uriCallback = urlencode('https://exemplo.com/qqauth.php');
$scope = 'get_user_info';
$estado = bin2hex(random_bytes(16));

$_SESSION['oauth_state'] = $estado;

$urlAutorizacao = "https://graph.qq.com/oauth2.0/authorize?" .
    "client_id={$appId}" .
    "&redirect_uri={$uriCallback}" .
    "&response_type=code" .
    "&scope={$scope}" .
    "&state={$estado}";

header("Location: {$urlAutorizacao}");
exit;

4.2 Tratamento do Callback e Obtenção do Token

Após o usuário autorizar, o provedor redireciona de volta com o parâmetro code. O sistema valida o state e troca o código por um access_token.

<?php
session_start();

if (!isset($_GET['code'], $_GET['state'])) {
    http_response_code(400);
    exit('Parâmetros de callback incompletos.');
}

$stateRecebido = $_GET['state'];

if (!hash_equals($_SESSION['oauth_state'] ?? '', $stateRecebido)) {
    http_response_code(403);
    exit('Token state inválido.');
}

unset($_SESSION['oauth_state']);

$appId = getenv('QQ_APP_ID');
$appSecret = getenv('QQ_APP_SECRET');
$callback = 'https://exemplo.com/qqauth.php';

$parametros = [
    'grant_type' => 'authorization_code',
    'client_id' => $appId,
    'client_secret' => $appSecret,
    'code' => $_GET['code'],
    'redirect_uri' => $callback
];

$urlToken = 'https://graph.qq.com/oauth2.0/token?' . http_build_query($parametros);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $urlToken);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);

$resposta = curl_exec($ch);
curl_close($ch);

parse_str($resposta, $tokens);

if (empty($tokens['access_token'])) {
    http_response_code(401);
    exit('Não foi possível obter o access token.');
}

$_SESSION['access_token'] = $tokens['access_token'];

4.3 Obtenção de Dados do Usuário e Criação de Sessão

Com o token em mãos, a aplicação consulat a API do provedor e cria ou atualiza o usuário local.

function obterOpenId(string $accessToken): ?string
{
    $url = 'https://graph.qq.com/oauth2.0/me?access_token=' . urlencode($accessToken);
    $retorno = file_get_contents($url);

    if (preg_match('/callback\((.*?)\);/i', $retorno, $correspondencias)) {
        $dados = json_decode($correspondencias[1], true);
        return $dados['openid'] ?? null;
    }

    return null;
}

function sincronizarUsuarioSocial(string $provedor, array $dados): int
{
    $pdo = conectarBanco();

    $stmt = $pdo->prepare(
        "SELECT usuario_id FROM vinculos_sociais
         WHERE provedor = :provedor AND identificador_externo = :id"
    );
    $stmt->execute([
        ':provedor' => $provedor,
        ':id' => $dados['openid']
    ]);

    if ($linha = $stmt->fetch()) {
        return (int) $linha['usuario_id'];
    }

    $pdo->beginTransaction();

    try {
        $stmt = $pdo->prepare(
            "INSERT INTO usuarios (nome, avatar, criado_em) VALUES (:nome, :avatar, NOW())"
        );
        $stmt->execute([
            ':nome' => $dados['nome'],
            ':avatar' => $dados['avatar']
        ]);

        $novoUsuarioId = (int) $pdo->lastInsertId();

        $stmt = $pdo->prepare(
            "INSERT INTO vinculos_sociais
             (usuario_id, provedor, identificador_externo, token_acesso)
             VALUES (:usuario, :provedor, :id, :token)"
        );
        $stmt->execute([
            ':usuario' => $novoUsuarioId,
            ':provedor' => $provedor,
            ':id' => $dados['openid'],
            ':token' => $dados['access_token']
        ]);

        $pdo->commit();

        return $novoUsuarioId;
    } catch (Exception $e) {
        $pdo->rollBack();
        throw $e;
    }
}

Para ambientes com múltiplos servidores, o armazenamento de sessão pode ser centralizado no Redis.

ini_set('session.save_handler', 'redis');
ini_set('session.save_path', 'tcp://127.0.0.1:6379');
session_start();
  1. Configuração Centralizada em config.php

O arquivo config.php concentra todas as definições sensíveis e comportamentais do sistema. Em vez de valores hardcoded, ele lê variáveis de ambiente, conforme os princípios do Twelve-Factor App.

<?php
declare(strict_types=1);

require_once __DIR__ . '/vendor/autoload.php';

if (file_exists(__DIR__ . '/.env')) {
    $linhas = parse_ini_file(__DIR__ . '/.env', false, INI_SCANNER_TYPED);
    foreach ($linhas as $chave => $valor) {
        putenv("{$chave}={$valor}");
    }
}

define('AMBIENTE', getenv('APP_ENV') ?: 'producao');
define('DEPURACAO', AMBIENTE === 'desenvolvimento');

define('DB_HOST', getenv('DB_HOST'));
define('DB_USUARIO', getenv('DB_USUARIO'));
define('DB_SENHA', getenv('DB_SENHA'));
define('DB_NOME', getenv('DB_NOME'));
define('DB_CHARSET', 'utf8mb4');

define('QQ_APP_ID', getenv('QQ_APP_ID'));
define('QQ_APP_SECRET', getenv('QQ_APP_SECRET'));
define('WECHAT_APP_ID', getenv('WECHAT_APP_ID'));
define('WECHAT_APP_SECRET', getenv('WECHAT_APP_SECRET'));

function conectarBanco(): PDO
{
    static $pdo = null;

    if ($pdo === null) {
        $dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NOME . ";charset=" . DB_CHARSET;
        $opcoes = [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false
        ];

        $pdo = new PDO($dsn, DB_USUARIO, DB_SENHA, $opcoes);
    }

    return $pdo;
}

O arquivo .env correspondente deve ter permissões restritas e nunca ser versionado.

APP_ENV=producao
DB_HOST=localhost
DB_USUARIO=app_user
DB_SENHA=senha_segura_aqui
DB_NOME=plataforma_social
QQ_APP_ID=123456789
QQ_APP_SECRET=chave_secreta
WECHAT_APP_ID=wx1234567890
WECHAT_APP_SECRET=segredo_wechat
  1. Considerações de Segurança e Conformidade

Ao operar uma plataforma que interage com redes sociais, é essencial adotar práticas que protejam tanto a aplicação quanto os usuários finais.

6.1 Proteção Contra Ataques Comuns

  • Injeção SQL: usar instruções preparadas em todas as consultas ao banco de dados.
  • Cross-Site Scripting (XSS): escapar saídas com htmlspecialchars() antes de exibir dados.
  • Cross-Site Request Forgery (CSRF): exigir tokens em formulários e validar o parâmetro state no OAuth.
  • Exposição de arquivos: bloquear acesso a .env, config.php e backups via .htaccess.

6.2 Controle de Taxa de Requisições

Limitar o número de chamadas por usuário ou IP reduz o risco de abuso e protege contra ataques de força bruta.

function verificarLimiteRequisicoes(string $identificador, int $limite = 60, int $janela = 60): bool
{
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);

    $chave = "rate_limit:{$identificador}";
    $atual = $redis->incr($chave);

    if ($atual === 1) {
        $redis->expire($chave, $janela);
    }

    return $atual <= $limite;
}

6.3 Conformidade e Uso Responsável

Plataformas de automação social devem respeitar os termos de serviço de cada rede integrada e as legislações de proteção de dados, como a LGPD no Brasil e o GDPR na Europa. A coleta de informações deve ser limitada ao estritamente necessário, e os usuários devem ser informados sobre como seus dados são armazenados e processados. O armazenamento de tokens de acesso deve ser criptografado, e logs de acesso devem ser mantidos apenas pelo tempo necessário, com anonimização quando apropriado.

Componente Responsabilidade Principal Tecnologias Envolvidas
.htaccess Roteamento, restrição de acesso, compressão Apache, mod_rewrite, mod_deflate
index.php Controlador frontal e distribuição de rotas PHP, sessões
api.php Endpoints para criação e consulta de tarefas PHP, JSON, PDO
social.php Abstração de provedores OAuth OAuth2.0, cURL
config.php Centralização de configurações e conexão com banco PHP, variáveis de ambiente
Redis Armazenamento de sessões e rate limiting Redis, extensão phpredis

Tags: PHP apache mod_rewrite OAuth2.0 cURL

Publicado em 7-10 07:31