kedao中间件-C++服务篇

@

一、概述

    本文主要介绍在kedao中间件中如何注册服务、编写服务代码、调试服务。
    使用 VSCode 作为开发工具,通过远程连接到开发服务器的方式,开展远程开发。

    kedao中间件官网:https://www.yckj-kedao.com
    系列文章:
        《kedao中间件-安装篇》
        《kedao中间件-Java服务篇》
        《kedao中间件-Vue开发篇》
        《kedao中间件-集群及发布篇》
        《kedao中间件-并发测试篇》
        《kedao中间件-Linux系统安装篇》
        《kedao中间件-数据库安装篇》

二、前提条件

    已经部署 kedao中间件 开发服务器、数据库,并且安装了 kedao creator 客户端,注册了用户。

    本文的涉及到开发用户、开发服务器和数据库服务器相关信息:
    1)开发服务器的IP:192.168.43.30,操作系统:Ubuntu24.04,系统用户:kedao
    2)kedao中间件的安装路径:/opt/kedao
    3)kedao中间件监听的端口:80,协议:http
    4)kedao中间件的注册用户:lym_test
    5)数据库服务器IP:192.168.43.135,端口:5432,用户:postgres,密码:postgres,数据库实例:asv_db

    如果还没有了解如何部署 kedao中间件 开发服务器,请参考《kedao中间件-安装篇》

三、创建C++系统

1、创建系统

    登录 kedao creator,在【基础功能】-【创建系统】功能中,点击右上角的创建系统按钮,打开创建系统对话框,填入系统名称为 prj_c_test,数据为 PostgreSQL,开发语言为C++,签名算法为 SHA256,如图:
在这里插入图片描述
    创建成功后,左侧菜单将自动出现刚才创建的系统,如图:
在这里插入图片描述

2、源代码目录结构

    C++项目源码的目录在: kedao中间件的安装路径/src_c,即 /opt/kedao/src_c
    进入 /opt/kedao/src_c 目录:

kedao@kedao:/$ cd /opt/kedao/src_c
kedao@kedao:/opt/kedao/src_c$ ls
include  lym_test

    目录说明:
    1)include:公共包含文件目录,所有创建的C++工程,均使用到该目录下的头文件
    2)lym_test:kedao中间件的注册用户名,所有的C++系统,都在该目录下

    进入 lym_test 目录:

kedao@kedao:/opt/kedao/src_c$ cd lym_test
kedao@kedao:/opt/kedao/src_c/lym_test$ ls
prj_c_test

    这时,看到刚才生成的C++工程prj_c_test,如果创建了多套C++工程,这里将会以工程名称生成相应的工程目录。
    继续进入prj_c_test工程目录:

kedao@kedao:/opt/kedao/src_c/lym_test$ cd prj_c_test
kedao@kedao:/opt/kedao/src_c/lym_test/prj_c_test$ ls
debugger  make_all.sh  model  modules  public  utils

    目录及文件说明:
    1)debugger:调试工程的文件夹,每一个用户一个调试工程,自动生成,不需要人为操作。
    2)make_all.sh:全编译脚本,自动生成,不需要人为操作;只有在需要全编译时使用,平常用不到。
    3)model:数据模型类文件夹,自动生成。
    4)modules:服务模块文件夹,服务按模块分组,每个模块下可以有多个服务,所有的服务代码都在这个目录里。
    5)public:公共函数(方法)文件夹,根据自己项目的需要自定义实现。
    6)utils:工具类文件夹,包含一些默认的工具类,可以根据自己项目的需要自定义实现。

    这里,需要重点了解 modules 的结构,进入modules目录:

kedao@kedao:/opt/kedao/src_c/lym_test/prj_c_test$ cd modules
kedao@kedao:/opt/kedao/src_c/lym_test/prj_c_test/modules$ ls
atom_service  basic_func_employee  basic_func_funcRounter  basic_func_organization  basic_func_post  basic_func_role_permission  sys_commons  sys_login

    目录说明:
    1)atom_service:原子服务文件夹,自动生成。需要注意的是:这里只生成数据表的原子服务,非数据表则不生成;同时,表结构必须要设有主键字段,否则原子服务将不会执行。
    2)其他为默认的基础功能模块,主要实现了组织机构、员工/用户、岗位、权限、菜单/路由配置、登录等基础功能。

    进入一个模块目录,以sys_login为例来了解一个模块的组成,进入sys_login目录:

kedao@kedao:/opt/kedao/src_c/lym_test/prj_c_test/modules$ cd sys_login
kedao@kedao:/opt/kedao/src_c/lym_test/prj_c_test/modules/sys_login$ ls
makefile  public  service  sys_login.cpp

    目录及文件说明:
    1)makefile:编译文件,创建模块时生成,可以根据自己的需求进行修改。
    2)public:模块内部的公共文件夹。
    3)service:注册服务的源码目录,该模块下的所有服务代码都在此目录下,编写代码的核心目录。
    4)sys_login.cpp:模块的 main 文件,自动生成,每次注册服务,或者服务调整时,会自动更新,不可以修改。注:即使修改了,一旦模块下的服务有变动,都会自动生成。每个模块的目录下只有一个 *.cpp 文件

    进入 service 目录:

kedao@kedao:/opt/kedao/src_c/lym_test/prj_c_test/modules/sys_login/service$ ls
svc_get_userInfo.hpp  svc_login.hpp

    这里每一个服务为一个独立的 *.hpp 文件。服务编程时,将围绕着对应服务的 *.hpp 文件进行编码。
    到这里,对C++工程源代码的结构有了初步了解。

3、编译工程

    点击操作下的编译按钮,进行一次全编译,如图:
在这里插入图片描述
    编译完成后,在 /opt/kedao/bin/lib/lym_test/prj_c_test 目录下看到编译好的动态库文件。

    也可以进入到工程的根目录 /opt/kedao/src_c/lym_test/prj_c_test 执行 make_all.sh 脚本进行全编译:

kedao@kedao:/$ cd /opt/kedao/src_c/lym_test/prj_c_test
kedao@kedao:/opt/kedao/src_c/lym_test/prj_c_test$ ./make_all.sh

4、动态库文件目录结构

    编译后生成的动态库存放路径为:kedao中间件的安装路径/bin/lib/kedao中间件的注册用户/工程名称/,即:/opt/kedao/bin/lib/lym_test/prj_c_test/
    进入/opt/kedao/bin/lib/lym_test/prj_c_test/目录:

kedao@kedao:~$ cd /opt/kedao/bin/lib/lym_test/prj_c_test/
kedao@kedao:/opt/kedao/bin/lib/lym_test/prj_c_test$ ls
backup  db_connect.ini  db_sql  libatom_service.so  libbasic_func_employee.so  libbasic_func_funcRounter.so  libbasic_func_organization.so  libbasic_func_post.so  libbasic_func_role_permission.so  libsys_commons.so  libsys_login.so  sqlite_db

    目录说明:
    1)backup:备份目录,目前没有使用。
    2)db_sql:数据库脚本目录,包含了数据初始化 和 查询表结构的SQL语句。注意:在【数据模型】功能中表结构导入(数据库)用到了这里的SQL语句。
    3)sqlite_db:如果数据库时 SQLite,数据库文件所在的目录。
    4)db_connect.ini:数据库连接配置文件,根据 odbc.ini 对应的配置来设置。
    5).so:动态库文件,每一个服务模块对应一个动态库。

5、数据库配置

    1)配置数据 /ect/odbc.ini

kedao@kedao:/opt/kedao/bin/lib/lym_test/prj_c_test$ sudo vi /etc/odbc.ini

    根据预先建好的数据库配置,数据库服务器IP:192.168.43.135,端口:5432,用户:postgres,密码:postgres,数据库实例:asv_db。配置如下:

[myPgDB]
Description = link to pg
Driver = PostgreSQL ANSI
Database = asv_db
Servername = 192.168.43.135
UserName = postgres
Password = postgres
Port = 5432
ReadOnly = 0
ConnSettings = set client_encoding to UTF8

    验证配置,执行命令 isql -v myPgDB

kedao@kedao:/opt/kedao/bin/lib/lym_test/prj_c_test$ isql -v myPgDB
+---------------------------------------+
| Connected!                            |
|                                       |
| sql-statement                         |
| help [tablename]                      |
| echo [string]                         |
| quit                                  |
|                                       |
+---------------------------------------+
SQL> 

    连接成功,quit 退出。
    2)配置 db_connect.ini

kedao@kedao:/opt/kedao/bin/lib/lym_test/prj_c_test$ sudo vi db_connect.ini

    文件内容设置如下:

[lym_test]
prj_c_test=postgres/postgres@myPgDB

    说明:
    1) lym_testkedao中间件的注册用户
    2)prj_c_test系统名称
    3)postgres分别为数据库的用户密码
    4)myPgDB/ect/odbc.ini 中数据库连接的节点名称

6、数据库初始化

    1)使用数据库管理工具连接到数据库,并打开预先创建好的数据库实例 asv_db
    2)在 db_sql 目录找到 PostgreSQL 数据库的初始化脚本:initialize_table_struct_for_PostgreSQL.sql
    3)在 asv_db 实例中执行初始化的脚本:initialize_table_struct_for_PostgreSQL.sql
    初始化完成后,数据库初始创建了:sys_employee、sys_function、sys_organization、sys_post、sys_role、sys_role_function_relation、sys_user、sys_user_role 等8张数据表,满足了一套系统最基本的框架结构需求,包括:组织管理、员工、角色、岗位、权限、登录、路由(菜单)配置等基础功能。
    这已经满足绝大多数的系统需求,可以直接使用,只需设计自己的业务数据表,添加业务即可;
    当然,如果不能满足自己系统的需求,也可以自行修改。

四、注册服务

    在kedao creator的菜单中找到【我的系统】-【prj_c_test】功能,如图:
在这里插入图片描述
    这里提供了系统基础功能的服务,只需根据业务需求注册新的服务。

1、增加模块

    点击增加模块按钮,在弹出的对话框中增加一个测试模块 mdl_test,如:
在这里插入图片描述

2、增加服务

    在模块列表中选中mdl_test,点击增加服务按钮,在弹出的对话框中增加一个测试服务 svc_test,如:
在这里插入图片描述

3、选择服务参数

    参数说明:
    1)默认的服务入参和出参分别是:SVC_REQUEST_OBJ<string> 和 SVC_RESPONSE_OBJ<string>
    2)SVC_REQUEST_OBJ 是默认的服务入参数据结构,Json 结构如下:

{
    "sys_head": {
		"usr_id": "",
		"org_id": "",
		"sys_id": "",
		"mdl_func_id": "",
		"login_key": ""
	},
	"data": ""
}

    其中data是一个泛型对象,也就是真正的请求业务数据;而 sys_head 主要保存服务请求者的身份信息,用于身份认证。
    C++中对应的数据结构:

	/* 请求头包对象*/
	class SYS_HEAD
	{
	public:
	    string usr_id = "";			// 用户登录名称
	    string org_id = "";			// 组织代码
	    string sys_id = "";			// 系统代码
	    string mdl_func_id = "";	// 功能代码
	    string login_key = "";		// 登录验证信息,每次登录成功后更新该值。服务调用时,必须传该值
	
	    AIGC_JSON_HELPER(usr_id, org_id, sys_id, mdl_func_id, login_key);
	};

    /* 请求参数对象*/
    template <typename T>
    class SVC_REQUEST_OBJ
    {
    public:
        SYS_HEAD sys_head;			// 头包对象
        T data;			            // 请求数据(泛型数据对象)

        AIGC_JSON_HELPER(sys_head, data);
    };

    3)SVC_RESPONSE_OBJ 是默认的服务出参数据结构,Json 结构如下:

{
	"code": 0,
	"err_msg": "",
	"otl_exc": {
		"code": 0,
		"msg": "",
		"stm_text": "",
		"sqlstate": "",
		"var_info": ""
	},
	"data": ""
}

    其中data是一个泛型对象,也就是真正的返回业务数据;code 是返回码;err_msg 是错误信息;otl_exc 是数据库错误信息。
    C++中对应的数据结构:

    /* OTL异常信息*/
    class OTL_EXC
    {
    public:
        int code = 0;			        // 错误码
        string msg = "";			    // 错误消息
        string stm_text = "";			// 导致错误的SQL语句
        string sqlstate = "";			// SQLSTATE消息
        string var_info = "";			// 导致错误的变量

        AIGC_JSON_HELPER(code, msg, stm_text, sqlstate, var_info);
    };
    
    /* 响应结果对象*/
    template <typename T>
    class SVC_RESPONSE_OBJ
    {
    public:
        int code = 0;			        // 返回码
        string err_msg = "";			// 错误信息
        OTL_EXC otl_exc;			    // 错误信息
        T data;			                // 响应数据

        AIGC_JSON_HELPER(code, err_msg, otl_exc, data);
    };

    根据业务需求选择自己的参数,这里,我们注册一个查询系统功能的服务,涉及到数据表是 sys_function,服务的入参是一个对象,返回是一个列表,入参选择界面,如:
在这里插入图片描述
    出参选择界面,如:
在这里插入图片描述
    注:选择参数的数据结构,在【数据模型】功能中维护。【数据模型】支持直接从数据库导入表结构功能,只需在数据库中设计好数据表,通过表结构导入功能一键导入,能节省很大部分的时间

4、保存服务

    选择完参数的最终服务注册界面,如:
在这里插入图片描述
    点击保存按钮,完成注册,返回服务列表界面:
在这里插入图片描述
    现在到系统源代码目录下,直接进入到服务所在目录:

kedao@kedao:/$ cd /opt/kedao/src_c/lym_test/prj_c_test/modules/mdl_test/service
kedao@kedao:/opt/kedao/src_c/lym_test/prj_c_test/modules/mdl_test/service$ ls
svc_test.hpp

    服务源代码文件 svc_test.hpp 已经生成。

五、编写代码

1、使用 VSCode 开发

    使用 VSCode 远程连接到开发服务器 192.168.43.30,连接用户为:kedao,打开服务源码文件 svc_test.hpp,如图:
在这里插入图片描述
    下面是服务函数代码,代码结构比较简单,自己花点时间阅读理解。现在直接在服务函数中增加业务逻辑代码,,为了便演示调用服务,先把调用服务者的身份认证注释掉,整个服务代码如下:

#pragma once

#include <iostream>
#include <vector>
#include "../../../model/tb_struct.hpp"
#include "../../../utils/OtlV4Helper.hpp"
#include "../../../utils/f_check_user_login.hpp"
#include "../../../../../include/kedao_sync_logs.h"
#include "../../../../../include/kedao_public.h"
#include "../../../../../include/openssl_crypto.h"

using namespace kedao_utils;
using namespace prj_c_test;

extern "C"
int svc_test(const string &in_data, string &out_data, const string &request_msg)
{
    int iRst = 0;
    string sql_txt = "";
    vector<string> v_params;
    SVC_REQUEST_OBJ<SYS_FUNCTION> req;
    SVC_RESPONSE_OBJ<vector<SYS_FUNCTION>> resp;
    OtlV4Helper &dbconn = *((OtlV4Helper *)g_SHARED_PTR->get());

    SVC_REQUEST_MSG svc_request_msg;
    JsonHelper::JsonToObject(svc_request_msg, request_msg);

    try {
        // 设置保持连接,不 logoff(),避免高并发下的数据库不停建立新连接带来的消耗(不同数据库建立连接的耗时在20~200ms不等)
        dbconn.set_keep_connection(true);
        dbconn.conn();
        const string &req_jsonStr = in_data;
        // 解析入参到对象
        if (!aigc::JsonHelper::JsonToObject(req, req_jsonStr))
        {
            // 解包失败
            iRst = -1;
            resp.code = iRst;
            resp.otl_exc.code = iRst;
            resp.err_msg = "服务 svc_test :解包失败。";
            JsonHelper::ObjectToJson(resp, out_data);
            // 打印错误日志
            sync_cerr << out_data << endl;
            // 断开数据库连接
            dbconn.logoff();
            return iRst;
        }

        // 检查用户登录有效性(为了便于演示调用服务,先把登录身份验证注释掉)
        /*
        if (check_user_login(dbconn, req.sys_head.usr_id, req.sys_head.login_key))
        {
            iRst = -1;
            resp.code = iRst;
            resp.otl_exc.code = iRst;
            resp.err_msg = "服务 svc_test :用户登录有效性校验失败。";
            JsonHelper::ObjectToJson(resp, out_data);
            // 打印错误日志
            sync_cerr << out_data << endl;
            // 断开数据库连接
            dbconn.logoff();
            return iRst;
        }
        */

        // 开始事务
        dbconn.begin();

        // 此处添加业务处理逻辑

        v_params.clear();
        // 组织查询 SQL语句 和 参数
        sql_txt = "SELECT * FROM sys_function ";
        sql_txt += "WHERE 1 = 1 ";
        if (req.data.func_code != "")
        {
            sql_txt += "    AND func_code = :P01<char[33]> ";
            // 参数
            v_params.push_back(req.data.func_code);
        }
        if (req.data.menu_title != "")
        {
            sql_txt += "    AND menu_title = :P02<char[65]> ";
            // 参数
            v_params.push_back(req.data.menu_title);
        }
        
        // 执行 SQL 查询,将数据返回到 resp.data
        iRst = dbconn.f_otl_query_to_objs(sql_txt, v_params, resp.data);
        if (iRst != 0)
        {
            resp.code = iRst;
            resp.otl_exc.code = iRst;
            resp.err_msg = "服务 svc_test :查询 sys_function 失败。";
            JsonHelper::ObjectToJson(resp, out_data);
            // 打印错误日志
            sync_cerr << out_data << endl;
            // 断开数据库连接
            dbconn.logoff();
            return iRst;
        }

        // 打包返回结果
        out_data = "";
        resp.code = iRst;
        resp.otl_exc.code = iRst;
        if (!aigc::JsonHelper::ObjectToJson(resp, out_data))
        {
            // 打包失败
            iRst = -1;
            resp.code = iRst;
            resp.otl_exc.code = iRst;
            resp.err_msg = "服务 svc_test :打包失败。";
            JsonHelper::ObjectToJson(resp, out_data);
            // 打印错误日志
            sync_cerr << out_data << endl;
            // 事务回滚
            dbconn.rollback();

            // 断开数据库连接
            dbconn.logoff();
            return iRst;
        }

        // 提交事务
        dbconn.commit();
    }
    catch(otl_exception& p){
        // 如果错误码是 8 或 35,通常是数据库驱动版本与数据库不兼容,升级数据库驱动或者对数据库版本降级
        if (p.code == 35 || p.code == 8) { *g_SHARED_PTR = make_shared<OtlV4Helper>(); }
        // 错误码
        iRst = p.code;
        resp.code = iRst;
        // 获取异常数据
        resp.otl_exc.code = p.code;
        resp.err_msg = (char *)p.msg;
        resp.otl_exc.stm_text = p.stm_text;
        resp.otl_exc.sqlstate = (char *)p.sqlstate;
        resp.otl_exc.var_info = p.var_info;
        JsonHelper::ObjectToJson(resp, out_data);
        // 打印错误日志
        sync_cerr << out_data << endl;
        // 事务回滚(如果是查询,出现异常时,这里没有必要进行回滚,否则,会再次触发异常,导致程序终止,前端得不到异常返回的信息)
        dbconn.rollback();
    }

    // 断开数据库连接
    dbconn.logoff();

    return iRst;
}

    服务开发说明
    C++服务开发数据库使用的是 otlv4.h ,并对其进一步封装成OtlV4Helper类,通过OtlV4Helper来实现对数据库的操作。重点掌握 OtlV4Helper 的实现逻辑,如果不满足需求,可以自行扩展。
    otlv4.h 组织SQL语句时参数结构 :参数名<参数类型[字段的长度 + 1]>
    其中:
     1)每个参数以冒号 ":" 标识;
     2)冒号后面跟着参数名称(参数名任意,在同一条语句中不重复即可);
     3)参数名后面用一对尖括号 <> ,尖括号中为传入参数的数据类型,即字段的类型;
     4)常用的参数类型有: char、double、int、long、long long 等等;
     5)如果字符串类型,要指定长度,且长度比字段长度多1(结束符);
     6)如果拼接的 SQL 语句中包含 ":",冒号要进行转义,比如 PostgreSQL 中通过获取 uuid_generate_v4()::text 获取 uuid 时,要写成 uuid_generate_v4()\\:\\:text

    OtlV4Helper 中常用的函数有:
     1)原子服务,实现数据表的增删改查;使用原子服务时,数据表必须有唯一健字段
        atom_exec_delete()
        atom_exec_insert()
        atom_exec_select()
        atom_exec_update()
     2)执行SQL语句,无结果返回
        f_otl_exec()
     3)执行SQL语句,返回单个值(字符串)
        f_otl_query_to_singleValue()
     4)执行SQL语句,返回多条记录容器 vector<>
        f_otl_query_to_objs()
     5)执行SQL语句,返回单条记录的数据对象
        f_otl_query_to_obj()
     6)执行SQL语句,返回多条记录 vector<map<string, string>>
        f_otl_query_to_maps()
     7)执行SQL语句,返回单条记录的 map<string, string>
        f_otl_query_to_map()

    关于日志输出
    日志输出有3种方式,都支持多线程高并发:
     1)日志输出到 stdout,通常用在开发过程中打印日志,用来辅助测试,发布生产环境时,应该删除此类日志的输出
        sync_cout << "日志输出到 bin/stdout 文件" << endl;
     2)错误日志输出到 stderr 文件,通常用在服务发生错误时的日志打印
        sync_cerr << "错误日志输出到 bin/stderr 文件" << endl;
     3)日志输出到 log 日志文件,每天产生一个log日志文件,在 bin/log 目录下;kedao中间件默认输出每一个服务的执行情况
        sync_clog << "日志输出到 bin/log/8位日期_log 文件" << endl;

    注:整个服务编码过程是比较简单的,只需要关心业务逻辑的实现,对于数据库操作来说,就是组织编写业务逻辑的 SQL 语句 和 组织返回数据的过程。
    数据库开发帮助类 OtlV4Helper,建议重点掌握。
    服务以模块分组,一个模块为一个独立动态库工程。

六、编译服务

    在服务模块下,执行 make 命令进行编译,如:

kedao@kedao:/opt/kedao$ cd src_c/lym_test/prj_c_test/modules/mdl_test/
kedao@kedao:/opt/kedao/src_c/lym_test/prj_c_test/modules/mdl_test$ ls
makefile  mdl_test.cpp  public  service
kedao@kedao:/opt/kedao/src_c/lym_test/prj_c_test/modules/mdl_test$ make
g++ --std=c++23 -O3 -mavx2 -DNDEBUG     -fPIC   -o      mdl_test.o      -c      mdl_test.cpp    -I/usr/include/ -I../../../../include/  -L/usr/lib64/   -L/usr/lib/     -L../../../../../bin/lib        -ldl    -lkedao_utils   -lodbc  -lstdc++fs      -fPIC   -rdynamic       -lpthread
g++ --std=c++23 -O3 -mavx2 -DNDEBUG     -shared -fPIC   -o      ../../../../../bin/lib/lym_test/prj_c_test/libmdl_test.so       mdl_test.o      -I/usr/include/ -I../../../../include/ -L/usr/lib64/    -L/usr/lib/     -L../../../../../bin/lib        -ldl    -lkedao_utils   -lodbc  -lstdc++fs      -fPIC   -rdynamic       -lpthread       -Wl,-rpath=./:./lib:'$ORIGIN':'$ORIGIN/lib'
rm      -rf mdl_test.o

    在 VSCode 中操作,如图:
在这里插入图片描述
    编译完成后,动态库自动生成到 /opt/kedao/bin/lib/lym_test/prj_c_test/ 目录。

七、调试服务

1、创建调试账号

    在kedao creator中打开【我的系统】->【系统名称】->【项目成员】功能,在 "成员列表" 中,选择要创建测试账号的项目组成员,点击操作列的【创建调试账号】,创建成功后,将会生成调试工程代码,如:
在这里插入图片描述
    调试工程的代码路径 /opt/kedao/src_c/lym_test/prj_c_test/debugger/debug_lym_test
    在配置 VSCode的 tasks.json 时,需参照调试工程的 makefile 文件

2、VSCode 中配置调试任务

    在 /opt/kedao/.vscode 配置目录中设置 launch.jsontasks.json 这两个文件。

  1)设置 launch.json

    在 /opt/kedao/.vscode 文件夹中找到 launch.json 文件,在 "configurations" 节点中增加预启动任务,如:

{
  "version": "0.2.0",
  "configurations": [
    {
      "preLaunchTask": "debug_lym_test",
      "name": "debug_lym_test",
      "type": "cppdbg",
      "request": "launch",
      "targetArchitecture": "x64",
      "program": "${workspaceFolder}/bin/debug_lym_test",
      "args": [
        ">>",
        "stdout"
      ],
      "stopAtEntry": false,
      "cwd": "${workspaceFolder}/bin/",
      "environment": [],
      "internalConsoleOptions": "openOnSessionStart",
      "externalConsole": false,
      "MIMode": "gdb",
      "setupCommands": [
        {
          "description": "为 gdb 启用整齐打印",
          "text": "-enable-pretty-printing",
          "ignoreFailures": true
        }
      ],
      "miDebuggerPath": "/usr/bin/gdb"
    }
  ]
}

其中:
    ${workspaceFolder}:VSCode远程连接的目录,即 kedao 的根目录
    preLaunchTask:为了便于记忆,设置为调试程序的名称 debug_lym_test
    program:调试程序的运行路径,默认 /opt/kedao/bin 目录下
    name:调试程序名称,与 preLaunchTask 相同,即 tasks.json 中任务的 lable ,建立预启动任务与调试任务的关系

  2)设置 tasks.json

在 launch.json 同级的目录中找到 tasks.json 文件,在 "tasks" 节点中增加调试任务,如:

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "debug_lym_test",
            "type": "shell",
            "command": "g++",
            "args": [
                "--std=c++23",
                "${workspaceFolder}/src_c/lym_test/prj_c_test/debugger/debug_lym_test/debug_lym_test.cpp",
                "-fPIC",
                "-o",
                "${workspaceFolder}/bin/debug_lym_test",
                "-g",
                "-Wall",
                "-I/usr/include/",
                "-I${workspaceFolder}/src_c/include",                              // 公共 include
                "-I${workspaceFolder}/src_c/lym_test/prj_c_test/debugger/debug_include",  // 系统下的 debug_include
                "-L/usr/lib64/",
                "-L${workspaceFolder}/bin/lib",
                "-ldl",
                "-lodbc",
                "-lkedao_utils",
                "-lstdc++fs",
                "-rdynamic",
                "-Wl,-rpath=./:./lib:'$$ORIGIN':'$$ORIGIN/lib'"
            ],

            "group": {
                "kind": "build",
                "isDefault": true
            },
            "presentation": {
                "echo": true,
                "reveal": "always",
                "focus": true,
                "panel": "shared"
            },
            
            "problemMatcher": "$msCompile"
        }
    ]
}

其中:
    labellaunch.json 文件预启动任务的 name 相同,两者通过此来建立关联关系;
    args:g++的编译参数,参照调试工程的 makefile 文件编写
    注意:路径别写错了

  3)开始调试

步骤1:打开调试界面

    在 kedao creator【注册服务】功能界面的操作列中,点击【调试】按钮,打开【服务debug调试(C/C++)】调试界面;
    在调试界面中,点击【增加测试用例】按钮,创建一个测试用例,如图:
在这里插入图片描述

步骤2:在 VSCode 中启动调试程序

    在 VSCode 中切换到 Run and Debug 窗口,选择调试任务 debug_lym_test,点击 运行 按钮,启动调试程序,如图:
在这里插入图片描述

步骤3:编辑服务请求参数

    根据调试的需求,编辑相应的请求参数。这里使用默认参数来调试。

步骤4:保存并调试

    在 kedao creator【服务debug调试(C/C++)】中点击【保存并调试】按钮,将服务参数发送到开发服务器,调试程序接收到请求参数后,进入调试界面,如:
在这里插入图片描述
    调试完成返回 kedao creator【服务debug调试(C/C++)】查看结果
    如果调试的时间比较长(超过65秒),调试结果返回服务超时的错误信息,不影响调试和服务逻辑,如:

{
	"code": -1,
	"err_msg": "timeout of 65000ms exceeded"
}

    如果响应结果返回 "调试程序[debug_lym_test]未启动。" 的错误提示信息,请先到 VSCode 中启动调试任务(步骤2),如:

{
	"code": -1,
	"err_msg": "调试程序[debug_lym_test]未启动。",
	"data": []
}

八、调用服务

示例一

    在 kedao creator【注册服务】功能界面的操作列中,点击【测试】按钮,弹出测试服务界面,增加测试用例,然后点保存并测试,如:
在这里插入图片描述

示例二

    在 Postman 中调用,由于服务默认需求进行签名,才能调用,手动签名比较麻烦,先将服务设置为无签名模式。
    在 kedao creator【注册服务】功能界面的操作列中,点击【修改】按钮,将签名算法改为:
在这里插入图片描述
    由于svc_test 服务已经加载为需要签名验证模式,需要重启服务 sudo systemctl restart kedao 才会加载成不需要签名模式。

kedao@kedao:/opt$ sudo systemctl restart kedao

    从示例一的测试服务界面中赋值 url 和 请求参数 到Postman 中。这里需要注意2点,一是 url 后面的 /api,不要忘记了;二是请求参数中的服务名称 svc_name 要把测试标识 @test去掉
    http方法:POST
    Url:http://192.168.43.30/api
    请求参数:

{
	"appid": "5bfb7eb48c9b4190b81e947e111ea6b1",
	"sys_name": "prj_c_test",
	"mdl_name": "mdl_test",
	"svc_name": "svc_test",
	"body": {
		"sys_head": {
			"usr_id": "",
			"org_id": "",
			"sys_id": "",
			"mdl_func_id": "",
			"login_key": ""
		},
		"data": {
			"func_id": "",
			"func_parent_id": "",
			"func_code": "",
			"menu_title": "",
			"router_path": "",
			"component_path": "",
			"icon": "",
			"func_type": 0,
			"sort_num": 0
		}
	}
}

    Postman调用如图:
在这里插入图片描述

九、并发测试

    测试服务器配置:
    Windows操作系统:Windows10;CPU:酷睿9;硬盘:SSD PCIe 4.0 x4
    Linux操作系统:虚拟机 Ubuntu24.04-live-server
    CPU:2核心
    内存:4G
    kedao并发数:server_concurrency_process=16
    在 kedao creator【注册服务】功能界面的操作列中,点击【测试】按钮,打开测试界面,调用服务仍然是 svc_test ,但服务代码中去掉了业务逻辑,同时去掉数据库连接(避免数据库对测试的影响),只保留基本的入参和出参,并且加上签名算法,这次测试服务次数 10万次并发 1000(模拟客户端数量),测试结果如下图:
在这里插入图片描述
    同时,在 /opt/kedao/bin/log 目录下查看服务调用日志,服务在服务器端的执行时长在毫秒级别,QPS > 1200
    注:这里通过客户端模拟并发访问服务器上的服务,受到客户端与服务器之间的连接数和请求参数、响应参数网络传输时间的影响,实际服务器端处理服务能力的QPS比现在看到的值要高很多。

    关于server_concurrency_process的一点说明:
    1)server_concurrency_process的值要根据CPU核心数、服务的业务类型来设置;
    2)假如服务的逻辑都在本服务器上运算,那么server_concurrency_process的值要与CPU核心(超线程)数量相当;
    3)假如服务的部分逻辑在其他服务器上,比如访问数据库,执行SQL的运算在数据库服务器上,在执行SQL语句期间,服务处于等待状态(CPU处于闲置,可以让给其他服务运算),那么server_concurrency_process的值要与大于CPU核心(超线程)数量;至于大多少,就要一点一点的测试调整。可以按CPU核心(超线程)数量的倍数调整。
    4)如果服务器运算量很少,主要用于服务分发(比如集群主服务器),那就要设置的比较大了。

    注:这里并非服务器并发性能测试,而是测试某个服务中并发能力,同时检测服务的平均执行时间,以验证单个服务的性能,为服务代码性能调优提供参考依据。
    具体的服务器性能测试,参见《kedao中间件-并发测试篇》

    至此,C++服务篇完成。

    如何在代码中调用服务,将在Vue工程篇讲解。

十、总结

    kedao中间件提供了一套完整体系的C++系统结构,包括服务源代码、数据库结构和前端vue工程源码,只需添加业务数据表、设计服务、开发功能即可。
    初学者可以通过本系统深入学习,快速入门,缩短编程学习周期,快速晋升高手行列。
    久经沙场的老将也如虎添翼,利刃在手,所向披靡。

posted @ 2025-05-07 16:14  lym_1978  阅读(72)  评论(0)    收藏  举报