1.å¦ä½å©ç¨CEF3å建ä¸ä¸ªç®åçåºç¨ç¨åº
2.浅析Selenium-WebDriver
3.使用CEF(四)— 在QT中集成CEF(1)基本集成
4.cef是面源码什么文件夹
5.JavaScript 获取整个网页的完整内容,包括 html 标签。面源码
6.chromium+embedded+framework能卸载吗?
å¦ä½å©ç¨CEF3å建ä¸ä¸ªç®åçåºç¨ç¨åº
cefsimple项ç®ä¸é»è®¤å è½½çURLæ¯ google.comï¼å½ç¶ï¼ä½ ä¹å¯ä»¥ç¨èªå®ä¹ç URL å»æ¿ä»£å®ï¼ææ¹ä¾¿çå°±æ¯éè¿å½ä»¤è¡æå®ã
# Load the local file âc:\example\example.htmlâ
cefsimple.exe --url=file://c:/example/example.html
é¤äºå½ä»¤è¡çæ¹æ³ï¼ä¹å¯ä»¥éè¿ç´æ¥ä¿®æ¹å¨ cefsimple/simple.cpp æ件ä¸ç代ç ï¼è¾¾å°ä½ çç®çã
# Load the local file âc:\example\example.htmlâ
â¦
if (url.empty())
url = file://c:/example/example.html;
åºç¨ç¨åºç»æ
ææç CEF åºç¨ç¨åºé½æä¸ä¸ä¸»è¦ç»æé¨åï¼
CEF çå¨æé¾æ¥åº ã(å¨ Windows å¹³å°ä¸å°±æ¯ libcef.dll)
æ¯æåºãï¼ICU,面源码 FFMPEGçï¼
èµæºã(html/js/css, stringsç)
客æ·ç«¯æ§è¡æ件ã(æ¬æç¨ä¸å°±æ¯ cefsimple.exe.)
è¦ç¹ï¼å¿ çï¼
CEF 使ç¨çæ¯å¤è¿ç¨ãåºç¨ç¨åºä¸»è¿ç¨æ¯æµè§å¨è¿ç¨ï¼èå ¶ä»åè¿ç¨æ¯ç± renderer, plugins, GPUçå建ã
å¨ Windows å Linux å¹³å°ä¸çæ§è¡æ件å¯ä»¥è¢«ä¸»è¿ç¨ååè¿ç¨ä½¿ç¨ã
CEF ä¸ææè¿ç¨é½å¯ä»¥æ¯å¤çº¿ç¨çãCEFæä¾äºè®¸å¤åè½åæ¥å£å¨ä¸åç线ç¨ä¸ä¼ éä»»å¡ã
ä¸äºåè°æ¹æ³åå½æ°åªè½å¨ç¹å®çè¿ç¨æè 线ç¨ä¸ä½¿ç¨ãå¨ä½ 第ä¸æ¬¡ä½¿ç¨æ°çåè°æ¹æ³æè å½æ°ä¹åï¼è¯·ç¡®ä¿ä½ å·²ç»é è¯»äº API 头æ件ä¸æºç ï¼ç使ç¨è¦æ±ã
æµç¨åæ
cefsimple åºç¨ç¨åºé¦å åå§åCEFï¼ç¶åå建äºä¸ä¸ªç®åçå¼¹åºæµè§å¨çªå£ãå½å ³éäºææçæµè§å¨çªå£ï¼åºç¨ç¨åºå°±ä¼ç»æãç¨åºæ§è¡æµç¨å¦ä¸ï¼
ç³»ç»æ§è¡å ¥å£ç¹å½æ°(main or wWinMain)ï¼å¹¶å建æµè§å¨è¿ç¨ã
å ¥å£ç¹å½æ°ï¼
å建è½å¤å¤çè¿ç¨çº§å«çåè°æ¹æ³ç SimpleApp å®ä¾ã
åå§å CEFï¼è¿å ¥ CEF æ¶æ¯å¾ªç¯ã
åå§å CEF ä¹åï¼è°ç¨ SimpleApp::OnContextInitialized() ãè¿ä¸ªæ¹æ³ä¸ï¼
å建åä¾ç SimpleHandler ã
ç± CefBrowserHost::CreateBrowsersync() æ¹æ³å建ä¸ä¸ªæµè§å¨çªå£ã
ææçæµè§å¨å ±äº« SimpleHandler å®ä¾ï¼ æ¤å®ä¾è½å®å¶æµè§å¨è¡ä¸ºãå¤çæµè§å¨ç¸å ³åè°æ¹æ³(life span, loading state, title displayç)ã
å½ä¸ä¸ªæµè§å¨çªå£å ³éçæ¶åï¼è°ç¨ SimpleHandler::OnBeforeClose() ãå½ææçæµè§å¨çªå£å ¨é¨å ³éæ¶ï¼OnBeforeClose() å½æ°å°±ä¼æ§è¡è·³åº CEF æ¶æ¯å¾ªç¯çè¡ä¸ºï¼éåºåºç¨ç¨åºã
å ¥å£ç¹å½æ°
ç¨åºçè¿è¡å¼å§äºæµè§å¨è¿ç¨ä¸çå ¥å£ç¹å½æ°ãè¿ä¸ªå½æ°ä¼åå§å CEF 以åææè·æä½ç³»ç»æå ³ç对象ã
Windows
#include <windows.h>
#include "cefsimple/simple_app.h"
#include "include/cef_sandbox_win.h"
// Set to 0 to disable sandbox support.
#define CEF_ENABLE_SANDBOX 1
#if CEF_ENABLE_SANDBOX
// The cef_sandbox.lib static library is currently built with VS. It may not
// link successfully with other VS versions.
#pragma comment(lib, "cef_sandbox.lib")
#endif
// Entry point function for all processes.
int APIENTRY wWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow) {
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
void* sandbox_info = NULL;
#if CEF_ENABLE_SANDBOX
// Manage the life span of the sandbox information object. This is necessary
// for sandbox support on Windows. See cef_sandbox_win.h for complete details.
CefScopedSandboxInfo scoped_sandbox;
sandbox_info = scoped_sandbox.sandbox_info();
#endif
// Provide CEF with command-line arguments.
CefMainArgs main_args(hInstance);
// SimpleApp implements application-level callbacks. It will create the first
// browser instance in OnContextInitialized() after CEF has initialized.
CefRefPtr<SimpleApp> app(new SimpleApp);
// CEF applications have multiple sub-processes (render, plugin, GPU, etc)
// that share the same executable. This function checks the command-line and,
// if this is a sub-process, executes the appropriate logic.
int exit_code = CefExecuteProcess(main_args, app.get(), sandbox_info);
if (exit_code >= 0) {
// The sub-process has completed so return here.
return exit_code;
}
// Specify CEF global settings here.
CefSettings settings;
#if !CEF_ENABLE_SANDBOX
settings.no_sandbox = true;
#endif
// Initialize CEF.
CefInitialize(main_args, settings, app.get(), sandbox_info);
// Run the CEF message loop. This will block until CefQuitMessageLoop() is
// called.
CefRunMessageLoop();
// Shut down CEF.
CefShutdown();
return 0;
}
SimpleApp
SimpleApp è´è´£å¤çè¿ç¨çº§å«çåè°æ¹æ³ãå®ä¼æé²åºä¸äºå¨å¤è¿ç¨ä¸å ±äº«æè 被ç¹å®è¿ç¨ä½¿ç¨çæ¥å£åæ¹æ³ãCefBrowserProcessHandler æ¥å£ï¼å¨æµè§å¨è¿ç¨ä¸è°ç¨ãè¿æä¸ä¸ªè¢«åç¦»åº CefBrowserProcessHandler æ¥å£ï¼ä¾å项ç®æ²¡æå±ç¤ºï¼åªä¼å¨æ¸²æè¿ç¨ä¸è¢«è°ç¨ãç±äº CefBrowserProcessHandler ä¸å å®ç°äº CefApp, åæ¶è¿æ CefBrowserProcessHandlerï¼æ以å®çè¿åå¼å¿ é¡»æ¯[this]ã
// simple_app.h
#include "include/cef_app.h"
class SimpleApp : public CefApp,
public CefBrowserProcessHandler {
public:
SimpleApp();
// CefApp methods:
virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler()
OVERRIDE { return this; }
// CefBrowserProcessHandler methods:
virtual void OnContextInitialized() OVERRIDE;
private:
// Include the default reference counting implementation.
IMPLEMENT_REFCOUNTING(SimpleApp);
};
// simple_app.cpp
#include "cefsimple/simple_app.h"
#include <string>
#include "cefsimple/simple_handler.h"
#include "cefsimple/util.h"
#include "include/cef_browser.h"
#include "include/cef_command_line.h"
SimpleApp::SimpleApp() {
}
void SimpleApp::OnContextInitialized() {
REQUIRE_UI_THREAD();
// Information used when creating the native window.
CefWindowInfo window_info;
#if defined(OS_WIN)
// On Windows we need to specify certain flags that will be passed to
// CreateWindowEx().
window_info.SetAsPopup(NULL, "cefsimple");
#endif
// SimpleHandler implements browser-level callbacks.
CefRefPtr<SimpleHandler> handler(new SimpleHandler());
// Specify CEF browser settings here.
CefBrowserSettings browser_settings;
std::string url;
// Check if a "--url=" value was provided via the command-line. If so, use
// that instead of the default URL.
CefRefPtr<CefCommandLine> command_line =
CefCommandLine::GetGlobalCommandLine();
url = command_line->GetSwitchValue("url");
if (url.empty())
url = "xxx";
// Create the first browser window.
CefBrowserHost::CreateBrowserSync(window_info, handler.get(), url,
browser_settings, NULL);
}
SimpleHandler
SimpleHandler è´è´£å¤çæµè§å¨çº§å«çåè°æ¹æ³ãè¿äºåè°æ¹æ³ä¼å¨æµè§å¨è¿ç¨ä¸æ§è¡ãå¨è¿ä¸ªé¡¹ç®ä¸ï¼é对ææçæµè§å¨ä½¿ç¨ç¸åç CefClient å®ä¾ï¼ä½æ¯å¦æä½ æ¿æï¼å¯ä»¥å¨èªå·±çåºç¨ç¨åºä¸ä½¿ç¨ä¸åç CefClientå®ä¾çã
// simple_handler.h
#include "include/cef_client.h"
#include <list>
class SimpleHandler : public CefClient,
public CefDisplayHandler,
public CefLifeSpanHandler,
public CefLoadHandler {
public:
SimpleHandler();
~SimpleHandler();
// Provide access to the single global instance of this object.
static SimpleHandler* GetInstance();
// CefClient methods:
virtual CefRefPtr<CefDisplayHandler> GetDisplayHandler() OVERRIDE {
return this;
}
virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() OVERRIDE {
return this;
}
virtual CefRefPtr<CefLoadHandler> GetLoadHandler() OVERRIDE {
return this;
}
// CefDisplayHandler methods:
virtual void OnTitleChange(CefRefPtr<CefBrowser> browser,
const CefString& title) OVERRIDE;
// CefLifeSpanHandler methods:
virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser) OVERRIDE;
virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser) OVERRIDE;
// CefLoadHandler methods:
virtual void OnLoadError(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
ErrorCode errorCode,
const CefString& errorText,
const CefString& failedUrl) OVERRIDE;
// Request that all existing browser windows close.
void CloseAllBrowsers(bool force_close);
private:
// List of existing browser windows. Only accessed on the CEF UI thread.
typedef std::list<CefRefPtr<CefBrowser> > BrowserList;
BrowserList browser_list_;
// Include the default reference counting implementation.
IMPLEMENT_REFCOUNTING(SimpleHandler);
};
// simple_handler.cpp
#include "cefsimple/simple_handler.h"
#include <sstream>
#include <string>
#include "cefsimple/util.h"
#include "include/cef_app.h"
#include "include/cef_runnable.h"
namespace {
SimpleHandler* g_instance = NULL;
} // namespace
SimpleHandler::SimpleHandler() {
ASSERT(!g_instance);
g_instance = this;
}
SimpleHandler::~SimpleHandler() {
g_instance = NULL;
}
// static
SimpleHandler* SimpleHandler::GetInstance() {
return g_instance;
}
void SimpleHandler::OnAfterCreated(CefRefPtr<CefBrowser> browser) {
REQUIRE_UI_THREAD();
// Add to the list of existing browsers.
browser_list_.push_back(browser);
}
void SimpleHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
REQUIRE_UI_THREAD();
// Remove from the list of existing browsers.
BrowserList::iterator bit = browser_list_.begin();
for (; bit != browser_list_.end(); ++bit) {
if ((*bit)->IsSame(browser)) {
browser_list_.erase(bit);
break;
}
}
if (browser_list_.empty()) {
// All browser windows have closed. Quit the application message loop.
CefQuitMessageLoop();
}
}
void SimpleHandler::OnLoadError(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
ErrorCode errorCode,
const CefString& errorText,
const CefString& failedUrl) {
REQUIRE_UI_THREAD();
// Don't display an error for downloaded files.
if (errorCode == ERR_ABORTED)
return;
// Display a load error message.
std::stringstream ss;
ss << "<html><body bgcolor=\"white\">"
"<h2>Failed to load URL " << std::string(failedUrl) <<
" with error " << std::string(errorText) << " (" << errorCode <<
").</h2></body></html>";
frame->LoadString(ss.str(), failedUrl);
}
浅析Selenium-WebDriver
年,当时在ThoughtWorks工作的面源码Jason Huggins开发了Selenium(Selenium RC)的第一版。年,面源码Google工程师基于Selenium开发了WebDriver。面源码全民神仙源码年,面源码Selenium和WebDriver合并,面源码形成了Selenium2(Selenium WebDriver)。面源码目前,面源码Selenium WebDriver的面源码模式已经升级到Selenium4,并有一个支线项目Selenium-Grid,面源码能够与Selenium配合进行多任务运行(主要针对分布式执行,面源码对于当前业务现状,面源码使用到的面源码可能性很小,本文不展开讲解)。
使用现状:虽然无法直接统计出每个公司的使用现状,但我们可以通过搜索趋势来侧面验证。通过Google Trends查询的结果显示,Selenium WebDriver主导的方案占据主流地位,而Selenium RC的方案正在逐步被淘汰。
Selenium RC:
组成部分:Selenium RC主要由客户端和服务器两部分组成。
工作原理:Selenium RC通过发送HTTP请求与服务器进行通信,服务器再将请求转发给浏览器执行。
缺点:Selenium RC的执行速度较慢,且需要各个浏览器厂商提供支持。
Selenium WebDriver:
组成部分:WebDriver主要由WebDriver接口和对应的浏览器驱动程序组成。
Web Driver:WebDriver提供了另一种与浏览器交互的陕西麻将源码方式,即利用浏览器原生的API,封装成一套面向对象的Selenium WebDriver API,直接操作浏览器页面里的元素,甚至操作浏览器本身(截屏、窗口大小、启动、关闭、安装插件、配置证书等)。由于使用的是浏览器的原生API,速度大大提高,但缺点是需要各个浏览器厂商各自提供。
各种编程语言编写的客户端:向remote server发起请求。
工作原理:底层通信包含以下两个过程:
Selenium -> ChromeDriver server:这个通信过程是基于HTTP协议。
例如,我们要打开一个浏览器页面,并访问www.google.com,先看下Selenium源码是怎么实现这个过程的。
首次建立连接的过程:
选择一个空闲的端口启动chromedriver。
具体发请求的接口:
最终的收口就是_request,发起一个blogs.com/uncleyong/p...
[8] cloud.tencent.com/devel...
[9] einverne.gitbook.io/sel...
使用CEF(四)— 在QT中集成CEF(1)基本集成
在QT,一个强大的跨平台C++开发框架,上构建基于CEF(Chrome Embedded Framework)的应用程序,我们之前的文章已经展示了基础的样例,但这些示例主要依赖原生或功能有限的CEF视图。本文将重新开始,通过VS,spring 事物源码实现一个结合QT和原生窗体的集成项目。环境搭建
本文不依赖QtCreator,而是使用VS和Qt VS Tools来构建环境,前提是你已安装并熟悉QT。安装Qt VS Tools插件
在VS扩展中搜索并安装相应的QT插件,安装后别忘了重启VS。配置Qt环境
在Extensions - Qt VS Tools - Options中,配置Qt-VS编译设置。创建Qt项目
在配置完成后,通过VS创建项目,Qt模板将自动出现。我们创建名为QtCefDemo的项目,Qt创建向导随即启动。项目结构
创建完成后,项目的文件结构如下,展示了一个基本的VS IDE下的QT项目。集成CEF环境
为了集成CEF,我们首先需要编译并准备相应的头文件、库文件和资源文件,包括libcef_dll_wrapper.lib(MDd和MD版本)。项目目录结构
按照步骤,你会看到项目的文件夹结构,包括CefFiles文件夹及其内部的头文件、库文件和资源。配置manifest文件和VS设置
为CEF项目配置manifest文件,以及头文件和库文件的路径,这里会根据Debug和Release环境自动调整。cfrez解包源码集成完成后的问题与解决方案
在集成过程中,可能会遇到运行时的库文件问题。需要手动或通过脚本将相关文件复制到输出目录。初始运行可能存在问题,但后续文章将深入探讨解决方法。源码和资源
项目源码可以在我的GitHub仓库找到,链接在此省略。此外,需要下载CefFiles文件夹,可以从网盘获取,链接同样省略。cef是什么文件夹
CEF是Chromium Embedded Framework的缩写,是基于Google Chrominum项目开发的一个web浏览器控件。
CEF是开源的,可以下载源码,去掉自己不需要的模块,然后自己编译,也可以直接使用CEF提供的库,CEF提供的库是libcef.dll,很多程序都有libcef.dll这个动态库,比如qq和evernote,但是大小都不一样,估计是自己裁剪过然后编译的。
JavaScript 获取整个网页的完整内容,包括 html 标签。
在探索网页内容的世界里,JavaScript 提供了强大的单词 app 源码工具来抓取我们需要的信息。首先,让我们聚焦于获取 body 标签内的核心内容,这是通过简单的 document.body.innerHTML</操作实现的,它能直接揭示页面的基础结构。 然而,如果你追求的是更全面的网页解析,那么仅仅触及 body 标签是不够的。这时候,document.documentElement.outerHTML</这一命令就显得尤为重要。它能帮你获取到整个 HTML 的结构,包括 head 和 html 标签,是抓取网页全貌的关键。 然而,CefSharp 的使用场景稍有不同,它基于 ChromiumWebBrowser 类型的 GetSourceAsync 方法,为我们提供了获取源代码的途径。这个方法返回的字符串确实包含了 HTML 标签,但动态生成的内容却未被囊括其中。如果你的目标是包括那些动态生成的部分,比如在数据采集中,那就需要采取更为灵活的策略。 为此,我们设计了一个实用的静态方法 GetDocumentHtml</,它巧妙地结合了 CefSharp 的功能。这个方法首先尝试通过 browser.EvaluateScriptAsync</执行 JavaScript 代码,试图获取动态渲染的 HTML,如果成功,就返回解析的结果;如果遇到动态内容的壁垒,它会退而求其次,利用 GetSourceAsync 方法获取完整的源代码,确保你得到的是网页的完整视图。通过这段代码,无论是静态还是动态的网页内容,都能在 JavaScript 的指引下,被准确无误地捕获,满足你对网页数据深度挖掘的需求。
chromium+embedded+framework能卸载吗?
Chromium Embedded Framework(CEF)本身是一个开源的框架,可以通过GitHub等开源平台获取其源代码。由于其是一个框架,因此不是一个独立的应用程序,而是被其他应用程序(例如Web浏览器)所使用。因此,CEF本身无法被卸载。
如果您想卸载使用CEF构建的应用程序,可以通过以下方式进行:
在操作系统中找到该应用程序的卸载程序,一般可以在“控制面板”或者应用程序的安装目录中找到。运行卸载程序后,该应用程序及其使用的CEF框架将被卸载。
如果无法通过卸载程序进行卸载,可以尝试使用第三方卸载工具,例如Revo Uninstaller等。
需要注意的是,在卸载使用CEF构建的应用程序之前,请确保该应用程序中的所有数据已经备份或者转移至其他地方,以免造成数据丢失。
使用CEF(二)— 基于VS编写一个简单CEF样例
在这一章节中,我们将基于VS构建一个简单的CEF(Chromium Embedded Framework)示例。首先,确保你已经阅读过《使用CEF(1)— 起步》,或了解如何获取libcef库以及libcef_dll_wrapper静态库。
接下来,我们将在Debug模式下进行代码开发。若选择Debug或Release模式,确保与libcef库和libcef_dll_wrapper完全一致。创建名为cef的文件夹,将所有相关文件放入其中。之后,创建一个名为simple-cef的C++Windows Desktop Application项目。
删除模板生成的代码,得到一个空白的应用程序。接下来,我们需要准备几个关键步骤:添加头文件,配置项目依赖,以及编写代码。
引入正确的头文件是关键。将cef文件夹包含在项目中,通过在VS中设置Additional Include Directories为$(ProjectDir)cef/include,确保头文件正确加载。添加libcef_dll_wrapper.lib、libcef.lib、cef_sandbox.lib等静态库到Additional Dependencies中。
现在,项目已完成编译和链接配置。接下来,通过cefsimple代码解释如何使用CEF,并适当添加细节。
在simple_app.h和simple_app.cpp中,实现CefApp和CefClient概念,并通过CefWindowDelegate和CefBrowserViewDelegate提供图形视图框架。同时,编写simple_client.cpp和simple_client_os_win.cpp以处理不同平台的具体细节。
最后,编写main.cpp作为入口代码。确保代码结构清晰,包括编译和运行步骤。在运行过程中,可能遇到编译和链接的不匹配问题,通过调整项目的Runtime Library设置为MTd以匹配libcef_dll_wrapper.lib。
在遇到运行问题时,如白屏和错误信息时,创建simple-cef.manifest文件,并将其添加到项目的Additional Manifest Files中。这将帮助解决跨平台版本兼容性问题,最终使页面正常加载。
通过这个示例,我们不仅实现了CEF应用的基本功能,还深入理解了CefApp、CefClient、CefWindowDelegate、CefBrowserViewDelegate等关键组件及其作用。CEF跨平台的实现策略、框架的内部工作原理等问题仍然值得进一步探索和研究。
源代码请自行编译并根据指定方式添加依赖文件。
使用CEF(三)— 从CEF官方Demo源码入手解析CEF架构与CefApp、CefClient对象
在《使用CEF(2)— 基于VS编写一个简单CEF样例》中,介绍了使用简单样例进行CEF(Common Engineering Framework)编程,其中涉及到了CefApp、CefClient等类。本文将深入解析这些概念,以及CEF的进程架构。
CEF3使用多进程模型,主进程负责窗口创建、绘制和网络访问,被称为“浏览器”进程。通常与宿主应用程序进程相同,多数应用逻辑在浏览器进程中执行。渲染和JavaScript执行在单独的“渲染”进程中进行。一些应用逻辑,如JavaScript绑定和DOM访问,在渲染进程中执行。默认情况下,每个唯一源(scheme + domain)运行一个新渲染进程。根据需要,还会生成插件进程处理Flash等,以及GPU进程处理加速合成。
浏览器进程(Browser Process)负责创建窗口和渲染HTML内容。渲染进程(Renderer Process)则执行Blink渲染和JavaScript执行。两者分别运行,确保高效率的多任务处理和资源管理。
在官方提供的Demo(cefsimple)中,将从源码解析Cef概念,重点是CefApp和CefClient。CefApp接口提供进程特定回调访问,CefClient接口提供浏览器实例特定回调访问,一个CefApp实例对应一个进程,CefClient实例可以共享多个浏览器实例。
cefsimple示例代码展示了如何创建CefApp和CefClient实例,并通过它们管理浏览器进程和渲染进程之间的交互。CefApp实例通过GetBrowserProcessHandler和GetRendererProcessHandler方法获取相关处理器。CefClient实例则用于处理浏览器实例间的事件回调,如生命周期变化、显示事件等。
通过理解CefApp和CefClient的作用,可以更好地掌握CEF架构及其在实际应用中的应用。官方文档提供了更深入的指南和详细信息,推荐进一步学习。
2024-12-28 19:41
2024-12-28 19:10
2024-12-28 19:02
2024-12-28 18:21
2024-12-28 18:12
2024-12-28 18:12