系统架构工作笔记-数据展示进程与读取数据进程分离,实现低耦合(展示软件可适用任意厂家数据库)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq78442761/article/details/88634919

目录

 

背景

实践环境

逻辑结构图

运行截图

数据展示进程源码

服务进程(插件调用)源码

插件源码

MySQL相关


 

背景

工作一年零1个月了(加上实习),靠着工作中学到的东西,花了周末2天时间,做了一个小系统,这个小系统中有2个进程,一个是用于数据展示的进程。

另外一个进程是用来调取Qt插件,这个插件可能连接了Mysql,也可能连接了SQLSever等数据库,调用插件后,读取插件返回的数据库的数据;

展示软件通过设置一个描述字符串,这个字符串可以是检索Mysql的语句!

把展示软件的描述字符串内容和这个字符串的ID,通过共享内存的方式给插件调用端,插件调用端是一个中转,把数据给对应的插件。获得了数据后!

再使用共享内存的方式给数据展示进程!

实践环境

语言有C++,和SQL,

其中C++使用Qt框架(Qt Creator 5.7作为IDE,编译器为MinGw)

数据库使用的是Mysql数据库;

逻辑结构图

所以本次实践涉及3个程序,一个是数据展示进程,一个是服务进程(提供展示数据),一个是插件

运行截图

话不多说,先看动态演示!

添加很多展示数据后!

拖动动态数据:

设置描述符后:

上面设置的描述符为:

select value from sourcenetload.datasample where idval=6

以上的数据都是从数据库中读取的!

这里数据改变的原因是,我在Mysql中写了一个存储过程,这个存储过程能修改表中所有数据,

用写了一个事件,每秒进行调用,下面将会详细给出!

服务进程中转站截图:

插件端

数据展示进程源码

程序结构如下:

dynamicdata.h

#ifndef DYNAMICDATA_H
#define DYNAMICDATA_H

#include <QGraphicsItem>

class DynamicData : public QGraphicsItem
{
public:
    DynamicData();
    ~DynamicData();

    QRectF boundingRect()const Q_DECL_OVERRIDE;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) Q_DECL_OVERRIDE;

    int getId();
    void setId(const int &id);

    QString getText();
    void setText(const QString &str);

    QString getDescrib();
    void setDescrib(const QString &str);


private:
    int m_id;
    QString m_text;
    QString m_describ;
};

#endif // DYNAMICDATA_H

mygraphicsscene.h

#ifndef MYGRAPHICSSCENE_H
#define MYGRAPHICSSCENE_H

#include <QObject>
#include <QPointF>
#include <QGraphicsView>

class MyGraphicsScene : public QGraphicsScene
{
    Q_OBJECT
public:
    MyGraphicsScene(QWidget *parent = 0);
    ~MyGraphicsScene();
    void createDynamicData(const QPointF &pt, const int &id);

protected:
    void dragEnterEvent(QGraphicsSceneDragDropEvent *event) Q_DECL_OVERRIDE;
    void dropEvent(QGraphicsSceneDragDropEvent *event) Q_DECL_OVERRIDE;
    void dragMoveEvent(QGraphicsSceneDragDropEvent *event) Q_DECL_OVERRIDE;

    void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event);
};

#endif // MYGRAPHICSSCENE_H

mygraphicsview.h

#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H

#include <QObject>
#include <QGraphicsView>

class MyGraphicsView : public QGraphicsView
{
    Q_OBJECT
public:
    MyGraphicsView(QWidget *parent = 0);
    ~MyGraphicsView();

protected:
    void dragEnterEvent(QDragEnterEvent *event) Q_DECL_OVERRIDE;
    void dropEvent(QDropEvent *event) Q_DECL_OVERRIDE;

private:
    int m_count;
};

#endif // MYGRAPHICSVIEW_H

mylistwidget.h

#ifndef MYLISTWIDGET_H
#define MYLISTWIDGET_H

#include <QObject>
#include <QListWidget>

class MyListWidget : public QListWidget
{
    Q_OBJECT
public:
    MyListWidget(QWidget *parent = 0);
    ~MyListWidget();

protected:
    void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
    void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;

private:
    QPoint m_dragPosition;
    QListWidgetItem *m_dragItem;
};

#endif // MYLISTWIDGET_H

setdatadialog.h

#ifndef SETDATADIALOG_H
#define SETDATADIALOG_H

#include <QDialog>

namespace Ui {
class setDataDialog;
}

class setDataDialog : public QDialog
{
    Q_OBJECT

public:
    explicit setDataDialog(QWidget *parent = 0, const int &id = -1, const QString &str = "NULL");
    ~setDataDialog();
    QString getDescrib();

protected slots:
    void okBtnClicked();
    void cancelBtnClicked();

private:
    Ui::setDataDialog *ui;
};

#endif // SETDATADIALOG_H

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
class QSharedMemory;
class QTimer;
QT_END_NAMESPACE

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

protected slots:
    void shareMemoryTimeout();

private:
    Ui::Widget *ui;
    QSharedMemory *m_sharedMemory;
    QSharedMemory *m_sharedMemory2;
    QTimer *m_timer;
};

#endif // WIDGET_H

dynamicdata.cpp

#include "dynamicdata.h"
#include <QPainter>
#include <QDebug>
#include <QBrush>
#include <QMessageBox>


DynamicData::DynamicData()
{
    m_text = "-123456789";
    m_describ = "NULL";
}

DynamicData::~DynamicData()
{

}

QRectF DynamicData::boundingRect() const
{
    qreal penWidth = 1;
    return QRectF(0 - penWidth / 2, 0 - penWidth / 2, 80 + penWidth, 10 + penWidth);
}

void DynamicData::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option)
    Q_UNUSED(widget)
    painter->setBrush(Qt::red);
    painter->drawText(0, 0, 80, 10, Qt::AlignCenter, m_text);
}

int DynamicData::getId()
{
    return m_id;
}

void DynamicData::setId(const int &id)
{
    m_id = id;
}

QString DynamicData::getText()
{
    return m_text;
}

void DynamicData::setText(const QString &str)
{
    m_text = str;
    update();
}

QString DynamicData::getDescrib()
{
    return m_describ;
}

void DynamicData::setDescrib(const QString &str)
{
    m_describ = str;
}

main.cpp

#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();

    return a.exec();
}

mygraphicsscene.cpp

#include "mygraphicsscene.h"
#include "dynamicdata.h"
#include "setdatadialog.h"
#include <QDebug>
#include <QMimeData>
#include <QPen>
#include <QMessageBox>
#include <QGraphicsSceneDragDropEvent>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QTransform>
#include <QDragMoveEvent>


MyGraphicsScene::MyGraphicsScene(QWidget *parent) : QGraphicsScene(parent)
{

}

MyGraphicsScene::~MyGraphicsScene()
{

}

void MyGraphicsScene::createDynamicData(const QPointF &pt, const int &id)
{
    DynamicData *item = new DynamicData;
    item->setPos(pt);
    item->setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable);
    item->setId(id);
    addItem(item);
}

void MyGraphicsScene::dragEnterEvent(QGraphicsSceneDragDropEvent *event)
{
    if(event->mimeData()->hasFormat("text/plain")){

        event->acceptProposedAction();
    }
}

void MyGraphicsScene::dropEvent(QGraphicsSceneDragDropEvent *event)
{
    QGraphicsScene::dropEvent(event);
}

void MyGraphicsScene::dragMoveEvent(QGraphicsSceneDragDropEvent *event)
{
    event->accept();
}

void MyGraphicsScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
    QTransform deviceTransform;
    DynamicData* item = static_cast<DynamicData*>(itemAt(event->scenePos(), deviceTransform));
    if(item != NULL){

        setDataDialog *msetDataDialog = new setDataDialog(NULL, item->getId(), item->getDescrib());
        if(msetDataDialog->exec() == QDialog::Accepted){

            item->setDescrib(msetDataDialog->getDescrib());
        }
        delete msetDataDialog;
    }
    QGraphicsScene::mousePressEvent(event);
}

mygraphicsview.cpp

#include "mygraphicsview.h"
#include "mygraphicsscene.h"
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QMimeData>
#include <QDebug>

MyGraphicsView::MyGraphicsView(QWidget *parent) : QGraphicsView(parent)
{
    MyGraphicsScene *scene = new MyGraphicsScene;
    scene->setSceneRect(-1000, -1000, 1000, 1000);
    setScene(scene);
    m_count = 0;
}

MyGraphicsView::~MyGraphicsView()
{

}

void MyGraphicsView::dragEnterEvent(QDragEnterEvent *event)
{
    if(event->mimeData()->hasFormat("text/plain")){

        event->acceptProposedAction();
    }
    QGraphicsView::dragEnterEvent(event);
}

void MyGraphicsView::dropEvent(QDropEvent *event)
{
    if(event->mimeData()->hasFormat("text/plain")){

        MyGraphicsScene *scene = (MyGraphicsScene*)this->scene();
        scene->createDynamicData(mapToScene(event->pos()), m_count++);
        event->acceptProposedAction();
    }

    QGraphicsView::dropEvent(event);
}

mylistwidget.cpp

#include "mylistwidget.h"
#include <QMouseEvent>
#include <QListWidgetItem>
#include <QDrag>
#include <QMimeData>
#include <QPainter>
#include <QApplication>
#include <QPixmap>

MyListWidget::MyListWidget(QWidget *parent) : QListWidget(parent)
{

}

MyListWidget::~MyListWidget()
{

}

void MyListWidget::mouseMoveEvent(QMouseEvent *event)
{

    if(!(event->buttons() & Qt::LeftButton))
        return;
    if((event->pos() - m_dragPosition).manhattanLength() < QApplication::startDragDistance())
        return;
    if(!m_dragItem)
        return;
    QDrag *drag = new QDrag(this);
    QMimeData *mimeData = new QMimeData;
    mimeData->setText(currentItem()->text());
    drag->setMimeData(mimeData);

    if(mimeData->text() == "动态数据"){
        QPixmap dragImg(100, 50);
        QPainter painter(&dragImg);
        painter.setBrush(QBrush(Qt::red));
        painter.drawRect(0, 0, 100, 50);
        drag->setPixmap(dragImg);
    }
    drag->exec(Qt::CopyAction|Qt::MoveAction);
}

void MyListWidget::mousePressEvent(QMouseEvent *event)
{

    m_dragPosition=event->pos();
    m_dragItem=this->itemAt(event->pos());

    QListWidget::mousePressEvent(event);

}

setdatadialog.cpp

#include "setdatadialog.h"
#include "ui_setdatadialog.h"

setDataDialog::setDataDialog(QWidget *parent, const int &id, const QString &str) :
    QDialog(parent),
    ui(new Ui::setDataDialog)
{
    ui->setupUi(this);
    this->setWindowTitle("CSDN IT1995设置描述符号");
    ui->idLineEdit->setText(QString::number(id));
    ui->idLineEdit->setEnabled(false);
    ui->describLineEdit->setText(str);

    connect(ui->okPushButton, SIGNAL(clicked(bool)), this, SLOT(okBtnClicked()));
    connect(ui->cancelPushButton, SIGNAL(clicked(bool)), this, SLOT(cancelBtnClicked()));
}

setDataDialog::~setDataDialog()
{
    delete ui;
}

QString setDataDialog::getDescrib()
{
    return ui->describLineEdit->text();
}

void setDataDialog::okBtnClicked()
{
    accept();
}

void setDataDialog::cancelBtnClicked()
{
    reject();
}

widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include "dynamicdata.h"
#include <QSharedMemory>
#include <QGraphicsItem>
#include <QTImer>
#include <QDebug>
#include <QDataStream>
#include <QBuffer>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->setWindowTitle("CSDN IT1995");
    ui->listWidget->insertItem(0, "动态数据");
    m_sharedMemory = new QSharedMemory("CSDN IT1995");
    m_sharedMemory2 = new QSharedMemory(this);
    m_sharedMemory2->setKey("5991TI NDSC");
    m_timer = new QTimer(this);
    connect(m_timer, SIGNAL(timeout()), this, SLOT(shareMemoryTimeout()));
    m_timer->start(500);
}

Widget::~Widget()
{
    delete ui;
}

//这里我偷懒了,应该要抽2-3个函数出来的。
void Widget::shareMemoryTimeout()
{
    //偷懒了
    QList<QGraphicsItem *> list = ui->graphicsView->items();
    if(list.isEmpty())
        return;

    if(m_sharedMemory->isAttached()){

        if(!m_sharedMemory->detach()){

            return;
        }
    }

    QString str;

    foreach(QGraphicsItem *item, list){

        int id = static_cast<DynamicData*>(item)->getId();
        QString describ = static_cast<DynamicData*>(item)->getDescrib();
        str.append(QString::number(id) + ";" + describ + "#");
    }
    str = str.left(str.length() - 1);

    QBuffer buffer;
    buffer.open(QBuffer::ReadWrite);
    QDataStream out(&buffer);
    out << str;

    if(!m_sharedMemory->create(buffer.size())){

        qDebug() << "create failed!";
        return;
    }

    m_sharedMemory->lock();
    char *to = static_cast<char*>(m_sharedMemory->data());
    memcpy(to, str.toStdString().c_str(), (int)buffer.size());
    m_sharedMemory->unlock();


    //获取数据 偷懒了
    if(!m_sharedMemory2->attach()){

        return;
    }

    QBuffer buffer2;
    m_sharedMemory2->lock();
    buffer2.setData((char*)m_sharedMemory2->constData(), m_sharedMemory2->size());
    buffer2.open(QBuffer::ReadOnly);
    QString data2 = buffer2.readAll();
    m_sharedMemory2->unlock();
    m_sharedMemory2->detach();

    //设置item的值 偷懒了
    QStringList listData = data2.split("#");
    listData.removeLast();
    qDebug() << listData;
    for(int i = 0; i < listData.size(); i++){

        QStringList idList = listData[i].split(";");
        foreach(QGraphicsItem *item, list){

            int id = static_cast<DynamicData*>(item)->getId();
            if(id == idList[0].toInt()){

                static_cast<DynamicData*>(item)->setText(idList[1]);
                ui->graphicsView->update();
            }
        }
    }
}

setdatadialog.ui

widget.ui

服务进程(插件调用)源码

程序结构如下:

appinterface.h

#ifndef APPINTERFACE_H
#define APPINTERFACE_H

#include <QObject>

class AppInterface{

public:
    virtual ~AppInterface(){}
    virtual QString name() = 0;
    virtual QWidget *widget() = 0;

    virtual void setSQLData(const QString &cmdData){ Q_UNUSED(cmdData); }
    virtual QString getSQLData(){ return""; }

    QString libDir(){

        return m_libDir.isEmpty() ? "./" : m_libDir;
    }

    void setLibDir(const QString &libDir){

        m_libDir = libDir;
    }

private:
    QString m_libDir;

};

QT_BEGIN_NAMESPACE

#define Interface_iid "com.IT1995.Interface"
Q_DECLARE_INTERFACE(AppInterface, Interface_iid)

QT_END_NAMESPACE

#endif // APPINTERFACE_H

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QMap>

namespace Ui {
class Widget;
}

QT_BEGIN_NAMESPACE
class QSharedMemory;
QT_END_NAMESPACE

class AppInterface;

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = 0);
    void readPlugin();
    ~Widget();

protected:
    void timerEvent(QTimerEvent *event) Q_DECL_OVERRIDE;
    void setMap(const QString &data);

private:
    Ui::Widget *ui;
    QSharedMemory *m_sharedMemory;
    QSharedMemory *m_sharedMemory2;
    int m_timerId;
    int m_timerId2;
    QMap<int, QString> m_map;
    QList<AppInterface*> m_widgetList;
    QString m_sendMsg;
};

#endif // WIDGET_H

main.cpp

#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();

    return a.exec();
}

widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include "appinterface.h"
#include <QDir>
#include <QPluginLoader>
#include <QMessageBox>
#include <QSharedMemory>
#include <QDebug>
#include <QDataStream>
#include <QBuffer>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->setWindowTitle("CSDN IT1995中转站");
    m_sharedMemory = new QSharedMemory(this);
    m_sharedMemory2 = new QSharedMemory(this);
    m_sharedMemory->setKey("CSDN IT1995");
    m_sharedMemory2->setKey("5991TI NDSC");
    m_timerId = startTimer(500);
    m_timerId2 = startTimer(500);
    m_sendMsg = "";
    readPlugin();
}

void Widget::readPlugin()
{
    QDir pluginsDir(qApp->applicationDirPath() + "/plugin");
    
    foreach(QString filename, pluginsDir.entryList(QDir::Files)){
        
        QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(filename));
        QObject *plugin = pluginLoader.instance();
        if(plugin){
            
            AppInterface *app = qobject_cast<AppInterface*>(plugin);
            if(app){
                
                m_widgetList.append(app);
            }
        }
    }

    if(m_widgetList.isEmpty()){
        
        QMessageBox::warning(this, "warning", "load plugin error");
    }
    else{
        
        foreach(AppInterface *w, m_widgetList){

            w->widget();
            qDebug() << w->name();
        }
    }
}

Widget::~Widget()
{
    delete ui;
}

void Widget::timerEvent(QTimerEvent *event)
{
    if(event->timerId() == m_timerId2){

        QBuffer buffer;
        buffer.open(QBuffer::ReadWrite);
        QDataStream out(&buffer);
        out << m_sendMsg;
        if(!m_sharedMemory2->create(buffer.size())){

            qDebug() << "create failed!";
            return;
        }

        m_sharedMemory2->lock();
        char *to = static_cast<char*>(m_sharedMemory2->data());
        memcpy(to, m_sendMsg.toStdString().c_str(), (int)buffer.size());
        m_sharedMemory2->unlock();

    }
    else if(event->timerId() == m_timerId){

        if(!m_sharedMemory->attach()){

            return;
        }

        QBuffer buffer;
        m_sharedMemory->lock();
        buffer.setData((char*)m_sharedMemory->constData(), m_sharedMemory->size());
        buffer.open(QBuffer::ReadOnly);
        QString data = buffer.readAll();
        m_sharedMemory->unlock();
        m_sharedMemory->detach();
        setMap(data);
        //qDebug() << data;

        //获取数据
        m_sendMsg.clear();
        foreach(AppInterface *w, m_widgetList){

            QList<int> mapList = m_map.keys();
            for(int i = 0; i < mapList.size(); i++){

               w->setSQLData(m_map.value(mapList[i]));
               m_sendMsg.append(QString::number(mapList[i]) + ";" + w->getSQLData() + "#");
            }
        }
        m_sendMsg.left(m_sendMsg.size() - 1);
        qDebug() << m_sendMsg;
    }
}

void Widget::setMap(const QString &data)
{
    QStringList list = data.split("#");
    for(int i = 0; i < list.size(); i++){

        QStringList singleList = list[i].split(";");
        m_map.insert(singleList[0].toInt(), singleList[1]);
    }
}

widget.ui

插件源码

程序结构如下:

这个插件主要是用于读取Mysql

appinterface.h

#ifndef APPINTERFACE_H
#define APPINTERFACE_H

#include <QObject>

class AppInterface{

public:
    virtual ~AppInterface(){}
    virtual QString name() = 0;
    virtual QWidget *widget() = 0;

    virtual void setSQLData(const QString &cmdData){ Q_UNUSED(cmdData); }
    virtual QString getSQLData(){ return""; }

    QString libDir(){

        return m_libDir.isEmpty() ? "./" : m_libDir;
    }

    void setLibDir(const QString &libDir){

        m_libDir = libDir;
    }

private:
    QString m_libDir;

};

QT_BEGIN_NAMESPACE

#define Interface_iid "com.IT1995.Interface"
Q_DECLARE_INTERFACE(AppInterface, Interface_iid)

QT_END_NAMESPACE

#endif // APPINTERFACE_H

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QSqlDatabase>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = 0);
    void setData(const QString &cmdData);
    QString getData();
    ~Widget();

protected:
    bool connectMySQL();
    QString getSqlData(const QString &cmd);

private:
    Ui::Widget *ui;
    QSqlDatabase m_db;
    QString m_cmd;
};

#endif // WIDGET_H

widgetplugin.h

#ifndef WIDGETPLUGIN_H
#define WIDGETPLUGIN_H

#include <QObject>
#include "appinterface.h"
#include "widget.h"


class WidgetPlugin : public QObject, AppInterface
{
    Q_OBJECT

    Q_PLUGIN_METADATA(IID "com.IT1995.Interface")
    Q_INTERFACES(AppInterface)

public:
    QString name(){ return QStringLiteral("MySQL插件");}
    QWidget *widget(){

        m_widget = new Widget();
        return m_widget;
    }
    void setSQLData(const QString &cmdData){

        m_widget->setData(cmdData);
    }


    virtual QString getSQLData(){

        return m_widget->getData();
    }

private:
    Widget *m_widget;
};

#endif // WIDGETPLUGIN_H

main.cpp

#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();

    return a.exec();
}

widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QMessageBox>
#include <QSqlQuery>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    if(!connectMySQL()){

        //QMessageBox::information(this, "tip", "连接Mysql数据库失败");
        return;
    }

    //QMessageBox::information(this, "tip", "连接Mysql数据库成功");
}

void Widget::setData(const QString &cmdData)
{
    m_cmd = cmdData;
}

QString Widget::getData()
{
    return getSqlData(m_cmd);
}


Widget::~Widget()
{
    delete ui;
}

bool Widget::connectMySQL()
{
    m_db = QSqlDatabase::addDatabase("QMYSQL");
    m_db.setHostName("127.0.0.1");
    m_db.setPort(3306);
    m_db.setDatabaseName("mysql");
    m_db.setUserName("root");
    m_db.setPassword("root");
    if(!m_db.open()){

        qDebug() << "error";
        return false;
    }

    return true;
}

QString Widget::getSqlData(const QString &cmd)
{

    QSqlQuery query;
    if(!query.exec(cmd) || query.size() <= 0){

            return "-1234567890";
    }
    while(query.next()){

            //理论只有一条
            return query.value(0).toString();
    }

    return "-1234567890";
}

widget.ui

这个ui里面没东西

MySQL相关

mysql存储过程,表结构,事件已经在下面的博文中给出了,在此不再详细说明:

MySQL工作笔记-使用事件和存储过程定时更新某表数据

https://blog.csdn.net/qq78442761/article/details/88597665

猜你喜欢

转载自blog.csdn.net/qq78442761/article/details/88634919