Azure Blob Storage - Anexos - Documentação v2.0

Visão Geral

Este documento descreve a implementação atualizada do Azure Blob Storage para gerenciamento de anexos na API CordenaAi com contêineres hierárquicos por ambiente. O sistema permite upload, download, listagem e gerenciamento de arquivos com separação automática entre desenvolvimento e produção.

🎉 Nova Versão 2.0 - Contêineres Hierárquicos

Arquitetura

Componentes Principais

  1. AnexosBlobController - Controller REST para operações de anexos
  2. AnexoBlobService - Serviço de negócio para operações de blob
  3. BlobStorageService - Serviço de infraestrutura para Azure Storage
  4. DTOs - Objetos de transferência de dados

Fluxo de Dados

Cliente → AnexosBlobController → AnexoBlobService → BlobStorageService → Azure Storage

Configuração v2.0 - Contêineres Hierárquicos

⚡ Migração Automática

O sistema detecta automaticamente o ambiente ASP.NET Core e aplica os prefixos corretos aos contêineres.

1. Configuração por Ambiente

appsettings.Development.json

{
  "ConnectionStrings": {
    "AzureStorage": "DefaultEndpointsProtocol=https;AccountName=cordenaaistg13344;AccountKey=SUA_CHAVE_AQUI;EndpointSuffix=core.windows.net"
  },
  
  "AzureStorage": {
    "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=cordenaaistg13344;AccountKey=SUA_CHAVE_AQUI;EndpointSuffix=core.windows.net",
    "Environment": "dev"
  }
}

appsettings.json (Produção)

{
  "AzureStorage": {
    "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=cordenaaistg13344;AccountKey=SUA_CHAVE_PROD;EndpointSuffix=core.windows.net",
    "Environment": "prod"
  }
}

2. Estrutura de Contêineres Hierárquicos

🔧 Desenvolvimento (Environment: "dev")

🚀 Produção (Environment: "prod")

✅ Contêineres Criados com Sucesso!

Todos os contêineres hierárquicos foram criados no Azure Storage Account cordenaaistg13344:

🔧 Desenvolvimento:
  • ✅ dev-anexos
  • ✅ dev-imagens
  • ✅ dev-documentos
  • ✅ dev-temporarios
  • ✅ dev-public-thumbnails
🚀 Produção:
  • ✅ prod-anexos
  • ✅ prod-imagens
  • ✅ prod-documentos
  • ✅ prod-temporarios
  • ✅ prod-public-thumbnails

Status: Sistema pronto para uso! Os uploads serão automaticamente direcionados para os contêineres corretos baseado no ambiente.

📝 Comandos Utilizados (Referência)

Para referência futura, os comandos utilizados foram:

# Contêineres de desenvolvimento
az storage container create --name dev-anexos --account-name cordenaaistg13344
az storage container create --name dev-imagens --account-name cordenaaistg13344
az storage container create --name dev-documentos --account-name cordenaaistg13344
az storage container create --name dev-temporarios --account-name cordenaaistg13344
az storage container create --name dev-public-thumbnails --account-name cordenaaistg13344

# Contêineres de produção
az storage container create --name prod-anexos --account-name cordenaaistg13344
az storage container create --name prod-imagens --account-name cordenaaistg13344
az storage container create --name prod-documentos --account-name cordenaaistg13344
az storage container create --name prod-temporarios --account-name cordenaaistg13344
az storage container create --name prod-public-thumbnails --account-name cordenaaistg13344

3. Configuração de Serviços v2.0

🔧 Nova Arquitetura de Serviços

A configuração agora usa AzureStorageConfig e detecta automaticamente o ambiente:

// Configurar Azure Blob Storage com nova estrutura hierárquica
EstaAplicacao.GravarLog("|debug|Configurando Azure Blob Storage com nova estrutura...", "Startup.ConfigureServices()");

// Configurar AzureStorageConfig via IOptions
services.Configure<AzureStorageConfig>(
    Configuration.GetSection("AzureStorage"));

var azureStorageSection = Configuration.GetSection("AzureStorage");
var connectionString = azureStorageSection["ConnectionString"];
var environment = azureStorageSection["Environment"];

EstaAplicacao.GravarLog($"|info|Ambiente detectado: {environment}", "Startup.ConfigureServices()");

if (!string.IsNullOrEmpty(connectionString))
{
    // Registrar serviço com nova implementação hierárquica
    services.AddScoped<IBlobStorageService, BlobStorageService>();
    services.AddScoped<AnexoBlobService>();
    
    EstaAplicacao.GravarLog($"|sucesso|Azure Blob Storage configurado com ambiente: {environment}", "Startup.ConfigureServices()");
}
else
{
    EstaAplicacao.GravarLog("|aviso|Azure Storage connection string não encontrada - registrando implementação mock", "Startup.ConfigureServices()");
    // Registrar uma implementação mock para evitar erro de DI
    services.AddScoped<IBlobStorageService, MockBlobStorageService>();
}

Novos Componentes da Arquitetura

Endpoints da API v2.0

🔄 Compatibilidade Total

Todos os endpoints existentes /api/anexos-blob/* continuam funcionando exatamente como antes! A migração é transparente:

✅ Como Funciona a Migração Automática

Quando você faz um upload via POST /api/anexos-blob/upload:

  1. AnexoBlobService chama _blobStorageService.GetContainerByFileType(contentType)
  2. BlobStorageService mapeia o tipo de arquivo para ContainerType (ex: image/jpeg → ContainerType.Imagens)
  3. AzureStorageConfig aplica o prefixo do ambiente (ex: "dev" + "imagens" = "dev-imagens")
  4. Arquivo é salvo no contêiner correto automaticamente

Tipos de Contexto

O parâmetro tipoContextoId do upload identifica o tipo de documento. A lista de tipos ativos pode ser obtida via GET /api/TipoContexto/ativos. Referência:

IDNomeDescrição
1RGRegistro Geral de Identidade
2CPFCadastro de Pessoa Física
3CNHCarteira Nacional de Habilitação
4COMPROVANTE_RESIDENCIAComprovante de Residência
5CERTIDAO_NASCIMENTOCertidão de Nascimento
6CERTIDAO_CASAMENTOCertidão de Casamento
7COMPROVANTE_ESCOLARIDADEComprovante de Escolaridade
8ATESTADO_MEDICOAtestado Médico
9DECLARACAO_SAUDEDeclaração de Saúde
10AUTORIZACAO_PAISAutorização dos Pais/Responsáveis
11FOTO_3X4Foto 3x4
12CONTRATOContrato
13TERMO_COMPROMISSOTermo de Compromisso
14FOTO_PERFILFoto de Perfil
15OUTROSOutros Documentos
16CARTEIRINHA_LOGOLogo da instituição ou entidade responsável

Upload de Arquivo

POST /api/anexos-blob/upload
Content-Type: multipart/form-data
Authorization: Bearer {token}

Parâmetros:
- file: IFormFile (obrigatório)
- tipoContextoId: int (obrigatório) — ID do tipo de contexto; ver seção "Tipos de Contexto" ou GET /api/TipoContexto/ativos
- entidade: string (obrigatório)
- usuarioUpload: string (opcional)
- tipoAnexo: int (opcional)

Resposta: 200 OK com AnexoBlobResponse
Conflito: 409 se já existe documento para usuário/contexto

Listagem de Anexos

GET /api/anexos-blob
Authorization: Bearer {token}
Resposta: Lista de todos os anexos ativos

GET /api/anexos-blob/usuario/{userId}
Authorization: Bearer {token}
Resposta: Lista de anexos do usuário específico

GET /api/anexos-blob/usuario/{userId}/contexto/{contexto}
Authorization: Bearer {token}
Resposta: Anexo específico por usuário e contexto

Operações por ID

GET /api/anexos-blob/{id}
Authorization: Bearer {token}
Resposta: Anexo específico ou 404 se não encontrado

PUT /api/anexos-blob/{id}
Authorization: Bearer {token}
Body: AtualizarAnexoBlobRequest
Resposta: Anexo atualizado ou 404 se não encontrado

DELETE /api/anexos-blob/{id}
Authorization: Bearer {token}
Resposta: Confirmação de exclusão (soft delete)

Download e URLs

GET /api/anexos-blob/{id}/download
Authorization: Bearer {token}
Resposta: Stream do arquivo para download

GET /api/anexos-blob/{id}/sas-url?expiryHours=1
Authorization: Bearer {token}
Resposta: URL SAS temporária (padrão: 1 hora)

Modelos de Dados v2.0

🆕 Novos Modelos Adicionados

AzureStorageConfig

namespace InMinds.CordenaAi.Api.Infra.BlobStorage;

public class AzureStorageConfig
{
    public string ConnectionString { get; set; } = string.Empty;
    public string Environment { get; set; } = string.Empty;
    
    /// <summary>
    /// Gera o nome do contêiner com prefixo do ambiente
    /// Exemplo: "dev" + "imagens" = "dev-imagens"
    /// </summary>
    public string GetContainerName(string baseContainer)
    {
        return $"{Environment}-{baseContainer}";
    }
}

ContainerType (Enum)

namespace InMinds.CordenaAi.Api.Infra.BlobStorage;

/// <summary>
/// Tipos de contêineres disponíveis no Azure Blob Storage
/// </summary>
public enum ContainerType
{
    /// <summary>Imagens (JPG, PNG, GIF, etc.)</summary>
    Imagens,
    
    /// <summary>Documentos (PDF, DOC, DOCX, etc.)</summary>
    Documentos,
    
    /// <summary>Anexos gerais</summary>
    Anexos,
    
    /// <summary>Arquivos temporários</summary>
    Temporarios,
    
    /// <summary>Thumbnails públicos</summary>
    PublicThumbnails
}

UploadAnexoRequest

public class UploadAnexoRequest
{
    [Required(ErrorMessage = "Arquivo é obrigatório")]
    public IFormFile File { get; set; } = null!;

    [Required(ErrorMessage = "Contexto é obrigatório")]
    [StringLength(255, ErrorMessage = "Contexto deve ter no máximo 255 caracteres")]
    public string Contexto { get; set; } = string.Empty;

    [Required(ErrorMessage = "Entidade é obrigatória")]
    [StringLength(255, ErrorMessage = "Entidade deve ter no máximo 255 caracteres")]
    public string Entidade { get; set; } = string.Empty;

    [StringLength(255, ErrorMessage = "Usuário upload deve ter no máximo 255 caracteres")]
    public string? UsuarioUpload { get; set; }

    public int? TipoAnexo { get; set; }
}

AnexoBlobResponse

public class AnexoBlobResponse
{
    public int Id { get; set; }
    public string NomeDoArquivo { get; set; } = string.Empty;
    public string TipoDoArquivo { get; set; } = string.Empty;
    public long Tamanho { get; set; }
    public string BlobUrl { get; set; } = string.Empty;
    public string BlobContainer { get; set; } = string.Empty;
    public string BlobFileName { get; set; } = string.Empty;
    public string Contexto { get; set; } = string.Empty;
    public string Entidade { get; set; } = string.Empty;
    public string UsuarioUpload { get; set; } = string.Empty;
    public DateTime DataUpload { get; set; }
    public int? TipoAnexo { get; set; }
    public bool IsActive { get; set; }
    public string? CreatedBy { get; set; }
    public DateTime? UpdatedAt { get; set; }
}

AtualizarAnexoBlobRequest

public class AtualizarAnexoBlobRequest
{
    [Required(ErrorMessage = "Nome do arquivo é obrigatório")]
    [StringLength(255, ErrorMessage = "Nome do arquivo deve ter no máximo 255 caracteres")]
    public string NomeDoArquivo { get; set; } = string.Empty;

    [StringLength(255, ErrorMessage = "Contexto deve ter no máximo 255 caracteres")]
    public string? Contexto { get; set; }

    public int? TipoAnexo { get; set; }
}

AnexoEntity (Banco de Dados)

[Table("anexos")]
public class AnexoEntity
{
    [Key]
    [Column("id")]
    public int Id { get; set; }

    [Required]
    [Column("NomeDoArquivo")]
    [StringLength(255)]
    public string NomeDoArquivo { get; set; } = string.Empty;

    [Required]
    [Column("TipoDoArquivo")]
    [StringLength(255)]
    public string TipoDoArquivo { get; set; } = string.Empty;

    [Column("Tamanho")]
    public int? Tamanho { get; set; }

    [Required]
    [Column("Contexto")]
    [StringLength(255)]
    public string Contexto { get; set; } = string.Empty;

    [Required]
    [Column("Entidade")]
    [StringLength(255)]
    public string Entidade { get; set; } = string.Empty;

    [Required]
    [Column("UsuarioUpload")]
    [StringLength(255)]
    public string UsuarioUpload { get; set; } = string.Empty;

    [Required]
    [Column("DataUpload")]
    public DateTime DataUpload { get; set; }

    [Column("TipoAnexo")]
    public int? TipoAnexo { get; set; }

    // Campos específicos para Blob Storage
    [Column("BlobUrl")]
    [StringLength(500)]
    public string? BlobUrl { get; set; }

    [Column("BlobContainer")]
    [StringLength(100)]
    public string? BlobContainer { get; set; }

    [Column("BlobFileName")]
    [StringLength(255)]
    public string? BlobFileName { get; set; }

    [Column("IsActive")]
    public bool IsActive { get; set; } = true;

    [Column("CreatedBy")]
    [StringLength(255)]
    public string? CreatedBy { get; set; }

    [Column("UpdatedAt")]
    public DateTime? UpdatedAt { get; set; }
}

Serviços Implementados v2.0

🔄 Interface Expandida

A interface IBlobStorageService foi expandida mantendo compatibilidade total com métodos antigos:

IBlobStorageService v2.0

public interface IBlobStorageService
{
    // ===== NOVOS MÉTODOS v2.0 (ContainerType) =====
    Task<string> UploadAsync(Stream fileStream, string fileName, ContainerType containerType);
    Task<Stream> DownloadAsync(string fileName, ContainerType containerType);
    Task<bool> DeleteAsync(string fileName, ContainerType containerType);
    Task<List<string>> ListFilesAsync(ContainerType containerType);
    Task<bool> ExistsAsync(string fileName, ContainerType containerType);
    
    // ===== MÉTODOS DE COMPATIBILIDADE (mantidos) =====
    Task<string> UploadFileAsync(IFormFile file, string container);
    Task<bool> DeleteFileAsync(string blobUrl);
    Task<Stream> DownloadFileAsync(string blobUrl);
    Task<string> GenerateSasUrlAsync(string blobUrl, TimeSpan expiry);
    string GetContainerByFileType(string contentType); // 🔥 AGORA COM PREFIXOS!
}

🎯 Mapeamento Automático de Contêineres

O método GetContainerByFileType agora retorna nomes com prefixo de ambiente:

// Exemplo em Development (Environment = "dev"):
GetContainerByFileType("image/jpeg")  // Retorna: "dev-imagens"
GetContainerByFileType("application/pdf") // Retorna: "dev-documentos"
GetContainerByFileType("text/plain")     // Retorna: "dev-anexos"

// Exemplo em Production (Environment = "prod"):
GetContainerByFileType("image/jpeg")  // Retorna: "prod-imagens"
GetContainerByFileType("application/pdf") // Retorna: "prod-documentos"
GetContainerByFileType("text/plain")     // Retorna: "prod-anexos"

AnexoBlobService

public class AnexoBlobService
{
    // Métodos principais implementados:
    Task<AnexoBlobResponse> UploadAnexoAsync(UploadAnexoRequest request);
    Task<IEnumerable<AnexoBlobResponse>> ListarTodosAsync();
    Task<AnexoBlobResponse?> ObterPorIdAsync(int id);
    Task<IEnumerable<AnexoBlobResponse>> ListarPorUsuarioAsync(string userId);
    Task<AnexoBlobResponse?> ObterPorUsuarioEContextoAsync(string userId, string contexto);
    Task<AnexoBlobResponse?> AtualizarAsync(int id, AtualizarAnexoBlobRequest request);
    Task<bool> ExcluirAsync(int id);
    Task<Stream?> DownloadFileAsync(int id);
    Task<string?> GenerateSasUrlAsync(int id, TimeSpan expiry);
    Task<bool> ExistePorUsuarioEContextoAsync(string userId, string contexto);
}

Funcionalidades do BlobStorageService

Segurança

Autenticação

Validações

URLs SAS

Tratamento de Erros

Códigos de Status

Mensagens de Erro

{
  "message": "Descrição do erro",
  "error": "Detalhes técnicos"
}

Monitoramento

Logs

Métricas

Limitações

Tamanho de Arquivo

Performance

Backup e Recuperação

Estratégia

Recuperação

Troubleshooting

Problemas Comuns

  1. Erro de conexão com Azure

  2. Upload falha

  3. Download não funciona

Logs Úteis

# Verificar logs da aplicação
dotnet run --verbosity detailed

# Logs do Azure Storage
az storage blob list --container-name anexos --account-name cordenaaistg13344

# Verificar containers criados
az storage container list --account-name cordenaaistg13344

📋 Changelog v2.0

✅ Implementado na v2.0

🚀 Próximos Passos v3.0

Melhorias Planejadas

Monitoramento Avançado


📄 Informações da Documentação

Versão: 1.0

Última Atualização: Outubro de 2025

Responsável: Equipe de Desenvolvimento CordenaAi

Status: ✅ Implementado e Testado

🎉 Migração v2.0 Concluída com Sucesso!

A migração para contêineres hierárquicos foi 100% concluída! O sistema agora:

Status: 🚀 Sistema pronto para produção! Todos os uploads serão automaticamente organizados nos contêineres corretos.