QT開發之旅-Udp聊天室編程


一、概要設計

  登錄對話框(繼承自QDialog類)進行用戶登錄查詢數據庫用戶是否存在,注冊插入數據到用戶表。用戶表字段:

(chatid int primary key, passwd varchar(30), name varchar(30), email varchar(30), history int)

 顯示好友列表(繼承自QWidget類),窗體間數據傳遞,顯示登錄用戶頭像及昵稱。輪詢數據庫用戶表顯示好友列表。點擊好友跳出聊天窗口(繼承自MainWindow類),窗體間數據傳遞,顯示好友昵稱,實現多用戶聊天,支持中文通信,設置快捷鍵,保存聊天記錄。

關鍵字:數據庫、窗體傳值、中文通信、udp協議、快捷鍵、文件操作。

二、詳細設計

  1.登錄對話框:

  數據庫操作:

  (1)創建數據庫和數據表

// 設置參數
    QString select_table = "select tbl_name name from sqlite_master where type = 'table'";
    QString create_sql = "create table user (chatid int primary key, passwd varchar(30), name varchar(30), email varchar(30), history int)";
    QString select_max_sql = "select max(chatid) from user";
    QString insert_sql = "insert into user values (?, ?, ?, ? ?)";
    //QString update_sql = "update user set name = :name where chatid = :chatid";
    QString select_sql = "select name from user";
    //QString select_all_sql = "select * from user";
    //QString delete_sql = "delete from user where chatid = ?";
    //QString clear_sql = "delete from user";

    QString select_nameInfo = "selcet * from user where name=";

database = QSqlDatabase::addDatabase("QSQLITE");
    database.setDatabaseName("database.db");

    //打開數據庫
    if(!database.open())
    {
        qDebug()<<database.lastError();
        qFatal("failed to connect.") ;
    }
    else
    {
        qDebug()<<"open success";
        QSqlQuery sql_query;        //變量必須在成功打開數據庫后定義才有效
        sql_query.prepare(select_table);
        if(!sql_query.exec())
        {
            qDebug()<<sql_query.lastError();
        }
        else
        {
            QString tableName;
            while(sql_query.next())
            {
                tableName = sql_query.value(0).toString();
                qDebug()<<tableName;
                if(tableName.compare("user"))
                {
                    tableFlag=false;
                    qDebug()<<"table is not exist";
                }
                else
                {
                    tableFlag=true;
                    qDebug()<<"table is exist";
                }
            }
        }
        // 創建數據表
        if(tableFlag==false)
        {
            sql_query.prepare(create_sql);
            if(!sql_query.exec())
            {
                qDebug()<<sql_query.lastError();
            }
            else
            {
                qDebug()<<"table created!";
            }
        }

        //database.close();
    }

   (2)注冊驗證及插入數據

if(ui->passwd1LineEdit->text()==""||ui->passwd2LineEdit->text()=="")
    {
        passwdFlag=false;
    }
    else if(ui->passwd1LineEdit->text()==ui->passwd2LineEdit->text())    //兩次密碼相同
    {
        //newpasswd=ui->passwd1LineEdit->text();
        passwdFlag=true;
    }
    else
    {
        QMessageBox::information(this, QString::fromLocal8Bit("提示"), QStringLiteral("密碼輸入不一致!"));
        qDebug()<<"passwd err";
        passwdFlag=false;
        //return;
    }

    //以下為數據庫的操作
    QSqlQuery sql_query;

    //查詢最大id
    max_id = 0;
    sql_query.prepare(select_max_sql);
    if(!sql_query.exec())
    {
        qDebug()<<sql_query.lastError();
    }
    else
    {
        while(sql_query.next())
        {
            max_id = sql_query.value(0).toInt();
            qDebug()<<QString("max chatid:%1").arg(max_id);
        }
    }


    //查詢部分數據(name)
    if(!sql_query.exec(select_sql))
    {
        qDebug()<<sql_query.lastError();
    }
    else
    {
        while(1)
        {
            if(sql_query.next())    //name有數據
            {
                QString name = sql_query.value("name").toString();
                qDebug()<<QString("name=%1").arg(name);

                if(ui->nameLineEdit->text()==name)    //用戶名已經存在
                {
                    QMessageBox::information(this, QString::fromLocal8Bit("提示"), QStringLiteral("用戶名已存在!"));
                    qDebug()<<"name existed";
                    nameFlag=false;
                    break;
                }
                else
                {
                    //newname=ui->nameLineEdit->text();
                    nameFlag=true;
                }
            }
            else
            {       //name列為空
                nameFlag=true;
                break;
            }
        }
    }

    newchatid=max_id+1;
    if(nameFlag==true) newname=ui->nameLineEdit->text();
    else                return;
    if(passwdFlag==true)    newpasswd=ui->passwd1LineEdit->text();
    else                    return;

    //插入數據
    sql_query.prepare(insert_sql);
    sql_query.addBindValue(newchatid);              //chatid
    sql_query.addBindValue(newpasswd);              //passwd
    sql_query.addBindValue(newname);                //name
    sql_query.addBindValue(newemail);               //email
    sql_query.addBindValue(0);                      //history
    if(!sql_query.exec())
    {
        qDebug()<<sql_query.lastError();
    }
    else
    {
        QMessageBox::information(this, QString::fromLocal8Bit("提示"), QStringLiteral("注冊成功!"));
        qDebug()<<"inserted!";
    }

   登錄界面:

  (1)登錄驗證

if(matchFlag==false)
    {
        //用戶名錯誤
        QMessageBox::warning(this, tr("警告"), tr("用戶不存在!"), QMessageBox::Yes);
        this->ui->et_username->clear();
        this->ui->et_pwd->clear();
        this->ui->et_username->setFocus();
    }
    else
    {
        if(usr_passwd!=ui->et_pwd->text())
        {
            //密碼錯誤
            QMessageBox::warning(this, tr("警告"), tr("用戶不存在!"), QMessageBox::Yes);
            this->ui->et_username->clear();
            this->ui->et_pwd->clear();
            this->ui->et_username->setFocus();
        }
        else
        {
            //用戶名和密碼均正確
//            ChatWindow cw(this);
//            this->hide();
//            cw.show();
//            cw.exec();
//            this->close();
            LoginDialog::NICKNAME = usr_name;
            accept();
        }

  (2)用戶頭像

QSqlQuery sql_query;        //變量必須在成功打開數據庫后定義才有效

    //查詢部分數據(name)

    QString tempstring="select * from user where name='"+name+"'";
    qDebug()<<tempstring;
    if(!sql_query.exec(tempstring))
    {
        qDebug()<<sql_query.lastError();
        matchFlag=false;
    }
    else
    {
        while(sql_query.next())
        {
            usr_id = sql_query.value(0).toInt();
            usr_passwd = sql_query.value(1).toString();
            usr_name = sql_query.value(2).toString();
            usr_email = sql_query.value(3).toString();
            usr_history = sql_query.value(4).toInt();

            qDebug()<<QString("chatid=%1    passwd=%2     name=%3       email=%4    history=%5").arg(usr_id).arg(usr_passwd).arg(usr_name).arg(usr_email).arg(usr_history);
        }
        if(usr_name==name)  matchFlag=true;
        else                matchFlag=false;
    }

    qDebug()<<matchFlag;
    if(matchFlag==true)
    {
        QString path=":/images/facex.jpg";
        QString diff="face"+QString::number(usr_id);
        path.replace("facex",diff);
        qDebug()<<path;

        QImage img;
        img.load(path);
        QPixmap pic=QPixmap::fromImage(img.scaled(ui->userPic->width(),ui->userPic->height()));
        ui->userPic->setPixmap(pic);
    }
    else
    {

        QPixmap pic;
        ui->userPic->setPixmap(pic);
    }

  2.好友列表:

  數據庫查詢及列表顯示:

database = QSqlDatabase::addDatabase("QSQLITE");
    database.setDatabaseName("database.db");

    QSqlQuery sql_query;        //改變量必須在成功打開數據庫后定義才有效

    //打開數據庫
    if(!database.open())
    {
        qDebug()<<database.lastError();
        qFatal("failed to connect.") ;
    }
    else
    {

        QSqlQuery query;        //改變量必須在成功打開數據庫后定義才有效
        QString execstring="select * from user";

        ctoolTip = new CToolTip();
        // 定義全局的ToolTip,方便使用
        g_toolTip = ctoolTip;

        // 本行代碼主要針對ListWidgetItem右鍵點擊時才生效的
        ui->listWidget->setMouseTracking(true);

        if(!query.exec(execstring))
        {
            qDebug()<<QString("chatid");
            qDebug()<<query.lastError();
    //        matchFlag=false;
        }
        else
        {
            // 添加測試數據
            while (query.next()) {
                usr_id = query.value(0).toInt();
                usr_passwd = query.value(1).toString();
                usr_name = query.value(2).toString();
                usr_email = query.value(3).toString();
                usr_history = query.value(4).toInt();

                qDebug()<<QString("chatid=%1    passwd=%2     name=%3       email=%4    history=%5").arg(usr_id).arg(usr_passwd).arg(usr_name).arg(usr_email).arg(usr_history);

                ItemWidget *itemWidget = new ItemWidget(ui->listWidget);
                itemWidget->setText(QPixmap(QString(":/images/face%1").arg(usr_id)).scaled(80, 80),
                                            QString("%1").arg(usr_name), QString("127.0.0.1:800%1").arg(usr_id));
                QListWidgetItem *listItem = new QListWidgetItem(ui->listWidget);
                // 此處的size如果不設置,界面被壓縮了看不出ItemWidget的效果,高度一定要設置
                listItem->setSizeHint(QSize(200, 70));
                ui->listWidget->setItemWidget(listItem, itemWidget);


            }
        }
    }
    QObject::connect(ui->listWidget,SIGNAL(itemClicked(QListWidgetItem*)),this,SLOT(conChat(QListWidgetItem*)));
    qDebug()<<"LoginWidget:"+LoginDialog::NICKNAME;
    ui->nickname->setText(LoginDialog::NICKNAME);
    QString tempstring="select * from user where name='"+LoginDialog::NICKNAME+"'";
    qDebug()<<tempstring;
    if(!sql_query.exec(tempstring))
    {
        qDebug()<<sql_query.lastError();
        matchFlag=false;
    }
    else
    {
        while(sql_query.next())
        {
            usr_id = sql_query.value(0).toInt();
            usr_passwd = sql_query.value(1).toString();
            usr_name = sql_query.value(2).toString();
            usr_email = sql_query.value(3).toString();
            usr_history = sql_query.value(4).toInt();

            qDebug()<<QString("chatid=%1    passwd=%2     name=%3       email=%4    history=%5").arg(usr_id).arg(usr_passwd).arg(usr_name).arg(usr_email).arg(usr_history);

        }
        if(usr_name==LoginDialog::NICKNAME)  matchFlag=true;
        else                matchFlag=false;

   登錄用戶信息設置:

QString path=":/images/facex.jpg";
        QString diff="face"+QString::number(usr_id);
        path.replace("facex",diff);
        qDebug()<<path;

        QImage img;
        img.load(path);
        QPixmap pic=QPixmap::fromImage(img.scaled(ui->userPic->width(),ui->userPic->height()));
        ui->userPic->setPixmap(pic);
        ui->ipaddress->setText(QString("127.0.0.1:800%1").arg(usr_id));

   列表項:

    labelIcon = new QLabel(this);
    labelName = new QLabel(this);
    labelName->setStyleSheet("QLabel{color: green; font: 23pt bold;}");
    labelInfo = new QLabel(this);
    labelInfo->setStyleSheet("QLabel{color: gray;}");

    verlayout = new QVBoxLayout();
    verlayout->setContentsMargins(0, 0, 0, 0);
    verlayout->addWidget(labelName);
    verlayout->addWidget(labelInfo);

    horLayout = new QHBoxLayout(this);
    horLayout->setContentsMargins(2, 2, 2, 2);
    horLayout->addWidget(labelIcon, 1, Qt::AlignTop);
    horLayout->addLayout(verlayout, 4);

   懸浮窗口:

    this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint);
    this->resize(200, 100); ;

    this->setObjectName("CToolTip");
    this->setStyleSheet("QWidget#CToolTip {border: 2px solid green; background-color: skyblue;}");

    groupBox = new QGroupBox(this);
    groupBox->setGeometry(10, 10, 180, 80);
    groupBox->setTitle("用戶信息");

    labelIcon = new QLabel(groupBox);
    labelName = new QLabel(groupBox);
    labelInfo = new QLabel(groupBox);

    verlayout = new QVBoxLayout();
    verlayout->setContentsMargins(0, 0, 0, 0);
    verlayout->addWidget(labelName);
    verlayout->addWidget(labelInfo);

    horLayout = new QHBoxLayout(groupBox);
    horLayout->setContentsMargins(10, 10, 10, 10);
    horLayout->addWidget(labelIcon, 1, Qt::AlignTop);
    horLayout->addLayout(verlayout, 4);

  選擇列表項:

QObject::connect(ui->listWidget,SIGNAL(itemClicked(QListWidgetItem*)),this,SLOT(conChat(QListWidgetItem*)));

void LoginWidget::conChat(QListWidgetItem*)
{
    qDebug()<<QString("onChat:%1").arg(usr_id);
    LoginWidget::ID = ui->listWidget->currentRow()+1;
    chatWindow=new ChatWindow;
    chatWindow->show();
}

  3.聊天窗口:

  數據庫查詢、信息設置及綁定端口號:

database = QSqlDatabase::addDatabase("QSQLITE");
    database.setDatabaseName("database.db");

    QSqlQuery chat_query;        //變量必須在成功打開數據庫后定義才有效

    //打開數據庫
    if(!database.open())
    {
        qDebug()<<database.lastError();
        qFatal("failed to connect.") ;
    }
    else
    {
        QString tempstring="select * from user where name='"+LoginDialog::NICKNAME+"'";
        qDebug()<<tempstring;
        if(!chat_query.exec(tempstring))
        {
            qDebug()<<chat_query.lastError();
            matchFlag=false;
        }
        else
        {
            while(chat_query.next())
            {
                usr_id = chat_query.value(0).toInt();
                usr_passwd = chat_query.value(1).toString();
                usr_name = chat_query.value(2).toString();
                usr_email = chat_query.value(3).toString();
                usr_history = chat_query.value(4).toInt();

                qDebug()<<QString("chatid=%1    passwd=%2     name=%3       email=%4    history=%5").arg(usr_id).arg(usr_passwd).arg(usr_name).arg(usr_email).arg(usr_history);
                port = 8000+usr_id;
            }
        }
        QString receiverId="select * from user where chatid="+QString("%1").arg(LoginWidget::ID)+"";
        qDebug()<<receiverId;
        if(!chat_query.exec(receiverId))
        {
            qDebug()<<chat_query.lastError();
            matchFlag=false;
        }
        else
        {
            while(chat_query.next())
            {
                usr_id = chat_query.value(0).toInt();
                usr_passwd = chat_query.value(1).toString();
                usr_name = chat_query.value(2).toString();
                usr_email = chat_query.value(3).toString();
                usr_history = chat_query.value(4).toInt();

                qDebug()<<QString("chatid=%1    passwd=%2     name=%3       email=%4    history=%5").arg(usr_id).arg(usr_passwd).arg(usr_name).arg(usr_email).arg(usr_history);
                ui->name->setText(usr_name);
            }
        }

   UDP接收端:

senderAno = new QUdpSocket(this);
        receiver = new QUdpSocket(this);
        receiver->bind(port, QUdpSocket::ShareAddress);
        connect(receiver, &QUdpSocket::readyRead, this, &ChatWindow::processPendingDatagram);

void ChatWindow::processPendingDatagram()
{
    //中文支持
//    QTextCodec *codec = QTextCodec::codecForName("GBK");
    // 擁有等待的數據報
    while(receiver->hasPendingDatagrams())
    {
        QDateTime time = QDateTime::currentDateTime();//獲取系統現在的時間
        QString str = time.toString("yyyy-MM-dd hh:mm:ss ddd"); //設置顯示格式
        QByteArray datagram;

        // 讓datagram的大小為等待處理的數據報的大小,這樣才能接收到完整的數據
        datagram.resize(receiver->pendingDatagramSize());

        // 接收數據報,將其存放到datagram中
        receiver->readDatagram(datagram.data(), datagram.size());
        //ui->label->setText(datagram);
        ui->listWidget->addItem(str);
        chat += str+"\n";
        qDebug()<<datagram<<QString::fromLocal8Bit(datagram);
        ui->listWidget->addItem(usr_name+":"+QString::fromLocal8Bit(datagram));
        chat += usr_name+":"+datagram+"\n";
    }
}

   UDP發送端:

//中文支持
//    QTextCodec *codec = QTextCodec::codecForName("GBK");
    QDateTime time = QDateTime::currentDateTime();//獲取系統現在的時間
    QString str = time.toString("yyyy-MM-dd hh:mm:ss ddd"); //設置顯示格式
    int port = LoginWidget::ID+8000;
    qDebug()<<port;
    QByteArray datagram = ui->textEdit->toPlainText().toUtf8();

    senderAno->writeDatagram(datagram.data(), datagram.size(),
                          QHostAddress("127.0.0.1"), port);
    ui->listWidget->addItem(str);
    chat += str+"\n";
    qDebug()<<ui->textEdit->toPlainText()<<QString::fromLocal8Bit(ui->textEdit->toPlainText().toUtf8());
    ui->listWidget->addItem("me:"+ui->textEdit->toPlainText());
    chat += "me:"+ui->textEdit->toPlainText()+"\n";
    this->ui->textEdit->clear();
    this->ui->textEdit->setFocus();

   快捷鍵設置:

ui->pushButton->setShortcut(tr("ctrl+return"));
        ui->pushButton_3->setShortcut(tr("alt+c"));
        ui->pushButton_8->setShortcut(tr("ctrl+s"));

   文件操作:

/*
     * 通過QFile實現數據操作
     */
    qDebug()<<tr("Save File");
    qDebug()<<chat;
    QFile file("/Users/apple/Documents/QT/ChatLog.txt");
    // 以只寫方式打開,如果文件不存在,那么會創建該文件
    if (!file.open(QIODevice::WriteOnly  | QIODevice::Text))
        qDebug() << file.errorString();
    file.write(chat);
    file.close();
    QMessageBox::information(this, QString::fromLocal8Bit("提示"), QStringLiteral("聊天記錄保存成功!"));

   4.窗體傳值:

  使用QT中的Signal&Slot機制進行傳值:

  QT中的Signal&Slot機制相比於MFC中的消息機制簡單了許多,它保證了任何對象之間均可以通過這種方式進行通信,甚至可以得到消息的sender。這里就拿一個簡單的窗體間傳值作為例子。

      首先看一下主窗體MainWindow:

      在設計器中拖拽一個Label和一個TextEdit控件到界面上,TextEdit用於顯示傳遞過來的數據。

  創建一個右下有兩個按鍵的對話框,放置一個Label和一個LineEdit。

  下面就是編碼的操作了,我們需要在Dialog中聲明一個信號,當用戶點擊OK時傳遞LineEdit中的內容到mainWindow中,具體的dialog.h代碼為:

    #ifndef DIALOG_H  
    #define DIALOG_H  
      
    #include <QDialog>  
      
    namespace Ui {  
    class Dialog;  
    }  
      
    class Dialog : public QDialog  
    {  
        Q_OBJECT  
          
    public:  
        explicit Dialog(QWidget *parent = 0);  
        ~Dialog();  
          
    private:  
        Ui::Dialog *ui;  
    signals:  
        void  sendData(QString);  
    private slots:  
        void on_buttonBox_accepted();  
    };  
      
    #endif // DIALOG_H   

   其中的signals:void sendData(QString)便是我們需要的信號函數,同時聲明了一個槽函數

       void on_buttonBox_accepted();用於相應確定按鈕的click事件。下面就是需要在該函數中產生一個信號。代碼如下:

 

    void Dialog::on_buttonBox_accepted()  
    {  
        emit sendData(ui->lineEdit->text());  
    }  

 

   代碼異乎尋常的簡單,只需要用emit的方式調用sendData函數,將需要的參數傳遞進去即可。而MainWindow中則需要聲明接收的槽函數,注意槽函數參數只能與信號函數少或相等,而不能多於信號函數參數個數。在MainWindow的頭文件中聲明槽函數:

    private slots:  
        void receiveData(QString data);  

   為了便於測試,我只在MainWindow的構造函數中創建了一個Dialog對象,並連接了信號和槽,具體為:

    MainWindow::MainWindow(QWidget *parent) :  
        QMainWindow(parent),  
        ui(new Ui::MainWindow)  
    {  
        ui->setupUi(this);  
        //信號槽方式下父子窗體傳值的測試  
        Dialog *dlg = new Dialog;  
        //關聯信號和槽函數  
        connect(dlg,SIGNAL(sendData(QString)),this,SLOT(receiveData(QString)));  
       // dlg->setModal(true); 不論是模態或者非模態都可以正常傳值  
        dlg->show();  
    }  

   這里,我沒有將父窗口的指針傳遞到Dialog中,如new Dialog(this),這種方式下,實際上可以歸結到第三類傳值方式中去。因為此時,可以使用MainWindow中的父窗口的函數進行數據的賦值和操作。

      這里,可能還有一個問題就是,父窗口如何給子窗口傳值,一方面,仍然可以使用信號和槽的方式進行,但是,我感覺更便利的方式倒是使用這種public接口的方式進行傳值。這種來的更直接和明顯。當然,可以看出Signal&Signal方式進行此類的處理會更有通用性。

    在receiveData(QString)的槽函數中進行接收到數據的處理,這里僅僅進行了簡單的顯示:

    void MainWindow::receiveData(QString data)  
    {  
        ui->textEdit->setText(data);  
    }  

   最后看下結果:

  

  最終的結果,因為信號和槽可以是多對多的,所以,在類似多個窗體廣播信息時,這種方式就很有用,當然也可以使用全局變量的形式。

   使用全局變量;

     使用public形式的函數接口;

     使用QT中的Event機制(這種沒有把握,但是感覺應該是可以的),但是實現起來應該比前幾種復雜,這里不做討論。

   5.中文支持:

  網上搜索一下,找到的都是這種:

#include < QTextCodec >
int main(int argc, char **argv)
{
....................
QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF8"));
QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF8"));
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF8"));
..........................
}

   Qt5中, 取消了QTextCodec::setCodecForTr()和QTextCodec::setCodecForCString()這兩個函數,而且網上很多都是不推薦這種寫法。

有一下幾種轉換方法:

1、

 

QTextCodec * BianMa = QTextCodec::codecForName ( "GBK" );

    QMessageBox::information(this, "提示", BianMa->toUnicode("中文顯示!"));

 

 2、也可以通過QString定義的靜態函數,先轉換成Unicode類型:

QString::fromLocal8Bit("提示")

 3、在Qt5中,提供了一個專門的處理宏,來支持中文常量,那就是QStringLiteral,但它只能處理常量。

QMessageBox::information(this, QString::fromLocal8Bit("提示"), QStringLiteral("中文顯示"));const char* info = "中文顯示";
    //不支持
    QString strInfo = QStringLiteral(info);
    //支持
    QString strInfo = QString::fromLocal8Bit(info);

QByteArray QString::toLatin1() const

  Latin1是ISO-8859-1的別名,有些環境下寫作Latin-1。
  ISO-8859-1編碼是單字節編碼,向下兼容ASCII,其編碼范圍是0x00-0xFF,0x00-0x7F之間完全和ASCII一致,0x80-0x9F之間是控制字符,0xA0-0xFF之間是文字符號。

  toLatin1壓縮掉了QString自動給每個英文字符加上的那些00字節.

QString與QByteArray互相轉換的方法

  QString轉QByteArray方法

//Qt5.3.2
QString str("hello");  
QByteArray bytes = str.toUtf8(); // QString轉QByteArray方法1 

QString str("hello");  
QByteArray bytes = str.toLatin1();  // QString轉QByteArray方法2

   QByteArray轉QString方法

//Qt5.3.2
    QByteArray bytes("hello world");
    QString string = bytes;   // QByteArray轉QString方法1

    QByteArray bytes("hello world");
    QString string;
    string.prepend(bytes);// QByteArray轉QString方法2

    qDebug() << string;

   QByteArray類同樣不以’\0’為結尾:如

QByteArray bytes;  
bytes.resize(5);  
bytes[0] = '1';  
bytes[1] = '2';  
bytes[2] = '3';  
bytes[3] = '\0';  
bytes[4] = 'a';  

cout << bytes << endl;  

三、結果圖

登錄界面:

好友列表:

注冊界面:

聊天窗口:

聊天記錄:

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM