Armazenar dados binários com MongoDB e C++
Avalie esse Tutorial
Em aplicativos modernos, armazenar e recuperar arquivos binários de forma eficiente é um requisito crucial. O MongoDB permite isso com tipo de dados binários no BSON, que é um formato de serialização binária usado para armazenar documentos no MongoDB. Um valor binário BSON é uma array de bytes e tem um subtipo (como subtipo binário genérico, UUID, MD5 etc.) que indica como interpretar os dados binários. Consulte BSON types — MongoDB Manual para obter mais informações.
Neste tutorial, escreveremos um aplicativo de console em C++, usando o driver MongoDB C++ para carregar e baixar dados binários.
Observação:
- Ao usar este método, lembre-se que o limite de tamanho do documento BSON no MongoDB é 16 MB. Se seus arquivos binários forem maiores que esse limite, considere usar o GridFS para um tratamento mais eficiente de arquivos grandes. Consulte o exemplo do GridFS em C++ para referência.
- Os desenvolvedores costumam avaliar as vantagens e desvantagens ao armazenar dados binários no MongoDB. É essencial garantir que você também considerou diferentes estratégias para otimizar sua abordagem de gerenciamento de dados.
- Conta do MongoDB Atlas com um cluster criado.
- Configuração IDE (como o Microsoft Visual Studio ou o Microsoft Visual Studio Code) com o driver MongoDB C e C++ instalado. Siga as instruções em Introdução ao MongoDB e C++ para instalar os drivers do MongoDB C/C++ e configurar o ambiente de desenvolvimento no Visual Studio. Instruções de instalação para outras plataformas estão disponíveis.
- Compilador com suporte a C++17 (para usar operações
std::filesystem
). - O endereço IP da sua máquina está na lista de permissões. Nota: Você pode adicionar 0.0.0.0/0 como o endereço IP, que deve permitir o acesso de qualquer máquina. Essa configuração não é recomendada para uso em produção.
Como parte dos BSON types, o driver C++ fornece a estrutura b_binary que pode ser usada para armazenar valor de dados binários em um documento BSON. Consulte a API.
Começamos definindo a estrutura do nosso documento BSON. Definimos três chaves:
name
, path
e data
. Eles contêm o nome do arquivo que está sendo carregado, seu caminho completo do disco e os dados reais do arquivo, respectivamente. Veja um documento de exemplo abaixo:No código, eles são definidos com um
#define
para que seja fácil modificá-los a partir de um único local.Vamos adicionar uma função auxiliar,
upload
, que aceita um caminho de arquivo e uma coleção MongoDB como entradas. Seu objetivo principal é carregar o arquivo na coleção MongoDB especificada convertendo o arquivo em um valor binário BSON e construindo um documento BSON para representar os metadados e o conteúdo do arquivo. Aqui estão as principais etapas dentro da funçãoupload
:- Abra o arquivo no caminho fornecido e obtenha seu tamanho.
- O tamanho do arquivo é determinado movendo o ponteiro do arquivo para o final do arquivo e, em seguida, recuperando a posição atual, que corresponde ao tamanho do arquivo.
- O ponteiro do arquivo é então redefinido para o início do arquivo para ler o conteúdo posteriormente.
- Ler conteúdo do arquivo em um buffer: um buffer
std::vector<char>
é criado com um tamanho igual ao tamanho do arquivo para manter os dados binários do arquivo. - Crie o valor binário BSON.
- Para representar o conteúdo do arquivo como valor binário BSON, o código cria um objeto
bsoncxx::types::b_binary
. - O objeto b_binary inclui o subtipo binário (definido como
bsoncxx::binary_sub_type::k_binary
), o tamanho do arquivo e os dados.
- Crie um documento BSON com três campos:
name
,path
edata
. - Inserir o documento na coleção.
1 #include <mongocxx/client.hpp> 2 #include <bsoncxx/builder/basic/document.hpp> 3 #include <mongocxx/uri.hpp> 4 #include <mongocxx/instance.hpp> 5 6 #include <iostream> 7 #include <fstream> 8 #include <vector> 9 #include <filesystem> 10 11 #define FILE_NAME "name" 12 #define FILE_PATH "path" 13 #define FILE_DATA "data" 14 15 using bsoncxx::builder::basic::kvp; 16 using bsoncxx::builder::basic::make_document; 17 18 // Upload a file to the collection. 19 bool upload(const std::string& filePath, mongocxx::collection& collection) 20 { 21 // Open the binary file 22 std::ifstream file(filePath, std::ios::binary | std::ios::ate); 23 if (!file) 24 { 25 std::cout << "Failed to open the file: " << filePath << std::endl; 26 return false; 27 } 28 29 // Get the file size. 30 std::streamsize fileSize = file.tellg(); 31 file.seekg(0, std::ios::beg); 32 33 // Read the file content into a buffer 34 std::vector<char> buffer(fileSize); 35 if (!file.read(buffer.data(), fileSize)) 36 { 37 std::cout << "Failed to read the file: " << filePath << std::endl; 38 return false; 39 } 40 41 // Create the binary object for bsoncxx. 42 bsoncxx::types::b_binary data{bsoncxx::binary_sub_type::k_binary, static_cast<std::uint32_t>(fileSize), reinterpret_cast<const std::uint8_t*>(buffer.data())}; 43 44 // Create a document with the file name and file content. 45 46 auto doc = make_document( 47 kvp(FILE_NAME, std::filesystem::path(filePath).filename()), 48 kvp(FILE_PATH, filePath), 49 kvp(FILE_DATA, data)); 50 51 // Insert the document into the collection. 52 collection.insert_one(doc.view()); 53 54 std::cout << "Upload successful for: " << filePath << std::endl; 55 return true; 56 }
Vamos escrever uma função auxiliar semelhante para executar o download. O código abaixo usa o nome do arquivo, a pasta de destino e uma coleção MongoDB como entradas. Esta função procura um arquivo por seu nome na coleção MongoDB especificada, extrai seus dados binários e os salva na pasta de destino especificada.
Aqui estão as principais etapas da função
download
:- Crie uma consulta de filtro para encontrar o arquivo.
- Use a consulta para encontrar o documento na coleção.
- Extrair e salvar dados binários — os dados binários são acessados usando
bsoncxx::document::view
e, em seguida, recuperados do documento usandobinaryDocView[FILE_DATA].get_binary()
. - Crie um arquivo na pasta de destino e grave o conteúdo binário no arquivo.
1 // Download a file from a collection to a given folder. 2 bool download(const std::string& fileName, const std::string& destinationFolder, mongocxx::collection& collection) 3 { 4 // Create a query to find the file by filename 5 auto filter = make_document(kvp(FILE_NAME, fileName)); 6 7 // Find the document in the collection 8 auto result = collection.find_one(filter.view()); 9 10 if (result) 11 { 12 // Get the binary data from the document 13 bsoncxx::document::view binaryDocView = result->view(); 14 auto binaryData = binaryDocView[FILE_DATA].get_binary(); 15 16 // Create a file to save the binary data 17 std::ofstream file(destinationFolder + fileName, std::ios::binary); 18 if (!file) 19 { 20 std::cout << "Failed to create the file: " << fileName << " at " << destinationFolder << std::endl; 21 return false; 22 } 23 24 // Write the binary data to the file 25 file.write(reinterpret_cast<const char*>(binaryData.bytes), binaryData.size); 26 27 std::cout << "Download successful for: " << fileName << " at " << destinationFolder << std::endl; 28 return true; 29 } 30 else 31 { 32 std::cout << "File not found in the collection: " << fileName << std::endl; 33 return false; 34 } 35 }
Com as funções auxiliares em vigor para realizar upload e download, vamos escrever a função principal que executará esse aplicativo. Aqui estão as principais etapas dentro da função
main
:- Conecte-se ao MongoDB: Estabeleça uma conexão com o MongoDB criando uma instância mongocxx::client.
- Obtenha o banco de dados (
fileStorage
) e a collection (files
) para armazenar os arquivos. - Carregar todos os arquivos encontrados na uploadFolder especificada: iterar recursivamente pela pasta usando
std::filesystem::recursive_directory_iterator
. Para cada arquivo encontrado, chame a função de upload paraupload
o arquivo na coleção MongoDB. - Faça o download de arquivos específicos com nomes de arquivos conhecidos (
fileName1
efileName2
) chamando a funçãodownload
para recuperar e salvar os arquivos nodownloadFolder
. - Da mesma forma, baixe todos os arquivos na coleção chamando
find({})
para obter um cursor e iterar por cada documento na coleção, extraindo o nome do arquivo e, em seguida, chamando a funçãodownload
para baixar e salvar o arquivo nodownloadFolder
.Observação: em uma situação do mundo real, a chamadafind({})
deve ser feita com algum tipo de filtragem/paginação para evitar problemas com o consumo de memória e o desempenho.
Certifique-se de obter a string de conexão (URI), atualizá-la para
mongoURIStr
e definir o caminho e os nomes de arquivo diferentes para os que estão em seu disco.1 int main() 2 { 3 try 4 { 5 auto mongoURIStr = "<Insert MongoDB Connection String>"; 6 static const mongocxx::uri mongoURI = mongocxx::uri{ mongoURIStr }; 7 8 // Create an instance. 9 mongocxx::instance inst{}; 10 11 mongocxx::options::client client_options; 12 auto api = mongocxx::options::server_api{ mongocxx::options::server_api::version::k_version_1 }; 13 client_options.server_api_opts(api); 14 mongocxx::client conn{ mongoURI, client_options}; 15 16 const std::string dbName = "fileStorage"; 17 const std::string collName = "files"; 18 19 auto fileStorageDB = conn.database(dbName); 20 auto filesCollection = fileStorageDB.collection(collName); 21 // Drop previous data. 22 filesCollection.drop(); 23 24 // Upload all files in the upload folder. 25 const std::string uploadFolder = "/Users/bishtr/repos/fileStorage/upload/"; 26 for (const auto & filePath : std::filesystem::directory_iterator(uploadFolder)) 27 { 28 if(std::filesystem::is_directory(filePath)) 29 continue; 30 31 if(!upload(filePath.path().string(), filesCollection)) 32 { 33 std::cout << "Upload failed for: " << filePath.path().string() << std::endl; 34 } 35 } 36 37 // Download files to the download folder. 38 const std::string downloadFolder = "/Users/bishtr/repos/fileStorage/download/"; 39 40 // Search with specific filenames and download it. 41 const std::string fileName1 = "image-15.jpg", fileName2 = "Hi Seed Shaker 120bpm On Accents.wav"; 42 for ( auto fileName : {fileName1, fileName2} ) 43 { 44 if (!download(fileName, downloadFolder, filesCollection)) 45 { 46 std::cout << "Download failed for: " << fileName << std::endl; 47 } 48 } 49 50 // Download all files in the collection. 51 auto cursor = filesCollection.find({}); 52 for (auto&& doc : cursor) 53 { 54 auto fileName = std::string(doc[FILE_NAME].get_string().value); 55 if (!download(fileName, downloadFolder, filesCollection)) 56 { 57 std::cout << "Download failed for: " << fileName << std::endl; 58 } 59 } 60 } 61 catch(const std::exception& e) 62 { 63 std::cout << "Exception encountered: " << e.what() << std::endl; 64 } 65 66 return 0; 67 }
Antes de executar este aplicativo, adicione alguns arquivos (como imagens ou áudios) no diretório
uploadFolder
.Execute o aplicativo e você observará uma saída como esta, significando que os arquivos foram carregados e baixados com êxito.
Você pode ver a coleção no Atlas ou MongoDB Compass refletindo os arquivos carregados por meio do aplicativo.
Você observará os arquivos sendo baixados no diretório
downloadFolder
especificado.Com este artigo, abordamos o armazenamento e a recuperação de dados binários de um MongoDB database, usando o MongoDB C++ driver. Os recursos robustos do MongoDB, combinados com a facilidade de uso fornecida pelo C++ driver, oferecem uma solução poderosa para lidar com o armazenamento de arquivos em aplicativos C++. Nós mal podemos esperar para ver o que você construirá a seguir! Compartilhe sua criação com a comunidade e conte para nós como ficou!