dialogue system
At present, NetEase Yunxin IM SDK supports all platforms. In addition to testing new functions for each release of IM SDK, regression testing also accounts for a large proportion. As IM SDK supports more and more platforms, more and more API interfaces The more abundant it is, the more scope of regression testing is required each time. If it is done manually, it is not only inefficient, but also may cause problems such as missed testing and misjudgment. In order to improve the regression efficiency and continuously monitor the quality of the program, Yunxin has invested a lot of energy in the construction of the "quality assurance system" to build and improve the "automatic test platform". Functions such as "Test", "Regression Test", and "Daily Build" provide strong support. It is very meaningful to access the automated test platform. The main process of "API automated test" is shown in the figure below. Discuss the key technologies implemented on the desktop side (Windows, MacOSX, Linux) for API calls" and "Conversion of API call results into use case execution result data".
Why use reflection
The content shown in the figure below is the use case information issued by the "automatic test platform" (for the convenience of display, the fields are cropped).
The className and methodName describe the NIMAuthService::login method of SDK-API, and the params node describes the parameters of the login method. Execution result data", there are two types of conversions involved:
- API conversion
Find the corresponding API according to the className and methodName passed in by the use case.
From the data in the above figure, it is necessary to find NIMAuthService::login to call.
- data conversion
JsonData2cppStruct, the test platform converts Json data to C++ data.
cppStruct2JsonData, the C++ execution result data is converted into Json data and reported to the test platform.
From the data in the above figure, it is necessary to convert the data of the data node into NIMAuthInfo as the calling parameter of NIMAuthService::login.
Using "reflection" for this kind of scene is undoubtedly the most appropriate and elegant way. Why do you say this? The following comparisons can be made:
IM SDK-API (example only):
namespace nim {
struct AuthInfo {
std::string accid;
std::string token;
std::string appkey;
};
struct AuthCallbackParam {
std::string accid;
bool result;
int step;
};
using LoginCallback = std::function<void(const AuthCallbackParam&)>;
struct NIMAuthService {
static bool login(const AuthInfo& info) {
std::cout << info.accid << " / " << info.token << " / " << info.appkey << std::endl;
return true;
}
static void loginEx(const AuthInfo& info,const LoginCallback& callback) {
std::cout << info.accid << " / " << info.token << " / " << info.appkey << std::endl;
if (callback != nullptr) {
callback(true);
}
}
};
}
Without using the reflection mechanism, you need to find the corresponding service according to the input Json data, find the corresponding method, convert the Json data into the corresponding C++ structure, and then make an API call, which will involve a lot of repetitive logic judgments.
struct ParameterConverter {
static bool FromJsonData(const std::string& param, nim::AuthInfo& auth_info) {
auto json_param = nlohmann::json::parse(param);
if (json_param.is_object()) {
if(auto it = json_param.find("accid");it != json_param.end())
it->get_to(auth_info.accid);
if (auto it = json_param.find("token"); it != json_param.end())
it->get_to(auth_info.token);
if (auto it = json_param.find("appkey"); it != json_param.end())
it->get_to(auth_info.appkey);
}
return true;
}
};
bool InvokeSDKAPI(
const std::string& serviceName,
const std::string& functionName,
const std::string param) {
if (serviceName.compare("NIMAuthService") == 0) {
if (functionName.compare("login") == 0) {
nim::AuthInfo auth_info;
if (ParameterConverter::FromJsonData(param, auth_info)) {
nim::NIMAuthService::login(auth_info);
}
}
......
}
......
return true;
}
After introducing the reflection mechanism:
RefRegStruct(nim::AuthInfo
, RefRegStructField(accid, "accid")
, RefRegStructField(token, "token")
, RefRegStructField(appkey, "appkey")
);
bool InvokeSDKAPI(
const std::string& serviceName,
const std::string& functionName,
const std::string param) {
auto res = SDKAPIRefManager::InvokeAPI({ serviceName, functionName }, { param });
std::cout << serviceName << "::" << functionName << " result:" << res << std::endl;
return true;
}
int main() {
std::string param("{\"accid\":\"test_accid\",\"token\":\"123456\",\"appkey\":\"45c6af3c98409b18a84451215d0bdd6e\"}");
auth_info.accid = "test_accid";
RegApi("NIMAuthService", "login",&nim::NIMAuthService::login);
auto res = SDKAPIRefManager::InvokeAPI({ "NIMAuthService", "login" }, { param });
std::cout << "NIMAuthService::login result:" << res << std::endl;
return 0;
}
When making API calls, you can directly query the registered reflection information from SDKAPIRefManager to make API calls.
The meaning of introducing reflection:
For a unified, common interface description, use reflection to construct runtime parameters from untyped data, look up the API, and complete the call.
- Test platform, complete SDK platform independence and business consistency test.
- The test access program has the characteristics of "convenient access", "compatible with multiple versions of SDK" and "easy maintenance".
With the automatic generation of code, the quality of the program can be effectively improved without investing too much effort. About "API reflection information registration" will be introduced in detail in the following chapters.
How reflection works
For programmers of Java, Object-c, and C#, "reflection" is a very common mechanism, but C++ does not implement a reflection mechanism for performance and language characteristics. Although C++ does not provide reflection implementation, its powerful The C++ language can easily implement the reflection mechanism.
The Json library used for conversion is nlohmann/json ( https://github.com/nlohmann/json )
Take a look at the sample code below:
struct Test {
int index{1};
std::string name{"test_1"};
void printInfo() const {
std::cout << "index:" << index << " name:" << name << std::endl;
}
};
int main() {
auto index_addr = &Test::index;
auto name_addr = &Test::name;
auto fun_print_addr = &Test::printInfo;
Test test;
test.index = 1;
test.name = "test_1";
test.printInfo();
test.*index_addr = 2;
test.*name_addr = "test_2";
(test.*fun_print_addr)();
return 0;int index{0};
}
Output result:
index:1 name:test_1
index:2 name:test_2
Observing the code in the example, we can find that the call to the member of the concrete class instance object is completed by obtaining the address of the class member variable.
auto index_addr = &Test::index;
auto name_addr = &Test::name;
auto fun_print_addr = &Test::printInfo;
test.*index_addr = 2;
test.*name_addr = "test_2";
(test.*fun_print_addr)();
And this mechanism is also the cornerstone of our implementation of C++ reflection.
The realization of reflection can be mainly divided into three aspects:
- metadata generation
Metadata is the description information of C++ objects, which saves information such as C++ global functions, global objects, addresses of member functions and member variables of objects, names used for reflection search, etc. Referring to the sample code, you can use auto index_addr, auto name_addr, auto fun_print_addr is understood as metadata.
- metadata reflection
Metadata reflection can be understood as reflection information based on external input, including object names, data format strings (such as Json, xml, etc.), method names, generating corresponding C++ objects or finding corresponding C++ methods.
- API call
The corresponding API call is made according to the method found and the C++ object generated from the format string of the data.
According to the timing of metadata generation, reflection can be divided into two types:
- dynamic reflection
The metadata information of the corresponding type is generated only when the program is running. The advantage is that the metadata information is generated only when it needs to be converted, which occupies a small amount of memory. The disadvantage is that the running speed is slightly slower.
- static reflection
The metadata information is generated during the compile time of the program. The advantage is that the metadata does not need to be dynamically generated, and the running speed is slightly faster. The disadvantage is that the package size and memory usage will increase.
For the scenario of automated testing, it is used internally and does not have much requirements on package size and memory usage. In order to improve the efficiency of use case execution and the convenience of automatic generation of reflection code, we use static reflection.
Implementation of static reflection
1. Save the metadata information of the structure
As in the sample code above, each field of struct Test can obtain its offset & type information in the class, save the offset & type information and variable name, generate the reflection information of the field, and use struct as the reflection information to find keyword.
C++11 provides std::tuple, which can easily store this information, such as:
std::tuple
< std::tuple<TSField1,std::string/*field1_name*/>
, std::tuple<TSField2,std::string/*field2_name*/>
, std::tuple<TSField3,std::string/*field3_name*/>
,......>;
The to_json and from_json of struct are realized by specializing nlohmann::adl_serializer combined with the metadata information of struct.
Regarding this part of the content, the great god has given a more detailed introduction "Modern C++ Compile Time Structure Field Reflection"
( https://zhuanlan.zhihu.com/p/88144082 )
2. API metadata information storage
For the test cases sent from the "test server", the SDK-API parameters are passed in the format of Json. Since the conversion between the structure and the data of Json is mastered, if the API call is converted into a string mapping, that is, the SDK-API is converted into a string mapping. Type erasure, when making an API call, according to the SDK-API registration information, convert jsonArray/stringArray to a tuple structure (eg std::tuple), and then perform a tupleCall to complete the API call.
The mapping relationship is as follows:
So there is the following definition:
/// 参数类型定义
using function_param_t = std::string;
/// 返回值定义
using function_return_t = std::string;
/// sdk-api名称定义,用于反射信息注册与查找
struct _function_name {
std::string service_name;
std::string api_name;
};
using function_name_t = _function_name;
using function_param_list_t = std::vector<function_param_t>;
/// 类型擦除后的api格式定义
using api_function_wrapper_t = std::function<function_return_t(const function_param_list_t&)>;
/// api反射信息的聚合包装
struct _api_wrapper {
function_name_t function_name;
api_function_wrapper_t api;
};
using api_wrapper_t = _api_wrapper;
/// api反射信息的存储容器定义
using api_function_list_t = std::map<function_name_t, api_wrapper_t>;
3. API type erasure
Due to space limitations, I only introduce the processing of static functions. The so-called API type erasure refers to unifying different types of API interfaces into the same definition method. Here we specify the unified definition using api_function_wrapper_t = std::function<function_return_t(const function_param_list_t&) >;
function_return_t: Json format string of API return value.
function_param_list_t: Json format string of API parameters.
The JsonParam2Tuple method maps the Json parameters sent by the test platform to a tuple structure, such as std::tuple, based on the "structure reflection information".
MakeInvokeApi is used to generate a generic API call object for type erasure
(api_function_wrapper_t)
_InvokeStaticApi can be understood as the real call of SDK-API. It retains the original description of SDK-API, and is responsible for converting JsonParams to tuple, calling tupleCall, and converting cppParam to JsonParam after obtaining the return value.
/// 注册api反射信息
template <typename TApi, TApi api,
typename std::enable_if_t<!std::is_member_function_pointer<TApi>::value,nullptr_t> = nullptr>
static void RegisterApi(const std::string& service_name,const std::string& api_name) {
api_wrapper_t _api_wrapper;
_api_wrapper.function_name = { service_name,api_name };
_api_wrapper.api = MakeInvokeApi<TApi>(api);
RegisterApi(_api_wrapper);
}
static void RegisterApi(const api_wrapper_t& api) {
api_list[api.function_name] = api;
}
/// 生成类型擦除后的api元信息
template <typename TApi,
typename std::enable_if_t<!std::is_member_function_pointer<TApi>::value,nullptr_t> = nullptr>
static api_function_wrapper_t MakeInvokeApi(TApi api) {
return [api](const function_param_list_t& info) -> function_return_t {
return _InvokeStaticApi(info, api);
};
}
template <typename R, typename... Args,
typename std::enable_if<!std::is_void<R>::value, nullptr_t>::type = nullptr>
static function_return_t _InvokeStaticApi(const function_param_list_t& info,R(*f)(Args...)) {
auto _tuple = JsonParam2Tuple<Args...>(info);
auto _value = TupleCall(f, _tuple);
return StructToJson(_value);
}
template<typename TStruct>
static TStruct JsonToStruct(const std::string& json_param) {
auto json_obj = nlohmann::json::parse(json_param);
if (json_obj.is_object())
return json_obj.get<TStruct>();
return TStruct();
}
template<typename TStruct>
static std::string StructToJson(const TStruct& param) {
return nlohmann::json(param).dump();
}
template <typename... Args, std::size_t... Is>
static auto JsonParam2TupleImpl(const function_param_list_t& info,
const std::index_sequence<Is...>) {
return std::make_tuple(JsonToStruct<std::decay<Args>::type>(info[Is])...);
}
template <typename... Args>
static auto JsonParam2TupleImpl(const function_param_list_t& info) {
return JsonParam2TupleImpl<Args...>(info, std::make_index_sequence<sizeof...(Args)>());
}
template <typename... TArgs>
static auto JsonParam2Tuple(const function_param_list_t& info) {
return JsonParam2TupleImpl<TArgs...>(info);
}
template <typename TReturn, typename... TArgs, std::size_t... I>
static TReturn TupleCallImpl(TReturn(*fun)(TArgs...),
const std::tuple<std::remove_reference_t<std::remove_cv_t<TArgs>>...>& tup,
std::index_sequence<I...>) {
return fun(std::get<I>(tup)...);
}
template <typename TReturn, typename... TArgs,
typename = typename std::enable_if<!std::is_member_function_pointer<
TReturn(*)(TArgs...)>::value>::type>
static TReturn TupleCall(TReturn(*fun)(TArgs...),
const std::tuple<std::remove_reference_t<std::remove_cv_t<TArgs>>...>& tup) {
return TupleCallImpl(fun, tup,std::make_index_sequence<sizeof...(TArgs)>());
}
4. API calls
Call the corresponding API through the registration information, find the registered metadata information through api_name, get the api_wrapper, and make an API call, which is no different from a normal function call.
static function_return_t InvokeAPI(
const function_name_t& api_name,
const function_param_list_t& param_list) {
auto it = api_list.find(api_name);
if (it != api_list.end())
return it->second.api(param_list);
return function_return_t();
}
5. About callback processing
There is a definition of NIMAuthService::loginEx in the simulation code of the SDK. The base parameter list is a LoginCallback, which is a function object and cannot be formatted by Json. At this time, the param_list[1] (LoginCallback) converted by loginEx is only equivalent to A placeholder whose type can be deduced when registering the API, and then processed through the specialization of JsonToStruct to generate a callback object compatible with the SDK-API, and notify the callback result during the execution of the generated callback object. go out.
Specialization and example code:
RefRegStruct(nim::AuthCallbackParam
, RefRegStructField(accid, "accid")
, RefRegStructField(step, "step")
, RefRegStructField(result, "result")
);
template <>
nim::LoginCallback SDKAPIRefManager::JsonToStruct<nim::LoginCallback>(
const function_param_t& value)
{
return SDKAPIRefManager::MakeAPICallback(
"nim::LoginCallback",
((nim::LoginCallback*)(nullptr))
);
}
int main() {
std::string param("{\"accid\":\"test_accid\",\"token\":\"123456\",\"appkey\":\"45c6af3c98409b18a84451215d0bdd6e\"}");
auth_info.accid = "test_accid";RegApi("NIMAuthService", "loginEx",nim::NIMAuthService::loginEx);
SDKAPIRefManager::InvokeAPI({ "NIMAuthService", "loginEx" }, { struct_text ,"LoginCallback"});
return 0;
}
RegApi("NIMAuthService", "login",&nim::NIMAuthService::login);
The corresponding implementation code:
template <typename TTup, typename... Args, size_t... Is>
static void TupleToCbArgsImpl(const TTup& tup,
function_param_list_t& args,
std::index_sequence<Is...>) {
args = { StructToJson(std::get<Is>(tup))... };
}
template <typename TTup, typename... Args>
static void TupleToCbArgsImpl( const TTup& tup,function_param_list_t& args) {
TupleToCbArgsImpl<TTup, Args...>(
tup, args, std::make_index_sequence<sizeof...(Args)>());
}
template <typename... TArgs>
static function_param_list_t TupleToCbArgs(const std::tuple<TArgs...>& tup) {
function_param_list_t args;
TupleToCbArgsImpl<std::tuple<TArgs...>, TArgs...>(tup, args);
return args;
}
template <typename TReturn, typename... TArgs>
static std::function<TReturn(TArgs...)>
MakeAPICallback(
const std::string& callback_name,
const std::function<TReturn(TArgs...)>* realcb) {
auto callback = [callback_name](TArgs... param) -> TReturn {
auto tup = std::make_tuple(std::forward<TArgs>(param)...);
auto ret = TupleToCbArgs(tup);
OnAPICallback(callback_name,ret);
return TReturn();
};
return callback;
}
static void OnAPICallback(const std::string& callback_name, const function_param_list_t& param) {
std::cout << callback_name << std::endl;
std::cout << "params:" << std::endl;
std::cout << "--begin" << std::endl;
for (auto& it : param) {
std::cout << it << std::endl;
}
std::cout << "--end" << std::endl;
}
Registration of callbacks can be done using macros:
#define CallbackDescription(Callback) ((Callback *)(nullptr))
#define CallbackSpecialization(callback_name,Callback) \
template <> \
Callback SDKAPIRefManager::JsonToStruct<Callback>( \
const function_param_t &value) { \
return SDKAPIRefManager::MakeAPICallback(callback_name, \
CallbackDescription(Callback)); \
}
Sample code overview:
namespace nim {
struct AuthInfo {
std::string accid;
std::string token;
std::string appkey;
};
struct AuthCallbackParam {
std::string accid;
bool result;
int step;
};
using LoginCallback = std::function<void(const AuthCallbackParam& param)>;
struct NIMAuthService {
static bool login(const AuthInfo& info) {
std::cout
<< "api-NIMAuthService::login"
<< info.accid << " / "
<< info.token << " / "
<< info.appkey << std::endl;
return true;
}
static void loginEx(const AuthInfo& info,const LoginCallback& callback) {
std::cout
<< "api-NIMAuthService::loginEx"
<< info.accid << " / "
<< info.token << " / "
<< info.appkey << std::endl;
if (callback != nullptr) {
AuthCallbackParam param;
callback({ info.accid,true,3 });
}
}
};
}
RefRegStruct(nim::AuthInfo
, RefRegStructField(accid, "accid")
, RefRegStructField(token, "token")
, RefRegStructField(appkey, "appkey")
);
RefRegStruct(nim::AuthCallbackParam
, RefRegStructField(accid, "accid")
, RefRegStructField(step, "step")
, RefRegStructField(result, "result")
);
CallbackSpecialization("nim::LoginCallback", nim::LoginCallback);
int main() {
std::string param("{\"accid\":\"test_accid\",\"token\":\"123456\",\"appkey\":\"45c6af3c98409b18a84451215d0bdd6e\"}");
RegApi("NIMAuthService", "login",&nim::NIMAuthService::login);
RegApi("NIMAuthService", "loginEx", &nim::NIMAuthService::loginEx);
auto res = SDKAPIRefManager::InvokeAPI({ "NIMAuthService", "login" }, { param });
std::cout << "NIMAuthService::login result:" << res << std::endl;
SDKAPIRefManager::InvokeAPI({ "NIMAuthService", "loginEx" }, { param ,"LoginCallback"});
return 0;
}NIMAuthService::login result:true
Output result:
api-NIMAuthService::logintest_accid / 123456 / 45c6af3c98409b18a84451215d0bdd6e
NIMAuthService::login result:true
api-NIMAuthService::loginExtest_accid / 123456 / 45c6af3c98409b18a84451215d0bdd6e
nim::LoginCallback
params:
--begin
{"accid":"test_accid","result":true,"step":3}
--end
Application of C++ Static Reflection in IM SDK Development
Generation of reflection information
The IM SDK provides a large number of interfaces. When generating reflection information, if you rely on the way of human flesh and hand coding, there will be the following problems:
- Huge workload
- Error-prone
- API changes, you need to search for the corresponding reflection information and modify them one by one
So we introduced libclang to automatically generate reflection code. For clang-based source-to-source translation tools, you can refer to our shared article "NeCodeGen: clang-based source-to-source translation tools" .
The automatically generated code is as follows, some code snippets are excerpted, where nim_api::NIM_AuthInfo is the encapsulation of the login service in Yunxin IM SDK (elite version), and NIMAuthService is the registrar of reflection information.
/**
* @brief
* 登录参数定义
*/
ReflectionDefinitionAndReg(nim_api_NIM_AuthInfo,nim_api::NIM_AuthInfo,
///应用的appkey
appKey, "appKey",
///登录账号
accid, "accid",
///登录密码
token, "token"
);
/**
* @brief
* 登录结果回调参数定义
*/
ReflectionDefinitionAndReg(nim_api_NIM_LoginCBParam,nim_api::NIM_LoginCBParam,
///错误码
code, "code",
///当前登录步骤
step, "step",
///当前登录步骤描述
message, "message",
///是否为重新登录
relogin, "relogin"
);
NE_GTAPI_CALLER::ApiObject NIMAuthService::Init(NE_GTAPI_CALLER::ApiEnv env, NE_GTAPI_CALLER::ApiObject exports) {
return InternalInit(TransClassName(NIMAuthService), env, exports, {
RegApi("registerKickout",&nim_api::AuthService::registerKickout),
RegApi("registerMultiSpot",&nim_api::AuthService::registerMultiSpot),
RegApi("registerDisconnect",&nim_api::AuthService::registerDisconnect),
RegApi("registerRelogin",&nim_api::AuthService::registerRelogin),
RegApi("login",&nim_api::AuthService::login),
RegApi("logout",&nim_api::AuthService::logout),
RegApi("kickOtherClient",&nim_api::AuthService::kickOtherClient),
RegApi("getLoginState",&nim_api::AuthService::getLoginState),
});
}
void NIMAuthService::RegisterNotifyCallback() {
RegisterSDKNotifyCallback("onKickout", GetSDKService(), &nim_api::AuthService::registerKickout);
RegisterSDKNotifyCallback("onMultiSpot", GetSDKService(), &nim_api::AuthService::registerMultiSpot);
RegisterSDKNotifyCallback("onDisconnect", GetSDKService(), &nim_api::AuthService::registerDisconnect);
RegisterSDKNotifyCallback("onRelogin", GetSDKService(), &nim_api::AuthService::registerRelogin);
}
Application scenarios
- automated test
The previous chapters specified the "API automation scenario" when introducing the implementation of C++ reflection. At first, C++ reflection was introduced to realize the access of automated tests. At present, the C++ automated test access SDK encapsulated by the reflection mechanism has completed the coverage of the desktop side. , and achieves the same effect as Java/object-c. The test team only needs to write a test case to test the current SDK.
- electron-sdk package
Yunxin IM sdk N-Api is connected to the native IM sdk in the way of node addon. Napi::CallbackInfo can be easily converted into Json format data, and it is more appropriate to call the underlying native sdk by reflection.
ts code excerpt
export interface NIM_AuthInfo {
appKey: string,
accountId: string,
token: string
}
export interface NIM_LoginCBParam {
code: number,
step: number,
message: string,
relogin: boolean
}
export interface AuthenticationAPI {
getParamRefValue(paramName:string) : string
login(authInfo: NIM_AuthInfo, callback: LoginCallback): void
logout(callback: LogoutCallback): void;
kickOtherClient(param: NIM_KickOtherClientRequestParam, callback: KickOtherClientCallback): void
getLoginState(): NIM_LoginState
}
class Authentication extends ServiceBase {
constructor(clientInstance: ClientInstance) {
super()
this._authService = new ServiceBase.nim.Authentication(clientInstance, this.emit.bind(this))
}
getParamRefValue(paramName:string) : string{
return this._authService.getParamRefValue(paramName)
}
login(authInfo: nim_api.NIM_AuthInfo, callback: nim_api.LoginCallback): void {
this._authService.login(authInfo, callback)
}
logout(callback: nim_api.LogoutCallback): void {
this._authService.logout(callback)
}
kickOtherClient(param: nim_api.NIM_KickOtherClientRequestParam, callback: nim_api.KickOtherClientCallback): void {
this._authService.kickOtherClient(param, callback)
}
getLoginState(): nim_api.NIM_LoginState {
return this._authService.getLoginState()
}
private _authService: nim_api.AuthenticationAPI
}
js code excerpt
funList: {
enter: {moduleName: '_chatroomService', funName: 'enter', funParam: ['NIM_ChatroomEnterParam'], cbCount: 1},
independentEnter: {moduleName: '_chatroomService', funName: 'independentEnter', funParam: ['NIM_ChatroomIndependentEnterParam'], cbCount: 1},
anoymityEnter: {moduleName: '_chatroomService', funName: 'anoymityEnter', funParam: ['NIM_ChatroomAnoymityEnterParam'], cbCount: 1},
exit: {moduleName: '_chatroomService', funName: 'exit', funParam: [], cbCount: 1},
getInfo: {moduleName: '_chatroomService', funName: 'getInfo', funParam: [], cbCount: 1},
updateInfo: {moduleName: '_chatroomService', funName: 'updateInfo', funParam: ['NIM_ChatroomInfoUpdateParam'], cbCount: 1}
}
invokApi (moduleName, funName) {
var _funParam = this.activeFunList[funName].funParam
var _cbCount = this.activeFunList[funName].cbCount
var _callback = (requestAck) => {
if (!requestAck && requestAck !== 0) {
console.log('\t🔹 Callback value:\n\t\t -- Null or Undefined')
} else {
console.log('\t🔹 Callback value:\n\t\t', requestAck)
}
}
var _callback2 = (requestAck) => {
if (!requestAck && requestAck !== 0) {
console.log('\t🔹 Callback value:\n\t\t -- Null or Undefined')
} else {
console.log('\t🔹 Callback value:\n\t\t', requestAck)
}
}
var _result
if (_funParam.length === 0) {
console.log('🔷 -- API【', moduleName, '/', funName, '】\n\t🔹 callbackCount:', _cbCount, '\n\t🔹 Param:\n\t\t', 'No parameters required')
if (_cbCount === 0) {
_result = this.clientInstance[moduleName][funName]()
} else if (_cbCount === 1) {
_result = this.clientInstance[moduleName][funName](_callback1)
} else if (_cbCount === 2) {
_result = this.clientInstance[moduleName][funName](_callback1, _callback2)
} else {
}
} else if (_funParam.length === 1) {
var paramOne = JSON.parse(this.requestParam1)
console.log('🔷 -- API【', moduleName, '/', funName, '】\n\t🔹 callbackCount:', _cbCount, '\n\t🔹 Param:\n\t\t', paramOne)
if (_cbCount === 0) {
_result = this.clientInstance[moduleName][funName](paramOne)
} else if (_cbCount === 1) {
_result = this.clientInstance[moduleName][funName](paramOne, _callback1)
} else if (_cbCount === 2) {
_result = this.clientInstance[moduleName][funName](paramOne, _callback1, _callback2)
} else {
}
} else if (_funParam.length === 2) {
var paramTwoOne = JSON.parse(this.requestParam1)
var paramTwoTwo = JSON.parse(this.requestParam2)
console.log('🔷 -- API【', moduleName, '/', funName, '】\n\t🔹 callbackCount:', _cbCount, '\n\t🔹 Param1:\n\t\t', paramTwoOne, '\n\t🔹 Param2:\n\t\t', paramTwoTwo)
if (_cbCount === 0) {
_result = this.clientInstance[moduleName][funName](paramTwoOne, paramTwoTwo)
} else if (_cbCount === 1) {
_result = this.clientInstance[moduleName][funName](paramTwoOne, paramTwoTwo, _callback1)
} else if (_cbCount === 2) {
_result = this.clientInstance[moduleName][funName](paramTwoOne, paramTwoTwo, _callback1, _callback2)
} else {
}
} else {
}
if (!_result && _result !== 0) {
console.log('\t🔹 Return value:\n\t\t -- Null or Undefined')
} else {
console.log('\t🔹 Return value:\n\t\t', JSON.stringify(_result))
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。