Merge branch 'dev' of ssh://git.bgbg.live:18150/root/SEONexus into dev

This commit is contained in:
make
2025-03-28 09:23:27 +08:00
52 changed files with 2424 additions and 1871 deletions

View File

@@ -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

View File

@@ -1,359 +0,0 @@
<!doctype html>
<html lang='zh'>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta name="renderer" content="webkit" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta http-equiv="Cache-Control" content="no-cache,must-revalidate" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
<meta http-equiv="Cache-Control" content="no-transform" />
<title>张柠檬2最新章节_张柠檬2伯未有_张柠檬2小说免费全文阅读_恋上你中文</title>
<meta name="keywords" content="张柠檬2,伯未有,恋上你中文" />
<meta name="description"
content="这仍是一部探索欲望与心理SM现实主义小说主人公张柠檬以自述的方式讲述了自己的生活。作者试图写出一篇严肃的色情小说表达一种独特的“异化美感”希望从冷酷中塑造一种黑暗的诗意。我也不知道我在说什么—————————介绍本文描绘了大量SM场景元素存在各式各样的拘束奴役、羞辱和强迫的内容。前作换过很多名字最早名为《我是如何成为富二代的的玩物的》又更名为《玩具》、《张柠檬》等还有一些我也忘记的名字看过就是缘。————————不知道诸位看官究竟喜欢什么内容故事的内容是无限的但作者的精力属实是有限的众所周知本作断更已然是一种常态~_~;所以大伙儿们喜欢什么请一定要告诉我我好更多的去侧重呀。诸如A五花八门的调教??B云朝雨暮的性爱C风花雪月的交往D刻骨铭心的爱情如果还有别的也请补充" />
<meta http-equiv="mobile-agent" content="format=html5; url=https://www.630zw.org/shu/531207.html" />
<link rel="alternate" type="application/vnd.wap.xhtml+xml" media="handheld"
href="https://www.630zw.org/shu/531207.html" />
<link href="https://www.630zw.org/shu/531207.html" rel="canonical" />
<base href="https://www.630zw.org" />
<link href="/xs/biquge_pc/css/style.css" rel="stylesheet" type="text/css" />
<script>var kdeh2 = "if04kddgsa"; var lg_url = "https%3A%2F%2Fwww.630zw.org%2Fshu%2F531207.html";; <script></script>
</script>
<meta property="og:type" content="novel" />
<meta property="og:title" content="张柠檬2" />
<meta property="og:description"
content="这仍是一部探索欲望与心理SM现实主义小说主人公张柠檬以自述的方式讲述了自己的生活。作者试图写出一篇严肃的色情小说表达一种独特的“异化美感”希望从冷酷中塑造一种黑暗的诗意。我也不知道我在说什么—————————介绍本文描绘了大量SM场景元素存在各式各样的拘束奴役、羞辱和强迫的内容。前作换过很多名字最早名为《我是如何成为富二代的的玩物的》又更名为《玩具》、《张柠檬》等还有一些我也忘记的名字看过就是缘。————————不知道诸位看官究竟喜欢什么内容故事的内容是无限的但作者的精力属实是有限的众所周知本作断更已然是一种常态~_~;所以大伙儿们喜欢什么请一定要告诉我我好更多的去侧重呀。诸如A五花八门的调教??B云朝雨暮的性爱C风花雪月的交往D刻骨铭心的爱情如果还有别的也请补充" />
<meta property="og:image" content="/img/531207.jpg" />
<meta property="og:novel:category" content="都市小说" />
<meta property="og:novel:author" content="伯未有" />
<meta property="og:novel:book_name" content="张柠檬2" />
<meta property="og:novel:read_url" content="https://www.630zw.org/shu/531207/180011428.html" />
<meta property="og:url" content="https://www.630zw.org/shu/531207.html" />
<meta property="og:novel:status" content="连载中" />
<meta property="og:novel:update_time" content="2025-03-19 17:06:56" />
<meta property="og:novel:latest_chapter_name" content="二重奏" />
<meta property="og:novel:latest_chapter_url" content="https://www.630zw.org/shu/531207/180011666.html" />
</head>
<body class="novel_info">
<div class="topbar">
<div class="topbar-con">
<div class="topbar-sethome"><a href="javascript:winSetHP();">将本站设为首页</a></div>
<div class="topbar-addfavorite"><a href="javascript:winAddFav()">收藏恋上你中文</a></div>
<div class="topbar-right" id="qs_login"><a
href="/login.html?url=https%3A%2F%2Fwww.630zw.org%2Fshu%2F531207.html">用户登录</a></div>
</div>
</div>
<div class="header">
<h1 class="logo"><a href="/"><span>恋上你中文</span></a></h1>
<form id="novel_search" class="search-form" method="post" action="/search.html"><input type="text"
class="input-text input-key" name="s" placeholder="输入书名/作者进行搜索"><button type="submit"
class="btn-tosearch" form="novel_search" value="submit">搜索</button></form>
</div>
<ul class="nav">
<li><a href="/">首 页</a></li>
<li><a href="/list/1/1.html">玄幻修真</a></li>
<li><a href="/list/2/1.html">重生穿越</a></li>
<li><a href="/list/3/1.html">都市小说</a></li>
<li><a href="/list/4/1.html">军史小说</a></li>
<li><a href="/list/5/1.html">网游小说</a></li>
<li><a href="/list/6/1.html">科幻小说</a></li>
<li><a href="/list/7/1.html">灵异小说</a></li>
<li><a href="/list/8/1.html">言情小说</a></li>
<li><a href="/list/9/1.html">其他小说</a></li>
<li><a href="/mybook.html">会员书架</a></li>
<li><a href="/jilu.html">阅读记录</a></li>
</ul>
<div class="container biqunaicc">
<div class="row row-detail">
<div class="layout layout-col1">
<h2 class="layout-tit xs-hidden"><a href="/">恋上你中文</a>&gt;<a href="/shu/531207.html">张柠檬2</a></h2>
<div class="detail-box">
<div class="imgbox"><img alt="张柠檬2" src="/img/531207.jpg" /></div>
<div class="info">
<div class="top">
<h1>张柠檬2</h1>
<div class="fix">
<p>&nbsp;&nbsp;者:伯未有</p>
<p class="xs-show">&nbsp;&nbsp;别:都市小说</p>
<p class="xs-show">&nbsp;&nbsp;态:连载中</p>
<p class="opt"><span class="xs-hidden">&nbsp;&nbsp;作:</span><a rel="nofollow"
href="javascript:addbookcase('https://www.630zw.org/shu/531207.html','531207');">加入书架</a><i
class="xs-hidden"></i><a href="/shu/531207_1.html">章节目录</a><i
class="xs-hidden"></i><a href="/shu/531207/180011428.html">开始阅读</a></p>
<p>最后更新2025-03-19 17:06:56 </p>
<p>最新章节:<a href="/shu/531207/180011666.html">二重奏</a></p>
</div>
</div>
<div class="desc xs-hidden">
这仍是一部探索欲望与心理SM现实主义小说主人公张柠檬以自述的方式讲述了自己的生活。作者试图写出一篇严肃的色情小说表达一种独特的“异化美感”希望从冷酷中塑造一种黑暗的诗意。我也不知道我在说什么—————————介绍本文描绘了大量SM场景元素存在各式各样的拘束奴役、羞辱和强迫的内容。前作换过很多名字最早名为《我是如何成为富二代的的玩物的》又更名为《玩具》、《张柠檬》等还有一些我也忘记的名字看过就是缘。————————不知道诸位看官究竟喜欢什么内容故事的内容是无限的但作者的精力属实是有限的众所周知本作断更已然是一种常态~_~;所以大伙儿们喜欢什么请一定要告诉我我好更多的去侧重呀。诸如A五花八门的调教??B云朝雨暮的性爱C风花雪月的交往D刻骨铭心的爱情如果还有别的也请补充
</div>
</div>
</div>
</div>
<div class="layout layout-col1">
<h2 class="layout-tit">《张柠檬2》二重奏</h2>
<div class="first_txt">
<p>令我两条胳膊无法从脑后拿开,我被他单臂环着,揉着胸,看着镜子扭来扭去。 我忽然领悟了为什么有人把女人比作蛇,柔软、妖娆,可如果被攥住了七寸,就从要命的野兽,变成扭捏着逞强的肉绳子。
我什么也做不了,连他把手从我身上拿下来,我都无法阻止。我无法攥住他的手,无法抱着他,我只能眼睁睁的看着他品尝了我之后,并不留恋的离开。 想要留下一个人,比想要追上一个人还要难。
我想让他继续环我的腰,揉我的胸,嗅我身上的气味,我想告诉他:「再尝我一口吧,我会更好吃的。」 悄悄的,在我不知道的时候,我腰上的金属环变松了,他要把这碍事的铁家伙从我身上取下了。
贞操带向下退着,我的心脏已为它敲响了欢送的战鼓。 可是,主人没...</p>
<p>相邻推荐:<a href="/shu/531195.html">斗罗,绝世之神</a>&nbsp;&nbsp;<a
href="/shu/531196.html">灭我全族?弃守帝关,化禁区之主</a>&nbsp;&nbsp;<a
href="https://www.shxsw.com/novel_545075/index.html"
target="_blank">我在斗气大陆与美女双修</a>&nbsp;&nbsp;<a
href="/shu/531194.html">七零大厂美人,改造反派崽暴富</a>&nbsp;&nbsp;<a
href="/shu/531204.html">凹凸世界之天光破云</a>&nbsp;&nbsp;<a
href="/shu/531188.html">可盐可甜美人穿</a>&nbsp;&nbsp;<a
href="/shu/531199.html">顶级陪玩</a>&nbsp;&nbsp;<a
href="/shu/531187.html">转身芳华依旧</a>&nbsp;&nbsp;<a
href="/shu/531189.html">未来通货膨胀,我却赚了几百亿</a>&nbsp;&nbsp;<a
href="/shu/531203.html">抄家流放?亲妈金手指已到账</a>&nbsp;&nbsp;<a
href="/shu/531197.html">复仇之蓝蝴蝶可以承载灵魂</a>&nbsp;&nbsp;<a
href="/shu/531198.html">原神:小鸟要从零基础养起</a>&nbsp;&nbsp;<a
href="/shu/531205.html">查理九世:怪谈游戏</a>&nbsp;&nbsp;<a
href="/shu/531191.html">空间通末世,我带飞全家很合理吧</a>&nbsp;&nbsp;<a
href="/shu/531201.html">我GSD一剑爆星</a>&nbsp;&nbsp;<a
href="/shu/531192.html">我手握无限物资,砸出末世安全区</a>&nbsp;&nbsp;<a
href="/shu/531193.html">太古封神</a>&nbsp;&nbsp;<a
href="/shu/531202.html">流放?医妃搬空仇人库房做女皇</a>&nbsp;&nbsp;<a
href="/shu/531190.html">大明朱由检,消费系统救国</a>&nbsp;&nbsp;<a
href="/shu/531200.html">穹顶2084</a>&nbsp;&nbsp;</p>
</div>
</div>
</div>
<div class="row row-section">
<div class="layout layout-col1">
<h2 class="layout-tit">《张柠檬2》最新章节</h2>
<ul class="section-list fix">
<li><a href="/shu/531207/180011666.html">二重奏</a></li>
<li><a href="/shu/531207/180011663.html">玩玩具</a></li>
<li><a href="/shu/531207/180011660.html">身为前辈</a></li>
<li><a href="/shu/531207/180011657.html">我的缺点</a></li>
<li><a href="/shu/531207/180011653.html">肉便器的服务流程</a></li>
<li><a href="/shu/531207/180011650.html">肉便器的自我修养</a></li>
<li><a href="/shu/531207/180011647.html">纣王</a></li>
<li><a href="/shu/531207/180011643.html">无尽模式 yes es uw u6c o</a></li>
<li><a href="/shu/531207/180011641.html">投降</a></li>
<li><a href="/shu/531207/180011638.html">巴尔神选</a></li>
<li><a href="/shu/531207/180011635.html">厨师与司机</a></li>
<li><a href="/shu/531207/180011631.html">一样坏</a></li>
</ul>
<h2 class="layout-tit">《张柠檬2》章节列表</h2>
<ul class="fix section-list">
<li><a href="/shu/531207/180011428.html">他被字分腿捆着拨弄阴唇像是拨弄琴弦</a></li>
<li><a href="/shu/531207/180011432.html">夸奖被分腿捆绑攥着脚操完扔在原地放置</a></li>
<li><a href="/shu/531207/180011439.html">生日纯爱无肉</a></li>
<li><a href="/shu/531207/180011442.html">视频微视频观赏洗澡</a></li>
<li><a href="/shu/531207/180011446.html">洗澡对着视频吃自己的乳头跪着清洗乳房</a></li>
<li><a href="/shu/531207/180011448.html">表演被命令跪着挺胸用指甲扫弄乳尖</a></li>
<li><a href="/shu/531207/180011450.html">交代对着镜头打自己屁股自罚抱头分腿蹲着</a></li>
<li><a href="/shu/531207/180011452.html">审判对着镜头自慰并说明流程</a></li>
<li><a href="/shu/531207/180011454.html">容器对着镜头穿上情趣内衣高跟鞋戴上项圈</a></li>
<li><a href="/shu/531207/180011456.html">别人一则生活里的小插曲</a></li>
<li><a href="/shu/531207/180011459.html">戏弄被极限驷马摆起来揪着奶头一晃一晃挠</a></li>
<li><a href="/shu/531207/180011462.html">道理极限驷马中被戴上口枷拍照口水流了一</a></li>
<li><a href="/shu/531207/180011466.html">盘点这些我捆过你的姿势你最喜欢哪个</a></li>
<li><a href="/shu/531207/180011469.html">酷刑在折磨中被迫以耻辱的语言解构自己而</a></li>
<li><a href="/shu/531207/180011472.html">观赏一场普通的性爱描写1开场部分</a></li>
<li><a href="/shu/531207/180011475.html">厮磨一场普通的性爱描写2</a></li>
<li><a href="/shu/531207/180011478.html">交媾一场普通的性爱描写3</a></li>
<li><a href="/shu/531207/180011481.html">世面关于他去逛淫趴的小故事</a></li>
<li><a href="/shu/531207/180011486.html">酒器淫趴里都是怎么玩儿的纪实1</a></li>
<li><a href="/shu/531207/180011489.html">消费淫趴纪实2</a></li>
<li><a href="/shu/531207/180011492.html">气质淫趴纪实3</a></li>
<li><a href="/shu/531207/180011495.html">暴雨淫趴纪实4</a></li>
<li><a href="/shu/531207/180011499.html">装裹淫趴纪实5</a></li>
<li><a href="/shu/531207/180011502.html">vp淫趴纪实6</a></li>
<li><a href="/shu/531207/180011506.html">故事目睹了淫趴里的四飞</a></li>
<li><a href="/shu/531207/180011510.html">盲盒入珠的男招待</a></li>
<li><a href="/shu/531207/180011513.html">巡演以刁钻的s思路在淫趴里露了一手</a></li>
<li><a href="/shu/531207/180011515.html">核心卖点</a></li>
<li><a href="/shu/531207/180011517.html">白色水渍</a></li>
<li><a href="/shu/531207/180011519.html">超级无敌究级大淫魔 pa o wen wu4c o</a></li>
<li><a href="/shu/531207/180011521.html">歌台暖响春光融融舞殿冷袖风雨凄凄</a></li>
<li><a href="/shu/531207/180011523.html">专业人士</a></li>
<li><a href="/shu/531207/180011526.html">美少年杯垫</a></li>
<li><a href="/shu/531207/180011529.html">掀盖头</a></li>
<li><a href="/shu/531207/180011531.html">整死你</a></li>
<li><a href="/shu/531207/180011534.html">人体研究</a></li>
<li><a href="/shu/531207/180011536.html">豌豆射手</a></li>
<li><a href="/shu/531207/180011539.html">家妓与贞操带</a></li>
<li><a href="/shu/531207/180011542.html">breakgbad</a></li>
<li><a href="/shu/531207/180011546.html">扁笼子里的小青蛙 q ix gz i</a></li>
<li><a href="/shu/531207/180011549.html">夹缝被装在扁笼子里的调教加更</a></li>
<li><a href="/shu/531207/180011556.html">勾引的意念</a></li>
<li><a href="/shu/531207/180011559.html">挂画与竹子惊鹿</a></li>
<li><a href="/shu/531207/180011562.html">好雅兴</a></li>
<li><a href="/shu/531207/180011565.html">第一次</a></li>
<li><a href="/shu/531207/180011568.html">权力结构</a></li>
<li><a href="/shu/531207/180011571.html">完美作品 j iz ai1 2 c o</a></li>
<li><a href="/shu/531207/180011576.html">月光与危险论</a></li>
<li><a href="/shu/531207/180011579.html">地牢悬挂对主人的惩罚</a></li>
<li><a href="/shu/531207/180011582.html">上下</a></li>
<li><a href="/shu/531207/180011585.html">生意与人情</a></li>
<li><a href="/shu/531207/180011588.html">关于害羞的讨论</a></li>
<li><a href="/shu/531207/180011592.html">一号木七号铁与推杆</a></li>
<li><a href="/shu/531207/180011595.html">最好时机</a></li>
<li><a href="/shu/531207/180011597.html">名词订正</a></li>
<li><a href="/shu/531207/180011600.html">过家家游戏</a></li>
<li><a href="/shu/531207/180011603.html">属于我的人</a></li>
<li><a href="/shu/531207/180011605.html">无聊交往</a></li>
<li><a href="/shu/531207/180011607.html">悬疑故事</a></li>
<li><a href="/shu/531207/180011611.html">忍耐前两天鸽了写够两千字以表歉意</a></li>
<li><a href="/shu/531207/180011614.html">正宗主人</a></li>
<li><a href="/shu/531207/180011616.html">报菜名</a></li>
<li><a href="/shu/531207/180011618.html">正宗捆绑</a></li>
<li><a href="/shu/531207/180011620.html">洒水车 sew uwu8c o</a></li>
<li><a href="/shu/531207/180011622.html">他总是会有新的女孩儿</a></li>
<li><a href="/shu/531207/180011625.html">我的贞操带使用心得</a></li>
<li><a href="/shu/531207/180011627.html">我的贞操带使用心得2补之前的鸽的</a></li>
<li><a href="/shu/531207/180011631.html">一样坏</a></li>
<li><a href="/shu/531207/180011635.html">厨师与司机</a></li>
<li><a href="/shu/531207/180011638.html">巴尔神选</a></li>
<li><a href="/shu/531207/180011641.html">投降</a></li>
<li><a href="/shu/531207/180011643.html">无尽模式 yes es uw u6c o</a></li>
<li><a href="/shu/531207/180011647.html">纣王</a></li>
<li><a href="/shu/531207/180011650.html">肉便器的自我修养</a></li>
<li><a href="/shu/531207/180011653.html">肉便器的服务流程</a></li>
<li><a href="/shu/531207/180011657.html">我的缺点</a></li>
<li><a href="/shu/531207/180011660.html">身为前辈</a></li>
<li><a href="/shu/531207/180011663.html">玩玩具</a></li>
<li><a href="/shu/531207/180011666.html">二重奏</a></li>
</ul><a href="/shu/531207_1.html" class="btn-mulu">查看更多章节...</a>
</div>
</div>
<div class="row row-section">
<h2 class="layout-tit">已完结热门小说推荐</h2>
<div class="layout layout-col3">
<div class="item">
<div class="image"><a href="/shu/129571.html"><img style="min-height:120px;" src="/img/129571.jpg"
alt="和鬼一起的日子"></a></div>
<dl>
<dt><span>稻村渔夫</span><a href="/shu/129571.html">和鬼一起的日子</a></dt>
<dd style="height:90px"><a href="/shu/129571.html"
style="color: #555">我小时候无意间救了一个厉鬼,从此,我就走不出这个圈子,也因此改写了人生,一切恐怖离奇的事情接踵而来,老村山塘的古怪浮尸,凶残老板夫妻的人肉包子,砖墙藏尸,富家老太死后的垫背童尸,一切看似与我无关,一切却又牵扯在我的身上...</a>
</dd>
</dl>
</div>
<div class="item">
<div class="image"><a href="/shu/6670.html"><img style="min-height:120px;" src="/img/6670.jpg"
alt="美女老板的男秘书"></a></div>
<dl>
<dt><span>不语怪力乱神</span><a href="/shu/6670.html">美女老板的男秘书</a></dt>
<dd style="height:90px"><a href="/shu/6670.html"
style="color: #555">男人最应该拥有的是什么?帅气的脸颊?万贯的家财?高雅的气质?很遗憾,林东跟这些东西都毫无联系,按照他自己的话来说我属于一个比较普通的人,既不是富二代,也不是官二代,没有万贯的家财,也没有高雅的气质至于脸面嘛,还勉强看得过去。...</a>
</dd>
</dl>
</div>
<div class="item">
<div class="image"><a href="/shu/139219.html"><img style="min-height:120px;" src="/img/139219.jpg"
alt="王牌兵王的同居美女"></a></div>
<dl>
<dt><span>雨阳</span><a href="/shu/139219.html">王牌兵王的同居美女</a></dt>
<dd style="height:90px"><a href="/shu/139219.html"
style="color: #555">龙飞,华夏国战龙队超级兵王,执行任务来到静海市,面对清纯傲娇的千金,性感火辣的校花,以及欲望都市各种利益的诱惑,他都谨记自己的使命。热血男儿当自强,保家卫国做忠良!...</a>
</dd>
</dl>
</div>
<div class="item">
<div class="image"><a href="/shu/482322.html"><img style="min-height:120px;" src="/img/482322.jpg"
alt="被战神宠成宝的小王妃是玄学大佬"></a></div>
<dl>
<dt><span>悠络</span><a href="/shu/482322.html">被战神宠成宝的小王妃是玄学大佬</a></dt>
<dd style="height:90px"><a href="/shu/482322.html"
style="color: #555">关于被战神宠成宝的小王妃是玄学大佬传闻大景战神王爷晏玄翊身负七杀,煞气缠身,恐活不过而立之年。国师批命,他须娶天命贵女苍妍玲,才能破除命中死劫,化解七杀。大婚当日,嫁入王府的却是被拴在家畜棚子里与家畜一起养,又疯又傻,...</a>
</dd>
</dl>
</div>
<div class="item">
<div class="image"><a href="/shu/66974.html"><img style="min-height:120px;" src="/img/66974.jpg"
alt="崩坏召唤"></a></div>
<dl>
<dt><span>隐语者</span><a href="/shu/66974.html">崩坏召唤</a></dt>
<dd style="height:90px"><a href="/shu/66974.html"
style="color: #555">不死不灭的吞魂者,一次次被召唤。一句让无尽世界无数人战栗的话开始流传你就是老子的玛斯塔?已有完本作品一击魔法师,人品保证,还请放心收藏阅读,书荒的小伙伴可以去看下。...</a>
</dd>
</dl>
</div>
<div class="item">
<div class="image"><a href="/shu/34731.html"><img style="min-height:120px;" src="/img/34731.jpg"
alt="无上圣王"></a></div>
<dl>
<dt><span>不吃西瓜皮</span><a href="/shu/34731.html">无上圣王</a></dt>
<dd style="height:90px"><a href="/shu/34731.html"
style="color: #555">被退婚了?呵呵,不是,而是我不娶了!求我也没用,终有一天,我会让那些看不起我叶晨的人,通通跪倒在我的脚下!...</a></dd>
</dl>
</div>
</div>
</div>
</div>
<div class="row">
<div class="layout layout3 layout-col3">
<h2 class="layout-tit">最新标签</h2>
<ul class="txt-list txt-list-row5 tag_news"><a href="/kan/12193271.html">巅峰战神在都市李峰胡媛媛</a><a
href="/kan/12193070.html">苏暮白凤清歌阅读</a><a href="/kan/12193146.html">我喜欢的样子你全有</a><a
href="/kan/12193058.html">桃花马上威风凛凛于毅</a><a href="/kan/12193278.html">小贵人番外by朵慕</a><a
href="/kan/12193094.html">雪豹冷情 老婆 你敢改嫁</a><a href="/kan/12193124.html">师姐变师兄的重生女配</a><a
href="/kan/12193226.html">超能者txt</a><a href="/kan/12193054.html">临渊问道笔趣阁无弹窗</a><a
href="/kan/12193059.html">转生魔法漫画</a><a href="/kan/12193193.html">潇潇是啥意思</a><a
href="/kan/12193200.html">本座不会生孩子免费读</a><a href="/kan/12193099.html">鸭王怎么样</a><a
href="/kan/12193062.html">总有光明面中字版</a><a href="/kan/12193019.html">三界婚介所by鱼之水全文</a><a
href="/kan/12193252.html">纯情少女是什么意思?</a><a href="/kan/12193183.html">双生龙影漫画结局</a><a
href="/kan/12193264.html">当你身无分文负债累累的时候怎么翻身</a><a href="/kan/12193132.html">兵王重生归来</a><a
href="/kan/12193211.html">女命三个太极贵人必为娼</a><a href="/kan/12193112.html">请君入婚(全集)</a><a
href="/kan/12193196.html">潇潇清枫百科</a><a href="/kan/12193077.html">自梳女怎么解决生理需要</a><a
href="/kan/12193076.html">罗浮梦断怅冰姿不似旧时风韵</a><a href="/kan/12193130.html">回归兵王俏总裁</a><a
href="/kan/12193310.html">大唐平阳昭公主有多强</a><a href="/kan/12193021.html">顾总太太又去男科给你挂号了 孟娜</a><a
href="/kan/12193180.html">最强BOSS系统境界</a><a href="/kan/12193230.html">凌皓铭</a><a
href="/kan/12193022.html">一个护士的故事原唱</a><a href="/kan/12193269.html">轻舔丝绒 电子书 百度</a><a
href="/kan/12193061.html">粮票什么时候废除的</a><a href="/kan/12193178.html">先婚后爱顾少</a><a
href="/kan/12193037.html">天道葫芦全文TXT免费</a><a href="/kan/12193098.html">鸭王主人公叫什么</a><a
href="/kan/12193272.html">将门嫡妃全集免费观看</a><a href="/kan/12193050.html">摘下你的面具全部歌曲</a><a
href="/kan/12193046.html">一弯腰肩胛骨就疼</a><a href="/kan/12193292.html">原神魔龙</a><a
href="/kan/12193102.html">魅魔以什么为食物</a><a href="/kan/12193232.html">游侠官方网站</a><a
href="/kan/12193133.html">兵王回归林重</a><a href="/kan/12193301.html">御兽主人</a><a
href="/kan/12193223.html">青春韶华拼音怎么写</a><a href="/kan/12193089.html">恋爱强制协奏曲免费</a><a
href="/kan/12193068.html">苏慕白风清歌</a><a href="/kan/12193092.html">花间神魔全文</a><a
href="/kan/12193078.html">自梳女是哪里的</a><a href="/kan/12193308.html">时光分期好不好</a><a
href="/kan/12193152.html">碟杀电动车怎样调刹车</a><a href="/kan/12193119.html">一宠到底爱上男闺蜜结局</a><a
href="/kan/12193091.html">逍遥花主最新章节</a><a href="/kan/12193154.html">碟杀漫画全集免费</a><a
href="/kan/12193029.html">8号当铺电视剧</a><a href="/kan/12193057.html">顾少宠妻上瘾顾霆</a><a
href="/kan/12193118.html">审神者幼崽养成计划百度</a><a href="/kan/12193309.html">时光分期条件</a><a
href="/kan/12193031.html">逆天成仙短剧在线观看</a><a href="/kan/12193134.html">重生 白蛇</a><a
href="/kan/12193216.html">开局解散系统</a><a href="/kan/12193171.html">少年丞相本红装</a><a
href="/kan/12193071.html">玫瑰之下的眼泪漫画</a><a href="/kan/12193247.html">农门丑妻番外</a><a
href="/kan/12193290.html">主角苟发育</a><a href="/kan/12193100.html">魅魔游戏角色</a><a
href="/kan/12193302.html">2020时光分期</a><a href="/kan/12193204.html">痒婚全文阅读</a><a
href="/kan/12193162.html">兰亭序杀局阅读</a><a href="/kan/12193128.html">重生回归的兵王阅读</a><a
href="/kan/12193040.html">娇小玲珑的芈瑶是什么人</a><a href="/kan/12193125.html">重生师姐要逆天风不欺</a><a
href="/kan/12193240.html">星际小白二维码</a><a href="/kan/12193135.html">重生白蛇修仙</a><a
href="/kan/12193288.html">王爷好坏啊</a><a href="/kan/12193023.html">冲绳岛战役惨烈程度</a><a
href="/kan/12193110.html">请君入宴</a><a href="/kan/12193149.html">揭秘空姐</a><a
href="/kan/12193186.html">规则怪谈之化身为人</a><a href="/kan/12193053.html">诸天从将夜开始</a><a
href="/kan/12193192.html">潇潇潇亦萧日</a><a href="/kan/12193179.html">你慢点宠</a><a
href="/kan/12193033.html">关于人生辉煌的成语</a><a href="/kan/12193038.html">冥夫缠上身一路欢歌</a><a
href="/kan/12193151.html">锦瑟华年电视剧全集免费</a><a href="/kan/12193201.html">小伙子合租女室友成为女朋友</a><a
href="/kan/12193190.html">潇潇全文</a><a href="/kan/12193275.html">狼性殿下请轻点</a><a
href="/kan/12193257.html">穿越斗罗大陆之我是帝天之子</a><a href="/kan/12193141.html">人间如狱调停</a><a
href="/kan/12193013.html">杯酒人生日本版免费观看</a><a href="/kan/12193244.html">[all空</a><a
href="/kan/12193249.html">农门丑妻逆袭攻略全文免费</a><a href="/kan/12193293.html">原初魔将</a><a
href="/kan/12193026.html">七号典当铺</a><a href="/kan/12193117.html">炼神是什么</a><a
href="/kan/12193111.html">请君入阁</a><a href="/kan/12193283.html">潜轨者番外之裸戏第188章娇踹连连大结局</a><a
href="/kan/12193165.html">原谅我是什么意思</a><a href="/kan/12193217.html">开局系统就离开</a><a
href="/kan/12193177.html">梦游京华录</a><a href="/sitemap_1.xml" target="_blank">网站地图</a></ul>
</div>
</div>
<div class="footer" id="footer">
<div class="pc-footer">
<p>本站所有内容来源于互联网公开且无需登录即可获取的网页内容本站爬虫遵循robots协议若您的网站不希望被本站爬虫抓取可通过屏蔽novelspider字段实现。<br>本站仅对抓取到的内容由程序自动进行排版处理再展现,不涉及更改内容,不针对任何内容表述任何立场。<br>同时您可手动提交相关目标站点网址给我们(站点内容必须允许游客访问,本站爬虫不会抓取需要登录后才展现内容的站点),爬虫程序会依据负载状态自动爬取相关页面。<br>如内容有违规,请通过本站反馈功能提交给我们进行删除处理。
</p>
</div>
</div>
<div id='shuqian'></div>
<script src="/xs/biquge_pc/js/jquery.min.js"></script>
<script src="/xs/biquge_pc/js/common.js"></script>
</body>
</html>

View File

@@ -1,73 +0,0 @@
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Promise;
require 'vendor/autoload.php';
$client = new Client([
'base_uri' => '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;
}

View File

@@ -1,85 +0,0 @@
<?php
require 'vendor/autoload.php'; // 引入 Composer 自动加载
use MongoDB\Client;
use MongoDB\Exception\Exception as MongoDBException;
class MongoSequenceGenerator {
private $client;
private $db;
// 构造函数,初始化 MongoDB 连接,带 IP、用户名和密码
public function __construct($uri, $dbName) {
try {
$this->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";
}
?>

View File

@@ -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);

View File

@@ -0,0 +1,118 @@
<?php
declare(strict_types=1);
namespace app\admin\controller;
use app\admin\BaseController;
use app\model\AdminUserModel;
use app\model\DomainModel;
use app\Request;
use PhpOffice\PhpSpreadsheet\IOFactory;
use think\Response;
class Domain extends BaseController
{
/**
* upload site domain info file
*
* @param Request $Request
* @param AdminUserModel|null $AdminUserModel
* @return Response
*/
public function uploadDomain(Request $Request, ?AdminUserModel $AdminUserModel)
{
$File = $Request->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);
}
}

View File

@@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace app\admin\controller;
use app\admin\BaseController;
use app\model\AdminUserModel;
use app\model\CategoryModel;
use app\model\FenLeiModel;
use app\model\GuangGaoFenZuModel;
use app\model\GuangGaoModel;
use app\model\GuangGaoPeiZhiModel;
use app\model\NovelModel;
use app\model\NvYouModel;
use app\model\ShiPinModel;
use app\Request;
use think\Response;
class Novel extends BaseController
{
/**
* 根据关键词修改小说评级
*
* @param Request $Request
* @param AdminUserModel|null $AdminUserModel
* @return Response
*/
public function updateNovelLevel(Request $Request, ?AdminUserModel $AdminUserModel)
{
$arrData = [
"keywords" => $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);
}
}

View File

@@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
namespace app\api;
use ddl\DDLManage;
use think\App;
use think\exception\ValidateException;
use think\facade\View;
use think\Validate;
/**
* 控制器基础类
*/
abstract class BaseController
{
/**
* Request实例
* @var \think\Request
*/
protected $request;
/**
* 应用实例
* @var \think\App
*/
protected $app;
/**
* 是否批量验证
* @var bool
*/
protected $batchValidate = false;
/**
* 控制器中间件
* @var array
*/
protected $middleware = [];
/**
* 构造方法
* @access public
* @param App $app 应用对象
*/
public function __construct(App $app)
{
$this->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);
}
}

2
code/app/api/common.php Normal file
View File

@@ -0,0 +1,2 @@
<?php
// 这是系统自动生成的公共文件

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
use app\api\controller\Novel;
use app\api\controller\Statis;
use think\facade\Route;
Route::post("/publish", [Statis::class, 'publish'])
// ->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');

View File

@@ -0,0 +1,12 @@
<?php
use think\middleware\Throttle;
use think\Response;
return [
'prefix' => '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);
},
];

View File

@@ -0,0 +1,14 @@
<?php
declare (strict_types = 1);
namespace app\api\controller;
use app\api\BaseController;
class Novel extends BaseController
{
public function search()
{
return '您好!这是一个[api]示例应用';
}
}

View File

@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace app\api\controller;
use app\api\BaseController;
use app\model\CategoryModel;
use app\model\NovelClicksModel;
use app\model\NovelModel;
use DateTime;
use db\mongo\MongoBase;
use ddl\DDLManage;
use Exception;
use think\Request;
use think\Response;
class Statis extends BaseController
{
/**
* novel statis
*
* @param Request $Request
* @return Response
*/
public function publish(Request $Request): Response
{
$intNId = $Request->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();
}
}

5
code/app/api/event.php Normal file
View File

@@ -0,0 +1,5 @@
<?php
// 这是系统自动生成的event定义文件
return [
];

View File

@@ -0,0 +1,4 @@
<?php
// 这是系统自动生成的middleware定义文件
return [
];

View File

@@ -762,4 +762,49 @@ namespace {
return false;
}
/**
* 从 URL、URI 或路径中提取文件名(不带后缀)
* @param string $path 输入的路径,比如 "/shu/1/4313672.html" 或 "https://example.com/files/test.pdf"
* @return string|null 返回文件名部分,如果提取失败返回 null
*/
function supperExtractFilename($path)
{
if (empty($path) || !is_string($path)) {
return null;
}
$slashPos = strrpos($path, '/');
if ($slashPos === false) {
$slashPos = -1;
}
$dotPos = strrpos($path, '.');
if ($dotPos === false || $dotPos <= $slashPos) {
return substr($path, $slashPos + 1);
}
$start = $slashPos + 1;
$length = $dotPos - $start;
return substr($path, $start, $length);
}
/**
* Undocumented function
*
* @param string $strZh
* @return string
*/
function zhToPinYin(string $strZh): string
{
$strPinYinToolsPath = root_path() . '/extend/tools/pinyin-tool';
$strPinYin = shell_exec(sprintf("%s %s", $strPinYinToolsPath, escapeshellcmd($strZh)));
$strPinYin = trim((string) $strPinYin);
return $strPinYin;
}
}

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
namespace app\home;
@@ -54,8 +55,7 @@ abstract class BaseController
}
// 初始化
protected function initialize()
{}
protected function initialize() {}
/**
* 验证数据
@@ -94,4 +94,10 @@ abstract class BaseController
return $v->failException(true)->check($data);
}
public function rending($strTemplate = '')
{
View::assign("DomainModel", $this->request->DomainModel);
View::assign("TemplatesModel", $this->request->TemplatesModel);
return View::fetch($strTemplate);
}
}

View File

@@ -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;
@@ -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 '<pre>';
// print_r($arrRecommendSerializationNovel);
// exit;
$arrRecommendSerializationNovel = [
['id' => 1, 'name' => '小说1', 'author' => '作者1'],
['id' => 2, 'name' => '小说2', 'author' => '作者2'],
@@ -137,23 +145,32 @@ class Index extends BaseController
];
// 模拟数据-推荐等级为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 '<pre>';
// 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 '<pre>';
// 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 '<pre>';
// 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 '<pre>';
// 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 '<pre>';
// print_r($arrBoyTotalRankNovel);
// exit;
$arrBoyTotalRankNovel = [
['id' => 1, 'name' => '小说1', 'author' => '作者1'],
['id' => 2, 'name' => '小说2', 'author' => '作者2'],
@@ -205,6 +234,10 @@ class Index extends BaseController
];
// 模拟数据-女生频道,最新的 随机5个小说
$arrGirlNewsNovel = $NovelModel->getSexNovelOnCache($Request->DomainModel->d_domain, 5, 2);
// echo '<pre>';
// print_r($arrGirlNewsNovel);
// exit;
$arrGirlNewsNovel = [
['id' => 1, 'name' => '小说1', 'author' => '作者1'],
['id' => 2, 'name' => '小说2', 'author' => '作者2'],
@@ -214,6 +247,10 @@ class Index extends BaseController
];
// 模拟数据-女生频道,周排行榜 随机10个小说
$arrGirlWeekRankNovel = $NovelModel->getRankNovelOnCache($Request->DomainModel->d_domain, 5, 2, 'weekly');
// echo '<pre>';
// 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 '<pre>';
// 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 '<pre>';
// 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 '<pre>';
// 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;
}
}

View File

@@ -52,7 +52,17 @@
</div>
<ul class="f2_list" mod="hot_tj">
{foreach $arrRecommendEndNovel as $key=>$Novel}
{include file="Pc/Public/endNovelList" /}
<li>
<a href="/pc/novel/334.html" title="{$Novel->og_novel_book_name}" target="_blank" class="book_cov">
<img class="lazyload_book_cover lazyload-img"
src="/static/img/default.jpg" data-src="{$Novel->n_cover_path}"
alt="{$Novel->og_novel_book_name}">
</a>
<div class="book_inf">
<h3><a href="/pc/novel/334.html" title="{$Novel->og_novel_book_name}" target="_blank">{$Novel->og_novel_book_name}</a></h3>
<span class="aut">{$Novel->og_novel_author}</span>
</div>
</li>
{/foreach}
</ul>

View File

@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace app\model;
use DateTime;
use think\facade\Cache;
use think\Model;
/**
* @mixin think\Model
*/
class CategoryModel extends MongoModel
{
protected $strCollection = 'category';
static $arrCategory = [
1 => '玄幻修真',
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;
}
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace app\model;
use DateTime;
use think\facade\Cache;
use think\Model;
/**
* @mixin think\Model
*/
class ChapterModel extends MongoModel
{
protected $strCollection = 'chapter';
}

View File

@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace app\model;
use db\mongo\MongoBase;
use ddl\DDLManage;
use MongoDB\Collection;
use think\facade\Cache;
use think\Model;
/**
* @mixin think\Model
*/
abstract class MongoModel
{
/**
* collection name
*
* @var string
*/
protected $strCollection;
/**
* Undocumented variable
*
* @var MongoBase
*/
private $MongoBase;
/**
* Undocumented function
*/
public function __construct()
{
DDLManage::load('MongoBase');
$this->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;
}
}

View File

@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace app\model;
use DateTime;
use think\facade\Cache;
use think\Model;
/**
* @mixin think\Model
*/
class NovelClicksModel extends MongoModel
{
protected $strCollection = 'novel_clicks';
/**
* Undocumented function
*
* @param integer $intNId
* @return void
*/
public function addStatis(int $intNId)
{
$NovelModel = new NovelModel;
$Novel = $NovelModel->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;
}
}

View File

@@ -0,0 +1,329 @@
<?php
declare(strict_types=1);
namespace app\model;
use DateTime;
use Exception;
use storage\StorageCore;
use think\facade\Cache;
use think\Model;
/**
* @mixin think\Model
*/
class NovelModel extends MongoModel
{
/**
* Undocumented variable
*
* @var string
*/
protected $strCollection = 'novel';
/**
* 根据关键词数组更新小说推荐等级
* @param array $arrKeywords 关键词数组,例如 ["斗罗", "遮天", "凡人"]
* @param int $intLevel 推荐等级1~9例如 3
* @return array 更新结果
*/
public function updateByKeywords(array $arrKeywords, int $intLevel)
{
if (empty($arrKeywords)) {
return false;
}
try {
$arrConditions = array_map(function ($strKeyword) {
return ['og_title' => ['$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 [];
}
}
}

View File

@@ -1,48 +0,0 @@
<?php
declare(strict_types=1);
namespace app\task\collection;
use app\model\CaiJiPeiZhiModel;
use app\model\SystemConfigModel;
use minserver\QueueManage;
use GuzzleHttp\Client;
/**
* @mixin think\Model
*/
class PullDataColBase
{
/**
* Undocumented variable
*
* @var \GuzzleHttp\Client
*/
protected $Client = NULL;
/**
* Undocumented variable
*
* @var \minserver\QueueManage
*/
protected $QueueManage = NULL;
protected $arrCaiJiPeiZhi = [];
public function __construct()
{
$this->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'];
}
}

View File

@@ -1,611 +0,0 @@
<?php
declare(strict_types=1);
namespace app\task\collection\zw630;
use app\model\XiaoShuoXiangQingModel;
use app\task\collection\PullDataColBase;
use db\mongo\MongoBase;
use GuzzleHttp\Cookie\CookieJar;
use processor\ImageProcessor;
use think\facade\Cache;
use QL\QueryList;
use minserver\QueueManage;
use GuzzleHttp\Client;
/**
* @mixin think\Model
*/
class Zw630Col extends PullDataColBase
{
private $strDomain = 'https://www.630zw.org';
/**
* Undocumented variable
*
* @var MongoBase
*/
private $MongoBase;
public function __construct()
{
$this->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);
// // 提取所有 <meta> 标签的属性
// $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');
// // 去掉 <p> 标签中的 class="content_detail" 属性
// $strNeiRong = preg_replace('/<p\s+class="content_detail"/i', '<p', $strNeiRong);
// // 清理多余的 HTML 标签
// $strNeiRong = strip_tags($strNeiRong, '<p>');
// // 替换不间断空格 (\xc2\xa0) 和 &nbsp; 为普通空格
// $strNeiRong = preg_replace('/\xc2\xa0/', ' ', $strNeiRong); // 替换 UTF-8 不间断空格
// $strNeiRong = preg_replace('/&nbsp;/', ' ', $strNeiRong); // 替换 &nbsp;
// // 清理多余空格和换行符
// $strNeiRong = preg_replace('/\s+/', ' ', $strNeiRong);
// $strNeiRong = preg_replace('/<p>\s+/', '<p>', $strNeiRong); // 清理 <p> 标签内部多余空格
// $strNeiRong = preg_replace('/\s+<\/p>/', '</p>', $strNeiRong); // 清理 <p> 标签结尾多余空格
// // 输出结果
// // 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;
// }
// }
}

View File

@@ -0,0 +1,184 @@
<?php
declare(strict_types=1);
namespace app\task\crawler\zw630;
use app\task\crawler\zw630\page\NovelPage;
use app\task\crawler\zw630\page\Site;
use microserver\QueueManage;
/**
* @mixin think\Model
*/
class Scheduler
{
/**
* Undocumented variable
*
* @var \microserver\QueueManage
*/
protected $QueueManage = NULL;
public function __construct()
{
$this->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);
}
}
}

View File

@@ -0,0 +1,258 @@
<?php
declare(strict_types=1);
namespace app\task\crawler\zw630\page;
use db\mongo\MongoBase;
use ddl\DDLManage;
use GuzzleHttp\Client;
use microserver\QueueManage;
use QL\QueryList;
use Psr\Http\Message\ResponseInterface;
/**
* @mixin think\Model
*/
abstract class BasePage
{
/**
* Undocumented variable
*
* @var string
*/
// protected $strDomain;
/**
* Undocumented variable
*
* @var string
*/
protected $strUri;
/**
* Undocumented variable
*
* @var MongoBase
*/
protected $MongoBase;
/**
* Undocumented variable
*
* @var \GuzzleHttp\Client
*/
protected $Client = NULL;
/**
* Undocumented variable
*
* @var \microserver\QueueManage
*/
protected $QueueManage = NULL;
/**
* Undocumented variable
*
* @var array
*/
protected $arrCaiJiPeiZhi = [];
/**
* Undocumented variable
*
* @var string
*/
protected $strContent;
/**
* Undocumented variable
*
* @var QueryList
*/
protected $QueryList;
/**
* Undocumented variable
*
* @var array
*/
protected $arrData = [];
/**
* Undocumented variable
*
* @var bool
*/
protected $boolIsMobile;
/**
* Undocumented variable
*
* @var ResponseInterface
*/
private $Response;
/**
* Undocumented function
*
* @param string $strUri
*/
public function __construct(Site $Site, string $strUri)
{
$this->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;
}
}

View File

@@ -0,0 +1,139 @@
<?php
declare(strict_types=1);
namespace app\task\crawler\zw630\page;
use storage\StorageCore;
class ChapterPage extends BasePage
{
/**
* Undocumented variable
*
* @var string
*/
protected $strChapterContent = '';
/**
* Undocumented function
*
* @return array
*/
public function getChapterInfo(): array
{
if ($this->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;
}
}

View File

@@ -0,0 +1,304 @@
<?php
declare(strict_types=1);
namespace app\task\crawler\zw630\page;
use ddl\DDLManage;
use QL\QueryList;
class NovelPage extends BasePage
{
/**
* Undocumented variable
*
* @var array
*/
protected $arrNovelInfo = [];
/**
* Undocumented variable
*
* @var array
*/
protected $arrRelatedNovel = [];
/**
* Undocumented variable
*
* @var array
*/
protected $arrForgeNovel = [];
private function findOrCreateNovel(int $intNSourceId, string $strName): array
{
$strUUId = 'zw630-' . $intNSourceId;
$Col = $this->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;
}
}

View File

@@ -0,0 +1,257 @@
<?php
declare(strict_types=1);
namespace app\task\crawler\zw630\page;
use GuzzleHttp\Client;
use GuzzleHttp\Promise;
use storage\StorageCore;
/**
* @mixin think\Model
*/
class Site
{
/**
* Undocumented variable
*
* @var Client
*/
private $Client;
/**
* Undocumented variable
*
* @var string
*/
protected $strDomain = 'https://www.630zw.org';
/**
* Undocumented function
*/
public function __construct()
{
// $this->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());;
}
}
}

View File

@@ -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
{

View File

@@ -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' => []
];

View File

@@ -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",

View File

@@ -26,6 +26,7 @@ return [
'domain_bind' => [
'*' => 'home',
'adapi' => 'admin',
'api' => 'api',
],
// 禁止URL访问的应用列表自动多应用模式有效

View File

@@ -2,10 +2,14 @@
// +----------------------------------------------------------------------
// | 控制台配置
// +----------------------------------------------------------------------
use ddl\DDLManage;
DDLManage::load('MicroServer');
return [
// 指令定义
'commands' => [
'task' => 'minserver\ServerConsole',
'task' => 'microserver\ServerConsole',
'db:manage' => 'db\DatabaseManage',
],
];

33
code/config/throttle.php Normal file
View File

@@ -0,0 +1,33 @@
<?php
// +----------------------------------------------------------------------
// | 节流设置
// +----------------------------------------------------------------------
use think\middleware\Throttle;
use think\middleware\throttle\CounterFixed;
use think\Request;
use think\Response;
return [
// 缓存键前缀,防止键值与其他应用冲突
'prefix' => '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);
},
];

View File

@@ -1,6 +1,5 @@
<?php
declare(strict_types=1);
namespace db;

View File

@@ -1,191 +0,0 @@
<?php
namespace db\mongo;
use MongoDB\Client;
use MongoDB\Exception\Exception as MongoDBException;
use MongoDB\BSON\UTCDateTime;
class MongoBase
{
private $client;
/**
* Undocumented variable
*
* @var \MongoDB\Database
*/
private $db;
private static $instance;
public static function getInstance(): self
{
// return [
// 'uri' => '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());
}
}
}

View File

@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace ddl;
class DDLManage
{
private static $arrLoadedModules = [];
private static $arrModuleFiles = [
'MicroServer' => '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);
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -1,27 +0,0 @@
<?php
declare (strict_types = 1);
namespace minserver;
use think\Container;
use think\facade\Db;
class ProcessSanitizer
{
static public function reloadRedis()
{
Container::getInstance()->delete('cache');
}
static public function reloadDb()
{
Db::close();
Container::getInstance()->delete('think\DbManager');
}
static public function destructConnectSource()
{
self::reloadDb();
self::reloadRedis();
}
}

View File

@@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace minserver;
use think\facade\Cache;
class QueueManage
{
public $strQueueName = '';
public function __construct($intIndex = 1, $strQueueName = '')
{
if ($strQueueName == '') {
$arrConfig = config('task.queue');
$strQueueName = $arrConfig[$intIndex]['name'];
}
$this->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);
}
}

View File

@@ -1,17 +0,0 @@
<?php
declare(strict_types=1);
namespace minserver;
class Rout
{
static public function httpDispense(\Swoole\Http\Request $Request, \Swoole\Http\Response $Response)
{
$Response->status(999, 'Hei Guys ~');
$Response->header("Content-Type", "text/html; charset=utf-8");
$Response->end("<h1>Hello reptile~. #" . rand(1000, 9999) . "</h1>");
}
static public function tcpDispense(\Swoole\Server $server, $fd, $reactor_id, $mixedData) {}
}

View File

@@ -1,19 +0,0 @@
<?php
declare(strict_types=1);
namespace minserver;
use minserver\ProcessSanitizer;
class ScheduledTasks
{
static public function hander($arrProcessTask = [])
{
ProcessSanitizer::destructConnectSource();
call_user_func($arrProcessTask['callback'], $arrProcessTask['param']);
sleep($arrProcessTask['execution_interval']);
}
}

View File

@@ -1,89 +0,0 @@
<?php
declare(strict_types=1);
namespace minserver;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\Output;
use think\console\input\Option;
class ServerConsole extends Command
{
private $arrArguments = [
'action' => ['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 <action> [-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! ");
}
}

View File

@@ -1,87 +0,0 @@
<?php
declare(strict_types=1);
namespace minserver;
class ServerCore
{
private $arrConfig;
private static $obj;
private $Server;
static $Process = NULL;
static function instance($arrConfig)
{
if (self::$obj == null) {
self::$obj = new self($arrConfig);
}
return self::$obj;
}
public function __construct($arrConfig)
{
$this->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)
{
}
}

View File

@@ -1,129 +0,0 @@
<?php
declare(strict_types=1);
namespace minserver;
class ServerManage
{
static $HttpService;
static $arrDynamicConfig = [];
public function __init($arrArgvs = [])
{
$this->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);
}
}

View File

@@ -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 = [])
{

Binary file not shown.

View File

@@ -1,21 +0,0 @@
<?php
use QL\QueryList;
require 'vendor/autoload.php'; // 引入 Composer 自动加载
$strContent = file_get_contents('123.html');
// 初始化 QueryList
$ql = QueryList::html($strContent);
// 提取所有 meta 标签,并将 property 的冒号替换为下划线
$result = [];
$ql->find('meta[property]')->each(function ($item) use (&$result) {
$property = str_replace(':', '_', $item->attr('property')); // 冒号替换为下划线
$result[$property] = $item->attr('content');
});
// 输出结果
print_r($result);