C++库封装mongodb(跨平台开发)

本文涉及的产品
云原生多模数据库 Lindorm,多引擎 多规格 0-4节点
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 MongoDB,通用型 2核4GB
简介: 我的初衷是在Linux平台下只提供动态库和头文件,windows平台下提供静态库和头文件给开发者,这个库mongo-proxy对外提供了一些对mongodb的连接,增删改查,创建索引,聚合等操作的封装,开发者只需要关心接口如何调用,而不需要关心接口是如何实现的,也不需要关心mongo-c-driver的相关依赖,这里我抽象出mongo_proxy类,

目录

1.开发环境准备

2.编译mongo-c-driver (linux环境)

3.编译mongo-c-driver (windows环境)

4.CMake工程创建

5.抽象接口设计

mongo_proxy类

mongo_task类

bson_doc类对bson对象的再次封装

mongo_proxy可实例化对象的创建和释放接口

6.接口实现

mongo_proxy_imp

mongo_proxy可实例化对象的创建和释放

bson_doc


1.开发环境准备

  需要搭建mongodb数据库,这里不赘述mongodb的搭建过程

  需要下载mongo-c-driver库,可以从官网下载MongoDB C Driver — MongoDB C Driver 1.24.3

  需要下载cmake 3.0以上版本

  如果你是windows环境只需要VS2015以上版本,如果你是linux环境,准备个GCC编译器即可。

2.编译mongo-c-driver (linux环境)

 一般情况下,我们的mongo-c-driver需要依赖openssl来支持mongo的用户名密码登录或者其他方式的密钥登录,因此我们需要编译下openssl库,实际上openssl的编译也很方便,这里我仍然使用官网的openssl https://www.openssl.org/source/ 你可以下载最新版本,

下载到openssl-3.0.10.tar.gz  ,解压,然后./Configure && make -j16 && make install 即可。

接下来进入到mongo-c-driver路径下,可以看到下面目录结构:

image.gif编辑

cd build路径下执行cmake ..

image.gif编辑 make install

image.gif编辑

然后我们可以看下libmongoc.so依赖的openssl相关库的路径刚好是我们刚才make install的ssl库路径。

image.gif编辑

3.编译mongo-c-driver (windows环境)

windows环境下也是先安装openssl,我们方便起见,直接安装openssl的开发库和头文件即可,从Win32/Win64 OpenSSL Installer for Windows - Shining Light Productions

OpenSSL 3.1, 3.0 and 1.1 Binaries for Microsoft Windows 任意一个网站可下载

image.gif编辑

安装好之后,能看到下面的头文件和动静态库路径:

image.gif编辑

然后进入到mongo-c-driver路径下,新建一个win32的工程文件夹

image.gif编辑

进入到win32目录下,打开cmd终端,输入 cmake ..

image.gif编辑

然后你就可以在win32目录下看到工程文件

image.gif编辑

用vs打开后,你主要编译这几个项目即可:

image.gif编辑

image.gif编辑

注意修改bson动态库的库名和输出路径

image.gif编辑

以及运行库类型:

image.gif编辑

同样的静态库的路径和动态库的库路径一样,都输出到lib文件夹下

image.gif编辑

同理,libmongoc库也这样生成。

最终生成对应的动态库和静态库:

image.gif编辑

当然你想省事了, 直接编译运行INSTALL工程

image.gif编辑

然后VS会执行INSTALL安装到默认的C盘库路径下如图:

image.gif编辑

4.CMake工程创建

    新建一个工程mongo_wapper

image.gif编辑

配置CMakeLists.txt,设置当前工程为mongo-proxy,生成动态库(linux)或静态库(windows)的版本为0.0.1,库文件生成在当前工程目录下的lib文件夹里。

PROJECT(mongo-proxy)
CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
SET(MONGOPROXY_LIB mongo-proxy)
SET(CMAKE_AR ar)
SET(LIB_VERSION 0.0.1)
IF(WIN32)
  MESSAGE(STATUS "windows do not need install")
ELSE(WIN32)
  SET(CMAKE_INSTALL_PREFIX /usr/local/${MONGOPROXY_LIB}-${LIB_VERSION})
ENDIF(WIN32)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
ADD_SUBDIRECTORY(src lib)

image.gif

然后我们在src路径下来写我们的mongo代理接口的相关实现,那么我们仍然需要在src下面来配置下我们的库依赖和头文件依赖:

IF(WIN32) 
  INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/../../mongo-c-driver/include/libbson-1.0)
  INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/../../mongo-c-driver/include/libmongoc-1.0)  
  ADD_DEFINITIONS(-D_REENTRANT) 
  LINK_LIBRARIES(mongoc-1.0 bson-1.0)
  LINK_DIRECTORIES(${PROJECT_SOURCE_DIR}/../../mongo-c-driver/lib/) 
ELSE(WIN32) 
  INCLUDE_DIRECTORIES(/usr/local/include/libmongoc-1.0/)
  INCLUDE_DIRECTORIES(/usr/local/include/libbson-1.0/)
  ADD_DEFINITIONS(-g -o3 -W -Wall -std=gnu++11 -Wno-invalid-offsetof -fPIC -Wno-deprecated -Wno-deprecated-declarations
  -D_REENTRANT   -lpthread) 
ENDIF(WIN32)

image.gif

完整的CMakeLists.txt文件如下:

IF(WIN32)
  INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/../../utils/trunk/src)
  INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/../../mongo-c-driver/include/libbson-1.0)
  INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/../../mongo-c-driver/include/libmongoc-1.0) 
  INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/../../concurrentqueue/) 
  ADD_DEFINITIONS(-D_REENTRANT) 
  LINK_LIBRARIES(utils mongoc-1.0 bson-1.0)
  LINK_DIRECTORIES(${PROJECT_SOURCE_DIR}/../../utils/trunk/lib)
  LINK_DIRECTORIES(${PROJECT_SOURCE_DIR}/../../mongo-c-driver/lib/) 
ELSE(WIN32)
  INCLUDE_DIRECTORIES(/usr/local/utils-0.0.1/include)
  INCLUDE_DIRECTORIES(/usr/local/include/libmongoc-1.0/)
  INCLUDE_DIRECTORIES(/usr/local/include/libbson-1.0/)
  ADD_DEFINITIONS(-g -o3 -W -Wall -std=gnu++11 -Wno-invalid-offsetof -fPIC -Wno-deprecated -Wno-deprecated-declarations
  -D_REENTRANT   -lpthread)
  #LINK_LIBRARIES(utils mongoc  bson rt)
  LINK_DIRECTORIES(/usr/local/utils-0.0.1/lib)
ENDIF(WIN32)
FILE(GLOB_RECURSE DIR_HEADERS "*.h")
AUX_SOURCE_DIRECTORY(. DIR_SRCS)
IF(WIN32)
  ADD_LIBRARY(mongo-proxy STATIC ${DIR_SRCS} ${DIR_HEADERS})
ELSE(WIN32)
  ADD_LIBRARY(mongo-proxy SHARED ${DIR_SRCS})
ENDIF(WIN32)
SET_TARGET_PROPERTIES(mongo-proxy PROPERTIES VERSION 0.0.2)
IF(WIN32)
ELSE(WIN32)
  INSTALL(TARGETS mongo-proxy LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/lib)
  INSTALL(DIRECTORY ./ DESTINATION 
    ${CMAKE_INSTALL_PREFIX}/include PATTERN "*.cpp" EXCLUDE PATTERN ".svn" EXCLUDE PATTERN "gen" EXCLUDE PATTERN "*.txt" EXCLUDE)
ENDIF(WIN32)

image.gif

5.抽象接口设计

     我的初衷是在Linux平台下只提供动态库和头文件,windows平台下提供静态库和头文件给开发者,这个库mongo-proxy对外提供了一些对mongodb的连接,增删改查,创建索引,聚合等操作的封装,开发者只需要关心接口如何调用,而不需要关心接口是如何实现的,也不需要关心mongo-c-driver的相关依赖,这里我抽象出mongo_proxy类,

mongo_proxy类

类图就不画了,直接上代码。

class mongo_proxy
{
public: 
  mongo_proxy(){} 
  virtual ~mongo_proxy() {} 
  virtual bool mongodb_init(const char* url, int pool_size) =0;
  virtual bool mongodb_stop() = 0;
  virtual bool  insert_coll(const char* databasename, const char* collname, const bson_doc& doc) = 0;
  virtual bool  delete_coll(const char* databasename, const char* collname, const bson_doc& doc) = 0;
  virtual bool  delete_many(const char* databasename, const char* collname, const bson_doc& filter) = 0;
    //其他接口...自己可以根据需要来实现
}

image.gif

mongo_task类

一般情况下,我们的游戏服务器是数据库读写线程分离,读放在主线程里,写操作可以放到子线程来异步执行,这种情况下,我们可以把每一个mongodb的写操作或者读操作可以封装成mongo_task抽象类,它包含了所有的增删改查操作类型,并且提供一个处理相关操作的接口,而这个操作发生了什么事情, 交给其继承子类来实现即可。

typedef void mongo_proxy_t;
class mongo_proxy; 
enum mongo_operator {
    mongo_op_insert = 0,
    mongo_op_del = 1,
    mongo_op_modify = 2,
    mongo_op_search = 3,
};
class mongo_task {
public: 
    mongo_operator m_op;
    mongo_task(mongo_operator op) : m_op(op) {}
    virtual ~mongo_task() {
    }
    virtual void handle(mongo_proxy* proxy) = 0;
};

image.gif

因此当我有一个保存玩家数据的mongo操作,我可以这样来继承和实现:

class mongo_save_player : public mongo_task {
public: 
    BsonDoc m_query;
    BsonDoc m_update;
    mongo_save_player(mongo_operator op) : mongo_task(op) { 
    }
    virtual ~mongo_save_player() {
        LOG(INFO)("~mongo_save_player");
    }
    virtual void handle(mongo_proxy* proxy){
      auto bret = proxy->upsert_coll("KOA", "player", m_query,     m_update);
      if (!bret)
      {
        LOG(ERROR)("upsert failed:");
        return;
      }
    }
};

image.gif

bson_doc类对bson对象的再次封装

原有的bson类的接口比较原始,不太方便为我们服务,因此我封装了一些常用的bson对象转json,查询追加字段,查询结果解析的方法。

class bson_doc
{
public:
    bson_doc();
    bson_doc(const bson_doc& doc);
    bson_doc(void* doc);
    ~bson_doc();
    bson_doc(const string& json);
    bson_doc(const char* i, ...);
    void* doc() const;
    void initid();
    void setid(const string& id);
    static string getid(void* pdoc);
    string toJson();
    void   fromJson(const string& json);
    static string toJson(void* pdoc);
    static string cursorToJson(void* pcursor); 
    static string& trim(string& str, const string& trimstr = "$");
    //更新set
    void append_document(string feild, bson_doc& doc);
    void append_bin(string feild, const uint8_t* str, int len);
    void append_string(string feild, const string& str);
    void append_uint32(string feild, uint32_t t); 
    void append_uint64(string feild, uint64_t t);
    void append_bool(string feild, bool t);
    //查找条件过滤
    void append_filter_expess(string feild, string value);
    void append_filter_string(string feild, const string& str);
    void append_filter_uint32(string feild, uint32_t t);
    void append_filter_uint64(string feild, uint64_t t);
    void append_filter_bool(string feild, bool t);
    void append_filter_array(string feild, const bson_doc& reg);
    // 去掉所有 trimstr
    std::string get_oid() const;  
    bool get_bool(const std::string& key) const; 
    bool get_string(std::string& result, const std::string& path) const; 
    std::string get_string(const std::string& path) const; 
    int32_t get_int32(const std::string& path) const;
    int64_t get_int64(const std::string& path) const;
    double  get_double(const std::string& path) const;
    int64_t get_datetime(const std::string& key) const;
    bson_doc get_document(const std::string& path) const;
    std::string get_binary(const std::string& key) const;
    std::vector<std::string> get_keys() const;
    std::vector<std::string> get_array(const std::string& path) const;
    std::vector<bson_doc> get_docs(const std::string& path) const;
    std::string getDateTimeStr(const std::string& key) const;
    std::string getDateStr(const std::string& key) const;
    std::string getDateStr(const std::string& format, const std::string& key) const;
    bool isDocument(const std::string& path) const;
    bool isString(const std::string& path) const;
    bool isDouble(const std::string& path) const;
    bool isInt64(const std::string& path) const;
    unsigned count() const;
    bool empty() const;
    bool has(const std::string& key) const;
    int get_type(const std::string& path) const;
    static bool isValid(const std::string& json);
    static bool removeDuplicates(std::vector<bson_doc>& vec, const std::string& key);
    bool clear();
    bson_doc fromFile(const std::string& filename, const std::string& path);
    std::string toString() const;
    bson_doc& append(const std::string& key, const bson_doc& val);
    bson_doc& append(const std::string& key, const char* val);
    bson_doc& append(const std::string& key, const std::string& val);
    bson_doc& append(const std::string& key, const std::vector<bson_doc>& val);
    bson_doc& append(const std::string& key, const std::vector<std::string>& vector);
    bson_doc& append(const std::string& key, const std::vector<uint8_t>& binary);
    bson_doc& append(const std::string& key, int val);
    bson_doc& append(const std::string& key, unsigned val);
    bson_doc& append(const std::string& key, double val);
    bson_doc& append(const std::string& key, time_t time);
    bson_doc& append(const std::string& key, bool val);
    bson_doc& appendOid();
    bson_doc& appendOid(const std::string& oid);
    bson_doc& appendRegex(const std::string& key, const std::string& regex, const std::string& options = "");
    bson_doc& appendNowUtc(const std::string& key);
    bson_doc& operator=(const bson_doc& val); 
    bson_doc(const std::string& key, bool val);
    bson_doc(const std::string& key, const bson_doc& value);
    bson_doc(const std::string& key, const char* value);
    bson_doc(const std::string& key, const std::string& value);
    bson_doc(const std::string& key, const std::vector<bson_doc>& val);
    bson_doc(const std::string& key, int val);
    bson_doc(const std::string& key, unsigned int val);
private:
    void* m_doc;
};

image.gif

mongo_proxy可实例化对象的创建和释放接口

那么,还需要提供一个接口给调用库的人来创建mongo_proxy实例化对象:

mongo_proxy* mp_create_mongo_proxy();
void mp_delete_mongo_proxy(mongo_proxy* mp);

image.gif

这几个都需要在对应的头文件里定义,而真正的实现则由抽象类的子类来实现,隐藏内部实现,也减少了其他开发者开发过程中对原始的mongo-c-driver的头文件依赖。

6.接口实现

mongo_proxy_imp

那么对于mongo_proxy的子类,我要去实现mongo_proxy的所有抽象接口,

image.gif编辑

class mongo_proxy_imp :public mongo_proxy
{
public:
  mongo_proxy_imp();
  ~mongo_proxy_imp();
  bool mongodb_init(const char* url, int pool_size = 5);
  bool mongodb_stop(); 
  bool  insert_coll(const char* databasename, const char* collname, const bson_doc& doc);
  bool  delete_coll(const char* databasename, const char* collname, const bson_doc& doc);
  bool  delete_many(const char* databasename, const char* collname, const bson_doc& filter) ;
private:
  int m_ulthreads = 10;
  mongoc_client_pool_t* m_pool = nullptr;
  mongoc_uri_t* m_url = nullptr;
}

image.gif

由于篇幅过长,这里我只提供几个实现看下:

mongo_proxy_imp::mongo_proxy_imp()
{
  mongoc_init();
}
mongo_proxy_imp::~mongo_proxy_imp()
{
  mongodb_stop();
}
bool mongo_proxy_imp::mongodb_init(const char* url, int pool_size)
{ 
  m_url = mongoc_uri_new(url);
  if (m_url == nullptr) {
    LOG(ERROR)("mongoc uri new failed url:%s", url);
    return false;
  }
  m_pool = mongoc_client_pool_new(m_url);
  if (m_pool == nullptr) {
    LOG(ERROR)("mongoc create pool failed url:%s", url);
    return false;
  }
  mongoc_client_pool_max_size(m_pool, pool_size);
  return true;
}
bool mongo_proxy_imp::mongodb_stop()
{
  if (m_url == nullptr || m_pool == nullptr)
  {
    return false;
  }
  mongoc_client_pool_destroy(m_pool);
  m_pool = nullptr;
  mongoc_uri_destroy(m_url);
  m_url = nullptr;
  mongoc_cleanup();
  return true;
}  
bool mongo_proxy_imp::insert_coll(const char* databasename, const char* collname, const bson_doc& doc)
{
  mongoc_client_pool_t* pool = static_cast<mongoc_client_pool_t*>(m_pool);
  mongoc_client_t* client;
  bson_error_t error;
  client = mongoc_client_pool_pop(pool); 
  if (client == nullptr) {
    return false;
  }
  mongoc_collection_t* collection = mongoc_client_get_collection(client, databasename, collname);
  if (!mongoc_collection_insert_one(collection, static_cast<const bson_t*>(doc.doc()), NULL,NULL, &error)) {
    mongoc_collection_destroy(collection);
    mongoc_client_pool_push(pool, client);
    return false;
  }
  mongoc_collection_destroy(collection);
  mongoc_client_pool_push(pool, client);
  return true;
}

image.gif

mongo_proxy可实例化对象的创建和释放

而且刚才我提到还需要提供一个对外接口来创建mongo_proxy对象,因此我们可以在mongo_proxy子类的实现cpp里来实现如下方法:

mongo_proxy* mp_create_mongo_proxy()
{
  return new mongo_proxy_imp();
}
void mp_delete_mongo_proxy(mongo_proxy* mp)
{
  delete mp;
  mp = NULL;
}

image.gif

bson_doc

#include <string.h> 
#include "mongo_proxy.h"
#include <log.h> 
#include <bson.h>
#include <mongoc.h>
#include <ctime>
#include <fstream>
#include <functional>
#include <iostream>
#include <iterator> 
#include <vector>
#define BSONDOC_APPEND(_bson, ...) BCON_APPEND(_bson,__VA_ARGS__)
bson_doc::bson_doc()
{
    m_doc = (void*)bson_new();
}
bson_doc::~bson_doc()
{
    if (m_doc != NULL)
        bson_destroy((bson_t*)m_doc);
    m_doc = NULL;    
} 
bson_doc::bson_doc(void* doc):m_doc(bson_copy((const bson_t*)doc))  //m_doc(bson_copy(&val))
{ 
} 
bson_doc::bson_doc(const string& json) {
    m_doc = (bson_t*)bson_new();
    bson_error_t error;
    bson_init_from_json((bson_t*)m_doc, json.c_str(), json.length(), &error);
}
void  bson_doc::fromJson(const string& json)
{
    bson_error_t error;
    bson_init_from_json((bson_t*)m_doc, json.c_str(), json.length(), &error);
}
void bson_doc::initid()
{
    if (m_doc == NULL)
        return;
    bson_oid_t oid;
    bson_oid_init(&oid, NULL);
    BSON_APPEND_OID((bson_t*)m_doc, "_id", &oid);
}
void bson_doc::setid(const string& id)
{
    if (m_doc == NULL)
        return;
    bson_oid_t oid;
    bson_oid_init_from_string(&oid, id.c_str());
    BSON_APPEND_OID((bson_t*)m_doc, "_id", &oid);
}
string bson_doc::getid(void* pdoc)
{
    string strid;
    if (pdoc == NULL)
        return strid;
    const bson_oid_t* ooid = NULL;
    BCON_EXTRACT((bson_t*)pdoc, "_id", BCONE_OID(ooid));
    if (ooid != NULL)
    {
        char id[64] = { 0 };
        bson_oid_to_string(ooid, id);
        strid += id;
    }
    return strid;
}
string bson_doc::toJson()
{
    string strret;
    if (m_doc == NULL)
        return strret;
    char* str = bson_as_json((bson_t*)m_doc, NULL);
    if (str != NULL)
    {
        strret += str;
        //trim(strret);
        bson_free(str);
    }
    return strret;
}

image.gif

完整代码可以通过下面链接下载

在windows/linux下如何调用该动态库/静态库下的接口,操作常见的增删改操作,我将在下节继续分享给大家,谢谢关注

相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。 &nbsp; 相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
相关文章
|
19天前
|
存储 C++ 容器
C++STL(标准模板库)处理学习应用案例
【4月更文挑战第8天】使用C++ STL,通过`std:vector`存储整数数组 `{5, 3, 1, 4, 2}`,然后利用`std::sort`进行排序,输出排序后序列:`std:vector<int> numbers; numbers = {5, 3, 1, 4, 2}; std:sort(numbers.begin(), numbers.end()); for (int number : numbers) { std::cout << number << " "; }`
19 2
|
1月前
|
算法 编译器 C语言
【C++ 异常】C++ 标准库异常类及其应用
【C++ 异常】C++ 标准库异常类及其应用
30 0
|
1月前
|
程序员 API 数据库
【Cmake工程 库相关教程 】深入理解CMake工程C/C++ 库管理技巧
【Cmake工程 库相关教程 】深入理解CMake工程C/C++ 库管理技巧
60 0
|
1月前
|
存储 JSON 安全
【C++ JSON库 json值的创建手段】深入探究C++中JSON对象定位与操作:从引用到回调函数
【C++ JSON库 json值的创建手段】深入探究C++中JSON对象定位与操作:从引用到回调函数
66 0
|
1月前
|
存储 JSON 算法
C++ JSON库 nlohmann::basic_json::boolean_t 的用法
C++ JSON库 nlohmann::basic_json::boolean_t 的用法
35 0
|
1月前
|
缓存 算法 C语言
【C++ 标准查找算法 】C++标准库查找算法深入解析(In-depth Analysis of C++ Standard Library Search Algorithms)
【C++ 标准查找算法 】C++标准库查找算法深入解析(In-depth Analysis of C++ Standard Library Search Algorithms)
46 0
|
1月前
|
Linux C++ iOS开发
【C++ 17 新特性 文件管理】探索C++ Filesystem库:文件和目录操作的全面指南(二)
【C++ 17 新特性 文件管理】探索C++ Filesystem库:文件和目录操作的全面指南
253 2
|
1月前
|
Linux API C++
【C++ 17 新特性 文件管理】探索C++ Filesystem库:文件和目录操作的全面指南(一)
【C++ 17 新特性 文件管理】探索C++ Filesystem库:文件和目录操作的全面指南
320 2
|
1月前
|
存储 缓存 C语言
【C/C++ 库的动态链接】深入理解动态链接器:RPATH, RUNPATH与$ORIGIN
【C/C++ 库的动态链接】深入理解动态链接器:RPATH, RUNPATH与$ORIGIN
65 0
|
17天前
|
C++
glog --- C++日志库
glog --- C++日志库

相关产品

  • 云数据库 MongoDB 版