QT: 基于QT和嵌入式的视频监控系统


项目开发报告
)

1 项目简介

1.1 概述

本系统是一个基于Qt开发的远程控制监控系统。采用了C/S架构,服务端运行在X4418实验平台上,客户端运行在与实验平台网络通畅的主机上。

本系统分为两大模块:登陆模块监控模块

服务器端能够通过外置摄像头监控,保存数据库,响应客户端的登陆,注册,截图,暂停等请求。
客户端能够远程访问服务器,在登陆后,进行截屏,暂停等操作。

1.2 开发环境

  • 基于Cortex-A9 Android&Linux(s4418)
  • X4418实验平台
  • Qt5.0以上版本

1.3 其他支持

  • sqlite数据库(QSql
  • 内核(课程提供)

1.4 应用界面

1.4.1 服务器端

在这里插入图片描述

1.4.2 客户端

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

1.5 程序使用

  • 1 服务器端运行后,此时没有用户登录,CurrentUser一栏显示为空。

在这里插入图片描述

  • 2 启动客户端,按照192.168.0.101:19999(默认显示)连接到服务器。

在这里插入图片描述

  • 3 开启三个页面,分别测试重复注册,登陆空账户,顺利注册。

重复注册

在这里插入图片描述

登陆无效账户

在这里插入图片描述

顺利注册(test@test)

在这里插入图片描述

  • 4 此时,用户登录,服务端页面CurrentUser显示test。点击暂停Stop,暂停屏幕。

在这里插入图片描述

  • 5 点击截图ScreenShot,获取截图。

在这里插入图片描述

2 项目开发

2.1 搭建基于实验平台数据库

2.1.1 数据库详情

  • Table: User
ItemDescriptionType
UserNameUser Account[regist and log]varchar
PasswordPasswordvarchar
  • 变量QSqlDatabase db;

  • 数据库初始化

    //open database
    db = QSqlDatabase::addDatabase("QSQLITE", "QSQLITE");
    db.setDatabaseName("./MyDB.db");
    bool ok = db.open();
    //create table
    if (ok) {
        QSqlQuery qsQuery = QSqlQuery(db);
        QString strSqlText = QString("CREATE TABLE User ( Username       VARCHAR(256),Password    VARCHAR(256));");
        qsQuery.prepare(strSqlText);
        if(!qsQuery.exec())
        {
            QSqlError lastError = qsQuery.lastError();
            qDebug() << lastError.driverText() << QString(QObject::tr("Create failed."));
        } else {
            qDebug() << "Create table user";
        }
        qsQuery.finish();
    }

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 需要支持:Qt += sql

2.1.2 数据库接口及实现

设计思想:

由类MonitoeServer来控制,数据库接口只提供 服务。

2.1.2.1 用户注册
  • sql语句
INSERT INTO USER (Username, Password)
    VALUES (_name, _pw)

 
 
  • 1
  • 2
  • void Regist(QString name, QString pw)
//user regist
void MonitorServer::Regist(QString name, QString pw)
{
    QSqlQuery qsQuery = QSqlQuery(db);
    qsQuery.prepare("INSERT INTO User (Username, Password) "
                      "VALUES (:name, :pw)");
    qsQuery.bindValue(":name", name);
    qsQuery.bindValue(":pw", pw);
    if(!qsQuery.exec())
    {
        QSqlError lastError = qsQuery.lastError();
        qDebug() << lastError.driverText() << QString(QObject::tr("Insert failed."));
    }
    qsQuery.finish();
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
2.1.2.2 用户查询
  • sql语句
SELECT * FROM USER
    WHERE Username = _name
        AND Password = _pw

 
 
  • 1
  • 2
  • 3
  • bool ComfirmUser(QString name, QString pw)
//comfirm account exsists or not
bool MonitorServer::ComfirmUser(QString name, QString pw)
{
    QSqlQuery qsQuery = QSqlQuery(db);
    qsQuery.exec("select * from User WHERE Username = '" + name + "' AND Password = '" + pw + "'");
    if(!qsQuery.exec())
    {
        QSqlError lastError = qsQuery.lastError();
        qDebug() << lastError.driverText() << QString(QObject::tr("Query failed."));
    }
    if (qsQuery.next()) {
        qsQuery.finish();
        return true;
    } else {
        qsQuery.finish();
        return false;
    }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

2.2 监控系统客户端及服务端设计

2.2.1 CS模型

采用Client-Server模型。

根据需求,将监控系统(实验平台)设置为服务器端[192.168.0.101:19999],而用户端设置为客户端。

服务器端具有

  • 用户数据库
  • 监控

而客户端不保留有数据(从服务器端请求的数据除外)。

2.2.2 功能

服务端能够实时监控。

客户端在登陆后能够控制客户端暂停 继续 获取截屏

2.2.2 服务机与客户机交互

  • 交互模式

客户端与服务器端[192.168.0.101:19999]连接后。每次交互时,客户端向服务器端发送一条请求,服务器端处理后向客户端发送一条响应。

由于过程简单,因此不设定具体形式。

采用tcp连接。

  • request response集合
  1. 登录请求
  • request: login-{UserName}-{Password}-end
  • response(success): login-success
  • response(fail: No Account): login-fail
  1. 注册请求
  • request: regist-{UserName}-{Password}-end
  • response(success): regist-success
  • response(fail: Has Account): regist-fail
  1. 截屏请求
  • request: shot-{UserName}-end-end
  • response: Image
    • Send two message back, first is response, second is Image(QByteArray)
  1. 暂停请求
  • request: stop-{UserNmae}-end-end
  • response(success): stop-success
  • response(fail: In Stop State): stop-fail
  1. 继续请求
  • request: continue-{UserName}-end-end
  • response(success): continue-success
  • response(fail): continue-fail

2.3 客户端设计

2.3.1 概述

为了配合Server使用QCamera,使用QWidget设计师类。

下面分为操作界面设计 操作流程 实现及关键代码 几个部分进行介绍。

2.3.2 操作界面设计

  • 连接到服务器

在这里插入图片描述

  • 登陆/注册

在这里插入图片描述

  • 登陆/注册失败

在这里插入图片描述

  • 主页面

在这里插入图片描述

  • 总设计图

由于只有两个场景,且每一个场景中的组件数目都不多,因此将所有组件放在一个页面中,通过主类的状态控制及组建的SetVisible()方法来控制显示。

在这里插入图片描述

在这里插入图片描述

2.3.3 操作流程

输入服务器地址及端口号(Initial: 127.0.0.1:19999, Server: 192.168.0.101:19999),并连接到服务器。

输入账号及密码,完成登陆/注册。

输入错误,返回上一页面重新输入。

通过按钮控制。(ScreenShot: 截屏; Stop/Continue: 暂停/继续)

Tips:

1. Stop按钮在点击之后会变成Continue,再次点击恢复。

2. 点击ScreenShot按钮后屏幕上会显示当前得到的截屏。

2.3.4 客户端实现及关键代码

  • 主类MonitorClient
class MonitorClient : public QWidget
{
    Q_OBJECT

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

QTcpSocket *pTcpSocketClient;           //socket(client)
QString name;                           //username

bool getPic;                            //recieve picture(true), other request
void SendMesg(QString strMesg);         //send request to server

private:
Ui::MonitorClient *ui;

private slots:
//connect to server
void slotConnectServer();

//get response from server &amp; deal response
void slotReadMesg();

//log in, send login request
void slotLogin();

//regist, send regist request
void slotRegist();

//return, return to the last layer
void slotReturn();

//screen shot, send screen shot request
void slotShot();

//stop/continue, send stop/ contimue request
void slotStop();

};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 初始化
    ui->setupUi(this);
    //connect slot
    this->connect(ui->pushButton_ConnectServer,SIGNAL(clicked()),this,SLOT(slotConnectServer()));
    this->connect(ui->pushButton_login,SIGNAL(clicked()),this,SLOT(slotLogin()));
    this->connect(ui->pushButton_regist,SIGNAL(clicked()),this,SLOT(slotRegist()));
    this->connect(ui->pushButton_return,SIGNAL(clicked()),this,SLOT(slotReturn()));
    this->connect(ui->pushButton_shot,SIGNAL(clicked()),this,SLOT(slotShot()));
    this->connect(ui->pushButton_stop,SIGNAL(clicked()),this,SLOT(slotStop()));
//set views
getPic = false;
ui-&gt;label_warning-&gt;setText("Input the server IP and port");
ui-&gt;label_name-&gt;setText("IP:");
ui-&gt;label_pw-&gt;setText("Port:");
ui-&gt;lineEdit_name-&gt;setText("127.0.0.1");
ui-&gt;lineEdit_pw-&gt;setText("19999");
ui-&gt;splitter_log-&gt;setVisible(false);
ui-&gt;splitter_screen-&gt;setVisible(false);
ui-&gt;label_image-&gt;setVisible(false);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 连接服务器 void slotConnectServer()
connect to server
void MonitorClient::slotConnectServer()
{
    //get IP and Port
    if(ui->lineEdit_name->text().isEmpty() ||ui->lineEdit_pw->text().isEmpty())
    {
        //Warning
        QMessageBox::warning(this,tr("Warning"),tr("Please input IP OR NetPort!"));
        return;
    }
    //abort and reconnect
    pTcpSocketClient = new QTcpSocket(this);
    pTcpSocketClient->abort();
    QString qStrIP = ui->lineEdit_name->text();
    quint16 netport = ui->lineEdit_pw->text().toInt();
    pTcpSocketClient->connectToHost(qStrIP,netport);
//bind the slot and readyRead
connect(pTcpSocketClient,SIGNAL(readyRead()),this,SLOT(slotReadMesg()));

//set visible
ui-&gt;label_warning-&gt;clear();
ui-&gt;lineEdit_name-&gt;clear();
ui-&gt;lineEdit_pw-&gt;clear();
ui-&gt;label_name-&gt;setText("UserName:");
ui-&gt;label_pw-&gt;setText("Password:");
ui-&gt;lineEdit_pw-&gt;setEchoMode(QLineEdit::Password);
ui-&gt;pushButton_ConnectServer-&gt;setVisible(false);
ui-&gt;splitter_log-&gt;setVisible(true);

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 向服务端发送请求 void SendMesg(QString strMesg)
//send message to server
void MonitorClient::SendMesg(QString strMesg) //发送消息
{
    pTcpSocketClient->write(strMesg.toStdString().c_str(),strlen(strMesg.toStdString().c_str()));
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 登陆 void slotLogin()
//log in, send login request
void MonitorClient::slotLogin()
{
    QString command = "login";
    QString name = ui->lineEdit_name->text();
    QString pw = ui->lineEdit_pw->text();
    QString msg = command + "-" + name + "-" + pw + "-end";
    this->SendMesg(msg);
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 注册 void slotRegist()
//regist, send regist request
void MonitorClient::slotRegist()
{
    QString command = "regist";
    QString name = ui->lineEdit_name->text();
    QString pw = ui->lineEdit_pw->text();
    QString msg = command + "-" + name + "-" + pw + "-end";
    this->SendMesg(msg);
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 截屏 void slotShot()
//screen shot, send screen shot request
void MonitorClient::slotShot()
{
    //need to get Image from server
    this->getPic = true;
    //set views
    QString command = "shot";
    QString name = ui->lineEdit_name->text();
    QString file = ui->lineEdit_test->text();
    QString msg = command + "-" + name + "-" + file + "-end";
    this->SendMesg(msg);
    ui->label_warning->setText("Screen Shot");
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 暂停/继续 void slotStop()
//stop/continue, send stop/ contimue request
void MonitorClient::slotStop() {
    if (ui->pushButton_stop->text() == "Stop") {
        //set pushButton
        ui->pushButton_stop->setText("Continue");
        QString command = "stop";
        QString name = ui->lineEdit_name->text();
        QString msg = command + "-" + name + "-end-end";
        this->SendMesg(msg);
    } else {
        //set pushButton
        ui->pushButton_stop->setText("Stop");
        QString command = "continue";
        QString name = ui->lineEdit_name->text();
        QString msg = command + "-" + name + "-end-end";
        this->SendMesg(msg);
    }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 信息读取及处理 void slotReadMesg()
//get response from server & deal response
void MonitorClient::slotReadMesg()
{
    //read the response
    QByteArray qba= pTcpSocketClient->readAll();
    //if it's a screen shot request
    if (getPic) {
        //convert QByteArray to Image
        QImage *image2 = new QImage;
        image2->loadFromData(qba, "PNG");
        //show the Image at the label
        ui->label_image->setPixmap(QPixmap::fromImage(*image2));
        ui->label_image->show();
        //set the getImage flag to false
        getPic = false;
        return;
    }
    //convert the request to the QString to handle
    QString ss=QVariant(qba).toString();
    QStringList resp = ss.split("-");
    if (resp[0] == "login") {
        //login response
        if (resp[1] == "success") {
            ui->label_warning->setText("Login success");
            name = resp[2];
            ui->splitter_user->setVisible(false);
            ui->splitter_screen->setVisible(true);
            ui->label_image->setVisible(true);
        } else{
            ui->label_warning->setText("Login fail");
            ui->splitter_name->setVisible(false);
            ui->splitter_pw->setVisible(false);
            ui->splitter_log->setVisible(false);
        }
    } else if (resp[0] == "regist"){
        //regist response
        if (resp[1] == "success") {
            ui->label_warning->setText("Regist success");
            name = resp[2];
            ui->splitter_user->setVisible(false);
            ui->splitter_screen->setVisible(true);
            ui->label_image->setVisible(true);
        } else{
            ui->label_warning->setText("Regist fail");
            ui->splitter_name->setVisible(false);
            ui->splitter_pw->setVisible(false);
            ui->splitter_log->setVisible(false);
        }
    }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

2.4 服务端设计

2.4.1 概述

包含数据库及一个摄像头。

启动后,能够相应客户端的请求,客户端能够通过请求控制监控。

  • QT += multimedia
  • QT += multimediawidgets

2.4.2 界面设计

在这里插入图片描述

在这里插入图片描述

2.4.3 实现及关键代码

  • 主类
class MonitorServer : public QWidget
{
    Q_OBJECT

public:
explicit MonitorServer(QWidget *parent = 0);
~MonitorServer();
QTcpServer *m_pTcpServer;

QTcpSocket *pTcpSocketServer;           //Server

QSqlDatabase db;                        //user account information database
QString user;                           //current user

//user regist
void Regist(QString name, QString pw);

//comfirm user exsist or not
bool ComfirmUser(QString name, QString pw);

private:
Ui::MonitorServer *ui;

//send response to client
void SendMesg(QString strMesg);

QCamera*             m_pCamera;         //load QCamera
QCameraViewfinder*   m_pViewfinder;     //randered QCamera
QCameraImageCapture* m_pImageCapture;   //get QCamera current frame

private slots:
//new client connect
void slotNewConnect();

//get request from client and deal with response
void slotReadMesg();

//test pushButton
void slotTest();

};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 初始化(数据库初始化见上一节)
    //initial server
    m_pTcpServer = new QTcpServer();
    m_pTcpServer->listen(QHostAddress::Any,19999);
    this->connect(m_pTcpServer,SIGNAL(newConnection()),this,SLOT(slotNewConnect()));
    this->connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(slotTest()));
    //initial QCarema
    m_pCamera = new QCamera(this);
    m_pViewfinder = new QCameraViewfinder(this);
    m_pImageCapture = new QCameraImageCapture(m_pCamera);
    mainLayout->addWidget(m_pViewfinder);
    connect(m_pImageCapture, SIGNAL(imageCaptured(int,QImage)), this, SLOT(cameraImageCaptured(int,QImage)));
    m_pImageCapture->setCaptureDestination(QCameraImageCapture::CaptureToFile);
    m_pCamera->setCaptureMode(QCamera::CaptureStillImage);
    m_pCamera->setViewfinder(m_pViewfinder);
    m_pCamera->start();
------

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 处理新的客户端连接 void slotNewConnect()
//new client connect
void MonitorServer::slotNewConnect()
{
    pTcpSocketServer =  m_pTcpServer->nextPendingConnection();
    this->connect(pTcpSocketServer,SIGNAL(readyRead()),this,SLOT(slotReadMesg()));
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 用户注册 void Regist(QString name, QString pw)
//user regist
void MonitorServer::Regist(QString name, QString pw)
{
    QSqlQuery qsQuery = QSqlQuery(db);
    qsQuery.prepare("INSERT INTO User (Username, Password) "
                      "VALUES (:name, :pw)");
    qsQuery.bindValue(":name", name);
    qsQuery.bindValue(":pw", pw);
    if(!qsQuery.exec())
    {
        QSqlError lastError = qsQuery.lastError();
        qDebug() << lastError.driverText() << QString(QObject::tr("Insert failed."));
    }
    qsQuery.finish();
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 确认用户是否存在
//comfirm account exsists or not
bool MonitorServer::ComfirmUser(QString name, QString pw)
{
    QSqlQuery qsQuery = QSqlQuery(db);
    qsQuery.exec("select * from User WHERE Username = '" + name + "' AND Password = '" + pw + "'");
    if(!qsQuery.exec())
    {
        QSqlError lastError = qsQuery.lastError();
        qDebug() << lastError.driverText() << QString(QObject::tr("Query failed."));
    }
    if (qsQuery.next()) {
        qsQuery.finish();
        return true;
    } else {
        qsQuery.finish();
        return false;
    }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 发送截屏 void cameraImageCaptured(int, QImage image)
//capture current Image & send response
void MonitorServer::cameraImageCaptured(int, QImage image)
{
    QByteArray ba;
    QBuffer buffer(&ba);
    buffer.open(QIODevice::WriteOnly);
    image.save(&buffer, "PNG");
    QImage *image2 = new QImage;
    image2->loadFromData(ba, "PNG");
    ui->label->setPixmap(QPixmap::fromImage(*image2));
    ui->label->show();
    this->SendMesg(QString(ba));
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 请求接收及处理 void slotReadMesg()
//get request from client and deal with response
void MonitorServer::slotReadMesg()
{
    QByteArray qba= pTcpSocketServer->readAll(); //读取
QString ss=QVariant(qba).toString();
QStringList req = ss.split("-");
if(req[0] == "login") {
    qDebug() &lt;&lt; "User Log in";
    if(this-&gt;ComfirmUser(req[1], req[2])) {
        qDebug() &lt;&lt; "Yes, ok";
        QString resp = "login-success-" + req[1] + "-end";
        this-&gt;SendMesg(resp);
        ui-&gt;label_user-&gt;setText("current user: " + req[1]);
    } else{
        QString resp = "login-fail-" + req[1] + "-end";
        this-&gt;SendMesg(resp);
    }
} else if (req[0] == "regist") {
    qDebug() &lt;&lt; "User Regist";
    if(this-&gt;ComfirmUser(req[1], req[2])) {
        qDebug() &lt;&lt; "No, ok";
        QString resp = "regist-fail-" + req[1] + "-end";
        this-&gt;SendMesg(resp);
    } else{
        this-&gt;Regist(req[1], req[2]);
        ui-&gt;label_user-&gt;setText("current user: " + req[1]);
        QString resp = "regist-success-" + req[1] + "-end";
        this-&gt;SendMesg(resp);
    }
} else if(req[0] == "shot") {
    this-&gt;m_pImageCapture-&gt;capture();
} else if (req[0] == "stop") {
    ui-&gt;label_user-&gt;setText("current user: " + this-&gt;user + " STOP");
    this-&gt;m_pCamera-&gt;stop();
} else {
    ui-&gt;label_user-&gt;setText("current user: " + this-&gt;user);
    this-&gt;m_pCamera-&gt;start();
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
                                </div><div data-report-view="{&quot;mod&quot;:&quot;1585297308_001&quot;,&quot;dest&quot;:&quot;https://blog.csdn.net/Yanzu_Wu/article/details/94068862&quot;,&quot;extend1&quot;:&quot;pc&quot;,&quot;ab&quot;:&quot;new&quot;}"><div></div></div>
            <link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-60ecaf1f42.css" rel="stylesheet">
                            </div>
  • 4
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值