目录
大一软工,C++的Qt课设成果:
基于MSVC的编译器,应用QWebenginewidget,当时配置MSVC还是搞了一天没搞明白花的钱从淘宝上找的人,发现是VS版本只支持2017,版本兼容性真是一个大问题!
这是应用的主页面:(链接默认设置的Bing的主页)
该模块负责显示和浏览用户输入的网页内容。具体设计如下:
模块功能:
- 提供用户界面,显示网页的内容。
- 允许用户在界面中浏览网页,包括滚动和缩放功能。
- 支持网页导航,包括前进和后退功能。
设计说明:
- 浏览网页模块可以使用一个 WebView 控件实现,用于显示网页内容。
- 用户可以使用滚动条进行页面的上下滚动,以查看完整的页面内容。
- 提供缩放功能,允许用户调整页面的大小比例。
- 通过前进和后退按钮,用户可以导航到浏览历史记录中的前一个或后一个页面。
以下是main.cpp的部分代码:
QUrl url;
if (argc > 1)
url = QUrl::fromUserInput(argv[1]);
else
url = QUrl("https://cn.bing.com/?scope=web&FORM=HDRSC1&mkt=zh-CN");
//这段代码用于解析命令行参数或设置默认的网页链接。如果命令行参数数量大于 1,那么将使用第
//一个命令行参数作为初始的网页链接,否则默认使用 "https://cn.bing.com/?scope=web&FORM=HDRSC1&mkt=zh-CN"。
MainWindow *browser = new MainWindow(url);//创建了一个 MainWindow 对象 browser,并传入初始的网页链接。
browser->resize(1536, 864);
browser->show();
//设置 browser 窗口的大小,并显示出来。
以下是MainWindow的构造函数的部分源码:
setAttribute(Qt::WA_DeleteOnClose, true);
progress = 0;
QFile file(":/jquery.min.js");
if (file.open(QIODevice::ReadOnly)) {
jQuery = file.readAll();
jQuery.append("\nvar qt = { 'jQuery': jQuery.noConflict(true) };");
file.close();
view = new QWebEngineView(this);
view->load(url);
connect(view, &QWebEngineView::loadFinished, this, &MainWindow::adjustLocation);
connect(view, &QWebEngineView::titleChanged, this, &MainWindow::adjustTitle);
connect(view, &QWebEngineView::loadProgress, this, &MainWindow::setProgress);
connect(view, &QWebEngineView::loadFinished, this, &MainWindow::finishLoading);
locationEdit = new QLineEdit(this);
locationEdit->setSizePolicy(QSizePolicy::Expanding, locationEdit->sizePolicy().verticalPolicy());
connect(locationEdit, &QLineEdit::returnPressed, this, &MainWindow::changeLocation);
QToolBar *toolBar = addToolBar(tr("Navigation"));
toolBar->addAction(view->pageAction(QWebEnginePage::Back));
toolBar->addAction(view->pageAction(QWebEnginePage::Forward));
toolBar->addAction(view->pageAction(QWebEnginePage::Reload));
toolBar->addAction(view->pageAction(QWebEnginePage::Stop));
toolBar->addWidget(locationEdit);
QMenu *viewMenu = menuBar()->addMenu(tr("&View"));
QAction *viewSourceAction = new QAction(tr("Page Source"), this);
connect(viewSourceAction, &QAction::triggered, this, &MainWindow::viewSource);
viewMenu->addAction(viewSourceAction);
QMenu *effectMenu = menuBar()->addMenu(tr("&Effect"));
effectMenu->addAction(tr("Highlight all links"), this, &MainWindow::highlightAllLinks);
rotateAction = new QAction(this);
rotateAction->setIcon(style()->standardIcon(QStyle::SP_FileDialogDetailedView));
rotateAction->setCheckable(true);
rotateAction->setText(tr("Turn images upside down"));
connect(rotateAction, &QAction::toggled, this, &MainWindow::rotateImages);
effectMenu->addAction(rotateAction);
QMenu *toolsMenu = menuBar()->addMenu(tr("&Tools"));
toolsMenu->addAction(tr("Remove GIF images"), this, &MainWindow::removeGifImages);
toolsMenu->addAction(tr("Remove all inline frames"), this, &MainWindow::removeInlineFrames);
toolsMenu->addAction(tr("Remove all object elements"), this, &MainWindow::removeObjectElements);
toolsMenu->addAction(tr("Remove all embedded elements"), this, &MainWindow::removeEmbeddedElements);
QMenu *giftMenu = menuBar()->addMenu(tr("Gift"));
QAction *giftAction = new QAction(tr("Pop Window"), this);
QAction *dialogueAction = new QAction(tr("Create New Page"), this);
giftMenu->addAction(giftAction);
giftMenu->addAction(dialogueAction);
connect(giftAction, &QAction::triggered, this, &MainWindow::giftPOP);
connect(dialogueAction, &QAction::triggered, this, &MainWindow::createWindow);
QMenu *bookmarkMenu = menuBar()->addMenu(tr("Bookmark"));
QAction *addMarkAction = new QAction(tr("Add Mark"), this);
bookmarkMenu->addAction(addMarkAction);
connect(addMarkAction, &QAction::triggered, this, &MainWindow::addMarkWindowx);
setCentralWidget(view);
}
浏览器工具栏介绍:
可以看到工具栏第一行,有五个toolBar,分别是view,effect,tools,gift和bookmark。这五个空间对应五个不同的功能,接下来我会为大家介绍五个功能:
View:
该功能模块允许用户查看当前加载的网页的源代码。具体设计如下:
模块功能:
- 允许用户查看当前加载的网页的源代码。
设计说明:
- 创建一个按钮或菜单项,用于触发查看网页源代码的操作。
- 在点击按钮或菜单项时,获取当前加载的网页的源代码。
- 显示源代码的文本内容,可以使用一个文本编辑器或类似的控件来展示源代码。
- 提供适当的界面布局,以便用户可以方便地查看和浏览源代码。
效果展示:
源码展示:
QTextEdit *textEdit = new QTextEdit(nullptr);
//在该函数中,首先创建了一个QTextEdit对象textEdit,用于显示网页的源代码。
textEdit->setAttribute(Qt::WA_DeleteOnClose);
//设置textEdit的属性为Qt::WA_DeleteOnClose,表示在关闭窗口时删除textEdit对象。
textEdit->adjustSize();
//调整textEdit的大小以适应内容。
//根据主窗口的位置和textEdit的尺寸,将textEdit移动到主窗口中心的位置。
textEdit->setAlignment(Qt::AlignHCenter);
textEdit->resize(1200,800);
textEdit->show();
//显示textEdit窗口。
view->page()->toHtml([textEdit](const QString &html){
textEdit->setPlainText(html);
});
//通过调用view对象的page()方法获取当前页面的QWebEnginePage对象,并调用该对象的toHtml()方法来获取页面的HTML源代码。
//toHtml()方法接受一个Lambda表达式作为参数,该Lambda表达式将获取到的HTML源代码作为参数传递给textEdit的setPlainText()方法,
//从而将源代码显示在textEdit窗口中。
Effect:
高亮所有链接功能模块旨在提升用户浏览网页时对链接的可视化效果,使链接更加明显和易于点击。该模块的功能及设计如下:
模块功能:
- 识别页面中的所有链接元素,包括超链接、图像映射等。
- 为每个链接元素添加特定的样式或效果,以突出显示链接的位置和边界。
设计说明:
- 提供一个选项或按钮,允许用户启用或禁用高亮所有链接功能。
- 当用户启用该功能时,页面中的所有链接将以高亮样式呈现。
- 当用户禁用该功能时,页面中的链接将恢复到默认样式。
效果展示:
QString code = QStringLiteral(
"qt.jQuery('a').each(function() {"
" var backgroundColor = qt.jQuery(this).css('background-color');"
" if (backgroundColor === 'rgb(255, 255, 0)' || backgroundColor === 'yellow') {"
" qt.jQuery(this).css('background-color', '');"
" } else {"
" qt.jQuery(this).css('background-color', 'yellow');"
" }"
"})"
);
view->page()->runJavaScript(code);
第二个功能turn images upside down可以将图片进行翻转 (再次点击则返回):
效果展示:
(倒过来的猴子看起来毫无违和感)
QString code;
if (invert)
code = QStringLiteral("qt.jQuery('img').each( function () { qt.jQuery(this).css('transition', 'transform 2s'); qt.jQuery(this).css('transform', 'rotate(180deg)') } )");
else
code = QStringLiteral("qt.jQuery('img').each( function () { qt.jQuery(this).css('transition', 'transform 2s'); qt.jQuery(this).css('transform', 'rotate(0deg)') } )");
view->page()->runJavaScript(code);
Tools:
移除GIF图像、内联框架和嵌入对象功能模块旨在提供对网页中的GIF图像、内联框架和嵌入对象的移除功能,以改善用户浏览体验和页面加载速度。该模块的功能及设计如下:
- 移除GIF图像功能:
- 提供一个选项或按钮,允许用户选择是否移除网页中的GIF图像。
- 当用户选择移除GIF图像时,该功能模块将会检测网页中的GIF图像,并将其移除或替换为静态图像。
- 移除内联框架和嵌入对象功能:
- 提供一个选项或按钮,允许用户选择是否移除网页中的内联框架和嵌入对象,例如嵌入的音频、视频等。
- 当用户选择移除内联框架和嵌入对象时,该功能模块将会检测网页中的内联框架和嵌入对象,并将其移除或隐藏。
- 页面加载性能优化:
在移除GIF图像、内联框架和嵌入对象时,该功能模块应考虑页面加载性能优化,确保移除操作不影响网页的正常显示和功能。
void MainWindow::removeGifImages()
{
QString code = QStringLiteral("qt.jQuery('[src*=gif]').remove()");
view->page()->runJavaScript(code);
}
//在该函数中,首先创建了一个QString类型的变量code,用于存储要执行的JavaScript代码。
//然后,将code设置为qt.jQuery('[src*=gif]').remove()这段JavaScript代码。这段代码使用qt.jQuery选择器选中所有src属性中包含"gif"的元素,
//并使用remove()方法将这些元素从DOM中移除,即从页面中移除所有的GIF图像。
//最后,使用view对象的page()方法获取当前页面的QWebEnginePage对象,并调用runJavaScript()方法来执行存储在code变量中的JavaScript代码。
void MainWindow::removeInlineFrames()
{
QString code = QStringLiteral("qt.jQuery('iframe').remove()");
view->page()->runJavaScript(code);
}
//然后,将code设置为qt.jQuery('iframe').remove()这段JavaScript代码。这段代码使用qt.jQuery选择器选中所有的<iframe>元素,并使用remove()方法将这些元
//素从DOM中移除,即从当前页面中移除所有的内联框架。
//最后,使用view对象的page()方法获取当前页面的QWebEnginePage对象,并调用runJavaScript()方法来执行存储在code变量中的JavaScript代码。
void MainWindow::removeObjectElements()
{
QString code = QStringLiteral("qt.jQuery('object').remove()");
view->page()->runJavaScript(code);
}
//在该函数中,首先创建了一个QString类型的变量code,用于存储要执行的JavaScript代码。
//然后,将code设置为qt.jQuery('object').remove()这段JavaScript代码。这段代码使用qt.jQuery选择器选中所有的<object>元素,并使用remove()方法
//将这些元素从DOM中移除,即从当前页面中移除所有的<object>元素。
//最后,使用view对象的page()方法获取当前页面的QWebEnginePage对象,并调用runJavaScript()方法来执行存储在code变量中的JavaScript代码。
void MainWindow::removeEmbeddedElements()
{
QString code = QStringLiteral("qt.jQuery('embed').remove()");
view->page()->runJavaScript(code);
//在该函数中,首先创建了一个QString类型的变量code,用于存储要执行的JavaScript代码。
//然后,将code设置为qt.jQuery('embed').remove()这段JavaScript代码。这段代码使用qt.jQuery选择器选中所有的<embed>元素,
//并使用remove()方法将这些元素从DOM中移除,即从当前页面中移除所有的<embed>元素。
//最后,使用view对象的page()方法获取当前页面的QWebEnginePage对象,并调用runJavaScript()方法来执行存储在code变量中的JavaScript代码。
}
Gift:
存在两个功能:
pop Window :
弹出窗口,显示本人进行神奇的PS扭曲旋转之后的特殊icon效果(纯属恶搞):
QString imagePath = ":/rotate.png";
QLabel* labelImage = new QLabel(this, Qt::Dialog | Qt::WindowCloseButtonHint);
labelImage->setAttribute(Qt::WA_DeleteOnClose);
labelImage->setWindowTitle("application icon");
QFileInfo file(imagePath);
if (file.exists()) {
QImage image;
image.load(imagePath);
labelImage->setFixedSize(300, 300); // 设置固定大小
labelImage->setPixmap(QPixmap::fromImage(image.scaled(labelImage->size(), Qt::KeepAspectRatio)));
labelImage->setScaledContents(true); // 图片自适应Label大小
} else {
qDebug() << "未找到该图片";
}
labelImage->show();
Create New Window:
该模块负责处理用户创建新页面的请求,并提供相应的界面和交互功能。具体设计如下:
模块功能:
- 提供用户界面,包括一个新页面的编辑器和相关工具按钮。
- 允许用户输入网址或直接编辑页面内容。
设计说明:
- 创建新页面模块包括一个新页面编辑器,使用QDockWidget控件实现。
- 用户可以在编辑器中输入网址或直接编辑页面内容。
同时窗口也具有此控件的特性,可以自由移动,甚至可以多次添加:
static int name = 1;
QDockWidget *dock = new QDockWidget(tr("New Window") + QString::number(name), this);
name++;
dock->setFeatures(QDockWidget::AllDockWidgetFeatures);
dock->setAllowedAreas(Qt::AllDockWidgetAreas);
QWidget *dockContents = new QWidget(dock);
QVBoxLayout *layout = new QVBoxLayout(dockContents);
te1 = new QLineEdit();
te1->setText(tr("https://cn.bing.com/?scope=web&FORM=HDRSC1&mkt=zh-CN"));
te1->setSizePolicy(QSizePolicy::Expanding, te1->sizePolicy().verticalPolicy());
connect(te1, &QLineEdit::returnPressed, this, &MainWindow::changeLocation2);
layout->addWidget(te1);
QUrl url;
if (te1->text().size() > 1)
url = QUrl::fromUserInput(te1->text());
else
url = QUrl("https://cn.bing.com/?scope=web&FORM=HDRSC1&mkt=zh-CN");
view2 = new QWebEngineView(dockContents);
view2->load(url);
QToolBar *toolBar = new QToolBar(tr("Navigation"), this);
toolBar->addAction(view2->pageAction(QWebEnginePage::Back));
toolBar->addAction(view2->pageAction(QWebEnginePage::Forward));
toolBar->addAction(view2->pageAction(QWebEnginePage::Reload));
toolBar->addAction(view2->pageAction(QWebEnginePage::Stop));
toolBar->addWidget(te1);
layout->addWidget(toolBar);
layout->addWidget(view2);
dock->setWidget(dockContents);
this->addDockWidget(Qt::RightDockWidgetArea, dock);
connect(view2, &QWebEngineView::titleChanged, dock ,[=]() {
connect(view2, &QWebEngineView::loadProgress, dock, [=](int p) {
if (p >= 0 && p < 100) {
dock->setWindowTitle(QStringLiteral("%1 (%2%)").arg(view2->title()).arg(p));
} else {
dock->setWindowTitle(view2->title());
}
});
});
BookMark:
添加书签功能模块旨在允许用户保存当前浏览的网页作为书签,方便以后快速访问该网页。该模块的功能及设计如下:
- 添加书签功能:
- 提供一个按钮或菜单选项,允许用户添加当前浏览的网页为书签。
- 当用户点击添加书签按钮时,将当前网页的标题、URL和其他相关信息保存到书签列表中。
- 书签列表管理:
- 显示保存的书签列表,每个书签包含标题和URL。
- 允许用户浏览和搜索书签列表,以便快速找到目标网页。
- 提供操作选项,例如编辑书签、删除书签等,以便用户管理书签列表。
- 数据持久化和同步:
- 将书签列表的数据进行持久化存储,以便在下次启动应用程序时恢复用户的书签。
- 支持将书签数据同步到云端或跨设备同步,以便用户在不同设备上访问和管理书签。
进行编辑的进行同步,当然未编辑的不需要同步,两者之间添加了Qsplitter进行窗口位置的自由调整,可以同时进行观看两边的内容,也可以在右边的大范围里面进行多种内容的添加,左边的list中只会显示前半部分的标题内容。
QWidget* widgetx = new QWidget();
widgetx->setWindowTitle(tr("bookmark"));
QPushButton* pushButton = new QPushButton(tr("add"));
QPushButton* deleteButton = new QPushButton(tr("delete"));
QListWidget* list = new QListWidget();
QStackedWidget* stack = new QStackedWidget();
static int num = 0;
connect(pushButton, &QPushButton::clicked, [=]() {
QString windowTitle = tr("mark") + QString::number(num+1);
QListWidgetItem* listItem = new QListWidgetItem(windowTitle);
list->addItem(listItem);
QTextEdit* textEdit = new QTextEdit();
textEdit->setText("Please edit here to store your website!\n"
"At the same time you can state the traits of "
"the website you like. ");
stack->addWidget(textEdit);
connect(textEdit, &QTextEdit::textChanged, widgetx, [=]() {
if (list->count() > 0) {
QListWidgetItem* currentItem = list->currentItem();
if (currentItem != nullptr)
currentItem->setText(textEdit->toPlainText());
}
});
list->setCurrentItem(listItem);
num++;
});
connect(deleteButton, &QPushButton::clicked, [=]() {
int currentIndex = list->currentRow();
if (currentIndex >= 0) {
list->takeItem(currentIndex);
stack->removeWidget(stack->currentWidget());
}
});
QSplitter* splitter = new QSplitter();
splitter->addWidget(list);
splitter->addWidget(stack);
QVBoxLayout* buttonLayout = new QVBoxLayout();
buttonLayout->addWidget(pushButton);
buttonLayout->addWidget(deleteButton);
QWidget* buttonsContainer = new QWidget();
buttonsContainer->setLayout(buttonLayout);
QHBoxLayout* mainLayout = new QHBoxLayout(widgetx);
mainLayout->setMargin(5);
mainLayout->setSpacing(5);
mainLayout->addWidget(splitter);
mainLayout->addWidget(buttonsContainer);
connect(list, &QListWidget::currentRowChanged, stack, &QStackedWidget::setCurrentIndex);
widgetx->resize(800, 600);
widgetx->show();
操作栏:
可以实现前进,后退,刷新,关闭四种功能,同时存在一个url填写的lineedit。
void MainWindow::changeLocation2()
{
QUrl url = QUrl::fromUserInput(te1->text());
//在该函数中,首先通过调用fromUserInput()方法将用户在地址栏输入的文本转换为QUrl对象。此方法会自动处理用户输入的文本,
//例如添加协议前缀(http://或https://)等。
view2->load(url);//使用view对象的load()方法将浏览器视图加载到由用户输入的URL对应的网页上。
view2->setFocus();//将焦点设置到浏览器视图上,以便用户可以与网页进行交互。
}
void MainWindow::adjustLocation()
{
locationEdit->setText(view->url().toString());
//在该函数中,首先通过调用view对象的url()方法获取当前页面的URL,然后将URL转换为字符串形式,
//并使用setText()方法将其设置为locationEdit小部件的文本。这样,每当页面加载完成或导航到新的URL时,
//adjustLocation函数将被调用以更新locationEdit中显示的URL。
}
void MainWindow::changeLocation()
{
QUrl url = QUrl::fromUserInput(locationEdit->text());
//在该函数中,首先通过调用fromUserInput()方法将用户在地址栏输入的文本转换为QUrl对象。此方法会自动处理用户输入的文本,
//例如添加协议前缀(http://或https://)等。
view->load(url);//使用view对象的load()方法将浏览器视图加载到由用户输入的URL对应的网页上。
view->setFocus();//将焦点设置到浏览器视图上,以便用户可以与网页进行交互。
}
void MainWindow::adjustTitle()
{
if (progress <= 0 || progress >= 100)
setWindowTitle(view->title());
else
setWindowTitle(QStringLiteral("%1 (%2%)").arg(view->title()).arg(progress));
//这段代码使用了arg()函数来替换字符串中的占位符 %1 和 %2。arg() 函数是 QString 类的成员函数,用于将参数的值插入到字符串中对应的占位符位置。
}
//在该函数中,首先判断progress变量的值是否小于等于0或大于等于100。
//如果progress的值小于等于0或大于等于100,表示加载进度未知或已完成,那么使用view对象的title()方法获取当前页面的标题,
//并将其设置为主窗口的标题(通过调用setWindowTitle()方法)。如果progress的值在0和100之间,表示加载进度正在进行中,
//那么使用view对象的title()方法获取当前页面的标题,并使用QStringLiteral、arg()等方法构建一个带有加载进度的标题字符串,然后将该字符串设置为主窗口的标题。
void MainWindow::setProgress(int p)
{
progress = p;
adjustTitle();
//在该函数中,首先将参数p的值赋给成员变量progress,以更新当前页面的加载进度。
//然后调用adjustTitle()函数,根据新的加载进度progress来调整主窗口的标题。adjustTitle()函数会根据加载进度的不同情况,更新主窗口的标题显示。
}
void MainWindow::finishLoading(bool)
{
progress = 100;
adjustTitle();
view->page()->runJavaScript(jQuery);
rotateImages(rotateAction->isChecked());
}
//在该函数中,首先将进度progress设置为100,表示页面加载已完成。然后调用adjustTitle()函数,根据加载进度更新主窗口的标题。
//接下来,使用view对象的page()方法获取当前页面的QWebEnginePage对象,并调用runJavaScript()方法来执行JavaScript代码。在这里,传入了变量jQuery作
//为要执行的JavaScript代码。这可能是用于操作和处理页面元素的特定JavaScript代码。
//最后,调用rotateImages()函数并传入rotateAction->isChecked()作为参数,来根据旋转动作的选中状态决定是否旋转页面中的图像。
项目打包:
附上博客园高手的教程,已经很全很完整,适合新手打包。
总 结:
我们的项目是一个基于Qt框架开发的简易交互式浏览器。通过项目的实施,我们获得了宝贵的经验和体会,同时也面临了一些挑战和改进的空间。
首先,项目的开发过程中,我们充分利用了Qt框架提供的功能和模块,如Qt WebEngineWidgets模块实现了浏览网页功能,QtextEdit、QlistWidget和QStackWidget等控件实现了书签功能。这些技术和工具为我们的项目提供了强大的基础和便利。
在项目的实施过程中,我们遇到了两个主要问题。其中一个是我们发现在最新版本的Qt中已经不支持对QWebenginewidget模块的静态编译,最开始我们的项目因为这个原因一度无法开展下去,后来我们发现需要重新配置MSVC,为此我们下载了Visual Studio 2017,并使用其内置的Microsoft Visual C++ Compiler 15.0 (amd64)版本来进行项目的编译,最终克服了这一难关。
还有一个问题是出现了书签无法存储的情况。我们发现,在每次关闭程序后,书签列表中的数据会丢失。经过分析,我们认识到这是因为我们没有使用持久化存储来保存书签数据,而只是将其存储在内存中。为了解决这个问题,我们意识到需要学习存储方面的知识,并探索如何使用文件或数据库来保存书签数据,以便在下次打开程序时能够恢复之前保存的书签列表。但由于课设时间紧迫,所以我们打算在后续对其进行完善。
此外,我们还发现一些待完善的地方。例如,在浏览网页功能中,我们目前只能手动输入URL进行网页访问,没有提供更友好和便捷的导航方式,如历史记录、书签导航等。我们认识到这是可以改进的地方,以提高用户体验和操作便利性。我们计划在未来的版本中增加这些功能,使用户能够更方便地浏览和导航网页。
通过这个项目,我们深入学习和应用了Qt框架,掌握了基本的GUI开发技术,并实现了一个简单但功能完善的交互式浏览器。我们从中获得了丰富的编程经验,提高了自己的开发能力。同时,我们也认识到在实际项目中会遇到各种问题和挑战,需要不断学习和改进。我们将继续努力,完善我们的浏览器项目,并在以后的项目中运用所学,不断提升自己的技术水平和团队合作能力。
虽然这是我们第一次进行课设,但我们对这个项目的开发过程和结果感到满意,同时也认识到了一些问题和改进的空间。通过这个项目,我们得到了宝贵的经验和教训,为我们未来的学习和开发之路打下了坚实的基础。我们期待继续学习和成长,并将所学应用于更加复杂和实用的项目中。
以上即是app的基本介绍,以下附上app的项目报告,安装包,源码,供大家体验参考:
链接:https://pan.baidu.com/s/1I0GnvKh5rCqboyirZixkoA?pwd=ix2v
提取码:ix2v
注:以上内容为学习内容,并非本人一人完全的成果,借鉴github,Qt官方的资源,如有涉嫌侵权,请告知我删除文章。
致谢:tpsspzy为我书写了项目报告,写的比我自己写的都全面且完整,Curry30Messi为我提供了流程图,清晰逻辑强。