阅读视图

蜀道难 — 有偿招募 Google Play 测试用户

懒惰,有时候带来的负面效果,后期想要修复的时候,要付出的代驾比当时处理要复杂的多。更恐怖的是,哪怕付出了这么多的代码,依然无法达到最开始的效果。

当 google playstore 开发者给我发邮件提示账号快过期的时候,并没有太留意,后来就给忘了。当然,gmail 的邮箱已经收到了数次提醒,但是由于那段时间不怎么翻墙,导致并没有看到这些邮件,等看到邮件的时候账号已经被用了。

看到这个停用的原因,真的是让人崩溃,这 tmd,当时但凡翻墙了,也就是点几下鼠标的事情。现在好了,重新注册账号依然面临一系列的问题。appid 被占用,旧应用无法下架,无法转让。现在连上线发布都需要面临另外一个问题,那就是需要开启封闭测试,只有封闭测试通过之后才能有发布正式版的权限。

google play开发者给出的答案是:

针对新创建的个人账号的测试要求简介
测试是应用开发流程中不可或缺的一环。通过持续对应用运行测试,您可以在公开发布应用之前验证其正确性、功能行为和易用性。这能让您及时解决发现的技术问题或用户体验问题,最大限度地降低这些问题对用户的影响,从而确保您在 Google Play 中发布的是应用的最佳版本。如果开发者在发布应用之前经常使用 Play 管理中心的测试工具进行测试,他们的应用将能够带给用户更优质的使用体验,从而在 Google Play 上赢得更高的评分,取得更大的成就。

为了帮助所有开发者确保提供高品质的应用,我们提出了新的测试要求。如果开发者使用的是 2023 年 11 月 13 日之后创建的个人账号,则其应用需要先经过测试,然后才能在 Google Play 上发布和分发。若应用未经过测试,系统会停用 Play 管理中心内的部分功能,例如正式版(测试和发布 > 正式版)和预注册(测试和发布 > 测试 > 预注册),直到开发者满足相关要求为止。

测试要求概览
如果您使用的是新创建的个人开发者账号,则必须对您的应用运行封闭式测试,且至少有 12 名测试人员在过去至少 14 天内选择持续参与测试。满足上述条件后,您便可以在 Play 管理中心的信息中心申请正式版发布权限,以便最终在 Google Play 上分发您的应用。申请时,您需要回答一些问题,帮助我们了解您的应用、测试流程及正式版发布准备情况。

下文详细介绍了不同类型的测试轨道和相关要求,以及关于申请正式版发布权限的更多详情。

了解不同的测试轨道和相关要求
Play 管理中心提供不同类型的测试轨道,以便您逐步扩大测试范围并改进应用,力求达到适合面向数十亿 Google Play 用户发布的水平。

内部测试:在完成应用设置之前,您可以自行将 build 快速分发给少量可信测试员。这有助于您排查问题并收集早期反馈。通常,build 被添加到 Play 管理中心几秒钟后,就会向测试人员开放。虽然内部测试并非强制性要求,但我们建议您从这里开始。
封闭式测试:利用封闭式测试,您可以与由您控管的众多用户分享应用。这样一来,您可以在发布前修复问题,并确保应用符合 Google Play 政策的要求。您必须先运行封闭式测试,然后才能申请发布正式版应用。当您申请正式版发布权限时,必须至少有 12 名测试人员选择参与您的封闭式测试。他们必须在过去 14 天内选择持续参与。完成应用设置后,您即可启动封闭式测试。
开放式测试:让您可在 Google Play 中发布测试版应用。进行开放式测试时,任何人都可以加入您的测试计划并向您提交非公开反馈。请先确保您的应用和商品详情已经准备好在 Google Play 上架,然后再选择该选项。如果您拥有正式版发布权限,则可以使用开放式测试。
正式版:让您可通过 Google Play 面向数十亿用户发布应用。您需要先运行符合我们条件的封闭式测试,然后才能申请将应用发布为正式版。在申请时,您还需要回答一些与封闭式测试相关的问题。当您申请正式版发布权限时,必须至少有 12 名测试人员选择参与您的封闭式测试。他们必须在过去 14 天内选择持续参与。

如果开发者使用的是 2023 年 11 月 13 日之后创建的个人账号,则其应用需要先经过测试,然后才能在 Google Play 上发布和分发。若应用未经过测试,系统会停用 Play 管理中心内的部分功能

测试要求概览

如果您使用的是新创建的个人开发者账号,则必须对您的应用运行封闭式测试,且至少有 12 名测试人员在过去至少 14 天内选择持续参与测试。

重要提示:向测试人员强调,他们需要选择持续参与至少为期 14 天的封闭式测试。

说实话就是现在google 的做法是越来越看不懂了,关键是很多操作也不知道后续该如何操作。这就很蛋疼了,这种事情当然更好的办法是去咸鱼之类的找个专门做测试的。

测试资格需要加入这个 google 群组(需要加入群组之后才能参与测试):guimiquan@googlegroups.com

https://groups.google.com/g/guimiquan

我也没咋用过这个东西,不清楚怎么去处理这个破玩意儿。有时候感觉老外设计的东西,的确是不怎么符合自己的使用习惯。

ap测试地址:

https://play.google.com/apps/testing/gma.dayi.app

app商店地址:

https://play.google.com/store/apps/details?id=gma.dayi.app

网上依然能找到这种测评服务了,价格大约 200+。

现在的问题在于重新注册开发者账号话费了 30 美元,现在不上架恶心,上架也恶心。既然如此那还是折腾一下吧,不过鉴于自己手上没那么多的设备,想请各位宝子帮帮忙测试一下,想参与的加一下 google 的群组。需要 12 名测试人员,根据提供的测试账号数量,在测试完成后给于相应的报酬,如果有时间,或者闲着没事的希望可以板帮我哦。

目前我自己还在修改一些问题,希望大家除了加下 google group,顺便加下群哦,QQ群:777692924obaby@mars

需要大家开始帮忙测试了会统一通知一下的。

都说蜀道难,现在发版比蜀道还难,蜀道难都是看得到的难,而这发版,完全是不知道的陷阱。

最后说下测试报酬哈,鉴于价格大约都是 200+,12 个账号,按照每个账号 20 元红包来计算(提供几个测试账号,会获得相应账号数量的红包)。如果不想要红包的,可以寄写真照片(3 张),如果喜欢的话。

如果种种原因,到时最后测试没通过,红包依然会发放,不过金额就变成 10 元了哈。毕竟不想让大家白费精力,这个钱属实不多。聊表心意,主要是这个应用也没啥盈利能力,全靠用爱发电,小女子先行写过啦。

  •  

完美主义

小时候跟姐姐学的用扑克牌算命,洗牌就那么一张一张的抽出来排上去,从 1 开始一直排到 13,不同的花色对应着好或者不好,每个数字代表不用的意义,事业啊、婚姻啊、学历啊等等。有时候,为了抽到一排红心,自己会将扑克牌在洗牌之后重新排号,按照一个特定的顺序,抽几张扔几张。最后总是能拿到一排红心。

慢慢长大之后发现,不要说一排红心,哪怕能拿到一颗红心已经实属不易。为了这颗所谓的红心,要付出的实在是太多太多。

反常的气温,忽然又升高到了十七度。给人一种暖春的感觉,周五的时候,对象说给宝子约了周六的牙医。宝子的这个牙,已经成了一种非常严重的问题。由于之前一直吃手,后来用这种手段干预,戴手套,贴嘴等等,虽然不吃了,但是舌头还是不自觉的就往前顶,现在牙齿已经有些变形了,甚至连骨头都开始过度生长。

去妇幼、齐鲁去问诊,给的建议都是等牙齿换完再处理,但是现在似乎依然等不到那个时候了,再不干预,以后要该起来就更难了。原本想着,简单的咨询一下,但是在经过一系列的检查之后,觉得还算靠谱,给的医疗建议也在接受范围之内。决定不再折腾了直接在这里处理,费用六千五。为这些所谓的坏习惯的付出依然不止这些。

有时候懒惰真的会付出代驾,小的时候想下狠心纠正这种坏习惯,但是宝子的姥姥各种觉得残忍,阻挠。最后的结果就是,虽然当时是痛快了。后患却没那么容易消除。虽然现在自己依然不是一个完美主义者,但是,这种过失,现在想起来却也时常后悔。

周末,有时候感觉时间是真的少,各种乱七八糟的事情就占据掉了大半。剩下一点点的时间,来处理下那些乱七八糟微不足道的事情。之前 google play store 的账号,因为长时间没登录被停用了,导致原有的闺蜜圈 app 也被下架。在重新注册开发者账号,想要重新发布应用的时候,提示 appid 被占用了,给 google 发邮件申请转移,给的答案是账号可以解除封禁,可以登录,但是转移却是一直失败的,根本没有转移权限。

既然如此,那暂时也就不再尝试使用原来的 appid 了,毕竟,这个流程一直持续下去,也不知道会到猴年马月能结束,就酱紫吧。完美主义,自己坚持有个 p 用,还得条件允许才能完美。

除了 google play,其实还有一个平台是自己之前也想上的,那就是鸿蒙,uniapp 刚支持打包鸿蒙 应用的时候,自己就尝试过向鸿蒙的迁移。然而,由于项目框架较老,需要做的工作不止一点点,需要先将 vue2 升级到 vue3,然后在将 vue3 版本打包成鸿蒙的 app。

升级这一步就不是很顺利,作为一个初学前端(vue 框架)的菜鸟,最开始项目建立的时候,代码结构设计的并不是非常好,并且硬编码了很多 vue 2 only的一些代码。升级到 vue3 之后,勉强编译通过,运行到了鸿蒙系统上。

再后来,这件事情也确实没什么动力,就不了了之了。然而,就在上周又接到了一个广东深圳的电话,接起来之后说是鸿蒙开发者中心的。问有没有app 升级或者开发计划,说看到在应用商店上架的闺蜜圈 app了,并且说帮忙给建立技术支持服务群,协助将项目从 vue2 到鸿蒙系统 app 的发版。 

在接到这一通电话之后,总觉得不做点什么真的对不起鸿蒙生态的付出。(这个电话打了很多次了,一直没接)之前,自己的那种完美主义追求,想要在国内的各大应用市场上架。然而在多年以后,所有的手机应用市场都关上了针对个人开发者的大门,除了华为。国内的个人开发者,真的连狗都不如。

在自己开发第一款 app 的时候,国内应用商店华为、小米、锤子、魅族还是针对个人开发者开放的,只是现在小米把个人开发者推出了门外,魅族也关闭了那扇大门,锤子死了。只剩下华为还算是对个人开发者开放,更何况现在,人家都找上门了,自己又有什么理由不做出点努力呢?

而至于完美主义,现在依然不可能了,如果要做,也只能部分完美。让那些自己有能力去完美的地方,能稍微完美一点吧。

为了能升级到 vue3 和支持最新版的鸿蒙开发工具,将 hbuilder 升级到了最新版,切换到原来 vue3 的分支,不得不说,最新版的 hbuilder 在鸿蒙的支持上友好了很多。

配置好一系列工具和插件之后,甚至应不需要在使用鸿蒙开发者工具打包就可以直接运行到模拟器了,虽然提示只支持 arm 架构的模拟器,但是运行是完全没有问题的。

当然,现在升级最大的优势在于,通过 cursor 可以帮忙解决大部分的 vue代码升级问题。

的确减少了自己的大部分工作量,只需要关注那些 ai 解决不了的问题就 ok 了。两天陆陆续续的修复,最终还是在鸿蒙系统上运行起来了,也修复了大部分的错误,当然,这个升级之后的功能,还需要进一步的细致测试。

实际运行效果:

有的事情,开始固然是艰难了一些,甚至,很长时间都看不到方向,然而,做了也就那样,没什么做不了。也没什么做不到。

网上总是说 hbuiler 这不好,那不好,性能太差,不如原生。有哪有啊完美无瑕的工具或者框架,如果通过这个工具或者框架实现了自己的目的,那么这个框架或者工具就是足够优秀的,哪怕不完美。国外的东西不见得就是好的,国内的东西也不见得就是不好。很多程序员为了争论 emacs 和 vi 到底哪个更好,能口诛笔伐。甚至连 vi 党和 vim 党都能同室操戈,我作为一个实用主义党是在不明白这种争论的意义和价值。

当然,其实这些年我说 hbuiler 好不是一次了,我也是目光短浅,没用过 flutter 之类的其他的跨平台语言。仅仅局限于自己的鼠目寸光,与我而言,这解决了我的问题,就足够了。通过自学,两个月的时间,能让我通过这门语言或者工具来做一款产品,这就够了。

只是,现在我站在了自己写的屎山代码上,有太多的东西需要优化,有太多的结构需要调整。

或许,是时候放弃完美了,对于用户来说,你的代码是不是屎并没那么重要,只要给用户呈现的不是💩就完了。

  •  

绽放

晚上下班之后,沿着那条跑过无数次的公路奔跑

路边的树上依然没了绿意

一阵北风吹来

树上的黄叶控制不住的在空中飞舞

身不由己的在空中划出一条不规则的曲线

落在地面上

一阵更猛烈的风吹来

地上的树叶又被裹挟着飞到了空中

耳机里伴随着风声传来的是张韶涵的《阿刁》

想大口呼吸

却不禁让狂风灌满了整个胸腔

虽然已经解开了薄羽绒服的扣子

微微出汗的身体依然想要抛弃这沉重的枷锁

只是啊

或许这被嫌弃的枷锁

是多少孩子的爱而不得

或许

我们生不逢时

或许

我们一路荆棘

但是

孩子啊,我们还是要努力的绽放

  •  

意义

中午吃完饭,打开电脑下载了点乱七八糟的东西,期间传来了 iphone 的两次短信提示音。平时垃圾短信、垃圾电话一大堆所以也没去看到底是什么东西。等到要出门上班的时候,看了下 p70 手机,推送栏有一条消息:“……驾驶 中型以上……汽车…… 超速 ……不超过 20% ……”,都不用仔细看就知道是超速了。

距离上次收到超速短信已经过了数年,最近几年除了路边停车收到的罚单,以及对象在快速路上超速吃了一张罚单。从来没再收到过超速罚单,倒不是因为没超速,只是山东高速的容错率还是蛮高的。

看了下拍的现场图片,应该是在原来的济潍高速被拍的。区间限速路段 22km 长度,限速 120km/h,实际的区间速度为 134km/h。之前跑 G20的时候,基本区间限速都是压着 140 左右跑,但是却没被拍过。只有回老家的时候才会跑济潍高速,跑的时候一般是压着 130 跑,然而这次稍微快了点。这一下,两百块没了,说实话就是这两百干啥不好呢,为什么要超速呢。

带着家人跑高速的时候,对象总是提示说,不要超速,被罚款太不值了。有时候被说,心里也感觉不爽,但是真的压着 120 跑,总是觉得有些憋屈。然而,等真被拍了之后,交了两百块钱,现在也会想着 200 花的有意义么?平均时速降 2km/h 就完全可以省掉这 200 快了。也常在网上看到有人说,限速 120 超速 20% 不要紧,然而,这个超速 20% 以内不要紧,不是所有的高速都适用。如果要超速,还是贴着 10% 之内超吧,毕竟,交的罚款都赶上来回的油费了。

周日带着宝子去上网球课,终于有重新能达到隔网接球了。跟着前一个教练学的内容,扔了一年多的钱,学了个不标准的动作。现在每节课都在纠正之前的错误,也在承担之前这错误的姿势带来的影响。目前已经又消耗掉 20 节课时费了。好在现在纠正的还算可以,最起码就是球能打实了,能听到打球的声音了。总说及时止损,有时候却因为自己的懒惰和松懈,浪费了太多的时间在那些错误的地方,然而这些错误最终的买单人还是自己。

有时候也不得不思考究竟什么是有意义的,学习?工作?玩耍?还是什么其他的东西?

然而,有时候想想这些都没什么意义,或许真正有意义的事情是做自己喜欢的事情,让自己快乐。然而,这种快乐又从何而来呢?是兴趣爱好还是什么其他的东西?亦或者说是一种无所谓的坚持?

坚持有意义吗?答案就是我也不知道有没有意义,只是从某种意义上在坚持一件事情。最近晚上跳绳的时候,有时候会一边看电视一边跳,这么跳的好处是看到入迷的时候,跳绳反而觉得没那么累了,甚至很容易就跳到 4000+。

昨晚跳的时候,看的第一集电视剧已经过半,在第一集结束之后,继续后面一集。既然开始跳了,自然是要坚持下去的。就这样,刚好差不多一个小时多一点的时间,完成了一万个。

虽然最开始的 4000 还能有点感觉,但是到了 6000+以后反而没什么太大的感觉了,除了有点出汗,并没有太吃力。或许这就是坚持的意义吧,只有牛马有个一个好身体,才能赚更多的牛马费不是吗?也只有更多的牛马费,才能给宝子营造更好的生活环境。

总说年轻人不愿意结婚生子,之前忘了在谁那里看到的一个专家的言论说,那年轻人不愿意生孩子,并不是年轻人没有责任心,而是现在的年轻人责任心比上一辈高太多了,知道为自己的孩子负责,知道为他们创造更好的生活条件。自己没有能力的时候,不想生而不养。

而这个好的条件,有需要付出多少的努力?然而,这些所谓的努力又有多少事有意义的?对于多数人来说,从来不会希望你过得比他们好。上周回去奔丧的时候,在殡仪馆,大姐夫跟二姑家的堂哥在那里聊天,自己去厕所的时候路过。堂哥玩笑了一句:“发财了,不认识亲戚了?”

我幽怨的回了一句:“嗐,发个屁才啊,不单工资没涨,还降工资了……”

简单的聊了几句就去洗手间了。努力或许从来都没什么意义,别人看到的都是你现在的表象,至于是不是他们希望的,大概率并不是。而之所以说这句话,大概率是因为这次回去自己没有开大白而是开粉皮回去的。毕竟这个时候在一堆车牌号鲁 V 和鲁 G 的车里,找到一辆鲁 B 的车还是蛮容易的,自己下车的时候他们大概率也是看到了。虽然不是什么豪车,但是也能戳痛某些人吧。不管承认不承认,有时候看到别人开着跑车出现,自己的内心也会触动,那种内心的由衷的羡慕,因为这些东西大概率自己这辈子无法体会了。而这些东西,不是自己努力就能得到的,如果要按这个来平评判,自己的努力就没有任何的意义。

穷,固然会被人看不起,但是你过得比他好,大概率他会记恨你。

于是,多数时候,我总是习惯展示给他们自己想看的那个自己。更像一个虚伪的骗子,哪怕是自己的家人,有时候竟然也会带着一份伪装,只是不知道这份伪装如果真的去掉,会变成什么样子?大概率不是自己想要的样子。

自己不断追求各种所谓的意义,以前却很少追求快乐。在某些时候,甚至为了单纯的快乐竟然有一些负罪的感觉。就想那个拉磨的驴,偶然从磨盘上下来之后,也会怀疑自己存在的意义和价值。内心的不自信,太多的时候需要所谓的外界的认可来证明自己的价值。诚然,这种病态从来没有真正消除过。

跟大多数人一样,人生的轨迹似乎也没什么太多的变化。上学、工作、结婚、生子,在父辈的言语中,听到的最多的就是多生几个孩子。这一辈子什么都不会留下,能留下的只有自己的孩子。然而,留下那么多孩子干嘛呢?如果给不了好的生活,宁可不要孩子。自己摸爬滚打从按个小村子里爬出来,难道要让自己的孩子继续重蹈覆辙吗?

四叔家的姐姐得了脑瘤,在鬼门关走了一遭之后,心态变了很多。自己很喜欢这个小姐姐,小时候经常在一起,长大了之后见的少了,但是感觉依然很亲切。然而也只有过年的时候才有时间去看看她,身体瘦削,这几年气色好了很多。她说:“有时候读书太多也不好,你看你跑那么远都见不到你。”

“这哪里算远,一点都不远”我答道,我握着她的手,能感到那只手冰冰凉凉。我想努力的给她暖一下,就这么紧紧的握着。

也许从来没什么所谓的感同身受,哪怕经历过同样的事情,每个人依然有每个人的感觉。而自己能做的,也仅仅是尽量去体会他人的内心的感觉。

人生,或许从来没什么意义。也许只有自己能给自己的人生赋予意义,快乐活着,痛快生活。

只是,说起来从来都简单,然而,追寻却从来不易。

  •  

如何下载电报 Telegram 视频

多数时候,要下载电报的视频并不需要太复杂的操作,直接点击视频资源的 save to gallery 就可以保存视频了。

然而,并不是所有的群组都能保存视频,有的群组甚至连截屏和分享的权限都没有,自然也就没法下载视频了。

这种群组不单没有开放下载群贤,连复制和转发的权限都没有。所以要想下载这种视频,之前我尝试了几种办法,包括但不限于:

1.通过浏览器插件下载

2.通过 tg 的开发者账号权限,通过接口获取数据

3.tdl

然而,第一种方法,最大的问题在于很多插件都是需要付费使用的。最多能下载四五个视频,超过这个数量就要收费了。另外,今天又试了一下发现插件失效了,应该是本地插件没有更新导致的。

之前用的时候还是可以的。

第二种方法最大的局限在于注册 tg 开发者大概率会直接失败,基于 tg bot 的实现方式,没有开发者账号是无法实现的。而注册失败,就一个弹窗提示 error,就很沙雕。也不告诉你为什么,看网上分析说什么各种风控,就一个破聊天工具,哪里那么多的风控?

原因:
• 当前 IP 地址地理位置受限,或是当前ip被滥用导致被feng控,使用代理可能会被 Telegram 拦截。
• 账户状态异常,例如被标记为垃圾账号、未绑定手机号、账号过新等。
• 多次重复尝试触发风控机制,系统会暂时禁止继续操作。
• 浏览器环境异常,如关闭了 JavaScript、使用了隐私插件或禁用了 Cookie。
解决方案:
• 使用稳定、纯净的住宅代理 IP
• 避免使用数据中心代理、JC节点这类 IP 容易被识别为爬虫来源。
• 建议使用真实住宅宽带出口的代理(如美国、欧洲地区),确保 IP 没有黑历史
• 优先选择长期未被用于 Telegram 操作的 IP,降低触发风控的概率
• 使用指纹浏览器
• 隔离的浏览器环境
• 每个指纹配置都是独立的“浏览器环境”

在尝试了无数次之后,只能放弃这种方法。

直到某一天发现了 tdl,不得不说,这是个好东西啊。

温馨提示: 此处隐藏内容需要发表评论,并且审核通过后才能查看。
(发表评论请勾选 在此浏览器中保存我的显示名称、邮箱地址和网站地址,以便下次评论时使用。
(请仔细检查自己的昵称和评论内容,以免被识别为垃圾评论而导致无法正常审核。)

Features:
Single file start-up
Low resource usage
Take up all your bandwidth
Faster than official clients
Download files from (protected) chats
Forward messages with automatic fallback and message routing
Upload files to Telegram
Export messages/members/subscribers to JSON

不过在实际使用的时候,发现几个问题,就是直接用 channel 的方式下载,下载失败了,所以,尝试导出记录的方式下载:

导出的 js 文件会包含所有的媒体资源:

命令参考上图的写法即可,导出之后,使用 json 文件进行下载即可:

tdl.exe dl -f result1.json  --skip-same

  •  

申请免费通配符证书

之前有个项目,用了个域名带下划线,结果申请证书的时候就悲剧了,嗯,你问为什么不买个通配符的?当然是为了省钱,不想花钱。

刚开始还以为是阿里的问题,后来去腾讯发现也不行,换了几家都不行。最后搜索发现这么个情况:

由于受CAB出台的新规(证书中所包含的域名不能有下划线)影响,从2018年12月7日起,所有新签发的证书的域名中不能包含下划线,在2018年12月7日之前如有签发过下划线的域名,则需要在2019年1月14日进行强制吊销。
如有证书用户受到影响,可以通过下述方法进行解决:
1.请用户将含有下划线的域名进行调整,然后CA对该老证书进行重签发,将新的标准FQDN添加到冲签发的证书中。
2.用Wildcard对原老证书进行替换(但如果老证是EV SSL证书,则Wildcard证书不适用于本解决方案,因为Wildcard是OV类型证书,不支持EV)。
域名命名规范 (RFC标准)
互联网的核心技术规范由IETF(互联网工程任务组)通过一系列名为RFC(意见征求稿)的文档来定义。关于域名如何命名的规则,主要在 RFC 1035 中明确规定。

合法字符集:RFC 1035规定,域名中的“标签”(例如 www、example、com 都是独立的标签)只能使用以下字符:

字母 a-z (不区分大小写)

数字 0-9

连字符 -

关键限制:连字符 - 不能出现在标签的开头或结尾。

根据这个标准,下划线 _ 根本不在允许的字符集之内。所以,从技术规范上讲,server_name.com 本身就是一个无效的域名。

所以,虽然之前能创建这种二级或者三级域名,但是,在申请对应的证书的时候就悲剧了。

所以为了不影响业务,尝试申请免费的通配符证书,还是通过 acmes.sh 搞吧。

1.安装

curl https://get.acme.sh | sh -s email=my@example.com

2.配置环境变量:

export Ali_Key="LTA**************6yn"
export Ali_Secret="q435*************EBSaDba5"

3.申请证书:

acme.sh --issue --dns dns_ali -d example.com -d *.example.com --debug

需要注意的是,通配符证书只能通过配置信息自动校验,不能通过添加解析的方式校验,所以要配置 key 和 secret。如果是不同的解析服务商,设置不同的环境变量即可。

zhongling@MacBookPro .acme.sh % export Ali_Key="LTA**************6yn"
export Ali_Secret="q435*************EBSaDba5"
zhongling@MacBookPro .acme.sh % ./acme.sh --issue -d lang.bi -d '*.lang.bi' -k ec-256 --dns dns_ali --dnssleep 60
[2025年11月21日 星期五 16时53分34秒 CST] Using CA: https://acme.zerossl.com/v2/DV90
[2025年11月21日 星期五 16时53分34秒 CST] Multi domain='DNS:lang.bi,DNS:*.lang.bi'
[2025年11月21日 星期五 16时53分41秒 CST] Getting webroot for domain='lang.bi'
[2025年11月21日 星期五 16时53分41秒 CST] Getting webroot for domain='*.lang.bi'
[2025年11月21日 星期五 16时53分41秒 CST] Adding TXT value: fcTxHx2osERz8mqJFaV2c0yKvo6vUSMA4SH1FR95PMQ for domain: _acme-challenge.lang.bi
[2025年11月21日 星期五 16时53分44秒 CST] The TXT record has been successfully added.
[2025年11月21日 星期五 16时53分44秒 CST] Sleeping for 60 seconds to wait for the the TXT records to take effect
[2025年11月21日 星期五 16时54分46秒 CST] lang.bi is already verified, skipping dns-01.
[2025年11月21日 星期五 16时54分46秒 CST] Verifying: *.lang.bi
[2025年11月21日 星期五 16时54分47秒 CST] Processing. The CA is processing your order, please wait. (1/30)
[2025年11月21日 星期五 16时54分52秒 CST] Success
[2025年11月21日 星期五 16时54分52秒 CST] Removing DNS records.
[2025年11月21日 星期五 16时54分52秒 CST] Removing txt: fcTxHx2osERz8mqJFaV2c0yKvo6vUSMA4SH1FR95PMQ for domain: _acme-challenge.lang.bi
[2025年11月21日 星期五 16时54分54秒 CST] Successfully removed
[2025年11月21日 星期五 16时54分54秒 CST] Verification finished, beginning signing.
[2025年11月21日 星期五 16时54分54秒 CST] Let's finalize the order.
[2025年11月21日 星期五 16时54分54秒 CST] Le_OrderFinalize='https://acme.zerossl.com/v2/DV90/order/7SLmDTCNs_Qw7zls2HFDpA/finalize'
[2025年11月21日 星期五 16时54分56秒 CST] Order status is 'processing', let's sleep and retry.
[2025年11月21日 星期五 16时54分56秒 CST] Sleeping for 15 seconds then retrying
[2025年11月21日 星期五 16时55分12秒 CST] Polling order status: https://acme.zerossl.com/v2/DV90/order/7SLmDTCNs_Qw7zls2HFDpA
[2025年11月21日 星期五 16时55分13秒 CST] Downloading cert.
[2025年11月21日 星期五 16时55分13秒 CST] Le_LinkCert='https://acme.zerossl.com/v2/DV90/cert/bvCTHYFrpbcye-ASpKoS5g'
[2025年11月21日 星期五 16时55分15秒 CST] Cert success.
-----BEGIN CERTIFICATE-----
MIID/zCCA4WgAwIBAgIQS5gLQdZXhrEHdsgVdwPdgzAKBggqhkjOPQQDAzBLMQsw
CQYDVQQGEwJBVDEQMA4GA1UEChMHWmVyb1NTTDEqMCgGA1UEAxMhWmVyb1NTTCBF
Q0MgRG9tYWluIFNlY3VyZSBTaXRlIENBMB4XDTI1MTEyMTAwMDAwMFoXDTI2MDIx
OTIzNTk1OVowFzEVMBMGA1UEAxMMaGFpa2VodWkubmV0MFkwEwYHKoZIzj0CAQYI
KoZIzj0DAQcDQgAEehCGvspbOuBBQjuauz9ghdv9bmvPGJmlz/LttbMjBlBi31Wh
****************************************************************
qaiMNTAnBgNVHREEIDAeggxoYWlrZWh1aS5uZXSCDiouaGFpa2VodWkubmV0MAoG
CCqGSM49BAMDA2gAMGUCMHlmfYvfKEWtJ/CM7UNx6sJPwzu5fU1c5j8v2Oj4REQh
/KE0yJHo3YZkXegvxlSAPAIxAOPw+ZwRsatCaRL8yEGp4mX0umkKx+XbtTlus5NK
aBIOcZiS307CH5mXKOb1jXMPpg==
-----END CERTIFICATE-----
[2025年11月21日 星期五 16时55分15秒 CST] Your cert is in: /Users/zhongling/.acme.sh/lang.bi_ecc/lang.bi.cer
[2025年11月21日 星期五 16时55分15秒 CST] Your cert key is in: /Users/zhongling/.acme.sh/lang.bi_ecc/lang.bi.key
[2025年11月21日 星期五 16时55分15秒 CST] The intermediate CA cert is in: /Users/zhongling/.acme.sh/lang.bi_ecc/ca.cer
[2025年11月21日 星期五 16时55分15秒 CST] And the full-chain cert is in: /Users/zhongling/.acme.sh/lang.bi_ecc/fullchain.cer
zhongling@MacBookPro .acme.sh % start
/Users/zhongling/.acme.sh/
zsh: command not found: start
zsh: permission denied: /Users/zhongling/.acme.sh/
zhongling@MacBookPro .acme.sh % start /Users/zhongling/.acme.sh/
zsh: command not found: start
zhongling@MacBookPro .acme.sh % open /Users/zhongling/.acme.sh/
zhongling@MacBookPro .acme.sh % open /Users/zhongling/.acme.sh/

最终,省去了更换域名的麻烦。先将就用着吧。

 

  •  

送别

周二早上,刚换好衣服准备去上班。手机响了,一个陌生的号码,归属地显示的是潍坊。之前也会接到各种陌生号码的电话,为了解决 iphone 的骚扰电话,开了自动录音功能。但是,这个功能并不稳定,录音有时候显示不完整,有时候对方打了很多次电话,手机却连响都不会响。周末的时候把这个功能关掉了。

电话接起来,对面传来一个熟悉的声音,是四叔家的哥哥:“二大爷(二伯)走了,明天的公事……”

“好的,我知道了,我明天回”简单的答复之后就挂断了电话。

快到中午的时候,又接到电话,通知了下时间,早上八点半。这个时间,如果早上从青岛走,六点之前就得出发。决定下班之后,往回赶。工作日的晚上,路上的车并不多。七点多出发,到家的时候也九点多了,好在提前远程开了空调,到家的时候,温度也到了 20 多度了。

客厅的桌子上还放着国庆走的时候落在上面的零食,袋子开着,并没封口。试了一下,已经潮了。把桌子上的东西都收拾了一下扔到了垃圾桶里。

好在冰箱里还有上次回来的时候放的饮料,就住一晚。没开电视,也没开饮水机,两瓶魔爪下肚之后,感觉也没那么渴了。躺床上,刷会儿手机,眨眼已经十一点多了,想着早上不能起太晚,简单洗刷就睡了。

睁眼看了下时间,七点,爬起来收拾下,准备出门。收拾好一切一定七点半了,刚要出门的时候姐姐打电话问出门了没。

开上车,去古城煎包吃个早餐。到小学门口的时候,依然堵的不动了。果断调头往另外一个方向走,只是,这县城的生活节奏,慢吞吞,一个红绿灯都过不了几辆车。那种闲庭信步的悠闲感,真的适应不了。

要了两个煎包,一碗豆浆。到角落的桌子边坐下来,开始吃,一口下去,竟然齁咸。一向味道不错的煎包,今天的确有失水准。好在还有碗豆浆,草草吃完,继续往殡仪馆赶。

等自己赶到的时候,依然很多然都到了。只是,现在很多人都变了模样,年轻的孩子们,自己甚至都已经认不出谁是谁。自己同辈的姐姐、哥哥们却基本都没怎么变样,很多人也老了很多。

这是自己第三次来这个地方,第一次是送自己的父亲,第二次是送对象的爷爷,这第三次是送自己的二伯。

第一次来的时候,依然太久远了,一眨眼二十年了。很多的事情都忘记了,记忆中仅剩的一点点记忆,就是领骨灰出来的时候,给的骨灰盒太小。烧完的骨灰,竟然没法完全装到骨灰盒里。有那么一块骨头还翘在外面。二伯用脚踩着骨灰盒才把骨灰盒给扣上,那小小的骨灰盒啊,竟然死了都那么不体面。因为父亲生病欠了太多的钱,那时也确实买不起更好的骨灰盒,更好的骨灰盒会更大一些。

现在二伯自己也走了,国庆期间自己又去探望了一次,说稍微好转了一点。尽管如此,但是这么久了没从重症监护室出来,总觉得,也是早晚的事情,这一天总是会来的。

到现在,父亲兄弟四人,现在也仅剩下四叔一人了。二伯在老家没有什么家产,也没房子,最终的选择就是县城的公墓。之前总是远远的看到过公墓,或者在电视上看到。自己走进公墓之后才发现,真的是一排一排,写满了名字。所有的人,最后终将成为一抔黄土,埋没在这黄土之中,两代之后,再也没人记得这个人曾经存在过。此时,这是个就真的从这个世界消失了。

一切结束后,回程的路上,想着去壳牌加油站加个油,预付卡还有两百多,依然不够一次了。又充了500 进去,结果到加油站 发现加油站给围起来了,不知道是在装修还是干嘛。这 500 块钱,不知道猴年马月才能再用上了。

今天早上出门的时候,看到绿化带的月季依然坚强的绽放着,虽然已是寒冬,哪怕不合时宜,只是赶上了这样的时间,也要努力的绽放。至于其他的,由他去吧。

  •  

龙葵

龙葵花语的核心寓意包括“沉不住气”象征冲动与不稳重‌,以及‌“执着与坚定”代表对目标、爱情和信仰的坚持,同时涵盖危险与诱惑、孤傲坚韧等多元象征。‌‌

龙葵这种植物,在自己小时候就见过,并且也吃过无数次。那时候,并不知道她的名字。直到后来玩到一款游戏《仙剑奇侠传 3》,看到里面的任务名字,龙葵,搜索了一下,才知道原来是它。

而游戏中各种人物名字,景天、雪见、长卿、重楼,也剧是药材名称。那时候才发现,自己所谓的常识是何其浅薄而又无知。

阳台上的花,花开花谢,落叶枯萎,来来回回换了无数次的花,阳台上空闲的花盆也越来越多。终于,有一天看淘宝的时候,看到了一个熟悉的身影,那就是龙葵。紫色的浆果,跟小时候看到的一模一样,只是,现在哪怕回到村里竟也不常见了。买了两包种子,回来之后收拾了一下花盆,饶有兴致的种了下去。

后来就是浇浇水,倒也不用特别的照顾。同时还买了一包蛇莓的种子,只是,这个种子自从种下去之后,就再也没见过,两个花盆里看不出有任何发芽的迹象,也找不到了那些种子。

终于某一天,自己去给从发芽的地瓜上拔下来的芽浇水的时候。看到了花盆里漏出头的龙葵,欣欣向荣,或许,她更像野草吧。

前段时间还怕没有蜜蜂授粉,龙葵没法结果。前段时间自己还用棉签尝试给花朵授粉,现在看来,似乎也没必要,哪怕没有蜜蜂授粉,现在也结满了小果子,圆圆的,绿绿的。

小的时候,这龙葵的果实,其实不单可以吃,还可以用来当玩具。夏天麦收的时候,也是龙葵大量结果的时候。这下一小段麦秆,去掉两头的关节,就剩下一条通透的小管子。

用指甲把麦秆的一头剥开成对称的十字,弯曲的时候不要太平,稍微有点幅度,把龙葵圆溜溜的果实去掉果蒂,放到那个十字口里。竖着举起来,仰起头,把麦秆的另外一头放进嘴里,向上吹气,龙葵的果子就会随着气流在麦秆的头上转动跳舞。

而至于为什么能跳舞,这个就该去问伯努利了,毕竟伯努利的流体力学方程能解答这个疑惑。

找到成熟的紫色的果子,就会摘下来吃掉。虽然小小的,但是味道真的很好。

然而,并不是所有的东西都有这种顽强的生命力,昨天的时候发现评论呢亲密度标签全部都挂了。

打开网站发现已经暂停服务了:

不是所有的服务都像自己的博客一样,有这么顽强的生命力,到目前位置已经熬死了无数的服务,好用的,不好用的,免费的,收费的。看着无数的服务倒下去。

好在这个东西是开源的,于是干脆自己又部署了一套。

昨天看到 枋柚梓 发布的 umami 升级的文章,之前的 umami 是通过 mysql 编译安装的。而现在这个新版本已经放弃了 mysql,尽管每天都看到升级提示,是在没动力去升级,每次升级编译是个问题,数据库升级也是个问题。于是放弃了所有的历史数据,干脆 docker 重新装了一个,只是换了 pg 数据库之后,确实麻烦了一些。

这几天的访问量和各种指标也的确离谱,应该还是有沙雕在陆陆续续的攻击。

尽管部署好了,但是还有一些问题没解决。现在却也不想解决了,例如那个网站图标不显示的问题。强迫症的问题,等以后哪天想处理了再处理吧,毕竟,还有一切其他的乱七八糟的事情要做。

有时候想想龙葵的花语也挺矛盾的,沉不住气,与执着。然而,这种矛盾又无比和谐,尽管沉不住气,但是却有着坚定的信仰。

挺好的,希望自己也能一直坚持下去。

  •  

未获取商用授权?! — 这很百度

上午的时候,收到 梦不见的梦 的一条 qq 消息,说出现了授权问题。

看了下提示域名,大概率就是 tm 百度地图弹的,那个域名做了个足迹应用就这么放着。

就在上个月自己更换 ssl 证书的时候还一切正常,结果现在来了这么一出。本来就是个人开发者,纯自用的东西,还经常收到百度的电话让升级企业认证,之前就是不小心升级了,结果一年要五万的授权费用。

我 tm 就自己玩的,还需要花钱,真 tm 服了。看来这戏破玩意儿都完犊子之后,最后能玩的也就只剩下天地图了。

刚开始是以为嵌入的问题,看了下嵌入页面都会提示:

各种提示信息:

【d45a31】未获取商用授权,平台资源与服务稳定性受限;详情信息请前往:https://lbs.baidu.com/faq/search?id=314&title=908

并且后来发现,不单纯是弹窗在地图的贴图上也会出现授权提示,不得不说。这狗皮膏药贴的有水平。

对应的 js:

地址:
https://api.map.baidu.com/?qt=cen&b=7597813.822562976%2C687420.7063757228%3B15519477.822562976%2C7879996.706375723&l=5&ie=utf-8&oue=1&fromproduct=jsapi&ak=BxlnBNX55clLsUHVFZlaukyJesN5F5VI&callback=BMapGL._rd._cbk28033&v=gl&seckey=hNJCxM58roJGqVuMKRuYPEZfDZ%2FdhlL4Pp7JxBsDoLNUSc3QN6CRIbBdAJ%2FOt7zPayXwFYMsbLGx0%2BZUValnOg%3D%3D%2ChNJCxM58roJGqVuMKRuYPEZfDZ_dhlL4Pp7JxBsDoLPlabo3s2HGFnIPFXI8e1esM9-LzywgHJdkZjwHcr89ZaWfdPB6XAgd7DE4lcFgxfu8J0x_GywX0u2he7lW2roGgTrZQMyK7kcSPMHFwMyFrx45ktBewEco-xsnR-zdIctg5lIvP6h-iihbsl6ehY9AbrnGwefDt0BtO6gKCedJ8PeXNIUPh2rilmKFv6PZRd0&timeStamp=1763012808396&sign=07d57e493b29
内容:
/**/BMapGL._rd._cbk28033 && BMapGL._rd._cbk28033({"result":{"b":"7597813.822562976,687420.7063757228;15519477.822562976,7879996.706375723","callback":"BMapGL._rd._cbk28033","catalogID":0,"count":0,"current_null":1,"db":0,"error":503,"error_msg":"未获取商用授权,平台资源与服务稳定性受限;详情信息请前往:https://lbs.baidu.com/faq/search?id=314\u0026title=908","fromproduct":"jsapi","ie":"utf-8","jump_back":0,"l":"5","op_gel":0,"oue":"1","popup":1,"qt":"cen","requery":"","res_l":-1,"res_x":"0.000000","res_y":"0.000000","return_query":"","seckey":"hNJCxM58roJGqVuMKRuYPEZfDZ/dhlL4Pp7JxBsDoLNUSc3QN6CRIbBdAJ/Ot7zPayXwFYMsbLGx0+ZUValnOg==,hNJCxM58roJGqVuMKRuYPEZfDZ_dhlL4Pp7JxBsDoLPlabo3s2HGFnIPFXI8e1esM9-LzywgHJdkZjwHcr89ZaWfdPB6XAgd7DE4lcFgxfu8J0x_GywX0u2he7lW2roGgTrZQMyK7kcSPMHFwMyFrx45ktBewEco-xsnR-zdIctg5lIvP6h-iihbsl6ehY9AbrnGwefDt0BtO6gKCedJ8PeXNIUPh2rilmKFv6PZRd0","sign":"07d57e493b29","spec_dispnum":0,"time":0,"timeStamp":"1763012808396","total":0,"tp":0,"type":11,"v":"gl","wd":"","wd2":"","what":"","where":""},"current_city":{"code":0,"geo":"","level":0,"name":"","sup":0,"sup_bus":0,"sup_business_area":0,"sup_lukuang":0,"sup_subway":0,"type":0,"up_province_name":""},"hot_city":["北京市|131","上海市|289","广州市|257","深圳市|340","成都市|75","天津市|332","南京市|315","杭州市|179","武汉市|218","重庆市|132"]})
地址:
https://api.map.baidu.com/?qt=verify&v=gl&type=webgl&ak=BxlnBNX55clLsUHVFZlaukyJesN5F5VI&time=1763012794639&callback=BMapGL.bmapVerifyCbk
内容:
/**/BMapGL.bmapVerifyCbk && BMapGL.bmapVerifyCbk({"error":503,"error_msg":"未获取商用授权,平台资源与服务稳定性受限;详情信息请前往:https://lbs.baidu.com/faq/search?id=314\u0026title=908","popup":1})

我这个破玩意儿就是纯粹个玩具啊,你何苦这么狠心呢?!

对于这个东西,其实我也没啥好办法,刚开始登录百度 lbs 地图后台提示账号要年审。结果进行账号年审之后依然提示这个错误:

这尼玛就离谱了啊,既然你年审不能解决,那就直接 hook 大法:

<!-- 拦截百度地图弹窗相关请求和Hook方法 -->
    <script>
        (function() {
            // 1. 拦截 fetch 请求
            const originalFetch = window.fetch;
            window.fetch = function(...args) {
                const url = args[0];
                if (typeof url === 'string' && url.includes('api.map.baidu.com')) {
                    // 检查是否是弹窗相关的请求
                    if (url.includes('qt=verify') || url.includes('qt=cen')) {
                        console.log('拦截百度地图弹窗请求:', url);
                        // 返回一个模拟的成功响应,避免弹窗
                        return Promise.resolve(new Response(JSON.stringify({
                            error: 0,
                            error_msg: "",
                            popup: 0,
                            result: {
                                error: 0,
                                popup: 0
                            }
                        }), {
                            status: 200,
                            headers: { 'Content-Type': 'application/json' }
                        }));
                    }
                }
                return originalFetch.apply(this, args);
            };

            // 2. 拦截 XMLHttpRequest
            const originalXHROpen = XMLHttpRequest.prototype.open;
            const originalXHRSend = XMLHttpRequest.prototype.send;
            
            XMLHttpRequest.prototype.open = function(method, url, ...rest) {
                this._url = url;
                if (typeof url === 'string' && url.includes('api.map.baidu.com')) {
                    if (url.includes('qt=verify') || url.includes('qt=cen')) {
                        console.log('拦截百度地图弹窗XHR请求:', url);
                        // 标记为已拦截,在send时处理
                        this._intercepted = true;
                    }
                }
                return originalXHROpen.apply(this, [method, url, ...rest]);
            };

            XMLHttpRequest.prototype.send = function(...args) {
                if (this._intercepted) {
                    // 模拟成功响应
                    Object.defineProperty(this, 'status', { value: 200, writable: false });
                    Object.defineProperty(this, 'statusText', { value: 'OK', writable: false });
                    Object.defineProperty(this, 'responseText', { 
                        value: JSON.stringify({
                            error: 0,
                            error_msg: "",
                            popup: 0,
                            result: {
                                error: 0,
                                popup: 0
                            }
                        }), 
                        writable: false 
                    });
                    Object.defineProperty(this, 'readyState', { value: 4, writable: false });
                    
                    // 触发事件
                    if (this.onreadystatechange) {
                        this.onreadystatechange();
                    }
                    if (this.onload) {
                        this.onload();
                    }
                    return;
                }
                return originalXHRSend.apply(this, args);
            };

            // 3. 拦截 JSONP 回调(百度地图使用JSONP)
            const originalCreateElement = document.createElement;
            const originalAppendChild = Node.prototype.appendChild;
            const originalInsertBefore = Node.prototype.insertBefore;
            
            document.createElement = function(tagName, ...rest) {
                const element = originalCreateElement.apply(this, [tagName, ...rest]);
                
                // 处理div元素,防止创建弹窗
                if (tagName.toLowerCase() === 'div') {
                    const originalSetAttribute = element.setAttribute;
                    const originalAddClass = element.classList?.add;
                    
                    // Hook setAttribute,检查可能用于弹窗的属性
                    element.setAttribute = function(name, value) {
                        if (typeof value === 'string' && (
                            value.includes('商用授权') || 
                            value.includes('business_accredit') ||
                            value.includes('lbs.baidu.com') ||
                            (name === 'class' && (value.includes('dialog') || value.includes('modal') || value.includes('popup')))
                        )) {
                            console.log('拦截百度地图弹窗元素创建:', name, value);
                            // 不设置属性,或者设置为隐藏
                            if (name === 'style') {
                                return originalSetAttribute.call(this, name, 'display:none !important;');
                            }
                            return; // 不设置属性
                        }
                        return originalSetAttribute.apply(this, arguments);
                    };
                    
                    // Hook classList.add
                    if (element.classList && originalAddClass) {
                        const classListAdd = element.classList.add;
                        element.classList.add = function(...tokens) {
                            for (let token of tokens) {
                                if (typeof token === 'string' && (
                                    token.includes('dialog') || 
                                    token.includes('modal') || 
                                    token.includes('popup') ||
                                    token.includes('alert')
                                )) {
                                    console.log('拦截百度地图弹窗class添加:', token);
                                    continue; // 跳过这个class
                                }
                            }
                            return classListAdd.apply(this, tokens);
                        };
                    }
                    
                    // Hook appendChild,如果添加了包含授权相关文本的子元素,则隐藏
                    const originalDivAppendChild = element.appendChild;
                    element.appendChild = function(child) {
                        if (child && child.textContent) {
                            const text = child.textContent;
                            if (text.includes('商用授权') || 
                                text.includes('business_accredit') ||
                                text.includes('lbs.baidu.com') ||
                                text.includes('未完成商用授权')) {
                                console.log('拦截百度地图弹窗内容添加');
                                // 隐藏元素而不是添加
                                if (child.style) {
                                    child.style.display = 'none';
                                }
                            }
                        }
                        return originalDivAppendChild.apply(this, arguments);
                    };
                }
                
                if (tagName.toLowerCase() === 'script') {
                    const originalSetAttribute = element.setAttribute;
                    const originalSetProperty = Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype, 'src')?.set;
                    
                    // Hook setAttribute
                    element.setAttribute = function(name, value) {
                        if (name === 'src' && typeof value === 'string' && value.includes('api.map.baidu.com')) {
                            // 拦截所有包含callback参数的JSONP请求(包括缩放等操作触发的请求)
                            // 只要包含callback参数就拦截,确保所有百度地图API回调都被处理
                            if (value.includes('callback=')) {
                                console.log('拦截百度地图JSONP请求 (setAttribute):', value);
                                value = modifyJsonpCallback(value);
                            }
                        }
                        return originalSetAttribute.apply(this, arguments);
                    };
                    
                    // Hook src 属性设置
                    if (originalSetProperty) {
                        Object.defineProperty(element, 'src', {
                            set: function(value) {
                                if (typeof value === 'string' && value.includes('api.map.baidu.com')) {
                                    // 拦截所有包含callback参数的JSONP请求
                                    if (value.includes('callback=')) {
                                        console.log('拦截百度地图JSONP请求 (src property):', value);
                                        value = modifyJsonpCallback(value);
                                    }
                                }
                                originalSetProperty.call(this, value);
                            },
                            get: function() {
                                return this.getAttribute('src');
                            },
                            configurable: true
                        });
                    }
                    
                    // 标记这个script元素,以便在添加到DOM时检查
                    element._isBaiduMapScript = true;
                }
                
                return element;
            };
            
            // Hook appendChild 和 insertBefore,在添加到DOM前最后检查
            Node.prototype.appendChild = function(child) {
                if (child && child._isBaiduMapScript && child.src) {
                    // 拦截所有包含callback的百度地图请求
                    if (child.src.includes('api.map.baidu.com') && child.src.includes('callback=')) {
                        // 确保已经修改了回调
                        if (!child.src.includes('_baidu_map_popup_blocker_')) {
                            child.src = modifyJsonpCallback(child.src);
                        }
                    }
                }
                return originalAppendChild.apply(this, arguments);
            };
            
            Node.prototype.insertBefore = function(newNode, referenceNode) {
                if (newNode && newNode._isBaiduMapScript && newNode.src) {
                    // 拦截所有包含callback的百度地图请求
                    if (newNode.src.includes('api.map.baidu.com') && newNode.src.includes('callback=')) {
                        // 确保已经修改了回调
                        if (!newNode.src.includes('_baidu_map_popup_blocker_')) {
                            newNode.src = modifyJsonpCallback(newNode.src);
                        }
                    }
                }
                return originalInsertBefore.apply(this, arguments);
            };
            
            // 修改JSONP回调的辅助函数
            function modifyJsonpCallback(url) {
                // 匹配 callback=xxx 或 callback=xxx.xxx.xxx 格式
                return url.replace(
                    /callback=([^&]+)/,
                    (match, callbackName) => {
                        // 创建一个包装函数,修改返回数据
                        const wrapperName = '_baidu_map_popup_blocker_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
                        const callbackPath = callbackName.split('.');
                        
                        // 创建包装回调函数
                        window[wrapperName] = function(data) {
                            // 深度修改所有可能的弹窗相关字段
                            function removePopupFields(obj) {
                                if (!obj || typeof obj !== 'object') return;
                                
                                // 修改当前对象的字段
                                if (obj.popup !== undefined) {
                                    obj.popup = 0;
                                }
                                if (obj.error !== undefined && obj.error === 503) {
                                    obj.error = 0;
                                    obj.error_msg = "";
                                }
                                
                                // 递归处理嵌套对象
                                for (let key in obj) {
                                    if (obj.hasOwnProperty(key) && typeof obj[key] === 'object') {
                                        removePopupFields(obj[key]);
                                    }
                                }
                            }
                            
                            if (data && typeof data === 'object') {
                                removePopupFields(data);
                            }
                            
                            // 调用原始回调
                            let callback = window;
                            for (let i = 0; i < callbackPath.length; i++) {
                                if (callback && callback[callbackPath[i]]) {
                                    callback = callback[callbackPath[i]];
                                } else {
                                    callback = null;
                                    break;
                                }
                            }
                            if (callback && typeof callback === 'function') {
                                try {
                                    callback(data);
                                } catch(e) {
                                    console.error('Error calling original callback:', e);
                                }
                            }
                            
                            // 延迟清理包装函数,确保回调已执行
                            setTimeout(function() {
                                try {
                                    delete window[wrapperName];
                                } catch(e) {}
                            }, 1000);
                        };
                        
                        return 'callback=' + wrapperName;
                    }
                );
            }

            // 4. Hook BMapGL 相关方法(如果已经加载)
            function hookBMapGLMethods() {
                if (window.BMapGL) {
                    // Hook bmapVerifyCbk 回调
                    if (window.BMapGL.bmapVerifyCbk) {
                        const originalVerifyCbk = window.BMapGL.bmapVerifyCbk;
                        // 深度修改弹窗字段的辅助函数
                        function removePopupFieldsDeepVerify(obj) {
                            if (!obj || typeof obj !== 'object') return;
                            if (obj.popup !== undefined) obj.popup = 0;
                            if (obj.error !== undefined && obj.error === 503) {
                                obj.error = 0;
                                obj.error_msg = "";
                            }
                            for (let key in obj) {
                                if (obj.hasOwnProperty(key) && typeof obj[key] === 'object' && obj[key] !== null) {
                                    removePopupFieldsDeepVerify(obj[key]);
                                }
                            }
                        }
                        
                        window.BMapGL.bmapVerifyCbk = function(data) {
                            if (data && typeof data === 'object') {
                                removePopupFieldsDeepVerify(data);
                            }
                            return originalVerifyCbk ? originalVerifyCbk.call(this, data) : undefined;
                        };
                    }
                    
                    // Hook 动态回调(如 _rd._cbk28033)
                    if (window.BMapGL._rd) {
                        const originalRd = window.BMapGL._rd;
                        
                        // Hook _cbk 方法(用于创建动态回调)
                        if (originalRd._cbk) {
                            const originalCbk = originalRd._cbk;
                            // 深度修改弹窗字段的辅助函数(局部定义)
                            function removePopupFieldsDeepLocal(obj) {
                                if (!obj || typeof obj !== 'object') return;
                                if (obj.popup !== undefined) obj.popup = 0;
                                if (obj.error !== undefined && obj.error === 503) {
                                    obj.error = 0;
                                    obj.error_msg = "";
                                }
                                for (let key in obj) {
                                    if (obj.hasOwnProperty(key) && typeof obj[key] === 'object' && obj[key] !== null) {
                                        removePopupFieldsDeepLocal(obj[key]);
                                    }
                                }
                            }
                            
                            originalRd._cbk = function(callbackName) {
                                const originalCallback = originalCbk.call(this, callbackName);
                                if (typeof originalCallback === 'function') {
                                    return function(data) {
                                        if (data && typeof data === 'object') {
                                            removePopupFieldsDeepLocal(data);
                                        }
                                        return originalCallback.call(this, data);
                                    };
                                }
                                return originalCallback;
                            };
                        }
                        
                        // 使用 Proxy 拦截所有动态创建的回调属性
                        try {
                            // 深度修改弹窗字段的辅助函数(用于Proxy)
                            function removePopupFieldsDeepProxy(obj) {
                                if (!obj || typeof obj !== 'object') return;
                                if (obj.popup !== undefined) obj.popup = 0;
                                if (obj.error !== undefined && obj.error === 503) {
                                    obj.error = 0;
                                    obj.error_msg = "";
                                }
                                for (let key in obj) {
                                    if (obj.hasOwnProperty(key) && typeof obj[key] === 'object' && obj[key] !== null) {
                                        removePopupFieldsDeepProxy(obj[key]);
                                    }
                                }
                            }
                            
                            const rdProxy = new Proxy(originalRd, {
                                set: function(target, prop, value) {
                                    if (typeof prop === 'string' && prop.startsWith('_cbk') && typeof value === 'function') {
                                        // 包装回调函数
                                        target[prop] = function(data) {
                                            if (data && typeof data === 'object') {
                                                removePopupFieldsDeepProxy(data);
                                            }
                                            return value.call(this, data);
                                        };
                                        return true;
                                    }
                                    target[prop] = value;
                                    return true;
                                }
                            });
                            window.BMapGL._rd = rdProxy;
                        } catch(e) {
                            console.log('Proxy not supported, using fallback method');
                        }
                    }
                    
                    // 深度修改弹窗字段的辅助函数
                    function removePopupFieldsDeep(obj) {
                        if (!obj || typeof obj !== 'object') return;
                        
                        // 修改当前对象的字段
                        if (obj.popup !== undefined) {
                            obj.popup = 0;
                        }
                        if (obj.error !== undefined && obj.error === 503) {
                            obj.error = 0;
                            obj.error_msg = "";
                        }
                        
                        // 递归处理嵌套对象
                        for (let key in obj) {
                            if (obj.hasOwnProperty(key) && typeof obj[key] === 'object' && obj[key] !== null) {
                                removePopupFieldsDeep(obj[key]);
                            }
                        }
                    }
                    
                    // 定期检查并Hook新创建的回调函数
                    const callbackMonitor = setInterval(function() {
                        if (window.BMapGL && window.BMapGL._rd) {
                            // 检查所有以_cbk开头的属性
                            for (let key in window.BMapGL._rd) {
                                if (key.startsWith('_cbk') && typeof window.BMapGL._rd[key] === 'function') {
                                    const originalCbk = window.BMapGL._rd[key];
                                    // 如果还没有被Hook过
                                    if (!originalCbk._hooked) {
                                        window.BMapGL._rd[key] = function(data) {
                                            if (data && typeof data === 'object') {
                                                removePopupFieldsDeep(data);
                                            }
                                            return originalCbk.call(this, data);
                                        };
                                        window.BMapGL._rd[key]._hooked = true;
                                        console.log('Hook BMapGL callback:', key);
                                    }
                                }
                            }
                        }
                        
                        // 也检查BMapGL上的其他回调
                        if (window.BMapGL) {
                            for (let key in window.BMapGL) {
                                if ((key.includes('cbk') || key.includes('Cbk') || key.includes('callback') || key.includes('Callback')) 
                                    && typeof window.BMapGL[key] === 'function' && !window.BMapGL[key]._hooked) {
                                    const originalCbk = window.BMapGL[key];
                                    window.BMapGL[key] = function(data) {
                                        if (data && typeof data === 'object') {
                                            removePopupFieldsDeep(data);
                                        }
                                        return originalCbk.call(this, data);
                                    };
                                    window.BMapGL[key]._hooked = true;
                                    console.log('Hook BMapGL callback:', key);
                                }
                            }
                        }
                    }, 100); // 更频繁的检查(100ms)
                    
                    // 30秒后停止监控(给足够时间处理所有回调)
                    setTimeout(function() {
                        clearInterval(callbackMonitor);
                    }, 30000);
                }
            }

            // 5. 监听百度地图API加载完成
            const checkInterval = setInterval(function() {
                if (window.BMapGL) {
                    hookBMapGLMethods();
                    clearInterval(checkInterval);
                }
            }, 100);

            // 6. 全局拦截弹窗显示方法
            // 拦截 alert
            const originalAlert = window.alert;
            window.alert = function(message) {
                if (typeof message === 'string' && (
                    message.includes('商用授权') || 
                    message.includes('lbs.baidu.com') ||
                    message.includes('business_accredit') ||
                    message.includes('未完成商用授权')
                )) {
                    console.log('拦截百度地图授权弹窗 (alert):', message);
                    return;
                }
                return originalAlert.apply(this, arguments);
            };
            
            // 拦截 confirm
            const originalConfirm = window.confirm;
            window.confirm = function(message) {
                if (typeof message === 'string' && (
                    message.includes('商用授权') || 
                    message.includes('lbs.baidu.com') ||
                    message.includes('business_accredit') ||
                    message.includes('未完成商用授权')
                )) {
                    console.log('拦截百度地图授权弹窗 (confirm):', message);
                    return true; // 返回true避免阻塞
                }
                return originalConfirm.apply(this, arguments);
            };
            
            // 拦截 prompt
            const originalPrompt = window.prompt;
            window.prompt = function(message, defaultText) {
                if (typeof message === 'string' && (
                    message.includes('商用授权') || 
                    message.includes('lbs.baidu.com') ||
                    message.includes('business_accredit')
                )) {
                    console.log('拦截百度地图授权弹窗 (prompt):', message);
                    return null;
                }
                return originalPrompt.apply(this, arguments);
            };
            
            // 7. 拦截可能用于显示弹窗的方法
            window.addEventListener('load', function() {
                // 拦截可能用于显示弹窗的全局函数
                const popupKeywords = ['商用授权', 'business_accredit', 'lbs.baidu.com', '未完成商用授权'];
                
                // 监控DOM变化,移除包含授权相关文本的元素
                const observer = new MutationObserver(function(mutations) {
                    mutations.forEach(function(mutation) {
                        mutation.addedNodes.forEach(function(node) {
                            if (node.nodeType === 1) { // Element node
                                const text = node.textContent || '';
                                const className = node.className || '';
                                const id = node.id || '';
                                
                                // 检查是否包含授权相关文本
                                if (popupKeywords.some(keyword => 
                                    text.includes(keyword) || 
                                    className.includes(keyword) || 
                                    id.includes(keyword)
                                )) {
                                    console.log('检测到百度地图弹窗元素,正在移除:', node);
                                    // 立即移除或隐藏
                                    if (node.parentNode) {
                                        node.style.display = 'none';
                                        // 延迟移除,避免影响其他功能
                                        setTimeout(function() {
                                            if (node.parentNode) {
                                                node.parentNode.removeChild(node);
                                            }
                                        }, 100);
                                    }
                                }
                            }
                        });
                    });
                });
                
                // 开始观察DOM变化
                observer.observe(document.body, {
                    childList: true,
                    subtree: true
                });
                
                // 定期检查并移除弹窗元素
                const popupChecker = setInterval(function() {
                    const allElements = document.querySelectorAll('*');
                    allElements.forEach(function(el) {
                        const text = el.textContent || '';
                        const className = el.className || '';
                        const id = el.id || '';
                        const style = window.getComputedStyle(el);
                        
                        // 检查是否是可见的弹窗元素
                        if (popupKeywords.some(keyword => 
                            (text.includes(keyword) || className.includes(keyword) || id.includes(keyword)) &&
                            style.display !== 'none' &&
                            (style.position === 'fixed' || style.position === 'absolute') &&
                            (parseInt(style.zIndex) > 1000 || style.zIndex === 'auto')
                        )) {
                            console.log('检测到百度地图弹窗,正在移除:', el);
                            el.style.display = 'none';
                            setTimeout(function() {
                                if (el.parentNode) {
                                    el.parentNode.removeChild(el);
                                }
                            }, 100);
                        }
                    });
                }, 500);
                
                // 30秒后停止检查
                setTimeout(function() {
                    clearInterval(popupChecker);
                }, 30000);
            });
        })();
    </script>

实际效果:

不过既然是 hook,存在一定的稳定性问题,有可能会失效。

等哪天彻底被恶心够了,就直接换地图 sdk 了!

  •  

基于 wp-cron.php 的拒绝服务攻击

这几天不知道是发生什么事了,说是不知道什么事情,但是大概率是被打了。只是这次打的挺高级的,外层的 eo 貌似也没什么反应。只是那个访问量通过 umami 看,直接爆炸了。

平常几百的访问量,昨天的时候,结果到了 2000 多,当然这不是最奇怪的,奇怪的是服务器过了会儿卡死了。之前都是因为请求太多 php-fpm 耗尽 cpu 资源卡死了,这次以为还是同样的问题。然而,并不是,发现 mysql 把 cpu 跑满了,查看日志的时候发现大量的 wp-cron.php 的请求,这尼玛,请求直接透传过来了。

另外还有一大堆 bot 的请求,包括 bing 以及一些乱起八糟的爬虫遍历。

最开始没想到什么好办法,简单粗暴的把 wp-cron.php 改名了,暂时解决了这个问题。

不过这个方法的确是高明,带着参数透传过来,wp 就是疯狂的执行,一条没执行完就到了下一条。然而,对于这种事情直接改名的确是可以解决办法,不过后来想了一下还是直接从 eo 下手吧。

尽管 eo 防住了 22 万次的攻击,但是,这些透传的请求,直接让 mysql 耗尽了 cpu 资源,也是个不错的办法,甚至请求频率都不用太高。流量到了 144g,这也不知道是哪个哥们又闲的蛋疼了,如果真的蛋疼来找姐姐啊,姐姐帮你治疗,直接给你割下来,塞你自己嘴里!

昨天晚上发现这个情况的时候,本来是想去处理下的,结果对象在用电脑,自己又不想去开笔记本,就用手机处理了一下,简单的改下了文件名。

今天早上才处理了一下,加到了 eo 的访问规则里:

尽管如此,还是对这几天的访问记录比较好奇,想看看请求了多少次。去拉 nginx 日志的时候发现文件已经 1.5G 了。直接截取这几天的记录,用 goaccess 跑了一下,但是比较奇怪的是这个 wp-cron.php 的请求竟然没有。

暂时放弃 goaccess 直接使用 ngxtop 进行数据分析:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
使用ngxtop分析Nginx日志中的POST请求
提供交互式菜单和多种分析选项
"""

import subprocess
import sys
import os
from pathlib import Path


def run_ngxtop(cmd_args):
    """运行ngxtop命令"""
    venv_python = Path(__file__).parent / "venv" / "bin" / "python"
    ngxtop_script = Path(__file__).parent / "venv" / "bin" / "ngxtop"
    
    if not ngxtop_script.exists():
        print("错误: ngxtop未安装,请先运行: source venv/bin/activate && pip install ngxtop")
        sys.exit(1)
    
    try:
        result = subprocess.run(
            [str(ngxtop_script)] + cmd_args,
            capture_output=True,
            text=True,
            check=False
        )
        print(result.stdout)
        if result.stderr and "error" in result.stderr.lower():
            print(result.stderr, file=sys.stderr)
        return result.returncode == 0
    except Exception as e:
        print(f"错误: {e}", file=sys.stderr)
        return False


def show_menu():
    """显示菜单"""
    print("\n" + "="*60)
    print("Nginx日志POST请求分析 - ngxtop工具")
    print("="*60)
    print("1. POST请求总览")
    print("2. 按URL统计POST请求 (Top 20)")
    print("3. 按IP统计POST请求 (Top 20)")
    print("4. 按状态码统计POST请求")
    print("5. POST请求中状态码为404的URL")
    print("6. POST请求中状态码为200的URL")
    print("7. 可疑POST请求 (xmlrpc, wp-login等)")
    print("8. POST请求详情示例")
    print("9. 自定义查询")
    print("0. 退出")
    print("="*60)


def analyze_post_requests(log_file):
    """分析POST请求"""
    if not os.path.exists(log_file):
        print(f"错误: 日志文件 {log_file} 不存在")
        return
    
    base_args = ["-l", log_file, "--no-follow", "-i", 'request.startswith("POST")']
    
    while True:
        show_menu()
        choice = input("\n请选择分析选项 (0-9): ").strip()
        
        if choice == "0":
            print("退出分析")
            break
        elif choice == "1":
            print("\n【POST请求总览】")
            print("-" * 60)
            run_ngxtop(base_args + ["--limit", "0"])
        elif choice == "2":
            print("\n【按URL统计POST请求 (Top 20)】")
            print("-" * 60)
            run_ngxtop(base_args + ["--group-by", "request_path", "--limit", "20"])
        elif choice == "3":
            print("\n【按IP统计POST请求 (Top 20)】")
            print("-" * 60)
            run_ngxtop(base_args + ["--group-by", "remote_addr", "--limit", "20"])
        elif choice == "4":
            print("\n【按状态码统计POST请求】")
            print("-" * 60)
            run_ngxtop(base_args + ["--group-by", "status", "--limit", "0"])
        elif choice == "5":
            print("\n【POST请求中状态码为404的URL (Top 10)】")
            print("-" * 60)
            run_ngxtop(["-l", log_file, "--no-follow", 
                       "-i", 'request.startswith("POST") and status == 404',
                       "--group-by", "request_path", "--limit", "10"])
        elif choice == "6":
            print("\n【POST请求中状态码为200的URL (Top 10)】")
            print("-" * 60)
            run_ngxtop(["-l", log_file, "--no-follow",
                       "-i", 'request.startswith("POST") and status == 200',
                       "--group-by", "request_path", "--limit", "10"])
        elif choice == "7":
            print("\n【可疑POST请求统计】")
            print("-" * 60)
            run_ngxtop(["-l", log_file, "--no-follow",
                       "-i", 'request.startswith("POST") and (request_path == "/xmlrpc.php" or request_path == "/wp-login.php" or request_path.startswith("/wp-admin"))',
                       "--group-by", "request_path", "--limit", "0"])
        elif choice == "8":
            print("\n【POST请求详情示例 (前10条)】")
            print("-" * 60)
            run_ngxtop(base_args + ["print", "remote_addr", "time_local", "request", "status", "bytes_sent", "--limit", "10"])
        elif choice == "9":
            print("\n【自定义查询】")
            print("-" * 60)
            print("示例查询:")
            print("  - 查看特定URL: ngxtop -l <file> -i 'request.startswith(\"POST\") and request_path == \"/wp-cron.php\"'")
            print("  - 查看特定IP: ngxtop -l <file> -i 'request.startswith(\"POST\") and remote_addr == \"114.66.247.160\"'")
            print("  - 查看错误请求: ngxtop -l <file> -i 'request.startswith(\"POST\") and status >= 400'")
            print("\n请输入自定义ngxtop命令参数 (用空格分隔):")
            custom_args = input("> ").strip().split()
            if custom_args:
                run_ngxtop(["-l", log_file, "--no-follow"] + custom_args)
        else:
            print("无效的选择,请重试")
        
        input("\n按回车键继续...")


def main():
    """主函数"""
    if len(sys.argv) < 2:
        # 查找默认日志文件
        log_files = list(Path(".").glob("*.txt"))
        if log_files:
            default_log = str(log_files[0])
            print(f"未指定日志文件,使用默认: {default_log}")
            log_file = default_log
        else:
            print("用法: python analyze_with_ngxtop.py <日志文件路径>")
            print("示例: python analyze_with_ngxtop.py 11-08_org.txt")
            sys.exit(1)
    else:
        log_file = sys.argv[1]
    
    analyze_post_requests(log_file)


if __name__ == "__main__":
    main()

运行命令:

python3 analyze_with_ngxtop.py 11-08_org.txt

分析结果:

【按URL统计POST请求 (Top 20)】
------------------------------------------------------------

running for 7 seconds, 23670 records processed: 3508.50 req/sec

Summary:
|   count |   avg_bytes_sent |   2xx |   3xx |   4xx |   5xx |
|---------+------------------+-------+-------+-------+-------|
|   23670 |         2381.924 |  4678 |    21 | 18574 |   397 |

Detailed:
| request_path                    |   count |   avg_bytes_sent |   2xx |   3xx |   4xx |   5xx |
|---------------------------------+---------+------------------+-------+-------+-------+-------|
| /wp-cron.php                    |   16454 |          731.309 |  3413 |     0 | 13034 |     7 |
| /xmlrpc.php                     |    3102 |          416.754 |   248 |     0 |  2853 |     1 |
| /wp-login.php                   |    2519 |        15204.250 |     0 |     0 |  2519 |     0 |
| /wp-admin/admin-ajax.php        |    1017 |          542.043 |   971 |     0 |    44 |     2 |
| /wp-comments-post.php           |     401 |         2551.357 |     0 |    14 |     0 |   387 |
| /xmrpc.php                      |      41 |          915.000 |     0 |     0 |    41 |     0 |
| /tslogin                        |      20 |        30543.150 |    16 |     4 |     0 |     0 |
| /alfacgiapi/perl.alfa           |      11 |        51292.455 |     0 |     0 |    11 |     0 |
| /ALFA_DATA/alfacgiapi/perl.alfa |      11 |        51323.636 |     0 |     0 |    11 |     0 |
| /index.php                      |      10 |        34570.900 |    10 |     0 |     0 |     0 |
| /wp-plain.php                   |       9 |         1331.000 |     0 |     0 |     9 |     0 |
| /                               |       9 |        28609.556 |     7 |     0 |     2 |     0 |
|                                 |       8 |          415.000 |     8 |     0 |     0 |     0 |
| /flow.php                       |       7 |          915.000 |     0 |     0 |     7 |     0 |
| /wp-admin/async-upload.php      |       5 |          736.000 |     5 |     0 |     0 |     0 |
| /php-cgi/php-cgi.exe            |       4 |        33911.500 |     0 |     0 |     4 |     0 |
| /graphql                        |       4 |        33469.750 |     0 |     0 |     4 |     0 |
| /wp-admin/post.php              |       3 |            5.000 |     0 |     3 |     0 |     0 |
| /member/success.aspx            |       2 |        16784.500 |     0 |     0 |     2 |     0 |
| /e/aspx/upload.aspx             |       2 |        16628.500 |     0 |     0 |     2 |     0 |

【按IP统计POST请求 (Top 20)】
------------------------------------------------------------
running for 7 seconds, 23670 records processed: 3586.40 req/sec

Summary:
|   count |   avg_bytes_sent |   2xx |   3xx |   4xx |   5xx |
|---------+------------------+-------+-------+-------+-------|
|   23670 |         2381.924 |  4678 |    21 | 18574 |   397 |

Detailed:
| remote_addr    |   count |   avg_bytes_sent |   2xx |   3xx |   4xx |   5xx |
|----------------+---------+------------------+-------+-------+-------+-------|
| 221.204.26.162 |    4407 |          696.960 |  1125 |     1 |  3279 |     2 |
| 221.204.26.233 |    4291 |          738.947 |  1054 |     1 |  3235 |     1 |
| 101.71.101.44  |    3168 |          686.088 |   911 |     4 |  2252 |     1 |
| 101.71.101.106 |    2564 |          868.693 |   183 |     2 |  2379 |     0 |
| 43.174.53.229  |    2094 |         7795.611 |     6 |     0 |  2088 |     0 |
| 43.174.53.236  |    2090 |         7811.496 |     4 |     0 |  2086 |     0 |
| 114.66.247.160 |    1810 |          743.818 |   520 |     1 |  1288 |     1 |
| 114.66.246.149 |    1123 |          507.375 |   538 |     1 |   582 |     2 |
| 101.71.105.47  |     104 |          574.404 |    57 |     0 |    47 |     0 |
| 43.175.19.192  |      29 |         5430.241 |     1 |     0 |    15 |    13 |
| 43.175.17.169  |      26 |         2520.500 |     0 |     0 |     8 |    18 |
| 43.175.18.81   |      25 |         2049.720 |     1 |     0 |     6 |    18 |
| 43.175.18.253  |      25 |         1835.800 |     1 |     0 |     8 |    16 |
| 43.175.18.195  |      25 |         5997.720 |     0 |     0 |     8 |    17 |
| 43.175.18.137  |      25 |         2101.840 |     1 |     0 |     5 |    19 |
| 43.175.17.87   |      24 |         2210.208 |     0 |     0 |     5 |    19 |
| 43.175.17.47   |      23 |         7488.043 |     0 |     0 |     9 |    14 |
| 43.175.18.51   |      22 |         3213.455 |     0 |     0 |     8 |    14 |
| 43.175.17.205  |      21 |         7011.381 |     1 |     0 |    10 |    10 |
| 43.175.169.137 |      16 |         1386.562 |     3 |     0 |     6 |     7 |

而至于这些 IP 地址,多数都是国内的,这个倒是也在意料之内,毕竟国外的被拦截的概率会更高一些。

然而,goaccess 就无法分析吗?也可以,添加忽略请求参数的参数就可以了:

#!/bin/bash
# 使用goaccess的--no-query-string参数移除查询参数
# 不需要修改日志文件!

LOG_FILE="${1:-11-08_org.txt}"
OUTPUT_FILE="${2:-goaccess_no_query_report.html}"

if [ ! -f "$LOG_FILE" ]; then
    echo "错误: 日志文件 $LOG_FILE 不存在"
    exit 1
fi

echo "=========================================="
echo "使用GoAccess分析(移除查询参数)"
echo "=========================================="
echo "日志文件: $LOG_FILE"
echo "输出文件: $OUTPUT_FILE"
echo ""
echo "使用参数: --no-query-string (或 -q)"
echo "这将移除URL中的查询参数,只保留路径"
echo ""

# 使用--no-query-string参数
goaccess "$LOG_FILE" \
  --log-format='%h %^[%d:%t %^] "%r" %s %b "%R" "%u"' \
  --date-format='%d/%m/%Y' \
  --time-format='%H:%M:%S' \
  --no-query-string \
  -o "$OUTPUT_FILE"

if [ $? -eq 0 ]; then
    echo ""
    echo "✅ 报告生成成功: $OUTPUT_FILE"
    echo ""
    echo "现在wp-cron.php应该能正确合并统计了!"
    echo ""
    echo "在浏览器中打开报告查看:"
    echo "  open $OUTPUT_FILE    # macOS"
    echo "  xdg-open $OUTPUT_FILE  # Linux"
    echo ""
    echo "在交互界面中使用:"
    echo "  goaccess $LOG_FILE \\"
    echo "    --log-format='%h %^[%d:%t %^] \"%r\" %s %b \"%R\" \"%u\"' \\"
    echo "    --date-format='%d/%m/%Y' \\"
    echo "    --time-format='%H:%M:%S' \\"
    echo "    --no-query-string"
else
    echo "❌ 报告生成失败"
    exit 1
fi

主要就是:–no-query-string参数。

实际效果:

文件没改名之前:

文件改名之后:

虽然加起来之后不到两万次,但是却让 mysql 把 cpu 资源耗尽了,这的确不失为一个低成本的攻击方式。

爬虫占比:

这几天也不知道爬虫是发什么疯

今天的访问量:

百度的统计:

咱就是说,有点时间干点正事不好吗?真是闲的。

 

  •  

不了了之

已经记不清楚入冬多长时间了,只是隐约记得冬天的节气一个接一个,似乎依然过了好几个了。

这气温也跟着起起起伏伏,从前天开始,暖气片竟然有一些温度了,不在那么冰冷。或许这冬天真的是来了,不过自己的体温似乎一点变化都没有,不管冷还是热,在这个冬天始终稳定在一个冰凉的状态,多少次想找个体温枪量一下自己脚的温度,却总是上床之后才想起来,又不想在去找体温枪。如果量一下的话应该是显示个 low 吧。

前段时间去拍的照片,依然有段时间了。百度网盘没有vip,所以下载照片基本都是挂在群晖的自动同步上下载。尽管速度只有 100k 左右,但是只要时间够长通知能下载完的,自动同步的好处是不用管到底下载到什么进度了。然而,想着看下下载的照片,当把内网的端口映射出来之后,各种扫描接踵而至,如逐臭的苍蝇一般,让人不胜其烦。

仅仅开了不到一天的时间就如此,这扫描也是无缝不入。虽然群晖自带防火墙,但是每次添加规则只能添加 15 个。这逻辑也是服了。 为了屏蔽国外的这些扫描器,只好添加一堆规则:

只是,这一系列昨晚之后。总觉得还是少了点东西,看了下系统上的年假还有四五天。这四五天年假,等过了元旦也就过期了。既然不能折现,何不浪费了他。

打定了主意,这大好的时光自然也不该浪费。尽管依然是冬天,但是树上的叶子依然是深绿色,枫树、银杏却早已变得火红或者金黄,尽情的飘散在这初冬之中。曾想着,等哪天下雪之后,穿一袭红色长裙站在皑皑白雪中,享受那种冰冷的热烈。

青岛的冬天,却不常见到雪,更不曾留住雪。一切都有些可遇不可求,现在的时光却是刚刚好,又怎堪辜负这大好的时光。

选了几套衣服,摄影师表示那件红的有点太突出了,可以拍个圣诞神马的。最终换了一条粉色的长裙,而至于拍摄的地点,原本是想去世博园的,摄影师却说之前去的时候,拍摄的姐妹,穿了件轻婚纱,在入口被拦住了不让进。现在这好景致,市南的公园,以及红岛的万亩枫林看比人发的都挺好看的,然而问题是太远了。尤其是从近城阳的地方开车过去也是比较费时间的,最后选择了世纪公园。

找个停车场停好车,步行到公园门口。门口一撮撮的大爷凑到一起,有的在下棋,有的在打扑克。这样的老年生活也挺好的,轻松自由。不过既然是拍照,那自然选几个场地就可以啦。

就在入口处转了转,拍了几套。这身打扮在这个季节还算是比较正常的。

等换上那条粉色的长裙之后,事情就变得没那么简单了,毕竟在这个季节还穿裙子的也的确是少数。补妆的时候,一只蚊子落在了胳膊上,化妆师小姐姐,眼疾手快直接给拍死了。没想到,冬天还有蚊子,蚊子也没想到,冬天还有穿的这么干净的,漏一大片的。

除了拍照也的确没有这种穿搭了,尽管在一周之前还看到不少拍婚纱照的,可能与工作日也有关系,可能也许是公园太偏僻,并没看到太多专程来拍照的。

直到看到这拍完的成片才发现,左下角那个姐妹的包包也入镜了。

拍照的时候,她就一直蹲在那个包包的旁边。过了几分钟,可能觉得无聊了,从包里拿出一包烟来,抽出来一只点上,抽了一口之后,就把烟夹在手指上,继续蹲那里看手机。拍照的时候,也不断的有人过来,可能都觉得这是个拍照的好地方吧。

等排到后面人越来越多了,再也没有多少空闲的地方。之前还想着往里走走,只是拍了一段时间,去湖边又拍了几站过之后却不想往里走了。总觉得有无数的好风光,但是这好风光却难以揽入怀中,毕竟时间终是有限,抱着这长裙在公园里面走也确实是有些累。

化妆的小姐姐说,裙子从后面已经扎到最紧了。感觉还是有点往下掉,有点走光。

减肥的事情依然在继续,这漫长的一个月有没有太大的进展。前天晚上,一边看《唐朝诡事录》一遍跳绳,一集电视剧看完,刚好跳了7000 多个。

昨天晚上想继续跳的时候,发身身体有点沉,跳不大动的样子,就跳了 2000 个就结束了。

状态不好的时候,真的跳不动。有的时候,挺想坚持一件事情,只是,稍微懈怠,这件事情就那么不了了之。再也没了后续,这坚持,又能坚持多久。

偶尔从镜子里看到自己,感觉似乎没那么老,但是,通过相机那高清的照片,看到自己的脸的时候,那种苍老感,却再也遮挡不住了。或许真的垂垂老矣。年龄这个东西,就这么一步步的赶尽杀绝,也不给任何喘息的机会。

的确是很多事情,再不做,可能就真的做不了了,在等待的时候,太多的事情拖到了终点,甚至是无疾而终。

多年以后,自己依然叛逆,那种自私而又无所畏惧的性格,或许,永远都不会变吧。

做的太多的事情,总是在补偿自己的童年,少年,年轻的时候那些所谓的爱而不得。

终于,现在能有能力补偿那个孩子的时候,有的时候又觉得太过于奢侈,或者不该去浪费。那种矛盾从来都没解决过,那种不了了之之后的失望更让人后悔。

不过是最终下定了决心:

我讨厌等待,我更讨厌爱而不得!

  •  

生如夏花

 

生命,一次又一次轻薄过。
轻狂不知疲倦。
–题记


我听见回声,来自山谷和心间。
以寂寞的镰刀收割空旷的灵魂。
不断地重复决绝,又重复幸福。
终有绿洲摇曳在沙漠。
我相信自己。
生来如同璀璨的夏日之花。
不凋不败,妖治如火。
承受心跳的负荷和呼吸的累赘。
乐此不疲。


我听见音乐,来自月光和胴体。
辅极端的诱饵捕获飘渺的唯美。
一生充盈着激烈,又充盈着纯然。
总有回忆贯穿于世间。
我相信自己。
死时如同静美的秋日落叶。
不盛不乱,姿态如烟。
即便枯萎也保留丰肌清骨的傲然。
玄之又玄。


我听见爱情,我相信爱情。
爱情是一潭挣扎的蓝藻。
如同一阵凄微的风。
穿过我失血的静脉。
驻守岁月的信念。


我相信一切能够听见。
甚至预见离散,遇见另一个自己。
而有些瞬间无法把握。
任凭东走西顾,逝去的必然不返。
请看我头置簪花,一路走来一路盛开。
频频遗漏一些,又深陷风霜雨雪的感动。


般若波罗蜜,一声一声。
生如夏花,死如秋叶。
还在乎拥有什么。

 

  •