【开源】基于Qt的跨平台插件式开发框架QCPFrame(二)

六、组件

       对于QCPFrame而言,一切皆插件的思想决定了其主要业务和功能都将以组件的形式存在,因此本章内容我们来讲讲QCPFrame组件的开发。

1. 创建一个插件工程

       如果我告诉你拷贝一个工程,然后修改*.pro和PluginIO类。你一定会觉得很Low,可是这也确实是最为快捷的一种创建插件工程的方式。好了,下面我们来看怎么老老实实创建一个QCPFrame插件工程。

1.1 新建动态链接库工程

你可以通过Qt创建一个动态库工程,如下图:

基于Qt的BS架构 基于qt的开发框架_c++

基于Qt的BS架构 基于qt的开发框架_json_02

1.2. 添加接口文件

在工程中添加interface文件夹下面的两个接口文件“coreinterface.h”和“plugininterface.h”。

1.3. 添加组件基类库文件

    在工程右键菜单选择“添加库...”,找到QCPF_PluginModel.h及QCPF_PluginModel.lib,典型的路径如下:

unix|win32: LIBS += -L$$PWD/../../../bin/ -lQCPF_PluginModel

INCLUDEPATH += $$PWD/../../QCPF_PluginModel

DEPENDPATH += $$PWD/../../QCPF_PluginModel

1.4. 新建组件实现类

在工程右键菜单选择“Add New”,创建“PluginIO.h/PluginIO.cpp”。其头文件如下所示:

  PluginIO.h

#ifndef QCPF_PLUGINIO_H
#define QCPF_PLUGINIO_H

#include "../../QCPF_PluginModel/qcpf_nonsystempluginmodel.h"

class PluginIO : public QCPF_NonSystemPluginModel
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID Plugin_Interface_iid FILE "QPlugin.json")
    Q_INTERFACES(Plugin_Interface)
public:
    PluginIO();
    ~PluginIO();

public:
    static PluginIO* getInstance();//静态获取实例
};

#endif

 PluginIO.cpp

#include "PluginIO.h"

#include <QVariant>
#include <QStringLiteral>
#include <QAction>


PluginIO* instance;

PluginIO::PluginIO()
{
    instance = this;

    I_PluginID = QStringLiteral("QPlugin1");
    I_PluginAliasName = QStringLiteral("QPlugin1");
    I_PluginAuther = QStringLiteral("xxxx");
    I_PluginVersion = QStringLiteral("1.0.0.1");
    I_PluginComment = QStringLiteral("QPlugin1 comment");
    I_PluginAuthority = AT_USER1;
    I_PluginTag = QStringLiteral("NON_SYSTEM\\DataSave");
}

PluginIO::~PluginIO(){}

PluginIO* PluginIO::getInstance()
{
    return instance;
}

        PluginIO.h中的代码#include "../../QCPF_PluginModel/qcpf_nonsystempluginmodel.h"表示包含非系统组件类的头文件,如果你要创建的是一个系统组件,那么就包含qcpf_systempluginmodel.h。同样的,继承的类也要与之对应。

1.4. 创建QPlugin.json文件

       在项目目录下创建QPlugin.json空文件,用于临时存储meta数据。

       至此一个典型的插件工程就建好了。你可以根据你的业务需求在“PluginIO.h/PluginIO.cpp”中添加代码,或者在工程中添加文件。但请记住“PluginIO.h/PluginIO.cpp”始终是你与系统之间通讯生命周期中的接口文件。

2. PluginIO文件功能及代码风格建议

    PluginIO文件是组件与内核连接的主要渠道,我们以系统组件“QCPF_UserManager”为例来讲讲如何在该文件中添加功能代码。QCPF_UserManager.h代码如下:

/*
Author : Jamie.Tong
QQ      : 260271262
Data    : 2020-09-07
License: GPL v3.0
*/

#ifndef QCPF_PLUGINIO_H
#define QCPF_PLUGINIO_H

#include "../../QCPF_PluginModel/qcpf_systempluginmodel.h"

class PluginIO : public QCPF_SystemPluginModel
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID Plugin_Interface_iid FILE "QPlugin.json")
    Q_INTERFACES(Plugin_Interface)
public:
    PluginIO();
    ~PluginIO();

    void InitActionList(Plugin_Interface* plugin) Q_DECL_OVERRIDE;
    void InitFunctionList(Plugin_Interface* plugin) Q_DECL_OVERRIDE;
    void InitWidgetList(Plugin_Interface* plugin) Q_DECL_OVERRIDE;

public:
    tagOutputInfo tinfo;
    static PluginIO* getInstance();
     int GetUsersInfoFromJson();
private:
    void Action_ShowUserManager(bool checkState);
    int Function_VerifyLoginInfo(QVariant arg_in, QVariant& arg_out);
public slots:
    //当core初始化时要执行的过程
    int OnCoreInitialize() Q_DECL_OVERRIDE;
    int slot_InputInfo(tagOutputInfo& info) Q_DECL_OVERRIDE;
};

#endif

        其中 InitActionList用于初始化和收集本组件中的所有Action,InitFunctionList用于初始化和收集本组件中的所有Function,InitWidgetList用于初始化和收集本组件中的所有Widget。

        getInstance() 用于获取this实例,在有些时候非常有用。

        OnCoreInitialize()是组件被内核连接时会被调用的过程,在本组件中,我们在OnCoreInitialize过程中调用GetUserInfoFromJson()函数,目的是组件被连接时就获取到用户登录信息。

        slot_InputInfo槽是用于接收输入信息而声名的,用于对输入的消息做出不同的响应。

        建议用于Action的函数以“Action_”开头,用于Function的函数以“Function_”开头,这能便于我们快速归类和查找我们要编辑的功能模块。

/*
Author : Jamie.Tong
QQ      : 260271262
Data    : 2020-09-07
License: GPL v3.0
*/

#include "PluginIO.h"

#include <QWidget>
#include <QVariant>
#include <qobject.h>

#include <QStringLiteral>
#include <QAction>

#include "usermanager.h"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QJsonValue>
#include <QCryptographicHash>//用于md5加密

PluginIO* instance;
PluginIO::PluginIO()
{
    instance = this;

    I_PluginID = tr("QCPF_UserManager");
    I_PluginAliasName = tr("User Manager");
    I_PluginAuther = tr("Jamie.T");
    I_PluginVersion = tr("1.0.0.4");
    I_PluginComment = tr("Manage users.");
    I_PluginTag = tr("SINGLETON\\SYSTEM\\USER_MANAGER");
    I_PluginAuthority = AT_MANAGER1;

    //GetUsersInfoFromJson();不能放在这里,基类的_core还没构造完
}

PluginIO::~PluginIO(){}

PluginIO* PluginIO::getInstance()
{
    return instance;
}

//接口方法实现
/***************************************************
*                       方法接口                                       *
***************************************************/
int PluginIO::OnCoreInitialize()
{
    return GetUsersInfoFromJson();
}

void PluginIO::InitActionList(Plugin_Interface* plugin)
{
    PluginActionInfo* pai = new PluginActionInfo();
    pai->_actionName = tr("Show User Manager");
    pai->_actionDetail = tr("User Manager.");
    pai->_pAction = (FPTR_ACTION)(&PluginIO::Action_ShowUserManager);

    plugin->I_ActionList.append(pai);

}

void PluginIO::InitFunctionList(Plugin_Interface* plugin)
{
    //--------------------------------------------
    PluginFunctionInfo* pfi_verifyLoginInfo = new PluginFunctionInfo();
    pfi_verifyLoginInfo->_functionName = tr("VerifyLoginInfo");
    pfi_verifyLoginInfo->_functionDetail = tr("Verify login info.");
    pfi_verifyLoginInfo->_pFunction = (FPTR_FUNC_CLS)(&PluginIO::Function_VerifyLoginInfo);

    plugin->I_FunctionList.append(pfi_verifyLoginInfo);
}

void PluginIO::InitWidgetList(Plugin_Interface* plugin)
{
    QCPF_Model* core = (QCPF_Model*)_core;
    PluginWidgetInfo *nFormInfo = new PluginWidgetInfo();

    nFormInfo->_showType = ST_POPUP;
    nFormInfo->_widget = new UserManager(core);
    nFormInfo->_origWidth = nFormInfo->_widget->width();
    nFormInfo->_origHeight = nFormInfo->_widget->height();
    nFormInfo->_widgetDetail = tr("It's used for managing the Non-System plugins");

    plugin->I_WidgetList.append(nFormInfo);
}

int PluginIO::slot_InputInfo(tagOutputInfo& info)
{
    if(info._type == INFT_PLUGIN_COLLECT_FINISHED)


    return 0;
}

int PluginIO::GetUsersInfoFromJson()
{
    QFile file(_core->I_ApplicationDirPath + "/Data/User/Users.dat");
    if(!file.open(QIODevice::ReadOnly))
        return -1;

    QByteArray allData = QByteArray::fromBase64(file.readAll());
    file.close();

    //进行JSON相关的处理
    QJsonParseError json_error;
    QJsonDocument jsonDoc(QJsonDocument::fromJson(allData, &json_error));
    if(json_error.error != QJsonParseError::NoError)
        return -2;

    QJsonObject rootObj = jsonDoc.object();
    //数据存入QJsonObject格式的rootObj中
    if (rootObj.contains("Users"))
    {
        QJsonValue value = rootObj.value("Users");
        if (value.isArray())
        {
            QJsonArray array = value.toArray();
            int nSize = array.size();

            _core->I_UserInfoLst.clear();
            for (int i = 0; i < nSize; ++i)
            {
                UserInfo* tUser = new UserInfo();
                tUser->_userName = array.at(i).toObject().value("UserName").toString();
                tUser->_password = array.at(i).toObject().value("Password").toString();
                QString authStr = array.at(i).toObject().value("Authority").toString();
                int auth = authStr.toInt();
                tUser->_authority = (AuthorityType)auth;
                tUser->_createDatetime = array.at(i).toObject().value("Datetime").toString();
                _core->I_UserInfoLst.append(tUser);
            }
        }
    }

    return 0;
}

/***********************************************************************
 *  action 函数指针所对应的回调函数
 * *********************************************************************/
void PluginIO::Action_ShowUserManager(bool checkState)
{
    QAction* actSender = (QAction*)sender();
    if(I_WidgetList.count()>0)
    {
        foreach (PluginWidgetInfo* pwi, instance->I_WidgetList) {
            if(pwi->_widget->objectName() == QStringLiteral("UserManager"))
            {
                pwi->_widget->setWindowIcon(actSender->icon());
                ((QDialog*)pwi->_widget)->exec();
            }
        }
    }
}

struct tagUserInfo{
    QString userName;
    QString password;
};
Q_DECLARE_METATYPE(tagUserInfo)

int PluginIO::Function_VerifyLoginInfo(QVariant arg_in, QVariant& arg_out)
{
    arg_out = false;

    if(arg_in.canConvert<tagUserInfo>())
    {
        tagUserInfo userInfo =arg_in.value<tagUserInfo>();

        foreach (UserInfo* uInfo, _core->I_UserInfoLst) {
            if(uInfo->_userName == userInfo.userName)
            {
                 if(uInfo->_password == userInfo.password)
                 {
                    _core->I_CurrentUserInfo._userName = uInfo->_userName;
                    _core->I_CurrentUserInfo._password = uInfo->_password;
                    _core->I_CurrentUserInfo._authority = uInfo->_authority;
                    _core->I_CurrentUserInfo._createDatetime = uInfo->_createDatetime;
                    _core->I_CurrentUserInfo._detail = uInfo->_detail;
                    arg_out = true;
                 }
                break;
            }
        }
    }

    return 0;
}

int GetUsersInfoFromJson(),而代码开始部分的以“I_”开头的系列接口的填充将用于本组件信息的暴露。

小结:

        QCPFrame插件开发的流程其实很简单,但是要实现复杂的功能还是有很多事情要做。仔细研究源码,你会发现QCPFrame的主要的功能如系统管理器,插件管理器,用户管理器等都是由插件来实现的,你完全可以从这些插件源码中看到一些插件设计及通讯的思路。为其嫁接新的业务时,需要考虑单独设计一个业务Model,并在一个插件的PluginIO中将该Model引入插件系统中。不要把业务与QCPFrame_Model进行耦合,那样就失去了框架最初设计的意义,我们只需要将其他业务的Model引入一个插件的PluginIO类中,使QCPF_Model/QCPF_ViewModel与你的业务Model在这一个插件中进行相互访问,即可解耦。即新的业务只跟该插件相关,而与QCPFrame插件系统无关。最终希望达到的目标是QCPF_Model.dll/QCPF_ViewModel.dll及QCPF_HostView.exe不再更新,更新的只是插件。

      最后,祝你开发愉快,如果您对此有什么疑问,可以留言进行讨论。