動態

詳情 返回 返回

node.js基於 cmake-js 進行插件開發實戰 - 動態 詳情

以前工作在node.js環境下,做微服務產品; 三年前轉回到C++環境,已經有一些代碼積攢。我將以往基於node.js與C++的相關項目結合起來(C++代碼以addon插件嵌入),實現了一個微服務快速(rest api service)開發框架。該框架以關係數據庫為基礎,現在支持(mysql、sqlite3、postgres),同時支持windows, linux, macos。本文以該項目為藍本,來説明使用C++為node.js開發插件的實踐經驗。

項目結構

  • addon : C++插件封裝代碼目錄,這是一個node.js與C++的適配器,具體的C++功能都在thirds目錄中
  • src : node.js源碼目錄,一套完整的智能微服務代碼,基於關係數據,提供標準的rest api service, 不需要寫一行代碼,詳見 gels項目
  • test : 單元測試代碼目錄,提供了全面測試,同時也是很好的示例代碼
  • thirds : C++項目都放在這個目錄下

    |-- CMakeLists.txt
    |-- addon                       //c++插件封裝
    |   |-- export.cc
    |   |-- index.cc
    |   `-- index.h
    |-- package.json
    |-- src                         //node.js核心源碼
    |   |-- config                  //只列出了目錄
    |   |-- dao
    |   |-- db
    |   |-- inits
    |   |-- middlewares
    |   `-- routers
    |-- test                        //rest api 測試
    |   `-- test.js
    |-- thirds                      //依賴的c++項目
    |-- package.json
    `-- tsconfig.json

Nodejs擴展基本開發

編譯擴展,兩種方式

  • node-gyp
  • cmake-js

開發環境

因為,本人的C++項目都使用cmake進行項目管理的,所以我選擇使用cmake-js來進行node.js的擴展開發。開發環境:

  • windows : cmake > 3.18, node.js >= 16, visual studio >= 2019; 若使用vs2022, windows SDK 必須安裝10.的版本,只裝11版本的話,編譯會出錯
  • linux : cmake > 3.18, node.js >= 16, gcc >= 7.5
  • macOs : cmake > 3.18, node.js >= 16, clang >= 12

項目依賴

項目依賴,請參看package.json中相關小節,與插件開發相關的主要是以下三個項目:

  • cmake-js
  • bindings
  • node-addon-api

CMakeLists.txt關鍵點説明

完整的代碼請自行到項目中去獲取,我再這裏只是節選,並進行一些説明

c++版本指定,因為依賴庫Zjson最低需要c++17
set (CMAKE_CXX_STANDARD 17)                             
SET(CMAKE_CXX_FLAGS "-D_GLIBCXX_USE_CXX17_ABI=0")
windows必須增加如下的參數設定,必須將動態鏈接庫的內存與主程序融合
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /MTd")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
linux下的特殊要求, 其它環境不設這個變量,在鏈接的時候就只有linux會加上 dl這個參數
set(dlLinkParam dl)
...
target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB} ${MysqlDll} ${pqName} ${sqliteName} ${dlLinkParam})
cmake-js最基本的編譯設定
set(NODE_LINK_LIBS "")
set(NODE_EXTERNAL_INCLUDES "")

FILE(GLOB_RECURSE SOURCE_FILES "./addon/*.cc") 
FILE(GLOB_RECURSE HEADER_FILES "./addon/*.h") 

add_library(${PROJECT_NAME} SHARED ${HEADER_FILES} ${SOURCE_FILES} ${CMAKE_JS_SRC})
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node")
message("-------- CMAKE_JS_INC -------" ${CMAKE_JS_INC})
# Include Node-API wrappers
target_include_directories(${PROJECT_NAME} PRIVATE  
    ${CMAKE_SOURCE_DIR}/node_modules/node-addon-api 
    ${CMAKE_SOURCE_DIR}/node_modules/node-addon-api/src
    ${CMAKE_JS_INC})
target_link_libraries(${PROJECT_NAME} PRIVATE ${CMAKE_JS_LIB})

注: sqlite3 必須以動態鏈接庫的形式接入,直接將.c和.h加入到主程序中,能編譯通過,也能運行,但查詢系統表的時候會出現異常

插件開發代碼解析

addon 目錄下是與C++項目適配的代碼,C++的功能,先寫成cmake管理的項目,放到thirds目錄,再適配進addon插件,這樣能做到相對的獨立
一般需要三個文件:export.cc, index.h, index.cc

export.cc

#include "index.h"

//導出接口
Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
  return Zorm::Init(env, exports);
}

NODE_API_MODULE(Zorm, InitAll)

index.h

#include <napi.h>           //node.js插件開發頭文件
#include "Idb.h"            //數據庫通用接口頭文件

class Zorm : public Napi::ObjectWrap<Zorm>{
public:
    //導出函數
    static Napi::Object Init(Napi::Env env, Napi::Object exports);
    static Napi::FunctionReference constructor;
    //構造函數,生成一個orm對象,保存到 成員變量 db 中
    Zorm(const Napi::CallbackInfo& info);
    //公用的類方法,要實現數據庫通用接口的所有方法適配
    Napi::Value select(const Napi::CallbackInfo& info);
    ...
private:
    ZORM::Idb* db;   //成員變量
};

index.cc

初始化函數,定義所有成員方法
Napi::Object Zorm::Init(Napi::Env env, Napi::Object exports)
{
    Napi::HandleScope scope(env);
    Napi::Function func =
        DefineClass(env, "Zorm",            //除了這個函數,其它基本都是規定寫法
                    {                       //定義外部能調用的所有成員方法
                        InstanceMethod("select", &Zorm::select),  
                        ...
                    });

    constructor = Napi::Persistent(func);
    constructor.SuppressDestruct();

    exports.Set("Zorm", func);
    return exports;
}
構造函數適配

Zorm::Zorm(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Zorm>(info), db(nullptr)
{
    int len = info.Length();
    Napi::Env env = info.Env();
    if (len < 2 || !info[0].IsString()) {               //函數參數解析,json對象我都用字符串進行傳遞;二進制使用Napi::Array jsNativeArray接收C++的char*
        Napi::TypeError::New(env, "String expected").ThrowAsJavaScriptException();
    }
    std::string dbDialect = info[0].As<Napi::String>().ToString();
    std::string opStr = info[1].As<Napi::String>();
    ZJSON::Json options(opStr);
    db = new ZORM::DbBase(dbDialect, options);
}
成員方法示例
Napi::Value Zorm::select(const Napi::CallbackInfo& info)
{
    int len = info.Length();
    Napi::Env env = info.Env();
    if (len < 1 || !info[0].IsString()) {
        Napi::TypeError::New(env, "String expected").ThrowAsJavaScriptException();
    }
    std::string tableName = info[0].As<Napi::String>().ToString().Utf8Value();

    ZJSON::Json params;
    if(len >= 2){
        params.extend(ZJSON::Json(info[1].As<Napi::String>().ToString().Utf8Value()));
    }
    std::string fieldStr;
    if(len >= 3){
        fieldStr = info[2].As<Napi::String>().ToString().Utf8Value();
    }

    ZJSON::Json rs = db->select(tableName, params, ZORM::DbUtils::MakeVector(fieldStr));
    return Napi::String::New(info.Env(), rs.toString());
}

項目地址

https://gitee.com/zhoutk/zrest
或
https://github.com/zhoutk/zrest

安裝運行

  • 新建配置文件,./src/config/configs.ts, 指定數據庫:

    export default {
        inits: {
            directory: {
                run: false,
                dirs: ['public/upload', 'public/temp']
            },
            socket: {
                run: false
            }
        },
        port: 12321,
        db_dialect: 'sqlite3',              //數據庫選擇,現支持 sqlite3, mysql, postgres
        db_options: {
            DbLogClose: false,              //是否顯示SQL語句
            parameterized: false,           //是否進行參數化查詢
            db_host: '192.168.0.12',
            db_port: 5432,
            db_name: 'dbtest',
            db_user: 'root',
            db_pass: '123456',
            db_char: 'utf8mb4',
            db_conn: 5,
            connString: ':memory:',         //內存模式運行
        }
    }
  • 在終端(Terminal)中依次運行如下命令

    git clone https://gitee.com/zhoutk/zrest
    cd ztest
    npm i -g yarn
    yarn global add typescript eslint nodemon
    yarn
    tsc -w          //或 command + shift + B,選 tsc:監視
    yarm configure  //windows下最低vs2019, gcc 7.5, macos clang12.0
    yarn compile    //編譯c++插件, 若有問題,請參照 [Zorm](https://gitee.com/zhoutk/zorm) 文檔,特別是最後的註釋
    yarn start      //或 node ./dist/index.js
    export PACTUM_REQUEST_BASE_URL=http://127.0.0.1:12321
    yarn test       //運行rest api接口測試,請仔細查看測試文件,其中有相當完善的使用方法
                    //修改配置文件,可以切換不同的數據,運行測試;使用mysql或postgres時,請先手動建立dbtest數據,編碼使用Utf-8
  • 測試運行結果圖
    測試運行輸出

    項目日誌(包括請求和sql語句)

相關項目

  • Zrest node.js嵌入c++插件項目,實現跨平台多數據庫無縫切換的微服務開發框架
  • gels node.js項目,基於koa2實現的rest api服務框架,功能齊全; 以gels為入口,實現本項目,c++項目以插件方式集成
  • Zjson c++項目,實現簡單高效的json處理
  • Zorm c++項目,以json對象為媒介,實現了一種ORM映射;設計了通用數據庫操作接口規範,能無縫的在多種數據庫之間切換

Add a new 評論

Some HTML is okay.