diff --git a/code/.example.env b/code/.example.env
index 9d54ea7..927e93d 100644
--- a/code/.example.env
+++ b/code/.example.env
@@ -2,7 +2,6 @@
APP_DEBUG = true
SHOW_ERROR_MSG = true
DEFAULT_LANG = zh-cn
-DEFAULT_
# 数据库
DB_TYPE = mysql
@@ -26,6 +25,13 @@ REDIS_PASSWORD =
REDIS_PREFIX =
REDIS_DATABASE = 0
+#MongoDB
+MONGO_HOST = 127.0.0.1
+MONGO_PORT = 27017
+MONGO_USER = admin
+MONGO_PASSWORD = qq123123
+MONGO_DB = novel
+
# TASK
TASK_PROCESS_NUM_01 = 4
TASK_PROCESS_NUM_02 = 2
diff --git a/code/123.html b/code/123.html
deleted file mode 100644
index a96cc61..0000000
--- a/code/123.html
+++ /dev/null
@@ -1,359 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- 张柠檬2最新章节_张柠檬2伯未有_张柠檬2小说免费全文阅读_恋上你中文
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
张柠檬2
-
-
作 者:伯未有
-
类 别:都市小说
-
状 态:连载中
-
动 作:加入书架、章节目录、开始阅读
-
最后更新:2025-03-19 17:06:56
-
最新章节:二重奏
-
-
-
- 这仍是一部探索欲望与心理SM现实主义小说,主人公张柠檬以自述的方式讲述了自己的生活。作者试图写出一篇严肃的色情小说,表达一种独特的“异化美感”,希望从冷酷中塑造一种黑暗的诗意。(我也不知道我在说什么)—————————介绍:本文描绘了大量SM场景元素,存在各式各样的拘束,奴役、羞辱和强迫的内容。前作换过很多名字,最早名为《我是如何成为富二代的的玩物的》,又更名为《玩具》、《张柠檬》等还有一些我也忘记的名字,看过就是缘。————————不知道诸位看官究竟喜欢什么内容,故事的内容是无限的,但作者的精力属实是有限的,众所周知本作断更已然是一种常态~_~;所以大伙儿们喜欢什么请一定要告诉我,我好更多的去侧重呀。诸如:A五花八门的调教??B云朝雨暮的性爱C风花雪月的交往D刻骨铭心的爱情如果还有别的也请补充
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/code/333.php b/code/333.php
deleted file mode 100644
index a65a9e8..0000000
--- a/code/333.php
+++ /dev/null
@@ -1,73 +0,0 @@
- 'https://www.630zw.org',
- 'http_errors' => false,
- 'version' => 2.0, // 强制使用 HTTP/2
- 'timeout' => 160,
- 'connect_timeout' => 30, // 连接超时 10 秒
-
- 'curl' => [
- CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0, // 强制 HTTP/2
- CURLOPT_TCP_KEEPALIVE => 1, // 启用 TCP Keep-Alive
- CURLOPT_SSL_VERIFYPEER => true, // 验证 SSL
- ],
- 'headers' => [
- 'User-Agent' => 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Mobile Safari/537.36 Edg/134.0.0.0',
- 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
- 'Accept-Language' => 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
- 'Referer' => 'https://www.630zw.org/',
-
- 'sec-ch-ua-mobile' => '?1',
- 'sec-ch-ua-platform' => '"Android"',
- 'sec-fetch-dest' => 'document',
- 'sec-fetch-mode' => 'navigate',
- 'sec-fetch-site' => 'same-origin',
- 'sec-fetch-user' => '?1',
- 'upgrade-insecure-requests' => '1',
- ],
-]);
-
-$i = 20284;
-while (true) {
-
- $strStartDate = date('Y-m-d H:i:s') . PHP_EOL;
-
-
- $promises = [];
-
- $j = $i + 15;
- for (; $i <= $j; $i++) {
- $promises['res' . $i] = $client->getAsync('/shu/' . $i . '.html');
- }
-
-
- // 等待所有请求完成
- $responses = Promise\Utils::unwrap($promises);
- $results = Promise\settle($promises)->wait();
-
- foreach ($responses as $key => $response) {
- // echo "$key: " . $response->getBody() . "\n";
-
- $html = (string) $response->getBody();
- $dom = new DOMDocument();
- @$dom->loadHTML($html);
- $title = $dom->getElementsByTagName('title')->item(0);
- $titleText = $title ? $title->textContent : 'No title found';
- echo "$key: Title: $titleText\n";
- }
-
-
- $strEndDate = date('Y-m-d H:i:s') . PHP_EOL;
-
- echo $strStartDate;
- echo $strEndDate;
-}
diff --git a/code/444.php b/code/444.php
deleted file mode 100644
index c724a18..0000000
--- a/code/444.php
+++ /dev/null
@@ -1,85 +0,0 @@
-client = new Client($uri);
- $this->db = $this->client->selectDatabase($dbName);
- } catch (MongoDBException $e) {
- die("MongoDB connection failed: " . $e->getMessage());
- }
- }
-
- // 获取下一个自增序列值
- public function getNextSequence($sequenceName) {
- try {
- $counters = $this->db->selectCollection('counters');
- $result = $counters->findOneAndUpdate(
- ['_id' => $sequenceName],
- ['$inc' => ['sequence_value' => 1]],
- [
- 'upsert' => true, // 如果不存在则创建
- 'returnDocument' => MongoDB\Operation\FindOneAndUpdate::RETURN_DOCUMENT_AFTER // 返回更新后的文档
- ]
- );
-
- // 如果是第一次创建,sequence_value 可能为 null,初始化为 1
- if ($result === null || !isset($result->sequence_value)) {
- return 1;
- }
-
- return $result->sequence_value;
- } catch (MongoDBException $e) {
- throw new Exception("Failed to generate sequence: " . $e->getMessage());
- }
- }
-
- // 示例:插入用户数据
- public function insertUser($name, $age) {
- try {
- $userId = $this->getNextSequence('userId');
- $users = $this->db->selectCollection('users');
- $result = $users->insertOne([
- '_id' => $userId,
- 'name' => $name,
- 'age' => $age,
- 'created_at' => new MongoDB\BSON\UTCDateTime()
- ]);
-
- return $result->getInsertedId();
- } catch (MongoDBException $e) {
- throw new Exception("Failed to insert user: " . $e->getMessage());
- }
- }
-}
-
-// 使用示例
-try {
- // 设置你的 MongoDB 连接信息
- $mongoUri = 'mongodb://admin:qq123123@127.0.0.1:27017'; // 替换为你的 IP、用户名和密码
- $dbName = 'test001'; // 替换为你的数据库名
-
- $generator = new MongoSequenceGenerator($mongoUri, $dbName);
-
- // 插入几个测试用户
- $id1 = $generator->insertUser('Alice', 25);
- echo "Inserted user with ID: " . $id1 . "\n";
-
- $id2 = $generator->insertUser('Bob', 30);
- echo "Inserted user with ID: " . $id2 . "\n";
-
- $id3 = $generator->insertUser('Charlie', 35);
- echo "Inserted user with ID: " . $id3 . "\n";
-
-} catch (Exception $e) {
- echo "Error: " . $e->getMessage() . "\n";
-}
-?>
\ No newline at end of file
diff --git a/code/app/admin/config/router.php b/code/app/admin/config/router.php
index ef340bb..cd3ffeb 100644
--- a/code/app/admin/config/router.php
+++ b/code/app/admin/config/router.php
@@ -2,6 +2,8 @@
declare(strict_types=1);
+use app\admin\controller\Domain;
+use app\admin\controller\Novel;
use app\admin\middleware\AdminAuth;
use think\facade\Route;
@@ -24,37 +26,19 @@ Route::group("/system", function () {
Route::get("/cjpz/list", "SystemConfig/getCaiJiPeiZhiList")->name("SystemConfig@getCaiJiPeiZhiList");
Route::post("/cjpz/save", "SystemConfig/saveCaiJiPeiZhi")->name("SystemConfig@saveCaiJiPeiZhi");
- Route::get("/zydd/list", "SystemConfig/getZiYuanDuanDianList")->name("SystemConfig@getZiYuanDuanDianList");
- Route::post("/zydd/save", "SystemConfig/saveZiYuanDuanDian")->name("SystemConfig@saveZiYuanDuanDian");
-
Route::get("/plan/list", "SystemConfig/getPlanTaskList")->name("SystemConfig@getPlanTaskList");
Route::post("/plan/save", "SystemConfig/savePlanTask")->name("SystemConfig@savePlanTask");
-
- Route::get("/keyword/list", "SeoKeywords/getSeoKeywordList")->name("SeoKeywords@getSeoKeywordList");
- Route::post("/keyword/save", "SeoKeywords/saveSeoKeyword")->name("SeoKeywords@saveSeoKeyword");
- Route::post("/keyword/del", "SeoKeywords/delSeoKeyword")->name("SeoKeywords@delSeoKeyword");
-})->middleware(AdminAuth::class);
-
-# 广告
-Route::group("/guanggao", function () {
- # 主
- Route::get("/main/group/list", "GuangGao/getGuangGaoFenZuList")->name("GuangGao@getGuangGaoFenZuList");
- Route::post("/main/group/save", "GuangGao/saveGuangGaoFenZu")->name("GuangGao@saveGuangGaoFenZu");
- Route::post("/main/group/del", "GuangGao/delGuangGaoFenZu")->name("GuangGao@delGuangGaoFenZu");
- Route::get("/main/list", "GuangGao/getGuangGaoList")->name("GuangGao@getGuangGaoList");
- Route::post("/main/save", "GuangGao/saveGuangGao")->name("GuangGao@saveGuangGao");
- Route::post("/main/del", "GuangGao/delGuangGao")->name("GuangGao@delGuangGao");
-
- # 广告配置
- Route::get("/ggpz/list", "GuangGao/getGuangGaoPeiZhiList")->name("GuangGao@getGuangGaoPeiZhiList");
- Route::post("/ggpz/save", "GuangGao/saveGuangGaoPeiZhi")->name("GuangGao@saveGuangGaoPeiZhi");
-
})->middleware(AdminAuth::class);
# 小说
-Route::group("/xiaoshuo", function () {
- Route::get("/main/list", "XiaoShuo/getXiaoShuoList")->name("XiaoShuo@getXiaoShuoList");
- Route::get("/zhangjie/list", "XiaoShuo/getXiaoShuoZhangJieList")->name("XiaoShuo@getXiaoShuoZhangJieList");
- Route::get("/fenlei/list", "XiaoShuo/getXiaoShuoFenLeiList")->name("XiaoShuo@getXiaoShuoFenLeiList");
+Route::group("/novel", function () {
+ Route::get("/list", [Novel::class, "getNovelList"])->name("Domain@getNovelList");
+ Route::post("/level/set", [Novel::class, "updateNovelLevel"])->name("Novel@updateNovelLevel");
+ Route::get("/category/list", [Novel::class, "getCategory"])->name("Domain@getCategory");
})->middleware(AdminAuth::class);
+# 站点
+Route::group("/site", function () {
+ Route::get("/domain/list", [Domain::class, "getDomainList"])->name("Domain@getDomainList");
+ Route::post("/domain/upload", [Domain::class, "uploadDomain"])->name("Domain@uploadDomain");
+})->middleware(AdminAuth::class);
diff --git a/code/app/admin/controller/Domain.php b/code/app/admin/controller/Domain.php
new file mode 100644
index 0000000..7090534
--- /dev/null
+++ b/code/app/admin/controller/Domain.php
@@ -0,0 +1,118 @@
+file('excel_file');
+ if (!$File) {
+ return $this->error("9999", '请上传 Excel 文件');
+ }
+
+ try {
+ $arrAllowedExt = ['xls', 'xlsx'];
+ $strExt = $File->getOriginalExtension();
+ if (!in_array($strExt, $arrAllowedExt)) {
+ return $this->error("9999", '仅支持 xls 或 xlsx 文件');
+ }
+
+ $FilePath = $File->getPathname();
+
+ $Spreadsheet = IOFactory::load($FilePath);
+ $Sheet = $Spreadsheet->getActiveSheet();
+ $arrData = $Sheet->toArray();
+
+ array_shift($arrData);
+
+ foreach ($arrData as $arrRows) {
+ $arrDomain = [
+ 'd_domain' => $arrRows[0],
+ 't_id' => $arrRows[1],
+ 'd_title' => $arrRows[2],
+ 'd_keywords' => $arrRows[3],
+ 'd_description' => $arrRows[4],
+ 'd_statis' => $arrRows[5],
+ 'd_logo_type' => (int)$arrRows[6],
+ 'd_text_log' => $arrRows[7],
+ 'd_img_log' => $arrRows[8],
+ ];
+ $DomainModel = DomainModel::where('d_domain', $arrDomain['d_domain'])->findOrEmpty();
+ $DomainModel->fill($arrDomain);
+ $DomainModel->save();
+ }
+
+
+ return $this->success();
+ } catch (\Exception $e) {
+ return $this->error("9999", '导入失败: ' . $e->getMessage());
+ }
+ }
+
+ /**
+ * 列表
+ *
+ * @param Request $Request
+ * @param AdminUserModel|null $AdminUserModel
+ * @return Response
+ */
+ public function getDomainList(Request $Request, ?AdminUserModel $AdminUserModel)
+ {
+ $arrData = [
+ "page" => $Request->get('page', 1),
+ "limit" => $Request->get('limit', 10),
+ "key" => $Request->get('key'),
+ ];
+
+ $arrRule = [
+ 'page' => 'require|number',
+ 'limit' => 'require|number',
+ ];
+
+ $this->validate($arrData, $arrRule);
+
+ $DomainModel = DomainModel::alias('d');
+
+ if (!empty($arrData['key'])) {
+ $strKey = $arrData['key'];
+ $DomainModel->where(function ($DomainModel) use ($strKey) {
+ $DomainModel->whereOr([
+ ['d.d_domain', 'like', "%" . $strKey . "%"],
+ ['d.d_id', 'like', "%" . $strKey . "%"],
+ ]);
+ });
+ }
+
+ $Paginate = $DomainModel->paginate([
+ 'list_rows' => $arrData['limit'],
+ 'page' => $arrData['page'],
+ ]);
+
+ $arrResult = [
+ 'items' => $Paginate->items(),
+ 'total' => $Paginate->total(),
+ 'current_page' => $Paginate->currentPage(),
+ 'total_pages' => $Paginate->lastPage(),
+ ];
+
+ return $this->success($arrResult);
+ }
+}
diff --git a/code/app/admin/controller/Novel.php b/code/app/admin/controller/Novel.php
new file mode 100644
index 0000000..b8fa98e
--- /dev/null
+++ b/code/app/admin/controller/Novel.php
@@ -0,0 +1,115 @@
+ $Request->post('keywords'),
+ "n_level" => (int)$Request->post('n_level'),
+ ];
+
+ $arrRule = [
+ 'keywords' => 'require',
+ 'n_level' => 'require',
+ ];
+
+ $this->validate($arrData, $arrRule);
+
+ $arrKeywords = explode(',', $arrData['keywords']);
+ $NovelModel = new NovelModel;
+ $NovelModel->updateByKeywords($arrKeywords, $arrData['n_level']);
+ return $this->success();
+ }
+
+ /**
+ * 小说分页
+ *
+ * @param Request $Request
+ * @param AdminUserModel|null $AdminUserModel
+ * @return Response
+ */
+ public function getNovelList(Request $Request, ?AdminUserModel $AdminUserModel)
+ {
+ $intPage = $Request->param('page', 1, 'intval');
+ $intLimit = $Request->param('limit', 10, 'intval');
+
+ try {
+
+ $NovelModel = new NovelModel;
+
+ $Col = $NovelModel->getCol();
+
+ // 查询条件(可选)
+ $arrFilter = [];
+ // 示例:如果需要筛选,例如 status=1
+ // $arrFilter['status'] = 1;
+
+ $arrOptions = [
+ 'sort' => ['_id' => -1],
+ 'skip' => ($intPage - 1) * $intLimit,
+ 'limit' => $intLimit,
+ ];
+
+ $Cursor = $Col->find($arrFilter, $arrOptions);
+ $arrData = iterator_to_array($Cursor);
+
+ $intTotal = $Col->countDocuments($arrFilter);
+
+ $arrResult = [
+ 'code' => 200,
+ 'msg' => '查询成功',
+ 'data' => $arrData,
+ 'total' => $intTotal,
+ 'page' => $intPage,
+ 'limit' => $intLimit,
+ 'pages' => ceil($intTotal / $intLimit)
+ ];
+ return $this->success($arrResult);
+ } catch (\Exception $e) {
+ return $this->error("9999", $e->getMessage());
+ }
+ }
+
+ /**
+ * get category data
+ *
+ * @param Request $Request
+ * @param AdminUserModel|null $AdminUserModel
+ * @return void
+ */
+ public function getCategory(Request $Request, ?AdminUserModel $AdminUserModel)
+ {
+ $arrResult = [
+ 'all' => CategoryModel::getCategory(),
+ 'man' => CategoryModel::getManCategory(),
+ 'women' => CategoryModel::getWomenCategory(),
+ ];
+ return $this->success($arrResult);
+ }
+}
diff --git a/code/app/api/BaseController.php b/code/app/api/BaseController.php
new file mode 100644
index 0000000..2e106a8
--- /dev/null
+++ b/code/app/api/BaseController.php
@@ -0,0 +1,98 @@
+app = $app;
+ $this->request = $this->app->request;
+
+ // 控制器初始化
+ $this->initialize();
+
+ // View::assign('')
+ }
+
+ // 初始化
+ protected function initialize() {}
+
+ /**
+ * 验证数据
+ * @access protected
+ * @param array $data 数据
+ * @param string|array $validate 验证器名或者验证规则数组
+ * @param array $message 提示信息
+ * @param bool $batch 是否批量验证
+ * @return array|string|true
+ * @throws ValidateException
+ */
+ protected function validate(array $data, string|array $validate, array $message = [], bool $batch = false)
+ {
+ if (is_array($validate)) {
+ $v = new Validate();
+ $v->rule($validate);
+ } else {
+ if (strpos($validate, '.')) {
+ // 支持场景
+ [$validate, $scene] = explode('.', $validate);
+ }
+ $class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate);
+ $v = new $class();
+ if (!empty($scene)) {
+ $v->scene($scene);
+ }
+ }
+
+ $v->message($message);
+
+ // 是否批量验证
+ if ($batch || $this->batchValidate) {
+ $v->batch(true);
+ }
+
+ return $v->failException(true)->check($data);
+ }
+}
diff --git a/code/app/api/common.php b/code/app/api/common.php
new file mode 100644
index 0000000..1243615
--- /dev/null
+++ b/code/app/api/common.php
@@ -0,0 +1,2 @@
+middleware(\think\middleware\Throttle::class, [
+ // 'visit_rate' => '1/m',
+ // 'key' => '__CONTROLLER__/__ACTION__/__IP__',
+ // ])
+ ->name('Statis@publish');
+
+
+Route::get("/search", [Novel::class, 'search'])
+ // ->middleware(\think\middleware\Throttle::class, [
+ // 'visit_rate' => '1/m',
+ // 'key' => '__CONTROLLER__/__ACTION__/__IP__',
+ // ])
+ ->name('Novel@search');;
+
+
+
+
+Route::get("/test", [Statis::class, 'test'])
+ // ->middleware(\think\middleware\Throttle::class, [
+ // 'visit_rate' => '1/m',
+ // 'key' => '__CONTROLLER__/__ACTION__/__IP__',
+ // ])
+ ->name('Statis@test');
diff --git a/code/app/api/config/throttle.php b/code/app/api/config/throttle.php
new file mode 100644
index 0000000..ce79257
--- /dev/null
+++ b/code/app/api/config/throttle.php
@@ -0,0 +1,12 @@
+ 'api:throttle_',
+ 'visit_method' => ["GET", "POST", "PUT", "DELETE", "HEAD"],
+ 'visit_fail_response' => function (Throttle $throttle, $request, $wait_seconds) {
+ return Response::create('抱歉,请求过于频繁, 请 ' . $wait_seconds . ' 秒后重新操作 。')->code(429);
+ },
+];
diff --git a/code/app/api/controller/Novel.php b/code/app/api/controller/Novel.php
new file mode 100644
index 0000000..c19970f
--- /dev/null
+++ b/code/app/api/controller/Novel.php
@@ -0,0 +1,14 @@
+post('n_id/d');
+
+ if ($intNId <= 0) {
+ return Response::create('非法请求!')->code(400);
+ }
+
+ $NovelClicksModel = new NovelClicksModel;
+
+ try {
+ // for ($intNId = 1; $intNId <= 20; $intNId++) {
+ // $NovelClicksModel->addStatis($intNId);
+ // }
+ $NovelClicksModel->addStatis($intNId);
+ } catch (\Exception $e) {
+ // throw $e;
+ }
+
+ return Response::create()->code(204);
+ }
+
+ /**
+ * test code
+ *
+ * @param Request $Request
+ * @return Response
+ */
+ public function test(Request $Request): Response
+ {
+ // $arrCategory = CategoryModel::getCategory();
+
+ // print_r($arrCategory);
+
+
+ $NovelModel = new NovelModel;
+ $NovelModel->updateByKeywords(["夜", "宝"], 9);
+ exit;
+ return Response::create();
+ }
+}
diff --git a/code/app/api/event.php b/code/app/api/event.php
new file mode 100644
index 0000000..4eff890
--- /dev/null
+++ b/code/app/api/event.php
@@ -0,0 +1,5 @@
+failException(true)->check($data);
}
+ public function rending($strTemplate = '')
+ {
+ View::assign("DomainModel", $this->request->DomainModel);
+ View::assign("TemplatesModel", $this->request->TemplatesModel);
+ return View::fetch($strTemplate);
+ }
}
diff --git a/code/app/home/controller/Index.php b/code/app/home/controller/Index.php
index f17ac48..35be71e 100644
--- a/code/app/home/controller/Index.php
+++ b/code/app/home/controller/Index.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace app\home\controller;
use app\home\BaseController;
+use app\model\NovelModel;
use storage\StorageCore;
use think\facade\View;
use app\Request;
@@ -27,7 +28,7 @@ class Index extends BaseController
['id' => 6, 'name' => '小说3', 'author' => '作者3'],
['id' => 7, 'name' => '小说3', 'author' => '作者3'],
['id' => 8, 'name' => '小说3', 'author' => '作者3'],
-
+
];
// 模拟数据-最新 随机8个小说
@@ -118,7 +119,14 @@ class Index extends BaseController
// 获取路由附加的参数
$boolIsMobile = $Request->param('boolIsMobile');
+ $NovelModel = new NovelModel;
+
+
// 模拟数据-推荐等级为9的连载的 随机15个小说
+ $arrRecommendSerializationNovel = $NovelModel->getHighLevelNovelOnCache($Request->DomainModel->d_domain, 13);
+ // echo '';
+ // print_r($arrRecommendSerializationNovel);
+ // exit;
$arrRecommendSerializationNovel = [
['id' => 1, 'name' => '小说1', 'author' => '作者1'],
['id' => 2, 'name' => '小说2', 'author' => '作者2'],
@@ -133,27 +141,36 @@ class Index extends BaseController
['id' => 8, 'name' => '小说3', 'author' => '作者3'],
['id' => 8, 'name' => '小说3', 'author' => '作者3'],
['id' => 8, 'name' => '小说3', 'author' => '作者3'],
-
+
];
// 模拟数据-推荐等级为9的完结的 随机12个小说
- $arrRecommendEndNovel = [
- ['id' => 1, 'name' => '小说1', 'author' => '作者1'],
- ['id' => 2, 'name' => '小说2', 'author' => '作者2'],
- ['id' => 3, 'name' => '小说3', 'author' => '作者3'],
- ['id' => 4, 'name' => '小说3', 'author' => '作者3'],
- ['id' => 5, 'name' => '小说3', 'author' => '作者3'],
- ['id' => 6, 'name' => '小说3', 'author' => '作者3'],
- ['id' => 7, 'name' => '小说3', 'author' => '作者3'],
- ['id' => 8, 'name' => '小说3', 'author' => '作者3'],
- ['id' => 9, 'name' => '小说3', 'author' => '作者3'],
- ['id' => 10, 'name' => '小说3', 'author' => '作者3'],
- ['id' => 11, 'name' => '小说3', 'author' => '作者3'],
- ['id' => 12, 'name' => '小说3', 'author' => '作者3'],
-
- ];
+
+ $arrRecommendEndNovel = $NovelModel->getFinishedNovelOnCache($Request->DomainModel->d_domain, 13);
+ // echo '';
+ // print_r($arrRecommendEndNovel);
+ // exit;
+ // $arrRecommendEndNovel = [
+ // ['id' => 1, 'name' => '小说1', 'author' => '作者1'],
+ // ['id' => 2, 'name' => '小说2', 'author' => '作者2'],
+ // ['id' => 3, 'name' => '小说3', 'author' => '作者3'],
+ // ['id' => 4, 'name' => '小说3', 'author' => '作者3'],
+ // ['id' => 5, 'name' => '小说3', 'author' => '作者3'],
+ // ['id' => 6, 'name' => '小说3', 'author' => '作者3'],
+ // ['id' => 7, 'name' => '小说3', 'author' => '作者3'],
+ // ['id' => 8, 'name' => '小说3', 'author' => '作者3'],
+ // ['id' => 9, 'name' => '小说3', 'author' => '作者3'],
+ // ['id' => 10, 'name' => '小说3', 'author' => '作者3'],
+ // ['id' => 11, 'name' => '小说3', 'author' => '作者3'],
+ // ['id' => 12, 'name' => '小说3', 'author' => '作者3'],
+
+ // ];
// 模拟数据-男生频道,最新的 随机5个小说
+ $arrBoyNewsNovel = $NovelModel->getSexNovelOnCache($Request->DomainModel->d_domain, 5, 1);
+ // echo '';
+ // print_r($arrBoyNewsNovel);
+ // exit;
$arrBoyNewsNovel = [
['id' => 1, 'name' => '小说1', 'author' => '作者1'],
['id' => 2, 'name' => '小说2', 'author' => '作者2'],
@@ -163,6 +180,10 @@ class Index extends BaseController
];
// 模拟数据-男生频道,周排行榜 随机10个小说
+ $arrBoyWeekRankNovel = $NovelModel->getRankNovelOnCache($Request->DomainModel->d_domain, 5, 1, 'weekly');
+ // echo '';
+ // print_r($arrBoyWeekRankNovel);
+ // exit;
$arrBoyWeekRankNovel = [
['id' => 1, 'name' => '小说1', 'author' => '作者1'],
['id' => 2, 'name' => '小说2', 'author' => '作者2'],
@@ -177,6 +198,10 @@ class Index extends BaseController
];
// 模拟数据-男生频道,月排行榜 随机10个小说
+ $arrBoyMoonRankNovel = $NovelModel->getRankNovelOnCache($Request->DomainModel->d_domain, 5, 1, 'monthly');
+ // echo '';
+ // print_r($arrBoyMoonRankNovel);
+ // exit;
$arrBoyMoonRankNovel = [
['id' => 1, 'name' => '小说1', 'author' => '作者1'],
['id' => 2, 'name' => '小说2', 'author' => '作者2'],
@@ -191,6 +216,10 @@ class Index extends BaseController
];
// 模拟数据-男生频道,总排行榜 随机10个小说
+ $arrBoyTotalRankNovel = $NovelModel->getRankNovelOnCache($Request->DomainModel->d_domain, 5, 1, 'total');
+ // echo '';
+ // print_r($arrBoyTotalRankNovel);
+ // exit;
$arrBoyTotalRankNovel = [
['id' => 1, 'name' => '小说1', 'author' => '作者1'],
['id' => 2, 'name' => '小说2', 'author' => '作者2'],
@@ -203,8 +232,12 @@ class Index extends BaseController
['id' => 9, 'name' => '小说3', 'author' => '作者3'],
['id' => 10, 'name' => '小说3', 'author' => '作者3'],
];
-
+
// 模拟数据-女生频道,最新的 随机5个小说
+ $arrGirlNewsNovel = $NovelModel->getSexNovelOnCache($Request->DomainModel->d_domain, 5, 2);
+ // echo '';
+ // print_r($arrGirlNewsNovel);
+ // exit;
$arrGirlNewsNovel = [
['id' => 1, 'name' => '小说1', 'author' => '作者1'],
['id' => 2, 'name' => '小说2', 'author' => '作者2'],
@@ -212,8 +245,12 @@ class Index extends BaseController
['id' => 4, 'name' => '小说3', 'author' => '作者3'],
['id' => 5, 'name' => '小说3', 'author' => '作者3'],
];
-
+
// 模拟数据-女生频道,周排行榜 随机10个小说
+ $arrGirlWeekRankNovel = $NovelModel->getRankNovelOnCache($Request->DomainModel->d_domain, 5, 2, 'weekly');
+ // echo '';
+ // print_r($arrGirlWeekRankNovel);
+ // exit;
$arrGirlWeekRankNovel = [
['id' => 1, 'name' => '小说1', 'author' => '作者1'],
['id' => 2, 'name' => '小说2', 'author' => '作者2'],
@@ -226,7 +263,12 @@ class Index extends BaseController
['id' => 9, 'name' => '小说3', 'author' => '作者3'],
['id' => 10, 'name' => '小说3', 'author' => '作者3'],
];
+
// 模拟数据-女生频道,月排行榜 随机10个小说
+ $arrGirlMoonRankNovel = $NovelModel->getRankNovelOnCache($Request->DomainModel->d_domain, 5, 2, 'monthly');
+ // echo '';
+ // print_r($arrGirlMoonRankNovel);
+ // exit;
$arrGirlMoonRankNovel = [
['id' => 1, 'name' => '小说1', 'author' => '作者1'],
['id' => 2, 'name' => '小说2', 'author' => '作者2'],
@@ -240,6 +282,10 @@ class Index extends BaseController
['id' => 10, 'name' => '小说3', 'author' => '作者3'],
];
// 模拟数据-女生频道,总排行榜 随机10个小说
+ $arrBGirlTotalRankNovel = $NovelModel->getRankNovelOnCache($Request->DomainModel->d_domain, 5, 2, 'total');
+ // echo '';
+ // print_r($arrBGirlTotalRankNovel);
+ // exit;
$arrBGirlTotalRankNovel = [
['id' => 1, 'name' => '小说1', 'author' => '作者1'],
['id' => 2, 'name' => '小说2', 'author' => '作者2'],
@@ -254,6 +300,10 @@ class Index extends BaseController
];
// 模拟数据-最近更新-随机50
+ $arrNewsUpdateNovel = $NovelModel->getSexNovelOnCache($Request->DomainModel->d_domain, 50);
+ // echo '';
+ // print_r($arrNewsUpdateNovel);
+ // exit;
$arrNewsUpdateNovel = [
['id' => 1, 'name' => '小说1', 'author' => '作者1'],
['id' => 2, 'name' => '小说2', 'author' => '作者2'],
@@ -300,25 +350,4 @@ class Index extends BaseController
}
}
-
-
- public function test()
- {
-
- $strNovelKey = "novels/12/chapters/3001.txt";
- $StorageCore = StorageCore::getInstance();
-
- // $content = '我是一个大傻逼';
-
- $content = file_get_contents('123.txt');
-
- if ($StorageCore->has($strNovelKey)) {
- // $MinIO->update($strNovelKey, $content); // 更新已有文件
- } else {
- $StorageCore->write($strNovelKey, $content); // 创建新文件
- }
-
- echo $StorageCore->read($strNovelKey);
- return;
- }
}
diff --git a/code/app/home/view/kuangyu/Pc/index.html b/code/app/home/view/kuangyu/Pc/index.html
index 5ee7c9d..36f129d 100644
--- a/code/app/home/view/kuangyu/Pc/index.html
+++ b/code/app/home/view/kuangyu/Pc/index.html
@@ -52,7 +52,17 @@
{foreach $arrRecommendEndNovel as $key=>$Novel}
- {include file="Pc/Public/endNovelList" /}
+ -
+
+
+
+
+
+
{$Novel->og_novel_author}
+
+
{/foreach}
diff --git a/code/app/model/CategoryModel.php b/code/app/model/CategoryModel.php
new file mode 100644
index 0000000..8ef6dec
--- /dev/null
+++ b/code/app/model/CategoryModel.php
@@ -0,0 +1,61 @@
+ '玄幻修真',
+ 2 => '军史小说',
+ 3 => '网游小说',
+ 4 => '科幻小说',
+ 5 => '重生穿越',
+ 6 => '都市小说',
+ 7 => '灵异小说',
+ 8 => '言情小说',
+ 9 => '其他小说',
+ ];
+
+ static $arrMan = [
+ 1 => '玄幻修真',
+ 2 => '军史小说',
+ 3 => '网游小说',
+ 4 => '科幻小说',
+ ];
+
+ static $arrWomen = [
+ 5 => '重生穿越',
+ 6 => '都市小说',
+ 7 => '灵异小说',
+ 8 => '言情小说',
+ 9 => '其他小说',
+ ];
+
+
+ static public function getCategory():array
+ {
+ return self::$arrCategory;
+ }
+
+ static public function getManCategory():array
+ {
+ return self::$arrMan;
+ }
+
+ static public function getWomenCategory():array
+ {
+ return self::$arrWomen;
+ }
+}
diff --git a/code/app/model/ChapterModel.php b/code/app/model/ChapterModel.php
new file mode 100644
index 0000000..403cda1
--- /dev/null
+++ b/code/app/model/ChapterModel.php
@@ -0,0 +1,17 @@
+MongoBase = MongoBase::getInstance();
+ }
+
+ /**
+ * Undocumented function
+ *
+ * @return MongoBase
+ */
+ public function getMongoBase(): MongoBase
+ {
+ return $this->MongoBase;
+ }
+
+ /**
+ * Undocumented function
+ *
+ * @return Collection
+ */
+ public function getCol(): Collection
+ {
+ return $this->getMongoBase()->getDb()->selectCollection($this->getCollectionName());
+ }
+
+ /**
+ * Undocumented function
+ *
+ * @return string
+ */
+ public function getCollectionName(): string
+ {
+ return $this->strCollection;
+ }
+}
diff --git a/code/app/model/NovelClicksModel.php b/code/app/model/NovelClicksModel.php
new file mode 100644
index 0000000..09c2ae8
--- /dev/null
+++ b/code/app/model/NovelClicksModel.php
@@ -0,0 +1,67 @@
+getCol()->findOne([
+ "n_id" => $intNId,
+ ]);
+
+ if (empty($Novel)) {
+ return false;
+ }
+
+ $Now = new DateTime();
+
+ $strDailyDate = $Now->format('Y-m-d');
+ $strWeeklyDate = $Now->modify('monday this week')->format('Y-m-d');
+ $MonthlyDate = $Now->format('Y-m-01');
+
+ $arrClickTypes = [
+ 'daily' => $strDailyDate,
+ 'weekly' => $strWeeklyDate,
+ 'monthly' => $MonthlyDate,
+ 'total' => null,
+ ];
+
+ foreach ($arrClickTypes as $strType => $strDate) {
+ $this->getCol()->updateOne(
+ [
+ 'n_id' => $intNId,
+ 'nc_date' => $strDate,
+ 'nc_type' => $strType,
+ ],
+ [
+ '$inc' => ['nc_clicks' => 1],
+ '$set' => ['n_category' => $Novel->og_novel_category]
+ ],
+ [
+ 'upsert' => true
+ ]
+ );
+ }
+ return true;
+ }
+}
diff --git a/code/app/model/NovelModel.php b/code/app/model/NovelModel.php
new file mode 100644
index 0000000..05e9320
--- /dev/null
+++ b/code/app/model/NovelModel.php
@@ -0,0 +1,329 @@
+ ['$regex' => trim($strKeyword), '$arrOptions' => 'i']];
+ }, $arrKeywords);
+
+ $UpdateResult = $this->getCol()->updateMany(
+ ['$or' => $arrConditions],
+ ['$set' => ['n_level' => $intLevel]]
+ );
+
+ $intUpdateNum = $UpdateResult->getModifiedCount();
+ } catch (Exception $e) {
+
+ return false;
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Undocumented function
+ *
+ * @param string $strSign
+ * @param integer $intCount
+ * @param integer $intType
+ * @param integer $intLevel
+ * @return array
+ */
+ public function getHighLevelNovelOnCache(string $strSign, int $intCount = 10, int $intType = 0): array
+ {
+ try {
+ $strKey = sprintf('%s:PC:HOME:FIRST', $strSign);
+
+ $intLifeTime = 24 * 3600;
+
+ $arrResult = Cache::get($strKey);
+
+ // if (empty($arrResult)) {
+ if (true) {
+ if ($intType == 1) {
+ $arrCategory = array_values(CategoryModel::getManCategory());
+ $arrFilter = [
+ 'og_novel_category' => ['$in' => $arrCategory]
+ ];
+ } elseif ($intType == 2) {
+ $arrCategory = array_values(CategoryModel::getWomenCategory());
+ $arrFilter = [
+ 'og_novel_category' => ['$in' => $arrCategory]
+ ];
+ } else {
+ $arrFilter = [];
+ }
+
+ $arrOptions = [
+ 'sort' => ['n_level' => -1],
+ 'limit' => $intCount * 3,
+ 'projection' => [
+ 'n_id' => 1,
+ 'og_novel_book_name' => 1,
+ 'og_novel_pin_yin' => 1,
+ 'n_cover_path' => 1,
+ 'n_novel_author' => 1,
+ 'og_novel_category' => 1,
+ 'og_description' => 1,
+ ],
+ ];
+
+ $Cursor = $this->getCol()->find($arrFilter, $arrOptions);
+ $arrResult = iterator_to_array($Cursor);
+
+ shuffle($arrResult);
+
+ $arrResult = array_slice($arrResult, 0, $intCount);
+
+ if (!empty($arrResult)) {
+ Cache::set($strKey, $arrResult, $intLifeTime);
+ }
+ }
+
+ return $arrResult;
+ } catch (\Exception $e) {
+ return [];
+ }
+ }
+
+
+ /**
+ * Undocumented function
+ *
+ * @param string $strSign
+ * @param integer $intCount
+ * @return array
+ */
+ public function getFinishedNovelOnCache(string $strSign, int $intCount = 10): array
+ {
+ try {
+ $strKey = sprintf('%s:PC:HOME:FINISHED', $strSign);
+
+ $intLifeTime = 24 * 3600;
+
+ $arrResult = Cache::get($strKey);
+
+ // if (empty($arrResult)) {
+ if (true) {
+ $arrFilter = [
+ 'og_novel_status' => "已完结",
+ ];
+
+ $arrOptions = [
+ 'sort' => ['og_novel_update_time' => -1],
+ 'limit' => $intCount * 3,
+ 'projection' => [
+ 'n_id' => 1,
+ 'og_novel_book_name' => 1,
+ 'og_novel_pin_yin' => 1,
+ 'n_cover_path' => 1,
+ 'og_novel_author' => 1,
+ 'og_novel_category' => 1,
+ 'og_description' => 1,
+ ],
+ ];
+
+ $Cursor = $this->getCol()->find($arrFilter, $arrOptions);
+ $arrResult = iterator_to_array($Cursor);
+
+ shuffle($arrResult);
+
+ $arrResult = array_slice($arrResult, 0, $intCount);
+
+ if (!empty($arrResult)) {
+ Cache::set($strKey, $arrResult, $intLifeTime);
+ }
+ }
+
+ return $arrResult;
+ } catch (\Exception $e) {
+ return [];
+ }
+ }
+
+ /**
+ * Undocumented function
+ *
+ * @param string $strSign
+ * @param integer $intCount
+ * @param integer $intType
+ * @param integer $intLevel
+ * @return array
+ */
+ public function getSexNovelOnCache(string $strSign, int $intCount = 10, int $intType = 0): array
+ {
+ try {
+ $strKey = sprintf('%s:PC:HOME:SIX:%s', $strSign, $intType);
+
+ $intLifeTime = 24 * 3600;
+
+ $arrResult = Cache::get($strKey);
+
+ // if (empty($arrResult)) {
+ if (true) {
+ if ($intType == 1) {
+ $arrCategory = array_values(CategoryModel::getManCategory());
+ $arrFilter = [
+ 'og_novel_category' => ['$in' => $arrCategory]
+ ];
+ } elseif ($intType == 2) {
+ $arrCategory = array_values(CategoryModel::getWomenCategory());
+ $arrFilter = [
+ 'og_novel_category' => ['$in' => $arrCategory]
+ ];
+ } else {
+ $arrFilter = [];
+ }
+
+ $arrOptions = [
+ 'sort' => ['og_novel_update_time' => -1],
+ 'limit' => $intCount * 3,
+ 'projection' => [
+ 'n_id' => 1,
+ 'og_novel_book_name' => 1,
+ 'og_novel_pin_yin' => 1,
+ 'n_cover_path' => 1,
+ 'n_novel_author' => 1,
+ 'og_novel_category' => 1,
+ 'og_description' => 1,
+ ],
+ ];
+
+ $Cursor = $this->getCol()->find($arrFilter, $arrOptions);
+ $arrResult = iterator_to_array($Cursor);
+
+ shuffle($arrResult);
+
+ $arrResult = array_slice($arrResult, 0, $intCount);
+
+ if (!empty($arrResult)) {
+ Cache::set($strKey, $arrResult, $intLifeTime);
+ }
+ }
+
+ return $arrResult;
+ } catch (\Exception $e) {
+ return [];
+ }
+ }
+
+
+
+ /**
+ * Undocumented function
+ *
+ * @param string $strSign
+ * @param integer $intCount
+ * @param integer $intType
+ * @param string strDateType
+ * * daily 天
+ * * weekly 周
+ * * monthly 月
+ * * total 总
+ * @return array
+ */
+ public function getRankNovelOnCache(string $strSign, int $intCount = 10, int $intType = 0, $strDateType = 'daily'): array
+ {
+ try {
+ $strKey = sprintf('%s:PC:HOME:SIX:%s:TYPE:%s', $strSign, $intType, $strDateType);
+
+ $intLifeTime = 24 * 3600;
+
+ $arrResult = Cache::get($strKey);
+
+ // if (empty($arrResult)) {
+ if (true) {
+ $arrFilter = [
+ 'nc_type' => $strDateType,
+ ];
+ if ($intType == 1) {
+ $arrCategory = array_values(CategoryModel::getManCategory());
+ $arrFilter['n_category'] = ['$in' => $arrCategory];
+ } elseif ($intType == 2) {
+ $arrCategory = array_values(CategoryModel::getWomenCategory());
+ $arrFilter['n_category'] = ['$in' => $arrCategory];
+ }
+ $arrOptions = [
+ 'sort' => ['nc_clicks' => -1],
+ 'limit' => $intCount * 3,
+ 'projection' => [
+ 'n_id' => 1,
+ ],
+ ];
+
+ $NovelClicksModel = new NovelClicksModel;
+
+ $Cursor = $NovelClicksModel->getCol()->find($arrFilter, $arrOptions);
+
+ $arrNId = [];
+ foreach ($Cursor as $Doc) {
+ $arrNId[] = $Doc['n_id'];
+ }
+
+ $Cursor = $this->getCol()->find(
+ ['n_id' => ['$in' => $arrNId]],
+ [
+ 'projection' => [
+ 'n_id' => 1,
+ 'og_novel_book_name' => 1,
+ 'og_novel_pin_yin' => 1,
+ 'n_cover_path' => 1,
+ 'n_novel_author' => 1,
+ 'og_novel_category' => 1,
+ 'og_description' => 1,
+ ],
+ ]
+ );
+
+
+ $arrResult = iterator_to_array($Cursor);
+
+ shuffle($arrResult);
+
+ $arrResult = array_slice($arrResult, 0, $intCount);
+
+ if (!empty($arrResult)) {
+ Cache::set($strKey, $arrResult, $intLifeTime);
+ }
+ }
+
+ return $arrResult;
+ } catch (\Exception $e) {
+ return [];
+ }
+ }
+}
diff --git a/code/app/task/collection/PullDataColBase.php b/code/app/task/collection/PullDataColBase.php
deleted file mode 100644
index 38144a8..0000000
--- a/code/app/task/collection/PullDataColBase.php
+++ /dev/null
@@ -1,48 +0,0 @@
-Client = new Client();
-
- $this->QueueManage = new QueueManage();
- }
-
- public function getAilisiDomain()
- {
- if (!key_exists('AILISI_DOMAIN', $this->arrCaiJiPeiZhi)) {
- $this->arrCaiJiPeiZhi['AILISI_DOMAIN'] = CaiJiPeiZhiModel::getValByCode('AILISI_DOMAIN');
- }
-
- return $this->arrCaiJiPeiZhi['AILISI_DOMAIN'];
- }
-}
diff --git a/code/app/task/collection/zw630/Zw630Col.php b/code/app/task/collection/zw630/Zw630Col.php
deleted file mode 100644
index 2f46683..0000000
--- a/code/app/task/collection/zw630/Zw630Col.php
+++ /dev/null
@@ -1,611 +0,0 @@
-MongoBase = MongoBase::getInstance();
-
- $this->Client = new Client([
- 'base_uri' => $this->strDomain,
- 'http_errors' => false,
- 'version' => 2.0, // 强制使用 HTTP/2
- 'timeout' => 160,
- 'connect_timeout' => 30, // 连接超时 10 秒
- 'curl' => [
- CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0, // 强制 HTTP/2
- CURLOPT_TCP_KEEPALIVE => 1, // 启用 TCP Keep-Alive
- CURLOPT_SSL_VERIFYPEER => true, // 验证 SSL
- ],
- 'headers' => [
- 'User-Agent' => 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Mobile Safari/537.36 Edg/134.0.0.0',
- 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
- 'Accept-Language' => 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
- 'Referer' => $this->strDomain,
- 'sec-ch-ua-mobile' => '?1',
- 'sec-ch-ua-platform' => '"Android"',
- 'sec-fetch-dest' => 'document',
- 'sec-fetch-mode' => 'navigate',
- 'sec-fetch-site' => 'same-origin',
- 'sec-fetch-user' => '?1',
- 'upgrade-insecure-requests' => '1',
- ],
- ]);
-
- $this->QueueManage = new QueueManage();
- }
-
-
- public function pull630ZwNovel()
- {
- // 全局扫描任务
- $QueueManage = $this->QueueManage;
- // for ($intNovelId = 1; $intNovelId <= 531208; $intNovelId++) {
- // for ($intNovelId = 1; $intNovelId <= 10000; $intNovelId++) {
- for ($intNovelId = 1; $intNovelId <= 1; $intNovelId++) {
- $arrTask = [
- 'callback' => [self::class, 'getNovelInfo'],
- 'data' => [
- 'n_id' => $intNovelId,
- 'page' => 0,
- ]
- ];
-
- // print_r($arrTask);
- $QueueManage->set($arrTask);
- }
- }
-
-
- public function getNovelInfo($arrData)
- {
- // content="mobile" 有这个的是手机,否则是PC
- print_r($arrData);
-
- if ($arrData['page'] == 0) {
- $strUri = sprintf('/shu/%s.html', $arrData['n_id']);
- } else {
- $strUri = sprintf('/shu/%s_%s.html', $arrData['n_id']);
- }
-
- $ResponseBase = $this->Client->get($strUri);
- $strNovelContent = $ResponseBase->getBody()->getContents();
-
- $NovelQuery = QueryList::html($strNovelContent);
-
- $arrNovel = $this->getNovelHead($NovelQuery, $arrData['n_id'], $arrData['page']);
-
- if (empty($arrNovel)) {
- return false;
- }
-
-
- print_r($arrNovel);
- // $strResult = (string)$Response->getBody();
- }
-
- private function getNovelHead(QueryList $QueryList, $intSourceNovelId, $intPage = 0)
- {
- $arrResult = [];
- $QueryList->find('meta[property]')->each(function ($Item) use (&$arrResult) {
- $property = str_replace(':', '_', $Item->attr('property'));
- $arrResult[$property] = $Item->attr('content');
- });
-
- if (empty($arrResult)) {
- return false;
- }
-
- // print_r($arrResult);
-
- $arrResult['novel_source_id'] = 'zw630-' . $intSourceNovelId;
-
- try {
- $Col = $this->MongoBase->getDb()->selectCollection('novel');
-
- $arrExisting = $Col->findOne(['novel_source_id' => $arrResult['novel_source_id']]);
-
- // use novel
- // db.novel.createIndex({ "novel_source_id": 1 }, { unique: true })
-
- if ($arrExisting) {
-
- $arrExisting = (array) $arrExisting; // MongoDB\BSON\Document 转为数组
- $arrResult = array_merge($arrExisting, $arrResult); // 合并,保留原有字段
-
- $arrResult['updated_at'] = new \MongoDB\BSON\UTCDateTime(); // 添加更新时间
- $Col->updateOne(
- ['novel_source_id' => $arrResult['novel_source_id']],
- ['$set' => $arrResult],
- // ['upsert' => false]
- );
- } else {
- $arrResult['novel_id'] = $this->MongoBase->getNextSequence('novel');
- $this->MongoBase->insertOne('novel', $arrResult);
- }
- return $arrResult;
- } catch (\MongoDB\Exception\Exception $e) {
- throw new \Exception("Upsert failed: " . $e->getMessage());
- }
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- public function getNovelInfo1($arrData)
- {
- //var_dump('getNovelFengMian');
- //var_dump($arrData);
- $Client = $this->Client;
- $QueueManage = $this->QueueManage;
-
- // 获取 Redis 实例
- $redis = Cache::store('redis')->handler();
- $intXiaoShuoSourceId = $arrData['xiaoshuo_id'];
- $intXiaoShuoSourceSeoId = $arrData['xiaoshuo_seo_id'];
- $redisKey = "task:novel_source_id:" . $intXiaoShuoSourceId;
-
- try {
- $arrCookie = $this->getAilisiCookie();
- $strHost = getDomainFromUrl($arrData['xiaoshuo_url']);
- $CookieJar = CookieJar::fromArray($arrCookie, $strHost);
-
- $strSiteUrl = $this->getAilisiDomain();
-
-
- $strCover = $QueryList->find('#fmimg img')->eq(0)->attr("src");
-
- if (strpos($strCover, 'http') === false) {
- $strCover = $strSiteUrl . $strCover;
- }
- $strCoverUri = $strCover;
-
- try {
- $strCoverUri = "";
- $strCoverUri = ImageProcessor::getInstance()->downloadAndEncryptImage($strCover, 'xs');
- $arrCover = [
- 'code' => 'AI_LI_SI_COVER',
- // 'uri' => getUriByUrl($strCover),
- 'uri' => $strCoverUri,
-
- ];
-
- $arrXiaoShuoInfo = [
- 'xsxq_feng_mian' => json_encode($arrCover),
-
- ];
- $XiaoShuoXiangQingModel = XiaoShuoXiangQingModel::addXiaoShuoXiangQing($arrXiaoShuoInfo);
- } catch (\Throwable $t) {
- $strErr = sprintf("采集[%s]小说图片错误,异常:%s", $strCover, $t->getMessage());
- echo $strErr;
- }
- } catch (\Throwable $T) {
- throw $T;
- }
- }
-
-
- // public function getNovelInfo($arrData)
- // {
- // $QueueManage = $this->QueueManage;
- // $Client = $this->Client;
- // $strSiteUrl = $this->getBiquxsDomain();
-
- // $strUrl = 'http://www.biquxs.com/book/' . $arrData['xs_source_id'] ; //'http://www.biquxs.com/book/20053';
- // $intXiaoShuoSourceId = $arrData['xs_source_id']; //20053;
- // $intXiaoShuoSourceSeoId = $arrData['xs_source_seo_id']; //1 ;
- // try {
- // $arrCookie = $this->getAilisiCookie();
- // $strHost = getDomainFromUrl($strUrl);
-
- // $CookieJar = CookieJar::fromArray($arrCookie, $strHost);
-
- // $proxy = getRandomProxies();
- // $userAgent = getRandomUserAgent();
- // $Response = $Client->request('GET', $strUrl, [
- // 'headers' => [
- // 'User-Agent' => $userAgent,
- // 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
- // 'Accept-Language' => 'en-US,en;q=0.5',
- // ],
- // 'cookies' => $CookieJar,
- // // 'proxy' => $proxy,
- // 'timeout' => 30,
- // ]);
-
- // $strResult = (string)$Response->getBody();
-
- // $QueryList = QueryList::html($strResult);
- // // var_dump($QueryList);
- // // 提取所有 标签的属性
- // $result = $QueryList->find('meta[property^="og:"]')->map(function ($item) {
- // return [
- // 'property' => $item->attr('property'),
- // 'content' => $item->attr('content'),
- // ];
- // });
-
- // // 打印结果
- // // print_r($result->all());
- // $strZuoZhe = '';
- // $strMingZi = '';
- // $strLastDate = '';
- // $strJieShao = '';
- // $strCover = '';
- // $strFenlei = '';
- // $strZiShu = 0;
- // $strZhuangTai = $QueryList->find('#info p')->eq(1)->text();
- // $strZhuangTai = getStringAfterWord($strZhuangTai, '状态:');
-
- // foreach ($result as $item) {
- // switch ($item['property']) {
- // case 'og:title':
- // $strMingZi = $item['content'];
- // break;
- // case 'og:description':
- // $strJieShao = $item['content'];
- // break;
- // case 'og:image':
- // $strCover = $item['content'];
- // break;
- // case 'og:book:category':
- // $strFenlei = $item['content'];
- // break;
- // case 'og:book:author':
- // $strZuoZhe = $item['content'];
- // break;
- // case 'og:book:update_time':
- // $strLastDate = $item['content'];
- // break;
- // }
- // }
-
-
- // $strLastDate = trim((string)$strLastDate);
- // if (!isValidDateTime($strLastDate)) {
- // $strLastDate = date('Y-m-d H:i:s');
- // }
-
- // $arrTag = [];
-
- // if (strpos($strCover, 'http') === false) {
- // $strCover = $strSiteUrl . $strCover;
- // }
- // $strCoverUri = $strCover;
-
- // try {
- // $strCoverUri = "";
- // $strCoverUri = ImageProcessor::getInstance()->downloadImage( $intXiaoShuoSourceId,$strCover, 'xs');
- // } catch (\Throwable $t) {
- // $strErr = sprintf("采集[%s]小说图片错误,异常:%s", $strCover, $t->getMessage());
- // echo $strErr;
- // }
-
- // $arrCover = [
- // 'code' => 'BI_QU_XS_COVER',
- // // 'uri' => getUriByUrl($strCover),
- // 'uri' => $strCoverUri,
-
- // ];
-
- // $strFenleiIId = 1 ;
- // switch ($strFenlei) {
- // case '玄幻小说':
- // $strName = "玄幻";
- // $strFenleiIId = 1;
- // break;
- // case '修真小说':
- // $strName = "修真";
- // $strFenleiIId = 2;
- // break;
- // case '都市小说':
- // $strName = "都市";
- // $strFenleiIId = 3;
- // break;
- // case '历史小说':
- // $strName = "历史";
- // $strFenleiIId = 4;
- // break;
- // case '网游小说':
- // $strName = "网游";
- // $strFenleiIId = 5;
- // break;
- // case '科幻小说':
- // $strName = "科幻";
- // $strFenleiIId = 6;
- // break;
- // case '女频小说':
- // $strName = "女频";
- // $strFenleiIId = 7;
- // break;
- // case '其它小说':
- // $strName = "其它";
- // $strFenleiIId = 8;
- // break;
- // default:
- // $strName = "其它";
- // $strFenleiIId = 8;
- // break;
- // }
-
- // $arrXiaoShuoInfo = [
- // 'xsxq_ming_zi' => $strMingZi,
- // 'xsxq_zuozhe' => $strZuoZhe , //$arrData['xiaoshuo_zuozhe'],
- // 'xsfl_id' => $strFenleiIId,
- // 'xsxq_zhuang_tai' => $strZhuangTai,
- // 'xsxq_zi_shu' => $strZiShu,
- // 'xsxq_jie_shao' => $strJieShao,
- // 'xsxq_feng_mian' => json_encode($arrCover),
- // 'xsxq_geng_xin_shi_jian' => $strLastDate,
- // 'xsxq_tag' => json_encode($arrTag, JSON_UNESCAPED_UNICODE),
- // 'xsxq_source_id' => $intXiaoShuoSourceId,
- // 'xsxq_source_seo_id' => $intXiaoShuoSourceId.'1',
- // 'xsxq_source_code' => 'BIQU_XIAOSHUO_' . $intXiaoShuoSourceId . '_' . $intXiaoShuoSourceSeoId,
- // ];
- // // print_r($arrXiaoShuoInfo);
- // var_dump($arrXiaoShuoInfo['xsxq_source_code']);
-
- // $XiaoShuoXiangQingModel = XiaoShuoXiangQingModel::addXiaoShuoXiangQing($arrXiaoShuoInfo);
-
- // $arrZhangjie = [];
- // $intChapterIndex = 0; // 定义一个计数器变量
-
- // // 检查是否找到元素
- // if ($QueryList->find('.listmain a')->count() > 0) {
- // $QueryList->find('.listmain a')->map(function ($ZhangJie) use (&$arrZhangjie, &$intChapterIndex) {
- // echo "Processing index $intChapterIndex: " . $ZhangJie->text() . PHP_EOL;
-
- // // 从索引6开始
- // if ($intChapterIndex >= 5) {
- // $arrZhangjie[] = [
- // 'mingzi' => $ZhangJie->text(),
- // 'url' => $ZhangJie->attr('href'),
- // ];
- // }
- // $intChapterIndex++;
- // });
-
- // // 输出最终结果
- // var_dump($arrZhangjie);
- // } else {
- // echo "No elements found for .listmain a" . PHP_EOL;
- // }
-
- // //unset($QueryList);
-
- // // 获取最大章节索引
- // $intMaxXiaoShuoZhangJiePaixu = XiaoShuoZhangJieModel::where('xsxq_id', $intXiaoShuoSourceId)
- // ->max('xszj_pai_xu'); // 获取最大值
- // var_dump('获取小说章节最大值-'.$intMaxXiaoShuoZhangJiePaixu);
-
- // // 如果没有结果,max 返回 null,可以设置默认值
- // $intMaxXiaoShuoZhangJiePaixu = $intMaxXiaoShuoZhangJiePaixu ?? 0;
-
- // foreach ($arrZhangjie as $intZhangJieNum => $arrZhangjieOne) {
- // var_dump($intZhangJieNum);
-
- // // 如果数据库里的章节最大索引大于当前索引,说明已经入库,不需要入队列了
- // if($intMaxXiaoShuoZhangJiePaixu > $intZhangJieNum){
- // var_dump("小说章节已经入库,不再推送任务");
- // continue;// 如果任务已存在,直接跳过 break
- // }
-
- // $strZhangJieUrl = $strSiteUrl . $arrZhangjieOne['url'];
-
- // $strZhangJieId = extractFilename($strZhangJieUrl);
-
- // # 'AILISI_XIAOSHUO_ZHANGJIE_小说源id_小说源章节id'
- // $strXszjSourceCode = 'AILISI_XIAOSHUO_ZHANGJIE_'. $XiaoShuoXiangQingModel->xsxq_source_id . '_' . $strZhangJieId;
-
- // $arrTask = [
- // 'callback' => [self::class, 'getNovelReader'],
- // 'data' => [
- // 'xszj_source_id' => $strZhangJieId,
- // 'xszj_paixu' => $intZhangJieNum,
- // 'xszj_name' => $arrZhangjieOne['mingzi'],
- // 'xszj_url' => $strZhangJieUrl,
- // 'xszj_source_code' => $strXszjSourceCode,
- // 'xsxq_source_id' => $XiaoShuoXiangQingModel->xsxq_source_id,
- // 'xsfl_id' => $strFenleiIId,
- // ]
- // ];
- // $QueueManage->set($arrTask);
-
- // }
- // var_dump("章节任务投递成功");
-
- // } catch (\Throwable $T) {
- // throw $T;
- // }
- // }
-
- // public function getNovelFengMian($arrData)
- // {
- // //var_dump('getNovelFengMian');
- // //var_dump($arrData);
- // $Client = $this->Client;
- // $QueueManage = $this->QueueManage;
-
- // // 获取 Redis 实例
- // $redis = Cache::store('redis')->handler();
- // $intXiaoShuoSourceId = $arrData['xiaoshuo_id'];
- // $intXiaoShuoSourceSeoId = $arrData['xiaoshuo_seo_id'];
- // $redisKey = "task:novel_source_id:" . $intXiaoShuoSourceId;
-
- // try {
- // $arrCookie = $this->getAilisiCookie();
- // $strHost = getDomainFromUrl($arrData['xiaoshuo_url']);
- // $CookieJar = CookieJar::fromArray($arrCookie, $strHost);
-
- // $strSiteUrl = $this->getAilisiDomain();
-
-
- // $strCover = $QueryList->find('#fmimg img')->eq(0)->attr("src");
-
- // if (strpos($strCover, 'http') === false) {
- // $strCover = $strSiteUrl . $strCover;
- // }
- // $strCoverUri = $strCover;
-
- // try {
- // $strCoverUri = "";
- // $strCoverUri = ImageProcessor::getInstance()->downloadAndEncryptImage($strCover, 'xs');
- // $arrCover = [
- // 'code' => 'AI_LI_SI_COVER',
- // // 'uri' => getUriByUrl($strCover),
- // 'uri' => $strCoverUri,
-
- // ];
-
- // $arrXiaoShuoInfo = [
- // 'xsxq_feng_mian' => json_encode($arrCover),
-
- // ];
- // $XiaoShuoXiangQingModel = XiaoShuoXiangQingModel::addXiaoShuoXiangQing($arrXiaoShuoInfo);
- // } catch (\Throwable $t) {
- // $strErr = sprintf("采集[%s]小说图片错误,异常:%s", $strCover, $t->getMessage());
- // echo $strErr;
- // }
-
-
-
- // } catch (\Throwable $T) {
- // throw $T;
- // }
- // }
-
- // public function getNovelReader($arrData)
- // {
- // var_dump('getNovelReader');
- // var_dump($arrData['xszj_url']);
- // $Client = $this->Client;
- // $strXszjSourceCode = $arrData['xszj_source_code'];
- // $strZhangJieUrl = $arrData['xszj_url'];
- // $intZhangJiePaiXu = $arrData['xszj_paixu'];
- // $intFenleiId = $arrData['xsfl_id'];
- // $intXiaoShuoSourceID = $arrData['xsxq_source_id'];
- // $strZhangJieSourceId = $arrData['xszj_source_id'];
- // $strZhangJieName = $arrData['xszj_name'];
- // try {
- // $arrCookie = $this->getAilisiCookie();
- // $strHost = getDomainFromUrl($strZhangJieUrl);
- // $CookieJar = CookieJar::fromArray($arrCookie, $strHost);
-
- // if (XiaoShuoZhangJieModel::checkZhangJieExists($strXszjSourceCode)) {
- // return true;
- // }
-
- // $proxy = getRandomProxies();
- // $userAgent = getRandomUserAgent();
- // $Response = $Client->request('GET', $strZhangJieUrl, [
- // 'headers' => [
- // 'User-Agent' => $userAgent,
- // 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
- // 'Accept-Language' => 'en-US,en;q=0.5',
- // ],
- // 'cookies' => $CookieJar,
- // // 'proxy' => $proxy,
- // 'timeout' => 30,
- // ]);
-
- // $strResult = (string)$Response->getBody();
- // unset($Response);
- // $ZhangJieQueryList = QueryList::html($strResult);
-
-
- // // 获取原始 HTML
- // $strNeiRong = $ZhangJieQueryList->find('#content')->eq(0)->html();
-
- // // 解码 HTML 实体
- // $strNeiRong = html_entity_decode($strNeiRong, ENT_QUOTES, 'UTF-8');
-
- // // 去掉 标签中的 class="content_detail" 属性
- // $strNeiRong = preg_replace('/
');
-
- // // 替换不间断空格 (\xc2\xa0) 和 为普通空格
- // $strNeiRong = preg_replace('/\xc2\xa0/', ' ', $strNeiRong); // 替换 UTF-8 不间断空格
- // $strNeiRong = preg_replace('/ /', ' ', $strNeiRong); // 替换
-
- // // 清理多余空格和换行符
- // $strNeiRong = preg_replace('/\s+/', ' ', $strNeiRong);
- // $strNeiRong = preg_replace('/
\s+/', '
', $strNeiRong); // 清理
标签内部多余空格
- // $strNeiRong = preg_replace('/\s+<\/p>/', '
', $strNeiRong); // 清理 标签结尾多余空格
-
- // // 输出结果
- // // var_dump($strNeiRong);
-
-
- // # '/novel/123yqw/分类id/小说源id/小说源章节id.txt'
- // $strNeiRongTxtPath = config("filesystem.novel").'/novel/biquxs/'.$intFenleiId.'/'.$intXiaoShuoSourceID.'/'.$strZhangJieSourceId.'.txt';
- // //$strNeiRongTxtPath = "/www/novel/123yqw/1/11/111.txt";
- // // 调用函数
- // $result = saveCompressAndDeleteTxt($strNeiRongTxtPath, $strNeiRong);
-
- // // 输出状态
- // if ($result['status']) {
- // var_dump("Success: " . $result['message']) ;
- // } else {
- // var_dump("Error: " . $result['message']) ;
- // }
-
- // $arrXiaoShuoZhangJie = [
- // 'xsxq_id' => $intXiaoShuoSourceID,
- // 'xszj_pai_xu' => $intZhangJiePaiXu,
- // 'xszj_ming_zi' => $strZhangJieName,
- // 'xszj_nei_rong' => $strNeiRongTxtPath,
- // 'xszj_source_id' => $strZhangJieSourceId,
- // 'xszj_source_code' => $strXszjSourceCode,
- // ];
-
- // XiaoShuoZhangJieModel::addXiaoShuoZhangJie($arrXiaoShuoZhangJie);
- // unset($ZhangJieQueryList);
- // // 延时 3 秒
- // //sleep(3);
-
- // } catch (\Throwable $T) {
- // throw $T;
- // }
- // }
-}
diff --git a/code/app/task/crawler/zw630/Scheduler.php b/code/app/task/crawler/zw630/Scheduler.php
new file mode 100644
index 0000000..0cee580
--- /dev/null
+++ b/code/app/task/crawler/zw630/Scheduler.php
@@ -0,0 +1,184 @@
+QueueManage = new QueueManage();
+ }
+
+ /**
+ * 全局扫描任务
+ *
+ * @return void
+ */
+ public function crawlFull()
+ {
+ $intSliceSize = 6;
+
+ $arrFilter = [];
+ for ($intNId = 1; $intNId <= 531208; $intNId++) {
+ // for ($intNId = 1; $intNId <= 10000; $intNId++) {
+ // for ($intNId = 1; $intNId <= 20; $intNId++) {
+ $arrFilter[] = [
+ 'n_source_id' => $intNId,
+ 'page' => 0,
+ ];
+
+ if ($intNId % $intSliceSize == 0) {
+ $this->fetchNovel($arrFilter);
+ $arrFilter = [];
+ }
+ }
+ if (!empty($arrFilter)) {
+ $this->fetchNovel($arrFilter);
+ }
+ }
+
+ /**
+ * Undocumented function
+ *
+ * @param array $arrFilter
+ * @return void
+ */
+ public function fetchNovel(array $arrFilter) //(int $intNSourceId, int $intPage = 0)
+ {
+ $arrTask = [
+ 'callback' => [self::class, 'getNovelInfo'],
+ 'data' => $arrFilter,
+ ];
+ $this->QueueManage->set($arrTask);
+ }
+
+ /**
+ * Undocumented function
+ *
+ * @param integer $intNId
+ * @param integer $intPage
+ * @param array $arrChapterList
+ * @return void
+ */
+ public function fetchChapter(int $intNId, int $intPage, array $arrChapterList = [])
+ {
+ $intSliceSize = 6;
+ $intPageSize = 100;
+
+ $arrTask = [
+ 'callback' => [self::class, 'getChapterInfo'],
+ 'data' => [
+ 'n_id' => $intNId,
+ ]
+ ];
+ $arrFilter = [];
+ foreach ($arrChapterList as $intKey => $arrChapter) {
+ $arrChapter['c_sort_num'] = $intPage * $intPageSize + $intKey + 1;
+ $arrChapter['c_source_id'] = supperExtractFilename($arrChapter['uri']);
+ $arrChapter['c_page'] = 0;
+
+ $arrFilter[] = $arrChapter;
+ if ($intKey % $intSliceSize == 0) {
+ $arrTask['data']['filter'] = $arrFilter;
+ $this->QueueManage->set($arrTask);
+ $arrFilter = [];
+ }
+ }
+
+ if (!empty($arrFilter)) {
+ $arrTask['data']['filter'] = $arrFilter;
+ $this->QueueManage->set($arrTask);
+ }
+ }
+
+ /**
+ * Undocumented function
+ *
+ * @param array $arrFilter
+ * @return void
+ */
+ public function getNovelInfo(array $arrFilter)
+ {
+ $Site = new Site;
+
+ $arrNovelPageList = $Site->getNovelPageList($arrFilter);
+
+ $arrCover = [];
+ foreach ($arrNovelPageList as $NovelPage) {
+ $arrNovel = $NovelPage->getNovelInfo();
+ if (empty($arrNovel)) {
+ continue;
+ }
+
+
+ $arrNextPageData = $NovelPage->getNextPageArgs();
+ if (!empty($arrNextPageData)) {
+ $this->fetchNovel([
+ [
+ 'n_source_id' => $arrNextPageData['n_source_id'],
+ 'page' => $arrNextPageData['page'],
+ ]
+ ]);
+ }
+
+ $arrCover[] = $NovelPage->getDownCoverInfo();
+ $arrChapterList = $NovelPage->getChapters();
+ $this->fetchChapter($arrNovel['n_id'], $NovelPage->getData()['page'], $arrChapterList);
+ }
+ if ($arrCover) {
+ $Site->downRemoteImgToLocal($arrCover);
+ }
+ }
+
+ /**
+ * Undocumented function
+ *
+ * @param array $arrData
+ * @return void
+ */
+ public function getChapterInfo(array $arrData)
+ {
+ $Site = new Site;
+
+ $arrChapterPageList = $Site->getChapterPageList($arrData['filter']);
+
+ $arrNextPageFilterList = [];
+ foreach ($arrChapterPageList as $ChapterPage) {
+
+ $ChapterPage->saveChapter($arrData['n_id']);
+
+ $arrNextPageFilter = $ChapterPage->getNextPage();
+
+ if (!empty($arrNextPageFilter)) {
+ $arrNextPageFilterList[] = $arrNextPageFilter;
+ }
+ }
+
+ if (!empty($arrNextPageFilterList)) {
+
+ $arrTask = $arrData;
+ $arrTask['filter'] = $arrNextPageFilterList;
+ $arrTask = [
+ 'callback' => [self::class, 'getChapterInfo'],
+ 'data' => $arrTask
+ ];
+ $this->QueueManage->set($arrTask);
+ }
+ }
+}
diff --git a/code/app/task/crawler/zw630/page/BasePage.php b/code/app/task/crawler/zw630/page/BasePage.php
new file mode 100644
index 0000000..491e5a6
--- /dev/null
+++ b/code/app/task/crawler/zw630/page/BasePage.php
@@ -0,0 +1,258 @@
+setSite($Site);
+
+ $this->strUri = $strUri;
+ }
+
+ /**
+ * Undocumented function
+ *
+ * @param array $arrData
+ * @return void
+ */
+ public function setData(array $arrData)
+ {
+ $this->arrData = $arrData;
+ }
+
+ /**
+ * Undocumented function
+ *
+ * @return array
+ */
+ public function getData(): array
+ {
+ return $this->arrData;
+ }
+
+ /**
+ * Undocumented function
+ *
+ * @return QueueManage
+ */
+ public function getQueueManage(): QueueManage
+ {
+ if ($this->QueueManage == NULL) {
+ $this->QueueManage = QueueManage::getInstance();
+ }
+
+ return $this->QueueManage;
+ }
+
+ /**
+ * Undocumented function
+ *
+ * @return MongoBase
+ */
+ public function getMongoBase(): MongoBase
+ {
+ if ($this->MongoBase == NULL) {
+ DDLManage::load('MongoBase');
+ $this->MongoBase = MongoBase::getInstance();
+ }
+
+ return $this->MongoBase;
+ }
+
+ public function getQueryList(): QueryList
+ {
+ if ($this->QueryList == NULL) {
+ $this->QueryList = QueryList::html($this->getContent());
+ }
+
+ return $this->QueryList;
+ }
+
+
+ /**
+ * Undocumented function
+ *
+ * @return string
+ */
+ public function getContent(): string
+ {
+ if ($this->strContent == NULL) {
+ $this->Response = $this->getClient()->get($this->strUri);
+ $this->strContent = $this->Response->getBody()->getContents();
+ }
+
+ return $this->strContent;
+ }
+
+ /**
+ * force set page content
+ *
+ * @param string $strContent
+ * @return void
+ */
+ public function setContent($strContent)
+ {
+ $this->strContent = $strContent;
+ }
+
+
+ /**
+ * Undocumented function
+ *
+ * @return boolean
+ */
+ public function isMobile($strMark = 'content="mobile"'): bool
+ {
+ if ($this->boolIsMobile === NULL) {
+ $this->boolIsMobile = is_numeric(strpos($this->getContent(), $strMark));
+ }
+ return $this->boolIsMobile;
+ }
+
+
+ /**
+ * Undocumented function
+ *
+ * @return Client
+ */
+ protected function getClient(): Client
+ {
+ if ($this->Client == NULL) {
+ $this->Client = $this->getSite()->getClient();
+ }
+ return $this->Client;
+ }
+
+
+ /**
+ * Undocumented variable
+ *
+ * @var Site
+ */
+ protected $Site;
+
+ public function setSite(Site $Site)
+ {
+ $this->Site = $Site;
+ }
+
+ public function getSite()
+ {
+ return $this->Site;
+ }
+
+ /**
+ * Undocumented variable
+ *
+ * @var boolean
+ */
+ protected $boolIsFirstSave = false;
+
+ /**
+ * Undocumented function
+ *
+ * @return boolean
+ */
+ public function getIsFirstSave(): bool
+ {
+ return $this->boolIsFirstSave;
+ }
+}
diff --git a/code/app/task/crawler/zw630/page/ChapterPage.php b/code/app/task/crawler/zw630/page/ChapterPage.php
new file mode 100644
index 0000000..f91a330
--- /dev/null
+++ b/code/app/task/crawler/zw630/page/ChapterPage.php
@@ -0,0 +1,139 @@
+arrChapterInfo === NULL) {
+
+ $this->arrChapterInfo = [];
+
+ $strPattern = "/qsbs\.bb\('([^']*)'\)/";
+
+ preg_match_all($strPattern, $this->getContent(), $arrMatches);
+
+ $strContent = '';
+
+ foreach ($arrMatches[1] as $str) {
+ $strContent .= base64_decode($str);
+ }
+
+ // $this->arrChapterInfo['c_content'] = $strContent;
+ $this->strChapterContent = $strContent;
+
+ $this->arrChapterInfo['c_page_title'] = $this->getQueryList()->find('title')->text();
+ $this->arrChapterInfo['c_page_keywords'] = $this->getQueryList()->find('meta[name="keywords"]')->attr('content');
+ $this->arrChapterInfo['c_page_description'] = $this->getQueryList()->find('meta[name="description"]')->attr('content');
+ }
+
+ return $this->arrChapterInfo;
+ }
+
+ /**
+ * Undocumented variable
+ *
+ * @var array
+ */
+ protected $arrChapterInfo;
+
+ public function saveChapter(int $intNId): array
+ {
+
+ try {
+ $this->getChapterInfo();
+
+ $strUUId = 'zw630-chapter-' . $this->arrData['c_source_id'] . '-' . $this->arrData['c_page'];
+ $Col = $this->getMongoBase()->getDb()->selectCollection('chapter');
+
+ $arrExisting = $Col->findOne(['c_site_uuid' => $strUUId]);
+
+ if ($arrExisting) {
+
+ $arrExisting = (array) $arrExisting;
+ $this->arrChapterInfo = array_merge($arrExisting, $this->getChapterInfo());
+
+ $this->arrChapterInfo['updated_at'] = new \MongoDB\BSON\UTCDateTime(); // 添加更新时间
+ $Col->updateOne(
+ ['c_site_uuid' => $strUUId],
+ ['$set' => $this->arrChapterInfo],
+ // ['upsert' => false]
+ );
+ } else {
+
+ $this->arrChapterInfo['n_id'] = $intNId;
+
+ $this->arrChapterInfo['name'] = $this->arrData['name'];
+
+ $this->arrChapterInfo['c_sort_num'] = $this->arrData['c_sort_num'];
+ $this->arrChapterInfo['c_page'] = $this->arrData['c_page'];
+
+ $this->arrChapterInfo['c_source_id'] = $this->arrData['c_source_id'];
+ $this->arrChapterInfo['c_source_uri'] = $this->arrData['uri'];
+ $this->arrChapterInfo['c_site_name'] = '恋上你中文';
+ $this->arrChapterInfo['c_site_code'] = 'zw630';
+ $this->arrChapterInfo['c_site_uuid'] = $strUUId;
+ $this->arrChapterInfo['c_content_path'] = '/novel/' . ($intNId % 100) . '/' . $intNId . '/' . $this->arrChapterInfo['c_sort_num'] . '_' . $this->arrChapterInfo['c_page'] . '.txt';
+
+ $this->getMongoBase()->insertOne('chapter', $this->arrChapterInfo);
+
+ if (!StorageCore::getInstance()->has($this->arrChapterInfo['c_content_path'])) {
+ StorageCore::getInstance()->set($this->arrChapterInfo['c_content_path'], $this->strChapterContent);
+ }
+
+ $this->boolIsFirstSave = true;
+ }
+ return $this->arrChapterInfo;
+ } catch (\MongoDB\Exception\Exception $e) {
+ throw new \Exception("Upsert failed: " . $e->getMessage());
+ }
+ }
+
+ protected $arrNextPage;
+
+ /**
+ * Undocumented function
+ *
+ * @return array
+ */
+ public function getNextPage(): array
+ {
+ if ($this->arrNextPage === NULL) {
+ $this->arrNextPage = [];
+
+ if ($this->isMobile()) {
+ $strPattern = "/var\s+hhekgsv\s*=\s*['\"](.*?)['\"]/";
+ preg_match_all($strPattern, $this->getContent(), $arrMatches);
+ $strNextPageUri = end($arrMatches[1]) ?: '';
+ } else {
+ $strHref = $this->getQueryList()->find('.read_btn a:contains("下一章")')->attr('href');
+ $strNextPageUri = $strHref ?: '';
+ }
+
+ if (is_numeric(strpos($strNextPageUri, '_'))) {
+ $this->arrNextPage = $this->arrData;
+ $this->arrNextPage['c_page'] += 1;
+ $this->arrNextPage['uri'] = $strNextPageUri;
+ }
+ }
+
+ return $this->arrNextPage;
+ }
+}
diff --git a/code/app/task/crawler/zw630/page/NovelPage.php b/code/app/task/crawler/zw630/page/NovelPage.php
new file mode 100644
index 0000000..7b44797
--- /dev/null
+++ b/code/app/task/crawler/zw630/page/NovelPage.php
@@ -0,0 +1,304 @@
+getMongoBase()->getDb()->selectCollection('novel');
+
+ $arrExisting = $Col->findOne(['n_site_uuid' => $strUUId]);
+
+ if ($arrExisting) {
+ $arrNovelInfo = (array) $arrExisting;
+ } else {
+ $arrNovelInfo['og_title'] = $strName;
+ $arrNovelInfo['og_novel_book_name'] = $strName;
+ $arrNovelInfo['og_novel_pin_yin'] = zhToPinYin($strName);
+ $arrNovelInfo['n_site_name'] = '恋上你中文';
+ $arrNovelInfo['n_site_code'] = 'zw630';
+ $arrNovelInfo['n_site_uuid'] = $strUUId;
+ $arrNovelInfo['n_source_id'] = $intNSourceId;
+ $arrNovelInfo['n_level'] = 1;
+ $arrNovelInfo['n_id'] = $this->getMongoBase()->getNextSequence('novel');
+ $this->getMongoBase()->insertOne('novel', $arrNovelInfo);
+ }
+ return $arrNovelInfo;
+ }
+
+ /**
+ * Find or create a category by name, handling concurrency safely.
+ *
+ * @param string $strCategory
+ * @return array|null The category document (existing or newly created)
+ */
+ public function findOrCreateCategory(string $strCategory)
+ {
+ if (empty($strCategory)) {
+ return false;
+ }
+ $Col = $this->getMongoBase()->getDb()->selectCollection('category');
+
+ $arrCategory = ['ca_name' => $strCategory];
+ try {
+ $Result = $Col->findOneAndUpdate(
+ $arrCategory,
+ ['$set' => $arrCategory],
+ [
+ 'upsert' => true,
+ 'returnDocument' => \MongoDB\Operation\FindOneAndUpdate::RETURN_DOCUMENT_AFTER
+ ]
+ );
+ return $Result;
+ } catch (\MongoDB\Driver\Exception\Exception $e) {
+ return null;
+ }
+ }
+
+
+ /**
+ * Undocumented function
+ *
+ * @return array
+ */
+ public function getNovelInfo($boolSave = true): array
+ {
+ if (empty($this->arrNovelInfo)) {
+ $this->getQueryList()->find('meta[property]')->each(function ($Item) {
+ $strKey = str_replace(':', '_', $Item->attr('property'));
+ $this->arrNovelInfo[$strKey] = $Item->attr('content');
+ });
+
+ $this->arrNovelInfo['n_page_title'] = $this->getQueryList()->find('title')->text();
+ $this->arrNovelInfo['n_page_keywords'] = $this->getQueryList()->find('meta[name="keywords"]')->attr('content');
+ $this->arrNovelInfo['n_page_description'] = $this->getQueryList()->find('meta[name="description"]')->attr('content');
+
+ $this->getQueryList()->find('.first_txt')->eq(0)->find("a")->map(function ($A) {
+ $strName = $A->text();
+ $strUri = $A->attr('href');
+ if (is_numeric(strpos($strUri, 'shu'))) {
+ $intNSourceId = (int)supperExtractFilename($strUri);
+ $arrNovelInfo = $this->findOrCreateNovel($intNSourceId, $strName);
+ $this->arrRelatedNovel[] = [
+ "n_id" => $arrNovelInfo["n_id"],
+ "n_source_id" => $arrNovelInfo["n_source_id"],
+ "og_title" => $arrNovelInfo["og_title"],
+ ];
+ } elseif (is_numeric(strpos($strUri, 'kan'))) {
+ $intNForgeId = (int)supperExtractFilename($strUri);
+ $this->arrForgeNovel[] = [
+ "n_forge_id" => $intNForgeId,
+ "n_forge_title" => $strName,
+ ];
+ }
+ });
+ $this->arrNovelInfo['n_related_novel'] = $this->arrRelatedNovel;
+ $this->arrNovelInfo['n_forge_novel'] = $this->arrForgeNovel;
+ }
+
+
+ if ($boolSave && !empty($this->arrNovelInfo)) {
+ $this->saveNovelInfo();
+
+ $this->findOrCreateCategory($this->arrNovelInfo['og_novel_category']);
+ }
+ return $this->arrNovelInfo;
+ }
+
+ /**
+ * Undocumented function
+ *
+ * @return void
+ */
+ public function saveNovelInfo()
+ {
+ try {
+ $strUUId = 'zw630-' . $this->arrData['n_source_id'];
+ $Col = $this->getMongoBase()->getDb()->selectCollection('novel');
+
+ $arrExisting = $Col->findOne(['n_site_uuid' => $strUUId]);
+
+ if ($arrExisting) {
+
+ $arrExisting = (array) $arrExisting;
+ $this->arrNovelInfo = array_merge($arrExisting, $this->arrNovelInfo);
+
+ if (!isset($this->arrNovelInfo['og_novel_pin_yin']) || empty($this->arrNovelInfo['og_novel_pin_yin'])) {
+ $this->arrNovelInfo['og_novel_pin_yin'] = zhToPinYin($this->arrNovelInfo['og_novel_book_name'] ?? '');
+ }
+
+ if (!isset($this->arrNovelInfo['n_cover_path']) || empty($this->arrNovelInfo['n_cover_path'])) {
+ $this->arrNovelInfo['n_cover_path'] = 'img/' . date('Y/m/d', time() - 24 * 3600 * 14) . '/' . $this->arrNovelInfo['n_id'] . '.jpg';
+ }
+
+ $this->arrNovelInfo['updated_at'] = new \MongoDB\BSON\UTCDateTime();
+ $Col->updateOne(
+ ['n_site_uuid' => $strUUId],
+ ['$set' => $this->arrNovelInfo],
+ // ['upsert' => false]
+ );
+ } else {
+ $this->arrNovelInfo['og_novel_pin_yin'] = zhToPinYin($this->arrNovelInfo['og_novel_book_name'] ?? '');
+ $this->arrNovelInfo['n_site_name'] = '恋上你中文';
+ $this->arrNovelInfo['n_site_code'] = 'zw630';
+ $this->arrNovelInfo['n_site_uuid'] = $strUUId;
+ $this->arrNovelInfo['n_source_id'] = $this->arrData['n_source_id'];
+ $this->arrNovelInfo['n_id'] = $this->getMongoBase()->getNextSequence('novel');
+ $this->arrNovelInfo['n_level'] = 1;
+ $this->arrNovelInfo['n_cover_path'] = '/img/' . date('Y/m/d', time() - 24 * 3600 * 14) . '/' . $this->arrNovelInfo['n_id'] . '.jpg';
+ $this->getMongoBase()->insertOne('novel', $this->arrNovelInfo);
+
+ $this->boolIsFirstSave = true;
+ }
+ return $this->arrNovelInfo;
+ } catch (\MongoDB\Exception\Exception $e) {
+ throw new \Exception("Upsert failed: " . $e->getMessage());
+ }
+ }
+
+ /**
+ * Undocumented function
+ *
+ * @return array
+ */
+ public function getDownCoverInfo(): array
+ {
+ return [
+ 'uri' => $this->arrNovelInfo['og_image'],
+ 'local_path' => $this->arrNovelInfo['n_cover_path'],
+ ];
+ }
+
+ /**
+ * Undocumented variable
+ *
+ * @var array
+ */
+ protected $arrLatestChapters = NULL;
+
+ /**
+ * Undocumented variable
+ *
+ * @var array
+ */
+ protected $arrChapters = NULL;
+
+ public function getLatestChaptersList(): array
+ {
+ if ($this->arrLatestChapters === NULL) {
+ $this->arrLatestChapters = [];
+
+ if ($this->isMobile()) {
+ $strClass = '.chapter-list';
+ } else {
+ $strClass = '.section-list';
+ }
+
+ if ($this->arrData['page'] == 0) {
+ $this->getQueryList()->find($strClass)->eq(0)->find("a")->map(function ($A) {
+ $this->arrLatestChapters[] = [
+ 'name' => $A->text(),
+ 'uri' => $A->attr('href'),
+ ];
+ });
+ }
+ }
+ return $this->arrLatestChapters;
+ }
+
+ public function getChapters(): array
+ {
+ if ($this->arrChapters === NULL) {
+ $this->arrChapters = [];
+ if ($this->isMobile()) {
+ $strClass = '.chapter-list';
+ } else {
+ $strClass = '.section-list';
+ }
+
+ $intDivIndex = 0;
+ if ($this->arrData['page'] == 0) {
+ $intDivIndex = 1;
+ }
+
+ $this->getQueryList()->find($strClass)->eq($intDivIndex)->find("a")->map(function ($A) {
+ $this->arrChapters[] = [
+ 'name' => $A->text(),
+ 'uri' => $A->attr('href'),
+ ];
+ });
+ }
+ return $this->arrChapters;
+ }
+
+
+ protected $arrNextPageArgs = NULL;
+
+ /**
+ * Undocumented function
+ *
+ * @return array
+ */
+ public function getNextPageArgs(): array
+ {
+ $this->arrNextPageArgs = [];
+ if ($this->arrData['page'] == 0) {
+ $this->getLatestChaptersList();
+ $this->getChapters();
+
+ $arrLatestChapterFirstBlock = count($this->arrLatestChapters) > 0 ? $this->arrLatestChapters[0] : null;
+ $arrLatestChapterSecondBlock = count($this->arrChapters) > 0 ? $this->arrChapters[count($this->arrChapters) - 1] : null;
+
+ if (
+ !empty($arrLatestChapterFirstBlock) && !empty($arrLatestChapterSecondBlock)
+ && ($arrLatestChapterFirstBlock['uri'] != $arrLatestChapterSecondBlock['uri'])
+
+ ) {
+ $this->arrNextPageArgs = [
+ 'n_source_id' => $this->arrData['n_source_id'],
+ 'page' => $this->arrData['page'] + 1,
+ ];
+ }
+ } else {
+ $strLastUri = $this->getQueryList()->find('select:first option:last')->attr('value');
+ if ($strLastUri != '' && $strLastUri != $this->strUri) {
+ $this->arrNextPageArgs = [
+ 'n_source_id' => $this->arrData['n_source_id'],
+ 'page' => $this->arrData['page'] + 1,
+ ];
+ }
+ }
+ return $this->arrNextPageArgs;
+ }
+}
diff --git a/code/app/task/crawler/zw630/page/Site.php b/code/app/task/crawler/zw630/page/Site.php
new file mode 100644
index 0000000..86adbb7
--- /dev/null
+++ b/code/app/task/crawler/zw630/page/Site.php
@@ -0,0 +1,257 @@
+Client = new Client([
+ // 'http_version' => '1.1', // 强制用 HTTP/1.1
+ // 'base_uri' => $this->strDomain,
+ // 'http_errors' => false,
+ // // 'version' => 2.0,
+ // 'timeout' => 160,
+ // 'connect_timeout' => 30,
+ // 'curl' => [
+ // // CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0,
+ // CURLOPT_TCP_KEEPALIVE => 1,
+ // CURLOPT_SSL_VERIFYPEER => true,
+ // ],
+ // 'headers' => [
+ // 'User-Agent' => 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Mobile Safari/537.36 Edg/134.0.0.0',
+ // 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
+ // 'Accept-Language' => 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
+ // 'Referer' => $this->strDomain,
+ // 'sec-ch-ua-mobile' => '?1',
+ // 'sec-ch-ua-platform' => '"Android"',
+ // 'sec-fetch-dest' => 'document',
+ // 'sec-fetch-mode' => 'navigate',
+ // 'sec-fetch-site' => 'same-origin',
+ // 'sec-fetch-user' => '?1',
+ // 'upgrade-insecure-requests' => '1',
+ // ],
+ // ]);
+
+ $this->Client = new Client([
+ 'http_version' => '1.1', // 强制用 HTTP/1.1,已有,保持
+ 'base_uri' => $this->strDomain, // 基础域名,已有,保持
+ 'http_errors' => false, // 不抛 HTTP 错误,已有,保持
+ 'timeout' => 60, // 从 160 秒改为 60 秒
+ 'connect_timeout' => 10, // 从 30 秒改为 10 秒
+ 'curl' => [
+ CURLOPT_TCP_KEEPALIVE => 1, // 启用 TCP Keep-Alive,已有,保持
+ CURLOPT_TCP_KEEPIDLE => 120, // Keep-Alive 空闲时间
+ CURLOPT_TCP_KEEPINTVL => 60, // Keep-Alive 探测间隔
+ CURLOPT_SSL_VERIFYPEER => true, // 验证 SSL,已有,保持
+ CURLOPT_SSL_VERIFYHOST => 2, // 确保主机名匹配
+ ],
+ 'headers' => [
+ // Mobile
+ // 'User-Agent' => 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Mobile Safari/537.36 Edg/134.0.0.0',
+ // 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
+
+ //PC
+ 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0',
+ 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
+
+ 'Accept-Language' => 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
+ 'Referer' => $this->strDomain,
+ 'Connection' => 'keep-alive', // 显式启用 Keep-Alive
+
+
+ // Mobile
+ // 'sec-ch-ua-mobile' => '?1',
+ // 'sec-ch-ua-platform' => '"Android"',
+ // 'sec-fetch-dest' => 'document',
+ // 'sec-fetch-mode' => 'navigate',
+ // 'sec-fetch-site' => 'same-origin',
+ // 'sec-fetch-user' => '?1',
+
+
+ // PC
+ 'sec-ch-ua' => '"Chromium";v="134", "Not:A-Brand";v="24", "Microsoft Edge";v="134"',
+ 'sec-ch-ua-mobile' => '?0',
+ 'sec-ch-ua-platform' => '"Windows"',
+ 'sec-fetch-dest' => 'document',
+ 'sec-fetch-mode' => 'navigate',
+ 'sec-fetch-site' => 'same-origin',
+ 'sec-fetch-user' => '?1',
+
+ 'upgrade-insecure-requests' => '1',
+ ],
+ 'pool_size' => 40, // 并发连接池大小(视需求调整)
+ ]);
+ }
+
+ /**
+ * Undocumented function
+ *
+ * @return Client
+ */
+ public function getClient(): Client
+ {
+ return $this->Client;
+ }
+
+ /**
+ * Undocumented function
+ *
+ * @param string $strUri
+ * @return NovelPage
+ */
+ public function getNovelPage(string $strUri): NovelPage
+ {
+ return new NovelPage($this, $strUri);
+ }
+
+
+ /**
+ * Undocumented function
+ *
+ * @param string $strUri
+ * @return NovelPage[]
+ */
+ public function getNovelPageList(array $arrFilter): array
+ {
+
+ /** @var NovelPage[] */
+ $arrNovelPage = [];
+
+ foreach ($arrFilter as $intKey => $arrItem) {
+
+ if ($arrItem['page'] == 0) {
+ $arrItem['uri'] = sprintf('/shu/%s.html', $arrItem['n_source_id']);
+ } else {
+ $arrItem['uri'] = sprintf('/shu/%s_%s.html', $arrItem['n_source_id'], $arrItem['page']);
+ }
+ $arrFilter[$intKey]['uri'] = $arrItem['uri'];
+
+ $arrPromises[$intKey] = $this->getClient()->getAsync($arrItem['uri']);
+ }
+
+ $arrResults = Promise\Utils::settle($arrPromises)->wait();
+
+ $arrSuccessfulResults = array_filter($arrResults, function ($arrResult) {
+ return $arrResult['state'] === 'fulfilled';
+ });
+
+ foreach ($arrSuccessfulResults as $intKey => $arrResult) {
+ $Response = $arrResult['value'];
+
+ $strContent = $Response->getBody()->getContents();
+ if ($strContent == '索引文件不存在!') {
+ continue;
+ }
+
+ $NovelPage = new NovelPage($this, $arrItem['uri']);
+
+ $NovelPage->setContent($strContent);
+ $NovelPage->setData($arrFilter[$intKey]);
+
+ $arrNovelPage[] = $NovelPage;
+ }
+
+
+ return $arrNovelPage;
+ }
+
+ /**
+ * Undocumented function
+ *
+ * @param string $strUri
+ * @return ChapterPage[]
+ */
+ public function getChapterPageList(array $arrFilter): array
+ {
+
+ /** @var ChapterPage[] */
+ $arrChapterPage = [];
+
+ $arrPromises = [];
+ foreach ($arrFilter as $intKey => $arrItem) {
+ $arrPromises[$intKey] = $this->getClient()->getAsync($arrItem['uri']);
+ }
+
+ $arrResults = Promise\Utils::settle($arrPromises)->wait();
+
+ $arrSuccessfulResults = array_filter($arrResults, function ($arrResult) {
+ return $arrResult['state'] === 'fulfilled';
+ });
+
+ foreach ($arrSuccessfulResults as $intKey => $arrResult) {
+ $Response = $arrResult['value'];
+
+ $strContent = $Response->getBody()->getContents();;
+
+ $ChapterPage = new ChapterPage($this, $arrItem['uri']);
+
+ $ChapterPage->setContent($strContent);
+ $ChapterPage->setData($arrFilter[$intKey]);
+
+ $arrChapterPage[] = $ChapterPage;
+ }
+
+
+ return $arrChapterPage;
+ }
+
+ /**
+ * Undocumented function
+ *
+ * @param array $arrFilter
+ * @return void
+ */
+ public function downRemoteImgToLocal(array $arrFilter)
+ {
+ $StorageCore = StorageCore::getInstance();
+
+ $arrPromises = [];
+
+ foreach ($arrFilter as $intKey => $arrItem) {
+ if (!$StorageCore->has($arrItem['local_path'])) {
+ $arrPromises[$intKey] = $this->getClient()->getAsync($arrItem['uri']);
+ }
+ }
+
+ $arrResults = Promise\Utils::settle($arrPromises)->wait();
+
+ $arrSuccessfulResults = array_filter($arrResults, function ($arrResult) {
+ return $arrResult['state'] === 'fulfilled';
+ });
+
+ foreach ($arrSuccessfulResults as $intKey => $arrResult) {
+ $Response = $arrResult['value'];
+
+ // $strContent = $Response->getBody()->getContents();
+
+ $StorageCore->writeStream($arrFilter[$intKey]['local_path'], $Response->getBody()->detach());;
+ }
+ }
+}
diff --git a/code/app/task/module/Listen.php b/code/app/task/module/Listen.php
index bab833e..b9130df 100644
--- a/code/app/task/module/Listen.php
+++ b/code/app/task/module/Listen.php
@@ -4,8 +4,8 @@ declare(strict_types=1);
namespace app\task\module;
-use minserver\ProcessSanitizer;
-use minserver\QueueManage;
+use microserver\ProcessSanitizer;
+use microserver\QueueManage;
class Listen
{
diff --git a/code/app/task/module/PlanTask.php b/code/app/task/module/PlanTask.php
index 2fb6fc7..db73150 100644
--- a/code/app/task/module/PlanTask.php
+++ b/code/app/task/module/PlanTask.php
@@ -5,9 +5,9 @@ declare(strict_types=1);
namespace app\task\module;
use app\model\PlanTaskModel;
-use app\task\collection\zw630\Zw630Col;
-use minserver\QueueManage;
-use minserver\ProcessSanitizer;
+use app\task\crawler\zw630\Scheduler as Zw630Scheduler;
+use microserver\QueueManage;
+use microserver\ProcessSanitizer;
class PlanTask
@@ -41,7 +41,7 @@ class PlanTask
public static function pull630ZwNovel()
{
$arrTask = [
- 'callback' => [Zw630Col::class, 'pull630ZwNovel'],
+ 'callback' => [Zw630Scheduler::class, 'crawlFull'],
'data' => []
];
diff --git a/code/composer.json b/code/composer.json
index f065d4e..16d9146 100644
--- a/code/composer.json
+++ b/code/composer.json
@@ -34,7 +34,9 @@
"aws/aws-sdk-php": "^3.337",
"league/flysystem": "^1.1",
"league/flysystem-aws-s3-v3": "^1.0",
- "mongodb/mongodb": "^1.21"
+ "mongodb/mongodb": "^1.21",
+ "topthink/think-throttle": "^2.0",
+ "phpoffice/phpspreadsheet": "^4.1"
},
"require-dev": {
"topthink/think-dumper": "^1.0",
diff --git a/code/config/app.php b/code/config/app.php
index c3a318d..6c56163 100644
--- a/code/config/app.php
+++ b/code/config/app.php
@@ -26,6 +26,7 @@ return [
'domain_bind' => [
'*' => 'home',
'adapi' => 'admin',
+ 'api' => 'api',
],
// 禁止URL访问的应用列表(自动多应用模式有效)
diff --git a/code/config/console.php b/code/config/console.php
index d64ab68..98da0ec 100644
--- a/code/config/console.php
+++ b/code/config/console.php
@@ -2,10 +2,14 @@
// +----------------------------------------------------------------------
// | 控制台配置
// +----------------------------------------------------------------------
+
+use ddl\DDLManage;
+
+DDLManage::load('MicroServer');
+
return [
- // 指令定义
'commands' => [
- 'task' => 'minserver\ServerConsole',
+ 'task' => 'microserver\ServerConsole',
'db:manage' => 'db\DatabaseManage',
],
];
diff --git a/code/config/throttle.php b/code/config/throttle.php
new file mode 100644
index 0000000..575662e
--- /dev/null
+++ b/code/config/throttle.php
@@ -0,0 +1,33 @@
+ 'throttle_',
+ // 缓存的键,true 表示使用来源ip
+ 'key' => true,
+ // 要被限制的请求类型, eg: GET POST PUT DELETE HEAD 等
+ 'visit_method' => ['GET', 'HEAD'],
+ // 设置访问频率,例如 '10/m' 指的是允许每分钟请求10次;'10/60'指允许每60秒请求10次。值 null 表示不限制, eg: null 10/m 20/h 300/d 200/300
+ 'visit_rate' => '100/m',
+ /*
+ * 设置节流算法,组件提供了四种算法:
+ * - CounterFixed :计数固定窗口
+ * - CounterSlider: 滑动窗口
+ * - TokenBucket : 令牌桶算法
+ * - LeakyBucket : 漏桶限流算法
+ */
+ 'driver_name' => CounterFixed::class,
+ // 响应体中设置速率限制的头部信息
+ 'visit_enable_show_rate_limit' => true,
+ // 访问受限时返回的响应
+ 'visit_fail_response' => function (Throttle $throttle, Request $request, int $wait_seconds) {
+ return Response::create('Too many requests, try again after ' . $wait_seconds . ' seconds.')->code(429);
+ },
+];
diff --git a/code/extend/db/DatabaseManage.php b/code/extend/db/DatabaseManage.php
index 06f67b6..89dc728 100644
--- a/code/extend/db/DatabaseManage.php
+++ b/code/extend/db/DatabaseManage.php
@@ -1,6 +1,5 @@
'mongodb://username:password@your_ip:27017',
- // 'database' => 'your_database',
- // ];
-
- if (!self::$instance) {
- self::$instance = new self();
- }
- return self::$instance;
- }
-
- /**
- * 构造函数,初始化 MongoDB 连接
- * @param string $uri MongoDB 连接字符串
- * @param string $dbName 数据库名称
- */
- public function __construct()
- {
- try {
- $arrConfig = config('mongodb');
- $strUri = sprintf('mongodb://%s:%s@%s:%s', $arrConfig['username'], $arrConfig['password'], $arrConfig['hostname'], $arrConfig['hostport']);
-
- $this->client = new Client($strUri);
- $this->db = $this->client->selectDatabase($arrConfig['database']);
- } catch (MongoDBException $e) {
- throw new \Exception("MongoDB connection failed: " . $e->getMessage());
- }
- }
-
- /**
- * 获取下一个自增序列值
- * @param string $sequenceName 序列名称
- * @return int 自增 ID
- */
- public function getNextSequence(string $sequenceName): int
- {
- try {
- $counters = $this->db->selectCollection('counters');
- $result = $counters->findOneAndUpdate(
- ['_id' => $sequenceName],
- ['$inc' => ['sequence_value' => 1]],
- [
- 'upsert' => true,
- 'returnDocument' => \MongoDB\Operation\FindOneAndUpdate::RETURN_DOCUMENT_AFTER
- ]
- );
- return $result->sequence_value ?? 1;
- } catch (MongoDBException $e) {
- throw new \Exception("Failed to generate sequence: " . $e->getMessage());
- }
- }
-
- /**
- * 插入单条数据
- * @param string $collection 集合名称
- * @param array $data 数据数组
- * @return mixed 插入的 ID
- */
- public function insertOne(string $collection, array $data)
- {
- try {
- $coll = $this->db->selectCollection($collection);
- $data['created_at'] = new UTCDateTime(); // 自动添加创建时间
- $result = $coll->insertOne($data);
- return $result->getInsertedId();
- } catch (MongoDBException $e) {
- throw new \Exception("Insert failed: " . $e->getMessage());
- }
- }
-
- public function getDb(): \MongoDB\Database
- {
- return $this->db;
- }
-
- /**
- * 插入多条数据
- * @param string $collection 集合名称
- * @param array $data 多条数据数组
- * @return array 插入的 ID 列表
- */
- public function insertMany(string $collection, array $data)
- {
- try {
- $coll = $this->db->selectCollection($collection);
- foreach ($data as &$item) {
- $item['created_at'] = new UTCDateTime();
- }
- $result = $coll->insertMany($data);
- return $result->getInsertedIds();
- } catch (MongoDBException $e) {
- throw new \Exception("Insert many failed: " . $e->getMessage());
- }
- }
-
- /**
- * 查询单条数据
- * @param string $collection 集合名称
- * @param array $filter 查询条件
- * @param array $options 查询选项
- * @return ?object 查询结果
- */
- public function findOne(string $collection, array $filter = [], array $options = []): ?object
- {
- try {
- $coll = $this->db->selectCollection($collection);
- return $coll->findOne($filter, $options);
- } catch (MongoDBException $e) {
- throw new \Exception("Find one failed: " . $e->getMessage());
- }
- }
-
- /**
- * 查询多条数据
- * @param string $collection 集合名称
- * @param array $filter 查询条件
- * @param array $options 查询选项
- * @return array 查询结果
- */
- public function findMany(string $collection, array $filter = [], array $options = []): array
- {
- try {
- $coll = $this->db->selectCollection($collection);
- $cursor = $coll->find($filter, $options);
- return iterator_to_array($cursor);
- } catch (MongoDBException $e) {
- throw new \Exception("Find many failed: " . $e->getMessage());
- }
- }
-
- /**
- * 更新单条数据
- * @param string $collection 集合名称
- * @param array $filter 查询条件
- * @param array $update 更新内容
- * @param array $options 更新选项
- * @return int 受影响的文档数
- */
- public function updateOne(string $collection, array $filter, array $update, array $options = []): int
- {
- try {
- $coll = $this->db->selectCollection($collection);
- $update['$set']['updated_at'] = new UTCDateTime(); // 自动添加更新时间
- $result = $coll->updateOne($filter, $update, $options);
- return $result->getModifiedCount();
- } catch (MongoDBException $e) {
- throw new \Exception("Update one failed: " . $e->getMessage());
- }
- }
-
- /**
- * 删除数据
- * @param string $collection 集合名称
- * @param array $filter 查询条件
- * @return int 删除的文档数
- */
- public function delete(string $collection, array $filter): int
- {
- try {
- $coll = $this->db->selectCollection($collection);
- $result = $coll->deleteMany($filter);
- return $result->getDeletedCount();
- } catch (MongoDBException $e) {
- throw new \Exception("Delete failed: " . $e->getMessage());
- }
- }
-}
diff --git a/code/extend/ddl/DDLManage.php b/code/extend/ddl/DDLManage.php
new file mode 100644
index 0000000..80a453b
--- /dev/null
+++ b/code/extend/ddl/DDLManage.php
@@ -0,0 +1,65 @@
+ 'extend/ddl/MicroCore.ddl',
+ 'MongoBase' => 'extend/ddl/MongoBase.ddl',
+ ];
+
+ /**
+ * 加载指定模块
+ * @param string $strModuleCode 模块编码
+ * @return bool 加载是否成功
+ */
+ public static function load($strModuleCode)
+ {
+ if (isset(self::$arrLoadedModules[$strModuleCode])) {
+ return true;
+ }
+
+ if (!isset(self::$arrModuleFiles[$strModuleCode])) {
+ return false;
+ }
+
+ $filename = self::$arrModuleFiles[$strModuleCode];
+
+ $strFileName = root_path() . $filename;
+
+ if (!file_exists($strFileName)) {
+ return false;
+ }
+
+ if (load_module_file($strFileName)) {
+ self::$arrLoadedModules[$strModuleCode] = true;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * 检查模块是否已加载
+ * @param string $strModuleCode 类名
+ * @return bool
+ */
+ public static function isLoaded($strModuleCode)
+ {
+ return isset(self::$arrLoadedModules[$strModuleCode]);
+ }
+
+ /**
+ * 获取所有已加载的模块
+ * @return array
+ */
+ public static function getarrLoadedModules()
+ {
+ return array_keys(self::$arrLoadedModules);
+ }
+}
diff --git a/code/extend/ddl/MicroCore.ddl b/code/extend/ddl/MicroCore.ddl
new file mode 100644
index 0000000..4a8fe8f
Binary files /dev/null and b/code/extend/ddl/MicroCore.ddl differ
diff --git a/code/extend/ddl/MongoBase.ddl b/code/extend/ddl/MongoBase.ddl
new file mode 100644
index 0000000..e06fd09
Binary files /dev/null and b/code/extend/ddl/MongoBase.ddl differ
diff --git a/code/extend/minserver/ProcessSanitizer.php b/code/extend/minserver/ProcessSanitizer.php
deleted file mode 100644
index f507515..0000000
--- a/code/extend/minserver/ProcessSanitizer.php
+++ /dev/null
@@ -1,27 +0,0 @@
-delete('cache');
- }
-
- static public function reloadDb()
- {
- Db::close();
- Container::getInstance()->delete('think\DbManager');
- }
-
- static public function destructConnectSource()
- {
- self::reloadDb();
- self::reloadRedis();
- }
-}
\ No newline at end of file
diff --git a/code/extend/minserver/QueueManage.php b/code/extend/minserver/QueueManage.php
deleted file mode 100644
index d5c8b0a..0000000
--- a/code/extend/minserver/QueueManage.php
+++ /dev/null
@@ -1,33 +0,0 @@
-strQueueName = $strQueueName;
- }
-
- public function get()
- {
- return Cache::store('redis')->lpop($this->strQueueName);
- }
-
- public function set($arrData)
- {
- $strData = json_encode($arrData);
- return Cache::store('redis')->rpush($this->strQueueName, $strData);
- }
-}
diff --git a/code/extend/minserver/Rout.php b/code/extend/minserver/Rout.php
deleted file mode 100644
index 4d088d9..0000000
--- a/code/extend/minserver/Rout.php
+++ /dev/null
@@ -1,17 +0,0 @@
-status(999, 'Hei Guys ~');
- $Response->header("Content-Type", "text/html; charset=utf-8");
- $Response->end("
Hello reptile~. #" . rand(1000, 9999) . "
");
- }
-
- static public function tcpDispense(\Swoole\Server $server, $fd, $reactor_id, $mixedData) {}
-}
diff --git a/code/extend/minserver/ScheduledTasks.php b/code/extend/minserver/ScheduledTasks.php
deleted file mode 100644
index 06cb867..0000000
--- a/code/extend/minserver/ScheduledTasks.php
+++ /dev/null
@@ -1,19 +0,0 @@
- ['start', 'stop', 'restart', 'status',],
- 'daemonize' => ['d', "false",],
- 'forcibly' => ['f', "false",],
- ];
-
- private $arrReflection = [
- \minserver\ServerManage::class,
- ];
-
- protected function configure()
- {
- $this->setName('php think task')
- ->addArgument('action', Argument::REQUIRED, "action: (" . implode('|', $this->arrArguments['action']) . ')')
- ->addOption('daemonize', 'd', Option::VALUE_NONE, "daemonize start service")
- ->addOption('forcibly', 'f', Option::VALUE_NONE, "ignore user forcibly start service")
- ->setDescription('swoole task server')
- ->setHelp('php think task [-d] [-f] run server');
- }
-
-
- protected function checkUser(Input $Input)
- {
-
- if (!$Input->getOption('forcibly')) {
- $strUser = get_current_user();
-
- if ($strUser == 'root') {
- $strError = sprintf("Sorry , Can't start service by [%s] user!,\n but you can add option '-f' forcibly start on you debug ,\n Do not use in production environment !!!!!!!!!",$strUser);
- throw new \InvalidArgumentException($strError);
- }
-
- if ($strUser != 'www') {
- throw new \InvalidArgumentException("Sorry , Can't start service by [{$strUser}] user!, please use [www] user !");
- }
- }
- }
-
- protected function execute(Input $Input, Output $Output)
- {
-
- $this->checkArguments(['action'], $Input, $Output);
-
- $strAction = trim($Input->getArgument('action'));
-
- if ($strAction == 'start' || $strAction == 'restart') {
- $this->checkUser($Input);
- }
-
- $arrArgs = array_merge($Input->getArguments(), $Input->getOptions());
-
- $this->{$strAction}($arrArgs);
- }
-
- private function checkArguments($arrArguments, Input $Input)
- {
- foreach ($arrArguments as $strArguments) {
- $strVal = trim($Input->getArgument($strArguments));
-
- if (!in_array($strVal, $this->arrArguments[$strArguments])) {
- throw new \InvalidArgumentException("Arguments [{$strVal}] is not defined! Optional parameters [" . implode(' ', $this->arrArguments[$strArguments]) . "] !");
- }
- }
- }
-
- public function __call($strAction, $arrArguments)
- {
- foreach ($this->arrReflection as $strClass) {
- if (method_exists($strClass, $strAction)) {
- return call_user_func([new $strClass, $strAction], $arrArguments);
- }
- }
- throw new \InvalidArgumentException("Action [{$strAction}] no method available! ");
- }
-}
diff --git a/code/extend/minserver/ServerCore.php b/code/extend/minserver/ServerCore.php
deleted file mode 100644
index b8f348e..0000000
--- a/code/extend/minserver/ServerCore.php
+++ /dev/null
@@ -1,87 +0,0 @@
-arrConfig = $arrConfig;
- $this->createServer();
- }
-
- private function serHttpConfig()
- {
- $this->Server->set($this->filterConfig());
- $this->Server->on('request', [\minserver\Rout::class, 'httpDispense']);
- }
-
- private function filterConfig()
- {
- $arrConfig = $this->arrConfig;
- unset($arrConfig['host']);
- unset($arrConfig['port']);
- unset($arrConfig['mode']);
- unset($arrConfig['sockType']);
- unset($arrConfig['server_type']);
- return $arrConfig;
- }
-
- private function serTcpConfig()
- {
- $this->Server->set($this->filterConfig());
- $this->Server->on('receive', [\minserver\Rout::class, 'tcpDispense']);
- }
-
- private function createServer()
- {
- switch ($this->arrConfig['server_type']) {
- case 'HTTP':
- $this->Server = new \Swoole\Http\Server($this->arrConfig['host'], $this->arrConfig['port']);
- $this->serHttpConfig();
- break;
- default:
- $this->Server = new \Swoole\Server($this->arrConfig['host'], $this->arrConfig['port'], $this->arrConfig['mode'], $this->arrConfig['sockType']);
- $this->serTcpConfig();
- break;
- }
-
- $this->Server->on('task', [$this, 'onTask']);
-
- $this->Server->on('finish', [$this, 'onFinish']);
- }
-
- public function getServer()
- {
- return $this->Server;
- }
-
- public function onTask($Http, $task_id, $from_id, $arrData)
- {
- $mixedResult = call_user_func_array($arrData[0], $arrData[1]);
-
- $Http->finish($mixedResult);
- }
-
- public function onFinish($Http, $task_id, $mixedData)
- {
- }
-}
diff --git a/code/extend/minserver/ServerManage.php b/code/extend/minserver/ServerManage.php
deleted file mode 100644
index f46e7c1..0000000
--- a/code/extend/minserver/ServerManage.php
+++ /dev/null
@@ -1,129 +0,0 @@
-setConfigDaemonize($arrArgvs[0]['daemonize'] ?? 'false');
- }
-
- private function setConfigDaemonize($strMode)
- {
- self::$arrDynamicConfig['daemonize'] = (bool)$strMode;
- }
-
- private function getPid()
- {
- return file_exists(config('task.service.pid_file')) ? file_get_contents(config('task.service.pid_file')) : NULL;
- }
-
- public function start($arrArgvs = [])
- {
- $this->__init($arrArgvs);
-
- $intPid = (int)$this->getPid();
-
- if ($intPid > 0 && \Swoole\Process::kill($intPid, SIG_DFL)) {
- echo " Service is \e[0;32mRunning\e[0m ! " . PHP_EOL;
- exit;
- }
- echo " Execute the \e[0;32m start\e[0m command ...... " . PHP_EOL;
-
- $this->createService();
-
- $this->addProcessList();
-
- $this->addScheduledTasks();
-
- self::$HttpService->getServer()->start();
-
- }
-
- public function stop($arrArgvs = [])
- {
- $this->__init($arrArgvs);
-
- $intPid = (int)$this->getPid();
-
- if ($intPid <= 0) {
- echo " Service is \e[0;31mStop\e[0m ! " . PHP_EOL;
- } else {
- echo " Execute the \e[0;32m stop\e[0m command ...... " . PHP_EOL;
- \Swoole\Process::kill($intPid, SIGTERM);
- echo " Service is \e[0;31mStop\e[0m ! " . PHP_EOL;
- }
- }
-
- public function status($arrArgvs = [])
- {
- $this->__init($arrArgvs);
-
- $intPid = (int)$this->getPid();
-
- if ($intPid <= 0) {
- echo " Service is \e[0;31mStop\e[0m ! " . PHP_EOL;
- } else if (\Swoole\Process::kill($intPid, SIG_DFL)) {
- echo " Service is \e[0;32mRunning\e[0m ! " . PHP_EOL;
- } else {
- echo " Service is \e[0;31mStop\e[0m ! " . PHP_EOL;
- }
- }
-
- public function restart($arrArgvs = [])
- {
- $this->__init($arrArgvs);
-
- $intPid = (int)$this->getPid();
-
- if ($intPid <= 0) {
- echo " \e[0;31m Get Pid Error\e[0m !" . PHP_EOL;
- echo " Execute the \e[0;32m start\e[0m command ...... " . PHP_EOL;
- $this->start();
- } else {
- echo " Execute the \e[0;32m restart\e[0m command ...... " . PHP_EOL;
- \Swoole\Process::kill($intPid, SIGUSR1);
- }
- }
-
- private function addScheduledTasks()
- {
- if (config('task.scheduled_tasks_pool')) {
- foreach (config('task.scheduled_tasks_pool') as $arrProcessTask) {
- if ($arrProcessTask['status']) {
- $Process = new \Swoole\Process(function () use ($arrProcessTask) {
- call_user_func([\minserver\ScheduledTasks::class, 'hander'], $arrProcessTask);
- });
- self::$HttpService->getServer()->addProcess($Process);
- }
- }
- }
- }
-
- private function addProcessList()
- {
- if (config('task.process_pool')) {
- foreach (config('task.process_pool') as $v) {
- for ($i = 0; $i < $v['Num']; $i++) {
- $Process = new \Swoole\Process($v['callback']);
-
- self::$HttpService->getServer()->addProcess($Process);
- }
- }
- }
- }
-
- private function createService()
- {
- $arrHttpServiceConfig = array_merge(config('task.service'), self::$arrDynamicConfig);
-
- self::$HttpService = ServerCore::instance($arrHttpServiceConfig);
- }
-}
diff --git a/code/extend/storage/StorageCore.php b/code/extend/storage/StorageCore.php
index 62a5ae2..bb2d579 100644
--- a/code/extend/storage/StorageCore.php
+++ b/code/extend/storage/StorageCore.php
@@ -56,6 +56,10 @@ class StorageCore
return $this->Filesystem->write($strKey, $strContent);
}
+ public function writeStream(string $strPath, $Resource)
+ {
+ $this->Filesystem->writeStream($strPath, $Resource);
+ }
public function write($strKey, $strContent, array $arrConfig = [])
{
diff --git a/code/extend/tools/pinyin-tool b/code/extend/tools/pinyin-tool
new file mode 100644
index 0000000..fc3f78e
Binary files /dev/null and b/code/extend/tools/pinyin-tool differ
diff --git a/code/fenxixq.php b/code/fenxixq.php
deleted file mode 100644
index 78f2e15..0000000
--- a/code/fenxixq.php
+++ /dev/null
@@ -1,21 +0,0 @@
-find('meta[property]')->each(function ($item) use (&$result) {
- $property = str_replace(':', '_', $item->attr('property')); // 冒号替换为下划线
- $result[$property] = $item->attr('content');
-});
-
-// 输出结果
-print_r($result);
\ No newline at end of file