普通视图

发现新文章,点击刷新页面。
昨天以前ChenYFanのBlog

关于枫糖博客事件的正面回应

作者 CYF
2022年2月3日 12:57

如果你能看到这篇文章,那真的很抱歉,说明枫糖这人又开始作妖了.

首先我们把事情捋一遍.

由于其博客已被其主动关闭,所有证据均是从我能收集到的部分截图的,包括网页快照和WayBackMachine

他干了什么

首先,这人在对valine评论系统的恶作剧一文中详细阐述了自己刷Valine的经过.原文可能打不开,你可以通过谷歌快照网页时光机阅读原文.

在这篇文章中,他以高调的形式,讲述自己如何利用Valine的XSS漏洞给他人网页植入恶意js脚本:

并且认为很好玩:

就这一点就可以看出他的态度极其不端正,以恶作剧别人来取乐自己.

经过别人提醒,他暂时隐藏了脚本:

但仍保持无所谓态度,并且强加因果,以因为我攻击了Valie推出我帮Valine修复了漏洞

并且依旧偏执保持着自己的观点(GoogleCache)

仍然继续发文炫耀(源站,目前是关闭的):

对Valine的恶意攻击其实不在少数,据我所知,小康也写过用python脚本恶意刷Valine的脚本.

然后关于我,我并不是受害者.我在看到群里头群友一直在辱骂他,顺便点进去看了看,然后立刻就被这奇葩的三观震惊到了.最后,我在他博客上留言:

首先我承认你的ctf技术比大多数人好,包括我,但利用漏洞攻击别人,赢得所谓的虚荣心,这就是你所说的技艺高超?你的行为已经严重影响到了别人的正常使用,即使你对valine不满,或对其他人的博客心存嫉妒,大可自己努力学习,写出更好的评论系统或文章,从正面碾压别人,赢得大家掌声,让人家心服口服,而不是恶心别人,做那君子行为,万世流芳。

我为什么要报复

首先,他是这么回复的:

纯粹是纯粹是为了玩一下哦,就几个评论,删除就完了,也不会对博主造成什么困扰啊?,能说出这种言论的,绝大多数都是反社会人格,偏激,自我主义,丝毫不顾忌他人感受.如果你是开玩笑,那么玩笑是建立在对方也觉得好笑的时候才能成立,否则这只能成为单方面的攻击,那么就没有理由在对方反击你的时候狡辩.

我恶作剧一两天,valine就发布了新版本,valine可是十多个月没有修复了,我怎么说也算有点贡献吧? 首先,Valine是个个人维护的开源项目,如果你发现了漏洞,应当是直接提出issue并等待修复,这才是一个正确的解决方法路径.即使你认为Valine开发者懒,不敲打就不会更新,那你也更不应该滥用此漏洞攻击无辜之人,从而满足自己的扭曲的虚荣心和变态的恶作剧心理.

手持利刃,刀口不应指向无辜之人

更何况,这还是把钝刀

留完言后,我就简单的看了一下他的回答,摇头叹息,这人怎么说道理都没用,三观已经腐化了.然后我就没有继续关注这件事情.

当然,我也在Valine群和其他水群中继续听闻了这件事情,但一直都没有怎么放在心上.

直到大过年翻邮件的时候看到了新的回复:

字都不会写,小学生确认完毕,三年级不能再多了.

这令我感到气愤而难过,如此欠揍又油盐不进,然后我赶到他的博客底下翻了翻

有人只是解析出了其博客CloudFlareCDN节点的ip,被他嘲讽了一波.

以及他还挺欢迎别人搞他博客的?

于是我直接找到了他的源站地址,并小小的威胁了他一下:

但是他是这么回复的:

我并不是那种锱铢必较的人,但他这份言论侮辱了我的人格和我的父母,既然找到了他的源ip,那我就对其小小的报复了一下.

报复的结果

CC攻击流量150GB持续两小时,UDPFlood流量600GB持续半小时,CC数据库高频查询200线程持续一小时

UDP攻击似乎大部分被DigitalOcean防火墙吃了,于是简单打了一下就主用CC

证据截图

2-2 10:24pm

2-2 10:39pm

2-2 10:53pm

2-2 10:57pm

2-2 11:03pm

2-2 11:06pm

2-2 11:23pm

2-2 11:33pm

2-3 00:01pm

此时对面博客已经打垮了,这是在我的博客上留言的

注意时间是在上文睡觉后发的

2-3 00:02pm

2-3 00:03pm

2-3 00:05pm

我的正面回应

首先总结一下骂人规律:以妈为中心,亲戚关系为半径,以性侮辱性关系为立体维度,画个球,然后在这个集合里面一个一个骂过去,主用你妈的,次用我是你爹,夹带白莲花 大头鬼等高端词语,尝试用强加伦理关系压制,并且搞不过就创造一个子虚乌有的高级朋.最后尝试各种侮辱性脏字堵住别人的嘴.

这种行为极其类似鲁迅笔下的阿Q,试图强加一个单方面的伦理关系自我满足,妥妥的一个可怜的小人行为,以这种姿态还想妄称为别人的爹,简直就是小丑行为。

以及各种奇葩言论,前叫嚣特别期待,后面又试图搬出一个朋友威胁我不要攻击;宣称自己有很多流量,被刷的时候又破口大骂;以及用自己扭曲的价值观来妄图矫正我的观点.

所有的言论更像是出于一种宣泄情绪的存在,没有共情能力,满口脏字却词汇贫乏,丝毫不见理性.

关于攻击理由,我并不是无缘无故就照着你的博客开始打,大部分人应该能看出其恶意。你攻击别人的行为只是处于一种恶趣味,尝试通过入侵的方式满足自己扭曲的傲气。并且你攻击别人的方式甚至不是自己研究出来的,而是照抄issue里面的样例代码实行攻击。而我攻击的原因是你侮辱了我,并且你自身完全没有意识到自己的错误,依旧偏执地认为自己只是开个玩笑无伤大雅。

这几把小学生的脾气,调侃一句就受不了了?如果你认为我是小学生脾气,并且你认为你的行为是调侃,那我并不承认.你的各种言语已经严重侵犯了我的人格,并且,在说我脾气不好的时候,请你自己看看你是为什么攻击别人的呢?

valine恶作剧回忆录(GoogleCache)

只是那就别用,并没有主动挑衅,一句简单的拒绝的话语连基本情感语句都读不出,还多次踩点报复.你这又是何德称我为小学生呢.

既然你认为我要早点搞死你,那我就真的这么做了.

如果你只是认为关站只是看不起我,那抱歉,在互联网中,这种攻击行为无论是君子游戏还是恶意打击,只要你防御不住,关站了,就意味着投降,无论是基于什么原因。

你认为我的攻击方式很low,只要你是选择了投降,就意味着你是个loser.然而loser是完全没有资格在君子游戏后还继续嘲讽。

无论我使用什么技术,你关站了,就意味着认输,你也就失去了对我挑衅的资格。

更可笑的是,在防御不住的时候还要搬出子虚乌有的朋友,声称上面有重要文件。你应该在高调宣称自己就是攻击者的时候就要做好防御措施,而不是发一份无能狂怒的威胁信。

如果你继续把你的行为称之为高尚,我只能说包括我在内,大部分人都不会附和。这完全是一种强加意愿,不顾他人的三观腐败行为,妥妥的九年义务教育没读完的那种。

你这种行为已经欺辱到我的人格,你可以不赞同我说的话,但你不能对我人身攻击。我不是那种锱铢必较的人,更非虚与委蛇,你对我造成了侮辱,我必定会报复。

End

可能会有人认为我报复的手段可能过于残忍,但对于我来说已经是最快捷的报复方式了,并且对于这种人来说,以牙还牙最为简便。

至于枫糖,你如果还想对线请只在这篇文章下骂,并且麻烦不要带脏字。我甚至可以给你搞一个置顶,只要你不担心别人把你喷死就行。

欲善其事,必利其器 - 论如何善用ServiceWorker

作者 CYF
2022年1月16日 21:57

ServiceWorker作为前端革命领袖,毫不夸张地被誉为前端黑科技,此文将阐述如何巧妙的使用它来实现一些看起来匪夷所思的事情。

Warning
此文为最基本的SW使用基础,如果你只是想来白嫖代码的建议退出选择下一篇实操文章.

起因 - 巨厦坍塌

2021/12/20日,赶在旧年的末尾,一则JSdelivrSSL证书错误缓缓上了v2ex论坛热点。

此前JSD由于各种原因,曾经不正常了一段时间,所以大家并未对此感冒.正当人们以为这只是JSdelivr每年一度的年经阵痛,发个issue,过一段时间就好了的时候.官方直接爆出大料:JSDelivr had lost their ICP license

由此可见,过去的几年里,当人们发现JSD对个人面向国内加速拥有者无与伦比的效果时,各种滥用方式层出不穷:图床曾一阵流行,国内搜索引擎JSdelivr十有八九都是作为图床的,连PicGo插件都出了Github+JSdelivr图床;猛一点的,直接做视频床,甚至为了突破单文件20M限制开发了一套ts切片m3u8一条龙服务;作妖的,托管了不少突破网络审查的脚本和规则集;寻死的,添加了大量的政治宗教敏感,有些甚至不配称为宗教,直接上来就是骗钱的.

jsd并不是没有发布许可条款,但这并不能阻止白嫖大军的进程。在羊毛大军中,只要是你是免费的、公益的,你就要做好被薅爆的结果。但是薅羊毛的前提是羊还活着,倘若羊被薅死了,哪来的羊毛给诸君所薅?

总之,不管怎样,JSDelivr在决定将节点设置为NearChina,可以肯定的是,在最近很长一段时间内,我们都无法享受国内外双料同时加速的快感,换句话说,jsd在中国就被永久地打入了冷宫。

视线转向国内,jsd的替代品并不少。早在我写图床的千层套路我就试着假想jsd不可用时,我们该用什么。最终我给出的一份较为完美的答案-npm图床,优点无非就是镜像多速度快,许可条款较为宽松,缺点也很明显,需要安装node,用专门的客户端上传。

那事情就逐渐变得扑朔迷离起来了,我们应当如何选择合理的CDN加速器呢。

这时候,我想起了前端黑科技Serviceworker。是的,这种情况下使用SW最为巧妙不过,它可以在后台自动优选最佳的CDN,甚至可以用黑中黑Promise.any打出一套漂亮的并行拳。经过两天的完善,我终于写出了一套具有离线可达、绕备、优选CDN、跟踪统计合一的SW脚本。此博客使用的SW

接下来我将从头开始讲述ServiceWorker的妙用。

Before Start

What Is The ServiceWorker

网上对于SW的解释比较模糊,在这里,我将其定义为用户浏览器里的服务器,功能强大到令人发指。是的,接下来的两张图你应该能显著的看到这一差距:

没有ServiceWorker中继,平淡无奇

ServiceWorker中继,刺激拉满

在第一张图中,用户和服务器的关系直的就像电线杆,用户想要什么,服务器就还给他什么。

第二张图中,用户在被ServiceWorker控制的页面中,无论向哪个服务器发起请求,其过程都会被SW捕获,SW可以仿佛不存在一般单纯地请求服务器,返回原本应该返回的内容【透明代理】;也可以对当前服务器返回的内容进行随意的捏造、修改【请求修改结果】;甚至可以将请求指向完全另一台服务器,返回不是此服务器应该返回的内容【移花接木】;当然,SW也可以直接返回已经存储在本地的文件,甚至离线的时候也能返回【离线访问可达性】。

由于SW对于用户页面的操纵实在过于强大,因此,它被设计成不可跨域请求SW脚本必须在同一域名下必须在HTTPS条件下运行不可操纵DOM和BOM,同样的,为了避免阻塞和延迟,SW也被特意设计成完全异步的。这些点将会在后面一一讲述。

当然,开发者至上,为了方便本地调试,本机地址localhost127.0.0.1被浏览器所信任,允许以非HTTPS方式运行serviceworker。

What Relationship Between ServiceWorker and PWA

很多人看到sw,第一反应是PWA,即渐进式Web应用。实际上,SW确实是PWA的核心与灵魂,但SW在PWA中起的主要作用是缓存文件,提供给离线访问。并没有完整地发挥出SW的巧妙用法。

SW可以完全脱离PWA存在,当然,PWA可离不开SW :)

And WorkBox ?

WorkBox是谷歌开发的一款基于SW的缓存控制器,其主要目的是方便维护PWA。核心依旧是SW,但还是没有SW原本的自定义程度高(

Why Not WorkBox ?

首先,博客呢,是没有必要用PWA,有SW做中间件足矣。同时,WorkBox只能简单的缓存数据,并不能做到拦截篡改请求的功能,尤其不能精准把握每一个资源的缓存情况,自定义程度并不高。

自己编写SW,格局就打开了

Start From Zero

安装 / Install

首先,SW的本质是JS脚本,要安装它必须要经过一个html。毕竟,只有拿到了html,JS才能运行于DOM上下文。

剥离层层加成,安装的代码只有一行

navigator.serviceWorker.register('/sw.js')

其中,/sw.js即为ServiceWorker脚本所在,由于安全性,你不能加载跨域的SW。

例如,当前网页为https://blog.cyfan.top,以下加载位置是允许的

/sw.jshttps://blog.cyfan.top/sw.js

以下加载是不允许的:

http://blog.cyfan.top/sw.js#非HTTPShttps://cyfan.top/sw.js#非同一域名,视为跨域https://119.91.80.151:59996/sw.js#虽然为同一文件,但非同一域名,视为跨域./sw.js#容易造成SW脚本获取路径不一致

在加载前,我们最好判断一下dom是否加载完了,不然安装sw可能会卡dom

加载完成后,register函数将返回一个Promise,由于前端大多不适用于异步,我们通常以同步的方式.then().catch()来获取是否加载成功。

为了方便判断脚本是否能够加载,我们还要判断navigator里有无sw这一属性'serviceWorker' in navigator

由于SW安装后,页面需要刷新后才能交给SW所宰割,同时为了避免浏览器缓存的影响,我通常采用修改search的方式强刷新,而不是通过reload函数。同样的,为了避免刚安装完就刷新的尴尬感,建议用setTimeout延迟一秒刷新。

简易的完整安装代码如下:

<script>window.addEventListener('load', async () => {    navigator.serviceWorker.register(`/sw.js?time=${new Date().getTime()}`)        .then(async reg => {            //安装成功,建议此处强刷新以立刻执行SW            if (window.localStorage.getItem('install') != 'true') {                window.localStorage.setItem('install', 'true');                setTimeout(() => {                    window.location.search = `?time=${new Date().getTime()}`                }, 1000)            }        }).catch(err => {            //安装失败,错误信息会由err传参        })});</script>

一刷新,世界就变成了ServiceWorker的瓮中之鳖,接下来,该是SW脚本正式登场的时候了。

SW安装初始化 / Installations

首先,先尴尬的开一个空缓存列表:

const CACHE_NAME = 'ICDNCache';//可以为Cache版本号,但这样可能会导致缓存冗余累积let cachelist = [];

cachelist里面填写的是预缓存网址,例如在离线时返回的错误页面。此处不宜添加过多网址,此处点名@一下Akilar。

此处我建议只缓存离线页面展示的内容:

let cachelist = [    '/offline.html',    'https://npm.elemecdn.com/chenyfan-os@0.0.0-r6'];

同时监听sw安装时开启此缓存空间:

self.addEventListener('install', async function (installEvent) {    self.skipWaiting();    installEvent.waitUntil(        caches.open(CACHE_NAME)            .then(function (cache) {                console.log('Opened cache');                return cache.addAll(cachelist);            })    );});

由于SW完全没有办法访问DOM,因此对于全局变量,不应当用window,而是self指代自己。

addEventListener这一监听器将监听install,也就是这一段代码只会在脚本首次安装和更新时运行.

skipWaiting的作用是促进新版本sw跳过waiting这一阶段,直接active。

关于SW的状态(waiting,installing,activing)将在文后详细解释。

installEvent.waitUntil的作用是直接结束安装过程的等待,待会在后台完成开启缓存空间这一操作。

cache.addAll将会直接获取cachelist里面所有的网址并直接缓存到CacheStorage。如果此处网址过多,将在页面加载时疯狂请求所有的url(例如1k个)

现在,SW初始化已经完成了。接下来,我将讲述SW如何捕获页面的请求。

捕获请求 / Fetch Event

添加监听器 / AddEventListener

self.addEventListener('fetch', async event => {    event.respondWith(handle(event.request))});const handle = async(req)=>{    //do something}

第一行很简单,绑定一个监听器,监听fetch事件,即网页向服务器获取请求,也就是相当于前端的XMLHTTPRequest

event.respondWith即设定返回内容,交给handle主函数处理,传参event.request。这是一个Request对象,里面包含了请求的详细信息。

接下来,我们开始实战吧。

以下所有内容均针对handle修改

透明代理 / Transparent Proxy

顾名思义,此实战脚本的作用是SW代理目前的所有流量但不进行修改,仿佛SW不存在一般。

const handle = async(req)=>{    return fetch(req)}

fetch这个函数相当于前端的ajax或者XMLHTTPRequest,作用是发起一个请求,获得一个返回值。由于sw不可访问window,在sw中是无法使用ajaxXMLHTTPRequest。同时,fetch是一个异步函数,直接调用它会返回一个Promise

fetch只能传递Requset对象,而Requset对象有两个参数(url,[option]),第一个参数是网址,第二个参数为Request的内容,例如bodyheader

此脚本适用于卸载ServiceWorker的替换脚本。因为sw在无法拉取新版本时不会主动卸载,依旧保持运行,填入一个透明代理sw即可。

由于SW冷启动【即页面关闭后SW】处于暂停状态是从硬盘读取的,这会导致第一次请求有少许性能延迟[~10ms]。

篡改请求 / Edit Requset

对于一张图片,有时候服务端会变态到让你必须用POST协议才能获得,此时用SW篡改最为方便。

const handle = async (req) => {    if ((req.url.split('/'))[2].match('xxx.com')) {        //xxx.com为图片所在域名        return fetch(req.url, {            method: "POST"        })    }    return fetch(req)}

注意,在ServiceWorker里面,header头是不能修改refferer和origin的,因此此方法无法绕开新浪图床反盗链

篡改响应 / Edit Response

这个例子会检测返回内容,若为html,将把所有的”TEST”都替换成”SHIT”

const handle = async (req) => {    const res = await fetch(req)    const resp = res.clone()    if (!!resp.headers.get('content-type')) {        if (resp.headers.get('content-type').includes('text/html')) {            return new Response((await resp.text()).replace(/TEST/g, 'SHIT'), {                headers: resp.headers,                status: resp.status            })        }    }    return resp}

const resp = res.clone()由于Responsebody一旦被读取,这个body就会被锁死,再也无法读取。clone()能够创造出响应的副本用于处理。

resp.headers.get('content-type')通过读取响应的头,判断是否包含text/html,如果是,将响应以text()异步流的方式读取,然后正则替换掉响应内容,并还原头和响应Code。

返回的内容必须是Response对象,所以new Response构建一个新对象,并直接返回。不匹配html头将直接原封不动地透明代理。

移花接木 / Graft Request To Another Server

unpkg.zhimg.comunpkg.com的镜像网站。此脚本将会把所有的unpkg.com流量直接拦截到unpkg.zhimg.com,用于中国大陆内CDN加速。

由于npm镜像固定为GET请求方式并且没有其他鉴权需求,所以我们没有必要还原Request其他数据。

const handle = async (req) => {    const domain = req.url.split('/')[2];    if (domain.match("unpkg.com")) {        return fetch(req.url.replace("https://unpkg.com", "https://zhimg.unpkg.com"));    }    else {        return fetch(req)    }}

domain.match捕获请求中是否有待替换域名,检查出来后直接replace掉域名,如果没有匹配到,直接透明代理走掉。

并行请求 / Request Parallelly

SW中又一大黑科技隆重登场=>Promise.any,这个函数拥有另外两个衍生兄弟Promise.all&Promise.race。下面我将简单介绍这三种方式

Promose.all

当列表中所有的Promiseresolve[即成功]后,这个函数才会返回resolve,只要有一个返回reject,整个函数都会reject

Promise.all([    fetch('https://unpkg.com/jquery'),    fetch('https://cdn.jsdelivr.net/npm/jquery'),    fetch('https://unpkg.zhimg.com/jquery')])

这个函数将会请求三个网址,当每一个网址都链接联通后,整个函数将会返回一个列表:

[Response1,Response2,Response3]

当任何一个fetch失败[即reject]后,整个Promise.all函数都会直接reject并报错。

此函数可以检测网络连通性,由于采取并行处理,相比以前的循环效率要高不少。

这是一段检测国内国外网络连通性的测试。

没有采用Promise.all的代码和效果:

const test = async () => {    const url = [                "https://cdn.jsdelivr.net/npm/jquery@3.6.0/package.json",        "https://unpkg.com/jquery@3.6.0/package.json",        "https://unpkg.zhimg.com/jquery@3.6.0/package.json"    ]    flag = true    for (var i in url) {        try {            const res = await fetch(url[i])            if (res.status !== 200) {                flag = false            }        }catch(n){            return false        }    }    return flag}

采用循环,await会堵塞循环,直到这次请求完成后才能执行下一个。如果有任何一个url长时间无法联通,将会导致极长的检测时间浪费。

const test = () => {    const url = [        "https://cdn.jsdelivr.net/npm/jquery@3.6.0/package.json",        "https://unpkg.com/jquery@3.6.0/package.json",        "https://unpkg.zhimg.com/jquery@3.6.0/package.json"    ]    return Promise.all(url.map(url => {        return new Promise((resolve, reject) => {            fetch(url)                .then(res => {                    if (res.status == 200) {                        resolve(true)                    } else {                        reject(false)                    }                })                .catch(err => {                    reject(false)                })        })    }    )).then(res => {        return true    }).catch(err => {        return false    })}

Promise.all几乎在一瞬间请求所有的url,其请求时并行,每一个请求并不会堵塞其他请求,函数总耗时为最长请求耗时。

Promise.race

此函数也是并行执行,不过与all不同的是,只要有任何一个函数完成,就立刻返回,无论其是否reject或者resolve

这个函数比较适合用于同时请求一些不关心结果,只要访问达到了即可,例如统计、签到等应用场景。

Promise.any

这个函数非常的有用,其作用和race接近,不过与之不同的是,any会同时检测结果是否resolve。其并行处理后,只要有任何一个返回正确,就直接返回哪个最快的请求结果,返回错误的直接忽视,除非所有的请求都失败了,才会返回reject

这是一段同时请求jquerypackage.json代码,它将从四个镜像同时请求:

const get_json = () => {    return new Promise((resolve, reject) => {        const urllist = [            "https://cdn.jsdelivr.net/npm/jquery@3.6.0/package.json",            "https://unpkg.com/jquery@3.6.0/package.json",            "https://unpkg.zhimg.com/jquery@3.6.0/package.json",            "https://npm.elemecdn.com/jquery@3.6.0/package.json"        ]        Promise.any(urllist.map(url => {            fetch(url)                .then(res => {                    if (res.status == 200) {                        resolve(res)                    } else {                        reject()                    }                }).catch(err => {                    reject()                })        }))    })}console.log(await(await get_json()).text())

函数将会在21ms上下返回json中的数据。

此函数的好处在于可以在用户客户端判断哪一个镜像发挥速度最快,并保证用户每一次获取都能达到最大速度。同时,任何一个镜像站崩溃了都不会造成太大的影响,脚本将自动从其他源拉取信息。

除非所有源都炸了,否则此请求不会失败。

但是,我们会额外地发现,当知乎镜像返回最新版本后,其余的请求依旧在继续,只是没有被利用到而已。

这会堵塞浏览器并发线程数,并且会造成额外的流量浪费。所以我们应该在其中任何一个请求完成后就打断其余请求。

fetch有一个abort对象,只要刚开始new AbortController()指定控制器,在init的里面指定控制器的signal即可将其标记为待打断函数,最后controller.abort()即可打断。

那么,很多同学就会开始这么写了:

const get_json = () => {    return new Promise((resolve, reject) => {        const controller = new AbortController();        const urllist = [            "https://cdn.jsdelivr.net/npm/jquery@3.6.0/package.json",            "https://unpkg.com/jquery@3.6.0/package.json",            "https://unpkg.zhimg.com/jquery@3.6.0/package.json",            "https://npm.elemecdn.com/jquery@3.6.0/package.json"        ]        Promise.any(urllist.map(url => {            fetch(url,{                signal: controller.signal            })                .then(res => {                    if (res.status == 200) {                        controller.abort();                        resolve(res)                    } else {                        reject()                    }                }).catch(err => {                    reject()                })        }))    })}console.log(await(await get_json()).text())

但很快,你就会发现它报错了:Uncaught DOMException: The user aborted a request.,并且没有任何数据输出。

让我们看一下Network选项卡:

其中,知乎返回的最快,但他并没有完整的返回文件[源文件1.8KB,但他只返回了1.4KB]。这也直接导致了整个函数的fail

原因出在fetch上,这个函数在获得响应之后就立刻resolveResponse,但这个时候body并没有下载完成,即fetch的返回基于状态的而非基于响应内容,当其中fetch已经拿到了完整的状态代码,它就立刻把Response丢给了下一个管道函数,而此时status正确,abort打断了包括这一个fetch的所有请求,fetch就直接工作不正常。

我个人采取的方式是读取arrayBuffer,阻塞fetch函数直到把整个文件下载下来。函数名为PauseProgress

const get_json = () => {    return new Promise((resolve, reject) => {        const controller = new AbortController();        const PauseProgress = async (res) => {            return new Response(await (res).arrayBuffer(), { status: res.status, headers: res.headers });        };        const urllist = [            "https://cdn.jsdelivr.net/npm/jquery@3.6.0/package.json",            "https://unpkg.com/jquery@3.6.0/package.json",            "https://unpkg.zhimg.com/jquery@3.6.0/package.json",            "https://npm.elemecdn.com/jquery@3.6.0/package.json"        ]        Promise.any(urllist.map(url => {            fetch(url, {                signal: controller.signal            })                .then(PauseProgress)                .then(res => {                    if (res.status == 200) {                        controller.abort();                        resolve(res)                    } else {                        reject()                    }                }).catch(err => {                    reject()                })        }))    })}console.log(await(await get_json()).text())

在这其中通过arrayBuffer()方法异步读取resbody,将其读取为二进制文件,并新建一个新的Response,还原状态和头,然后丢给管道函数同步处理。

在这里,我们就实现了暴力并发,以流量换速度的方式。同时也获得了一个高可用的SW负载均衡器。

这一段函数可以这样写在SW中:

//...const lfetch = (urllist) => {    return new Promise((resolve, reject) => {        const controller = new AbortController();        const PauseProgress = async (res) => {            return new Response(await (res).arrayBuffer(), { status: res.status, headers: res.headers });        };        Promise.any(urllist.map(url => {            fetch(url, {                signal: controller.signal            })                .then(PauseProgress)                .then(res => {                    if (res.status == 200) {                        controller.abort();                        resolve(res)                    } else {                        reject()                    }                }).catch(err => {                    reject()                })        }))    })}const handle = async (req) => {    const npm_mirror = [        'https://cdn.jsdelivr.net/npm/',        'https://unpkg.com/',        'https://npm.elemecdn.com/',        'https://unpkg.zhimg.com/'    ]    for (var k in npm_mirror) {        if (req.url.match(npm_mirror[k]) && req.url.replace('https://', '').split('/')[0] == npm_mirror[k].replace('https://', '').split('/')[0]) {            return lfetch((() => {                let l = []                for (let i = 0; i < npm_mirror.length; i++) {                    l.push(npm_mirror[i] + req.url.split('/')[3])                }                return l            })())        }    }    return fetch(req)}

另外,Promise.any,兼容性比较差:

因此,我的解决办法是判断浏览器如果不支持,就提前polyfill一下:

if (!Promise.any) {        Promise.any = function (promises) {            return new Promise((resolve, reject) => {                promises = Array.isArray(promises) ? promises : []                let len = promises.length                let errs = []                if (len === 0) return reject(new AggregateError('All promises were rejected'))                promises.forEach((promise) => {                    promise.then(value => {                        resolve(value)                    }, err => {                        len--                        errs.push(err)                        if (len === 0) {                            reject(new AggregateError(errs))                        }                    })                })            })        }    }

缓存控制 / Cache

持久化缓存 / Cache Persistently

对于来自CDN的流量,大部分是持久不变的,因此,如果我们将文件获得后直接填入缓存,之后访问也直接从本地缓存中读取,那将大大提升访问速度。

const handle = async (req) => {    const cache_url_list = [        /(http:\/\/|https:\/\/)cdn\.jsdelivr\.net/g,        /(http:\/\/|https:\/\/)cdn\.bootcss\.com/g,        /(http:\/\/|https:\/\/)zhimg\.unpkg\.com/g,        /(http:\/\/|https:\/\/)unpkg\.com/g    ]    for (var i in cache_url_list) {        if (req.url.match(cache_url_list[i])) {            return caches.match(req).then(function (resp) {                return resp || fetch(req).then(function (res) {                    return caches.open(CACHE_NAME).then(function (cache) {                        cache.put(req, res.clone());                        return res;                    });                });            })        }    }    return fetch(req)}

cache_url_list列出所有待匹配的域名(包括http/https头是为了避免误杀其他url),然后for开始遍历待列表,如果url中匹配到了,开始执行返回缓存操作。

cache是一个近似于Key/Value(键名/键值),只要有对应的Request(KEY),就能匹配到响应的Response(VALUE)。

caches.match(req)将会试图在CacheStorage中匹配请求的url获取值,然后丢给管道同步函数then,传参resp为Cache匹配到的值。

此时管道内将尝试返回resp,如果resp为nullundefined[即获取不到对应的缓存],将执行fetch操作,fetch成功后将open打开CacheStorage,并put放入缓存。此时如果fetch失败将直接报错,不写入缓存。

在下一次获取同一个URL的时候,缓存匹配到的将不再是空白值,此时fetch不执行,直接返回缓存,大大提升了速度。

由于npm的cdn对于latest缓存并不是持久有效的,所以我们最好还是判断一下url版本中是否以@latest为结尾。

const is_latest = (url) => {    return url.replace('https://', '').split('/')[1].split('@')[1] === 'latest'}//...for (var i in cache_url_list) {    if (is_latest(req.url)) { return fetch(req) }    if (req.url.match(cache_url_list[i])) {        return caches.match(req).then(function (resp) {            //...        })    }}

离线化缓存 / Cache For Offline

对于博客来说,并不是所有内容都是一成不变的。传统PWA采用SW更新同时刷新缓存,这样不够灵活,同时刷新缓存的版本号管理也存在着很大的漏洞,长时间访问极易造成庞大的缓存冗余。因此,对于博客的缓存,我们要保证用户每次获取都是最新的版本,但也要保证用户在离线时能看到最后一个版本的内容。

因此,针对博客来说,策略应该是先获取最新内容,然后更新本地缓存,最后返回最新内容;离线的时候,尝试访问最新内容会回退到缓存,如果缓存也没有,就回退到错误页面。

即:

Online:发起Request => 发起fetch => 更新Cache => 返回ResponseOffline:发起Request => 获取Cache => 返回Response
const handle = async (req) => {    return fetch(req.url).then(function (res) {        if (!res) { throw 'error' } //1        return caches.open(CACHE_NAME).then(function (cache) {            cache.delete(req);            cache.put(req, res.clone());            return res;        });    }).catch(function (err) {        return caches.match(req).then(function (resp) {            return resp || caches.match(new Request('/offline.html')) //2        })    })}

if (!res) { throw 'error' } 如果没有返回值,直接抛出错误,会被下面的Catch捕获,返回缓存或错误页面

return resp || caches.match(new Request('/offline.html')) 返回缓存获得的内容。如果没有,就返回从缓存中拿到的错误网页。此处offline.html应该在最开始的时候就缓存好

持久化存储 / Storage Persistently

由于sw中无window,我们不能使用localStoragesessionStorage。SW脚本会在所有页面都关闭或重载的时候丢失原先的数据。因此,如果想要使用持久化存储,我们只能使用CacheAPIIndexdDB

IndexdDB

这货结构表类型类似于SQL,能够存储JSON对象和数据内容,但版本更新及其操作非常麻烦,因此本文不对此做过多解释。

CacheAPI

这东西原本是用来缓存响应,但其本身的特性我们可以将其改造成一个简易的Key/Value数据表,可以存储文本/二进制,可扩展性远远比IndexdDB要好。

self.CACHE_NAME = 'SWHelperCache';self.db = {    read: (key) => {        return new Promise((resolve, reject) => {            caches.match(new Request(`https://LOCALCACHE/${encodeURIComponent(key)}`)).then(function (res) {                res.text().then(text => resolve(text))            }).catch(() => {                resolve(null)            })        })    },    read_arrayBuffer: (key) => {        return new Promise((resolve, reject) => {            caches.match(new Request(`https://LOCALCACHE/${encodeURIComponent(key)}`)).then(function (res) {                res.arrayBuffer().then(aB => resolve(aB))            }).catch(() => {                resolve(null)            })        })    },    write: (key, value) => {        return new Promise((resolve, reject) => {            caches.open(CACHE_NAME).then(function (cache) {                cache.put(new Request(`https://LOCALCACHE/${encodeURIComponent(key)}`), new Response(value));                resolve()            }).catch(() => {                reject()            })        })    }}

使用操作:

写入key,value:

await db.wtite(key,value)

以文本方式读取key:

await db.read(key)

以二进制方式读取key:

await db.read_arrayBuffer(key)

其余的blob读取、delete操作此处不过多阐述。

页面与SW通信 / Build Communication with Page and ServiceWorker

单向连接 / Unidirectional Connect

Clients To SW

浏览器 => SW

ServiceWorker中有一个非常简单的APIpostMessage,全路径为navigator.serviceWorker.controller.postMessage.

因此,如果你只是作为页面单方面传递给SW,此api是个不错的选择.

前端写法

const data = 123navigator.serviceWorker.controller.postMessage(data)//发送123

SW接收

self.addEventListener('message', (event) => {  console.log(event.data)  //输出123});

此方法可用于单方面向SW提交数据,但无需返回值.比如提示SW可以SkipWaiting,或者提交前端统计数据等等.

SW To Clients

首先,Clients必须要从SW中的一个event事件中获取,比如fetch.无法从message事件中获取client.

addEventListener('fetch', event => {    event.waitUntil(async function () {        if (!event.clientId) return;        const client = await clients.get(event.clientId);        if (!client) return;        const data = 123;        client.postMessage(data);    }());});

双向通讯 / Connect Each

浏览器 <=> SW

我们拥有两种方式双向通讯:

  1. Broadcast Channel API 多对多,广播形式.
  2. Message Channel 一对一

MessageChannel

顾名思义,MessageChannel API 设置了一个可以发送消息的通道。

该实现可以归结为3个步骤。

1.在两侧设置事件侦听器以接收message 事件
2.通过发送port并将其存储在SW中,建立与SW的连接。
3.使用存储的port回复客户端

前端写法

const messageChannel = new MessageChannel();navigator.serviceWorker.controller.postMessage({  type: 'INIT',//发送init信息,表示以port2为接收端[即SW的发送端]}, [messageChannel.port2]);messageChannel.port1.onmessage = (event) => {  //监听port1  navigator.serviceWorker.controller.postMessage({    type: 'PING'//发送PING  });};

SW端写法

self.addEventListener("message", event => {    const data = event.data;    if (!!data) {        switch (data.type) {            case 'INIT':                self.ClientPort = event.ports[0];            default:                self.ClientPort.postMessage({                    type: 'DATA',                    data: 'pong'                });        }    }})

然后查看控制台,你就会看到里面一直在乒乒乓乓,说明成功了.

我们简单的改写一下,变成异步形式传输数据:

const mCh = {        init: () => {            window.messageChannel = new MessageChannel();            navigator.serviceWorker.controller.postMessage({                type: 'INIT',            }, [messageChannel.port2]);        },        send: (data) => {            return new Promise((resolve, reject) => {                const uuid = (() => {                    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {                        var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);                        return v.toString(16);                    });                })()                navigator.serviceWorker.controller.postMessage({                    type: 'DATA',                    data: data,                    id: uuid                });                setTimeout(() => {                    reject({                        message: 'timeout',                        ok: false                    })                }, 2000);                messageChannel.port1.onmessage = (event) => {                    if (event.data.id === uuid) {                        resolve({                            message: event.data.data,                            ok: true                        })                    };                }            })        }    }

由于MessageChannel特性,一个port只要不是连续传输数据就会被断开.所以每次传输时我们要先初始化,后发送数据.

由于传输时无状态的,我们将每一个包都打上特定的uuid,返回包里也写上对应的uuid即可判断那个包是哪个对应的返回值.

SW端也要做一点点相应的改动

self.ClientPort.postMessage({//...id: data.id});

这样,一个兼容性较好的SW双向传输就解决了.

Broadcast Channel

请注意,BroadCast虽然写法建议,但是对浏览器兼容性要求非常高[Chrome 54,IOS Safari全线不支持].用此api请三思.

另外,由于是广播形式,一个页面如果有多个SW,他们会同时收到消息.

前端

const broadcast = new BroadcastChannel('Channel Name');const send_ping = () => {    broadcast.postMessage({        type: 'PING'    });}broadcast.onmessage = (event) => {    console.log('PONG')    setTimeout(() => {        send_ping()    }, 500);};send_ping()

SW端

const broadcast = new BroadcastChannel('Channel Name');broadcast.onmessage = (event) => {    broadcast.postMessage({ type: "PONG" })};

只要ChannelName对应,即可在里面顺利传输消息.

细节与注意 / Something Small But Need to Be Mentioned

无法修改的 Header

由于ServiceWorker本质上仍然属于浏览器,因此,你无法控制例如header中的Host\Refferer\Access-Control-Allow-Origin用于绕过防盗链\CORS\指定host选择ip

难以卸载的 SW

SW一大特性,一旦被安装,就不能通过传统方式卸载掉.

如果你直接删除sw.js文件并删除安装代码,那么,新用户是不会被安装的,但是原先已经安装过的用户sw会继续劫持这他们的网页,导致老用户网页不更新或者出现异常.

正确的删除方法是将安装代码改成卸载代码:

navigator.serviceWorker.getRegistrations().then(function(registrations) { for(let registration of registrations) {  registration.unregister()} })

并将sw内容改成透明代理,方便没有卸载的用户正常使用.

堵塞整个浏览器的代码

由于SW运行在DOM上下文,如果在sw中执行一些消耗资源的代码会直接耗尽浏览器资源,与dom不同的是,dom耗资源代码只会堵塞一个线程,另一个线程依旧可以正常工作,而sw一旦堵塞会将整个浏览器堵死.因此sw固然可以更快的计算,但万不可将一些极易死机的代码交给sw处理.

End

这篇文章结尾的很仓促,毕竟已经快拖了一个月了,也有很多东西没有讲清楚,未来可能会小修小改.

篇幅所限,一些sw其他功能并没有详细讲述,比如后台更新或者推送通知.这些功能在实际开发中并不是特别有用,或者在国内大环境下并不适合.可能这些功能会在下一篇文章,sw的实操中讲述.

停下笔的时候,hexo统计这篇文章已经将近1万字,阅读时间近100分钟.不过我认为这值得,毕竟sw就是这么一个凭借着奇思妙想就能绽放出Spark的事物.唯有独特的创造力才能激发无限可能.

当然,这篇文章讲述的都是些非常基础的东西,那在下一篇文章,我会贴出一个个demo,希望你们能对这些充满着智慧的样例激发你们的灵感.

另外,在祝贺你们,虎年大吉,新年快乐!

再见Z16,Hi Laffey!

作者 CYF
2021年4月19日 16:26

2019-7-16 本站建成,第二天,Z16进入了我的博客。今天,我更换了陪伴我641天的看板娘。

越来越多的模型采用了最新版本的Live2d Cubism 3或4【以下简称Live2d V3/V4】,而我用的hexo live2d插件hexo-helper-live2d已经很久没更新了,直接使用v3模型显然是不行的。

原来用的Z16【V2】模型地址:

https://npm.elemecdn.com/chenyfan-cdn@2.0.0/js/live2d-widget-model-z16/assets/z16.model.json

格式如下:

{    "version": "Live2DViewerEX Config 1.0",    "model": "moc/z16.moc",    "textures": [        "moc/z16.1024/texture_00.png"    ],    "layout": {        "center_x": 0,        "center_y": 0,        "width": 2    },    "motions": {        "idle": [            {                "file": "mtn/idle.mtn"            }        ]    },    "expressions": [        {            "name": "f00.exp.json",            "file": "exp/f00.exp.json"        }    ],    "physics": "z16.physics.json"}

而V3模型普遍长这样:

{    "Version": 3,    "FileReferences": {        "Moc": "lafei_4.moc3",        "Textures": [            "textures/texture_00.png"        ],        "Physics": "lafei_4.physics3.json",        "Motions": {            "": [                {                    "File": "motions/complete.motion3.json"                },                ...            ]        }    },    "Groups": [        {            "Target": "Parameter",            "Name": "LipSync",            "Ids": [                "ParamMouthOpenY"            ]        }    ]}

将所有除了Ver信息全部移入了FileReferencesKey里面。

当然,V3和V2的差别肯定不止json格式的差异,所以我们很显然要一个新的js加载V3。

更新V3/4 Core

Live2d官网已经决定后来的live2d版本都允许向前兼容到V3,所以V4的core还是能加载V3的模型[当然V2不行]

啃了一遍官文,我们首先需要这些js插件:

live2dcubismcore.jslive2dcubismframework.jspixi.jslive2dcubismpixi.js

首先说live2dcubismcore.js,这是一个急需注意的js,由于版权原因,你需要自行去官网下载再上传到自己的cdn,所以不能随便找一个人的网站就爬下来[包括我的],这样会收到官方警告的。

前往https://cubism.live2d.com/sdk-web/cubismcore/live2dcubismcore.min.js下载。

然后剩下的你可以选择嫖我的

https://cdn.jsdelivr.net/combine/npm/chenyfan-oss@2.0.3/pixi.min.js,npm/chenyfan-oss@2.0.3/live2dcubismframework.min.js,npm/chenyfan-oss@2.0.3/live2dcubismpixi.min.js

加载模型

我选择白嫖https://github.com/Himehane/live2d_on_website/blob/master/loadModel.js

当然这有一点点小问题,比如上面这么配置:

var baseModelPath = 'https://npm.elemecdn.com/chenyfan-oss@2.0.2'var modelNames = ["lafei_4"];

那么他会这么请求

https://npm.elemecdn.com/chenyfan-oss@2.0.2/lafei_4/lafei_4.model3.json

可是问题是我没有加一层文件夹。。。我的位置是

https://npm.elemecdn.com/chenyfan-oss@2.0.2/lafei_4.model3.json

修改loadModel 288行

- modelPath =  baseModelPath + modelName + "/" + modelName + ".model3.json";+ modelPath = baseModelPath + "/" + modelName + ".model3.json";

样式调整

首先给live2d一个div位置

<div id="live2d" class="live2d">    <canvas id="live2dm" class="live2d" style="z-index: 999!important;"></canvas></div>

然后样式微调

.live2d {    position: fixed;     left: -100px;    bottom: -20px;    width: 500px !important;    height: 437.5px !important;    z-index: 998;}

bottom将其固定页面底端,大小用!important强制固定【不规范写法,请勿模仿】

判断屏幕大小进行懒加载

嘿嘿,这么大的js怎么不能懒加载呢

function loadScript(src, callback) {        var script = document.createElement('script'),            head = document.getElementsByTagName('head')[0];        script.type = 'text/javascript';        script.charset = 'UTF-8';        script.src = src;        if (script.addEventListener) {            script.addEventListener('load', function () {                callback();            }, false);        } else if (script.attachEvent) {            script.attachEvent('onreadystatechange', function () {                var target = window.event.srcElement;                if (target.readyState == 'loaded') {                    callback();                }            });        }        head.appendChild(script);    }    function loadlive2d() {        if (document.body.clientWidth > 600) {            document.onreadystatechange = function () {                if (document.readyState == "complete") {                    loadScript('https://npm.elemecdn.com/chenyfan-os@0.0.0-r1/load.js',function(){                        loadModel();})                }            }        }    }loadlive2d()

document.body.clientWidth判断可见宽度,loadScript强制异步执行loadModel,丢上去就行了

然后你就可以正常使用live2d了,这是一个demo:

<style>    .live2d {        position: fixed;        left: -100px;        bottom: -20px;        width: 500px !important;        height: 437.5px !important;        z-index: 998;    }</style><div id="live2d" class="live2d">    <canvas id="live2dm" class="live2d" style="z-index: 999!important;"></canvas></div><script src="https://npm.elemecdn.com/chenyfan-oss@2.0.3"></script>

然后添加到Hexo就改模板吧…没什么好说的

HexoPlusPlus-从一个妄想到现实

作者 CYF
2021年2月11日 15:40

我一直都习惯在线写作,但因为口袋里没钱,不能买服务器用动态博客,使用Hexo,即使实现了集成部署,想要在github上直接书写,尤其是出门在外有所灵感,国内手机登陆github真的是极其糟糕的体验。博客本就是碎片化写作和高质量文章发布处,使用hexo却使我无法发挥博客的用处。

先前,我曾使用白嫖的Euserv搭建的Typecho,也是用过wordpress.com白嫖的wordpress,但两个都不符合我对速度和可用性的追求,一个连CloudFlare能不能连上都是问题,另一个中国支持贼差【虽然可以用万能Worker可以替换加解决,但是就是不想用啊】。免空的选择又难以择手,弄来弄去还是用回Hexo。

但是Hexo就是有一点不爽,每次使用的时候就必须要在本地进行构建静态网页,然后上传到GithubPage。后来实现了集成部署【没想到折腾了很长时间的集成部署最后用到这里了】,方便了不少,直接在Github上面改源代码。但相较于Typecho和Wordpress,没有后台的写作总感觉有点难受,每次更改源代码都要上Github,在国内这种大环境下总是不太好使的。

2020年最后一个月,我总是在想如何解决这个问题,我的要求很简单,能弄个在线书写环境就好了。

在当时,真的只是睡觉的时候想想,现在回头不禁感慨,这妄想真的实现了。

由于我的文件是存储在Github上,于是我第一个先去Github文档查找相关资料,果不其然,Github的API能够上传、删除、下载【废话】、列表文件,并且能通过base64上传,直接免去了手写头的问题.关于调用限制,没鉴权时每个ip每小时只有60次,但一旦鉴权每个用户每小时就有5000次。这些api完全能够支撑起一个在线写作的环境,https://developer.github.com/v3/guides/getting-started/更是详细讲解并提供了数个例子。

这篇文章,就是详细讲解我如何把这个梦想变成现实.具体步骤很多,请慢慢咀嚼

这篇不是使用文档,而是教程

原理 - GithubAPI

譬如罢,上传一个文件,首先你要鉴权,在header中写入:

content-type: "application/json;charset=UTF-8"Authorization: "token  ${hpp_githubimagetoken}"

Anyone,你也可以在url后面加上?access_token=传参,但是这样不安全,Github官方也是提示将在明年彻底禁用传参鉴权

但是记得GithubAPI不允许空User-Agent,所以你还得在header中加入UA:

user-agent: "GoogleChrome",

OK这么一搞鉴权这一块就完毕了,接下来,我们要搞基本功能

Github更改一个文件的url是一样的,为了方便接下来的书写和表达,我们统一将以下url称为RESTURL:

https://api.github.com/repos/${Github用户名}/${Github仓库名字}/contents/${Github文件路径}/${Github文件名}?ref=${Github分支}

拉取信息

默认情况下,直接GET RESTURL就能获取该文件/文件夹的信息,例如获取我AVorBV.md源文件,那么RESTURL如下:

https://api.github.com/repos/ChenYFan/blog/contents/source/_posts/AVorBV.md?ref=master

直接GET[我的是公开仓库,不需要鉴权就能获取],得到数据如下:

{"name": "AVorBV.md","path": "source/_posts/AVorBV.md","sha": "a0bd826f999a9bb73ac56251415f9e57199600a7","size": 15742,"url": "https://api.github.com/repos/ChenYFan/blog/contents/source/_posts/AVorBV.md?ref=master","html_url": "https://github.com/ChenYFan/blog/blob/master/source/_posts/AVorBV.md","git_url": "https://api.github.com/repos/ChenYFan/blog/git/blobs/a0bd826f999a9bb73ac56251415f9e57199600a7","download_url": "https://raw.githubusercontent.com/ChenYFan/blog/master/source/_posts/AVorBV.md","type": "file","content": "dGl0bGU6IEFWP0JWIQphdX...","encoding": "base64","_links": {"self": "https://api.github.com/repos/ChenYFan/blog/contents/source/_posts/AVorBV.md?ref=master","git": "https://api.github.com/repos/ChenYFan/blog/git/blobs/a0bd826f999a9bb73ac56251415f9e57199600a7","html": "https://github.com/ChenYFan/blog/blob/master/source/_posts/AVorBV.md"}}

这样子,我们只要提取json中的sha,就能知道到hash,进而进行修改.
但这样子有个非常尴尬的一点,单文件获取会把content一口气拿过来
例如下面的RESTURL

https://api.github.com/repos/ChenYFan/CDN/contents/img/hpp_upload/1612843011000.jpg?ref=master

你获取的时候会发现返回了这个:

{"message": "This API returns blobs up to 1 MB in size. The requested blob is too large to fetch via the API, but you can use the Git Data API to request blobs up to 100 MB in size.","errors": [{"resource": "Blob","field": "data","code": "too_large"}],"documentation_url": "https://docs.github.com/rest/reference/repos#get-repository-content"}

很显然,直接用GithubAPI不能获取单个文件的hash值

那怎么办?

答:列表获取

我们把之前的RESTURL去掉小尾巴,变成这样:

https://api.github.com/repos/ChenYFan/CDN/contents/img/hpp_upload?ref=master

这样就能获取这个目录下整个列表,然后用json循环查找遍历name,再通过name拉hash即可.

只是这样查询时间会略微变长.

新建

如果是新建,body中这么写

{    branch: ${上传的分支},    message: ${上传的信息},    content: ${base64过的文件},     sha: ""}

接着使用PUT形式访问RESTURL

创建成功后状态码应该返回:

201 Created

更新

body与新建类似,但是首先你要通过拉取信息获取该文件sha值.

{    branch: ${上传的分支},    message: ${上传的信息},    content: ${base64过的文件},     sha: "${此文件hash}"}

接着使用PUT形式访问RESTURL

更新成功后状态码应该返回:

200 OK

删除

相对来说,删除就更简单了

{    branch: ${删除文件的分支},    message: ${删除的信息},    sha: "${此文件hash}"}

hash这一步逃不掉,用DELETE形式访问RESTURL,返回200说明删除成功

原理 - CloudFlareWorkers

之前看过Laziji-VBlog项目,这个项目新颖的一点是将文章发布在gists,然后用户通过api访问获取.

但这样有两个致命问题:

1.API没鉴权,每小时单个ip只能访问60次,一开就爆
2.在国内受干扰,不稳定

并且什么迁入迁出麻烦、token容易忘记等等问题

最最最早版本中,我是打算纯静态实现文章编辑和更改的,但很快我就遇到了和VBlog一样的缺陷,这逼使我切换了平台。

好诶,既然直连效果那么差,我们就选择中继。利用服务器中继我们首先排除【用Hexo基本就是贪无服务器】。目前比较流行的无服务器平台有Heroku、CloudFlareWorker和Vercel,Heroku支持了多种服务器语言,CFWorker基于GoogleV8,因为JSProxy在国内意外走红,Vercel在国内拥有较好的运营商线路。

我们第一个排除heroku,冷启动唤醒需要10s,并且无法绑定域名【这里其实也可用worker反代(bushi】。目光看向worker和vercel,又有一个新问题出来,自定义配置存哪?

存变量里当然是个好主意,但是很难修改。外部存储也不是什么大问题,mongodb、firebase、Leancloud都可以上手,但我个人终究不喜欢为了查询而发送子请求。

由于我是OIer【虽然很差】,习惯使用C++的逻辑,因为JS的逻辑和C++其实差不多,所以我更倾向用WorkerJS书写。

非常赞的是,去年11月,CloudFlare官方宣布KV在一定额度内免费,并且免费额度喜人:

存:1GB大小读:10W次/天【注:这里和Worker免费版本调用次数相同】写:1k次/天删:1k次/天列:1k次/天单个限额:25MB

并且worker里面使用KV函数异常简单,绑定KVNAME后:

async function FUNCNAME(){await KVNAME.get(INDEX) //读await KVNAME.put(INDEX,VALUE) //写await KVNAME.delete(INDEX) //删}

按照官方文档的说法,实际读取与读取静态页面差不多,我写了个简单测试函数,根据时间戳判断,单次读取只需要不超过2ms。

并且worker有非常赞的fetch函数,无痛自定义header,拉取后端无压力。

好,那么就开始吧。

实现 - 迈出的第一步

首先你要绑定个监听器:

addEventListener("fetch", event => {  event.respondWith(handleRequest(event.request))})

由于fetch只能在async函数执行,于是我们写个async:

async function handleRequest(request) {return new Response()}

可以,这样我们就简单实现了一个无服务器函数

接下来的函数就应该在async这个主函数写。

然后是最基本的fetch,fetch应该说是worker里最特色的函数了。

如果直接返回,那么就不用加await,因为在async里面返回了一个await

return fetch('https://api.github.com/repos/ChenYFan/blog/contents/source/_posts')

如果要拉回来做运算,那么要加await,毕竟fetch返回的是promise

const res = await fetch('https://api.github.com/repos/ChenYFan/blog/contents/source/_posts')

CFWorker能用.text()函数和.json()函数处理返回的内容:

这地方我偷懒了,本来应该用then来获取promise的值,但是个人习惯了await嵌套写法,所以这地方写的其实不标准,轻喷

const first_name = await JSON.parse(await(await fetch('https://api.github.com/repos/ChenYFan/blog/contents/source/_posts')).text())[0]["name"]return new Response(first_name)

这个其实等价下面的:

const first_name = (await(await fetch('https://api.github.com/repos/ChenYFan/blog/contents/source/_posts')).json())[0]["name"]return new Response(first_name)

当然显然是下面的好写,但我习惯测试方便都用上面的

我们也可以通过自定义方式来自定义header完成鉴权和UA设置:

const getinit = {          method: "GET",          headers: {            "content-type": "application/json;charset=UTF-8",            "user-agent": `${USERAGENT}`,            "Authorization": `token ${TOKEN}`          },}const first_name = await JSON.parse(await(await fetch('https://api.github.com/repos/ChenYFan/blog/contents/source/_posts',getinit)).text())[0]["name"]return new Response(first_name)

那么接下来就很简单了。

实现 - 面板的设计

Worker支持返回数据的设置,我们可以通过修改content-type达到返回页面的效果,并且可以通过JS奇妙的语法完成PHP难以做到的事情。

首先先定义一个网页:

const re_html =  `<h1>Hello,World!</h1>`

然后要返回吧:

return new Response(re_html, {    headers: { "content-type": "text/html;charset=UTF-8" }})

这个地方content-type务必要设置,不然默认返回是文本形式

然后打开预览就能看到了:

然后关于拼接,其实完全不必用+连接,可以用``包裹,然后用${变量名}来代替

const inner = `Hello,World!`const re_html =  `<h1>${inner}</h1>`return new Response(re_html, {    headers: { "content-type": "text/html;charset=UTF-8" }})

这种写法帮我省下精力重看代码

面板怎么说,其实直接用material-dashboard套的

实现 - 后端API的设计

后端API本质上是一个中继,简单如我

废话不说直接上代码。

问题解决 - 存储问题

KV是能存东西.配置是符合键的形式的,一个键名配对一个键值,这和KV的存储方式相同.但是这么多配置项,如果一个一个读过去,KV迟早比worker早读爆.缓存没用,还得赔一个清除缓存的APIKey,太亏了.

所以HPP将所有配置JSON.stringify后存储到了一个键名为hpp_config的键.

那关于账户密码,难道不能存KV吗?

能,当然能,但是问题是如果在登录页面还要读KV,那被打了怎么办

况且,在粘贴代码完后到设置界面,中间有一段时间,万一有个人搞你咋办呢.

所以HPP学习Twikoo进行强鉴权,在保证不被盗取的情况下还能减少KV读取量,岂不美哉

问题解决 - 多层文件夹

默认情况下,访问无文件名的RESTURL会列出当前文件夹下的所有文件,但列不出文件夹下的文件.我们先看获取示例,以https://api.github.com/repos/ChenYFan/blog/contents/source/_drafts?ref=master为例子:

[    {        "name": "TEST.md",        "path": "source/_drafts/TEST.md",        "sha": "3b12464976a5fd9e07d67dd7d5cf4f0f10188410",        "size": 4,        "url": "https://api.github.com/repos/ChenYFan/blog/contents/source/_drafts/TEST.md?ref=master",        "html_url": "https://github.com/ChenYFan/blog/blob/master/source/_drafts/TEST.md",        "git_url": "https://api.github.com/repos/ChenYFan/blog/git/blobs/3b12464976a5fd9e07d67dd7d5cf4f0f10188410",        "download_url": "https://raw.githubusercontent.com/ChenYFan/blog/master/source/_drafts/TEST.md",        "type": "file",        "_links": {            "self": "https://api.github.com/repos/ChenYFan/blog/contents/source/_drafts/TEST.md?ref=master",            "git": "https://api.github.com/repos/ChenYFan/blog/git/blobs/3b12464976a5fd9e07d67dd7d5cf4f0f10188410",            "html": "https://github.com/ChenYFan/blog/blob/master/source/_drafts/TEST.md"        }    },    {        "name": "TEST",        "path": "source/_drafts/TEST",        "sha": "18391dac960bd390d4213818b7a79c63dcd2fb44",        "size": 0,        "url": "https://api.github.com/repos/ChenYFan/blog/contents/source/_drafts/TEST?ref=master",        "html_url": "https://github.com/ChenYFan/blog/tree/master/source/_drafts/TEST",        "git_url": "https://api.github.com/repos/ChenYFan/blog/git/trees/18391dac960bd390d4213818b7a79c63dcd2fb44",        "download_url": null,        "type": "dir",        "_links": {            "self": "https://api.github.com/repos/ChenYFan/blog/contents/source/_drafts/TEST?ref=master",            "git": "https://api.github.com/repos/ChenYFan/blog/git/trees/18391dac960bd390d4213818b7a79c63dcd2fb44",            "html": "https://github.com/ChenYFan/blog/tree/master/source/_drafts/TEST"        }    }]

文件夹是dir,文件是file,甚至可以通过self往下找,连路径都不用拼接了,那事情就好办了,写个搜索递归吧.

这个地方在群里我一直和2X吵架,因为我觉得此处用广搜比较好,然后我一直想写BFS,结果写着写着就成DFS了,你甚至现在还能看到一个叫fetch_bfs的函数

async function fetch_bfs(arr, url, getinit) { //开始深搜          try {            const hpp_getlist = await JSON.parse(await (await fetch(url, hpp_githubgetdocinit)).text()) //拉取github列表            for (var i = 0; i < getJsonLength(hpp_getlist); i++) { //循环查找              if (hpp_getlist[i]["type"] != "dir") { //如果不是文件夹                arr.push(hpp_getlist[i])//弹到目标数组末尾              } else { //否则                await fetch_bfs(arr, hpp_getlist[i]["_links"]["self"], getinit) //进入该文件夹深搜              }            }            return arr;          } catch (e) { return {} }}

代码本意很简单,传入一个空数组,抓取列表,循环递归,如果不是文件夹就扔到数组,是的话就向下搜索其实就是DFS嘛

try的原因是因为莫些人没有草稿,不用try的话这个函数就会炸,没草稿返回空数组。

然后就试试呗,以获取草稿列表为例:

if (path == "/hpp/admin/api/get_draftlist") { //判断路径          let hpp_doc_draft_list_index = await KVNAME.get("hpp_doc_draft_list_index") //获取索引          if (hpp_doc_draft_list_index === null) {//如果没有索引            const filepath = githubdocdraftpath.substr(0, (githubdocdraftpath).length - 1) //分离路径            const url = `https://api.github.com/repos/${hpp_githubdocusername}/${hpp_githubdocrepo}/contents${filepath}?ref=${hpp_githubdocbranch}` //拼接RESTURL            hpp_doc_draft_list_index = await JSON.stringify(await fetch_bfs([], url, hpp_githubgetdocinit)) //开始深搜            await KVNAME.put("hpp_doc_draft_list_index", hpp_doc_draft_list_index) //保存索引          }          return new Response(hpp_doc_draft_list_index, { //返回路径            headers: {              "content-type": "application/json;charset=UTF-8",              "Access-Control-Allow-Origin": hpp_cors            }          })}

我们来做个小实验:

结构如下:

-source/_drafts  ~TEST.md  -TEST    ~TEST.md    -TEST      ~TEST.md      -TEST        ~TEST.md

那么CloudFlareWorker会这样搜索:

其实我本来想这样的

【考虑到大多数人都没有建立文件夹的习惯,本来bfs的效率会更高的(´இ皿இ`)】

【但其实两者子请求数目是一样的】

我们去CloudFlare发一个请求啊,结果非常Amazing啊:

dfs完美解决嵌套问题。

问题解决 - 缓存问题

手机端POST之谜

之前开发网页的时候,我总是希望缓存越长越好,因为有些资源从来没有变过却要重复使用。于是,我给博客加上了ServiceWorker这就是我咕咕咕的理由

但hpp不能进行太强的缓存,否则可能造成获取文件不够及时.

于是,在文章获取这一块,我故意将get写成post,发送空值,电脑端乖乖的每次都把请求发出去,毫无异常.

然后手机端炸了

万万没想到,safari会将post请求给缓存了

缓存也就罢了,结果ajax连onreadystatechange都缓存了不返回,然后接下去的函数全炸了

没办法,只好在post里面加时间戳

ajax.send(new Date().getTime());

文章索引问题

然后是索引问题【本质上是把结果缓存在KV里】,因为在文件夹众多的情况下dfs会将每个文件夹找过去,先不说时间这个问题(毕竟一次子请求大约在60ms-150ms徘徊,文件夹多的情况下也尚能忍受),主要是文件夹一多,子请求跟着多起来了,worker子请求超时是30s(10ms是运算时间,我寻思只要没有上亿篇文章,加个数组应该不会炸10ms时间),并且子请求算总请求,要是这么搞一次,worker怕是不够用了,所以得加个KV强缓存:

await KVNAME.put("hpp_doc_list_index", hpp_doc_list_index)await KVNAME.put("hpp_doc_draft_list_index", hpp_doc_draft_list_index)

在发布、删除等可能会导致缓存失效的情况下清除KV缓存:

await KVNAME.del("hpp_doc_list_index")await KVNAME.del("hpp_doc_draft_list_index")

功能实现 - 自动更新

这怕是所有Worker程序里面第一个实现自动更新的程序了【所以我最近发包很快啊】

其实刚开始没想到这么多,后来@MCSeekeri 开了#21,其中提到了这一点,然后我就开了#23

查一遍CloudFlareAPI文档,我们就会发现这做起来简直轻而易举:

curl -X PUT "https://api.cloudflare.com/client/v4/accounts/9a7806061c88ada191ed06f989cc3dac/workers/scripts/this-is_my_script-01" \     -H "X-Auth-Email: user@example.com" \     -H "X-Auth-Key: c2547eb745079dac9320b638f5e225cf483cc5cfdda41" \     -H "Content-Type: application/javascript" \--data "addEventListener('fetch', event => { event.respondWith(fetch(event.request)) })"

curl,我寻思fetch也能做到.

const update_script = await (await fetch(`https://raw.githubusercontent.com/HexoPlusPlus/HexoPlusPlus/main/index.js`)).text() //获取更新脚本const up_init = {            body: update_script,//更新脚本内容            method: "PUT",//method是put            headers: {              "content-type": "application/javascript",//content-type和文档一样              "X-Auth-Key": hpp_CF_Auth_Key,//GlobalKey,账户最高Token              "X-Auth-Email": hpp_Auth_Email//登录邮箱            }}          const update_resul = await (await fetch(`https://api.cloudflare.com/client/v4/accounts/${hpp_account_identifier}/workers/scripts/${hpp_script_name}`, up_init)).text()//拼接workerid,请求url,上传          return new Response(JSON.parse(update_resul)["success"])//查询更新状态

OK那没问题了,手动更新完成.

那自动更新呢?

目前自动更新理论上可以实现,使用CronJob每天定时执行函数.
但我懒

其实你也可以用其它什么能定时访问的带上cookie访问/hpp/admin/api/update就行

功能实现 - 文章管理&草稿

其实就是上传文件

当然因为是hexo,本来就有/source/_posts/source/_drafts两个草稿分区,所以在1.1.0版本,将docpath改为了docroot,通过定位hexo根目录来实现全站自适应管理.

功能实现 - 图床

我知道有很多人还是困扰于图床这个问题,PicGo虽然能实现上传,但是配置一大堆,麻烦,并且配置不能随意迁移;PicX也使用Github+JSD做图床,但是没有中继速度慢,国内难以上传.

其实还是上传文件

但是我们必须知道,CFWorker单次执行最多10ms,正常图片三四百KB,在worker里base64,这能不超时我把CF吃了.

没办法,我们只能在前端进行base64,然后将编码后的值直接上传,用Worker中继.

功能实现 - 说说

这个最早受Artitalk影响,在artitalk官方群里潜伏了一年,我明确知道说说这一块的用户需求是多么大,并且大多数都是小白,不想用太多配置。

于是,HPP_TALK诞生了。诞生的初衷就是简化发布和配置流程,在1.1.2版本版本中自带了一个预览页面,实现了无域名也能使用说说。

'说说发布页面'

'用户查看界面'

甚至支持自定义主题:

'由2X开发的说说主题'

HPPTALK配置也简单,后端配置可以直接缺省发布,而前段也只要传递4个变量。

【但是我但是傻乎乎用了cookie记录,下次绝壁用LocalStr】

功能实现 - TwikooPlus

其实这个东西写的很粗糙,大家就看看行了哈

Twikoo首次匿名登录实在把我看傻了,6个请求,放国外不得炸掉.

然后就看,实际上只有前面几个有效的,后面其实是获取配置.

首先规定一下RESTURL=https://tcb-api.tencentcloudapi.com/web?env=${ENVID}

三步走:

1.空手拉refresh_token2.用refresh_token套access_token3.用access_token套评论

其中refresh_token两小时有效,access_token30天有效

那就很有意思了同学们:

async function get_refresh_token() {        /*第一步获得refresh_token*/        const step_1_body = {          action: "auth.signInAnonymously",          anonymous_uuid: "",          dataVersion: "1970-1-1",          env: env_id,          refresh_token: "",          seqId: ""        }        const step_1 = {          body: JSON.stringify(step_1_body),          method: "POST",          headers: {            "content-type": "application/json;charset=UTF-8"          }        }        /*refresh_token到手*/        //console.log(step_1_body)        return JSON.parse(await (await fetch(url, step_1)).text())["refresh_token"]      }      async function get_access_token(refresh_token) {        const step_2_body = {          action: "auth.fetchAccessTokenWithRefreshToken",          anonymous_uuid: "",          dataVersion: "1970-1-1",          env: env_id,          refresh_token: refresh_token,          seqId: ""        }        const step_2 = {          body: JSON.stringify(step_2_body),          method: "POST",          headers: {            "content-type": "application/json;charset=UTF-8"          }        }        /*access_token到手*/        return JSON.parse(await (await fetch(url, step_2)).text())["access_token"];      }      async function get_comment(access_token, path, before) {        const re_data = { "event": "COMMENT_GET", "url": path, "before": before }        const step_3_body = {          access_token: access_token,          action: "functions.invokeFunction",          dataVersion: "1970-1-1",//开始时间          env: env_id,          function_name: "twikoo",          request_data: JSON.stringify(re_data),          seqId: ""        }        const step_3 = {          body: JSON.stringify(step_3_body),          method: "POST",          headers: {            "content-type": "application/json;charset=UTF-8"          }        }        return (await (await fetch(url, step_3)).text())      }

这里要注意以下,套评论的时候要传递两个参数pathbefore,path是当前文章路径,before是上一条评论的创建时间戳CreatedAt

然后使用的时候来一波:

refresh_token = await get_refresh_token()access_token = await get_access_token(refresh_token)val = await get_comment(access_token, path, before)

同时用KV缓存

await KVNAME.put("hpp_comment_refresh_token", refresh_token)await KVNAME.put("hpp_comment_access_token", access_token)

OK起飞

问题解决 - EditorMD移动端问题

本来HPP开始写的时候就是用EditorMD的,好康,功能多.

但是很快手机端就炸出问题了:

安卓:打一个字换一行
苹果:打一个字复制一遍

非常有问题,原仓库有一个Close的issues说把codemirror更新到最新版本就行,但是我更新到5.x最后一个版本问题仍复发.

Github上面大多数编辑器也用的是CodeMirror.

然后找了半天实在没有解决方案,就花一个下午时间手写了一个编辑器

用的是最基础的textarea,这能出兼容性问题我把Github整个吃了

预览功能是靠markedjs通过调整display在一个div里面预览,在1.1.0版本支持了代码高亮.

话说手写一个很多功能就很好集成了诶

结尾

还有很多开发细节想不起来了,先水到这里了,滚回去修bug了

QQ群:467731779

最后加一句,用HPP时CI强烈建议使用GithubAction并公开,我是也不明白我Travis-CI怎么把积分耗完的

预计会添加的功能:

  • Hexo、主题配置修改
  • 输入框粘贴上传图片
  • 友链系统
  • 基于KV/IPFS的自动保存功能
  • 列表分页功能
  • 博主工具箱

无服务器搭建Artalk评论系统后端

作者 CYF
2020年10月1日 16:57

这篇的无服务器部署Artalk,指的是NoServer而不是Serverless

这篇所写的是部署后端,关于前端部署十分简单,这里不多阐述

Artalk,一款简洁有趣的自托管评论系统。此时,Valine作为老大哥就不得不跳出来了。但是,作为Valine的Leancloud作为第三方托管,数据放在别人那里总是不舒服的,譬如2020/9/24Leancloud华北节点云引擎被 DDoS 攻击

image-20201001071514956

或者说LeanCloud将开发版限额一限在限,亦或者leancloud多次宕机,作为自由开放的我自然不舒服。加上leancloud开发版的SLA实在令人担忧【不包括休眠时间,一个月内宕机超过20次(不过leancloud开发版确实没有保证SLA)】,以及比较严重的管理员冒充。我一直再找一个能用自己服务器托管评论系统,真巧,我找到了Artalk。Artalk的优点:

  • 轻量简洁 (~23kB gzipped)

  • 有趣有爱

  • 自托管

  • Markdown

  • 表情自定

  • 滑稽表情包

  • 管理员密码,防冒名

  • 验证码,提交频率限制

  • 通知中心,邮件提醒

  • 自定义某些页面仅管理员可评论

  • 无限层级回复

  • 滚动加载更多

  • 评论折叠

  • 一页多个评论

  • TypeScript

  • 提交频繁验证码

  • 无数据库

    当然没有垃圾评论检测就很蛋疼

有服务器部署起来相当简单,宝塔【虽然被炸0day,不过修修补补还能用】+Artalk能实现5分钟部署完毕【Jalen的Artalk 自托管评论系统的后端部署】,但是,习惯Valine的群友一看到后端部署就立刻皱起了眉头:我没有服务器,怎么办?

Artalk的后端是PHP的,虽然作者也承诺了会开发别的后端Go API / Node API / Python API,但迟迟没有写出来,考虑到QWQCODE是个学生【我也是】,那么咕咕咕就情有可原了。

实际上,我用的是Euserv白嫖的,至少SLA还是过的去【>=99%】,但是无论是申请还是部署都非常麻烦。此时,我就在想,既然有免费的php托管商,何苦不用呢?

目前找到两个:Gearhost和000webhost

注意,heroku虽然也有免费容器部署php,但是heroku是沙盒制度,一个评论存储为文件后会删除,所以heroku并不适合作为artalk后端。

安装

设置Artalk

与其它评论系统不同,artalk本身并没有做到开箱即用这一特点,所以,你还要做一些事先准备。

GithubAction+Composer安装

Artalk为了缩小原文件大小,并没有安装好依赖,依赖需要你自己安装。

如果你本地有composer,那就直接克隆本地运行composer。但不论其便携性还是效率都不高【composer安装起来比较麻烦】,所以,我建议此处用GithubAction实现composer安装。当然你有composer环境就可以直接clone在本地安装。

进入原项目,Fork到你自己的账户

image-20201001073812407

image-20201001073913581

新建一个文件,文件名为:.github/workflows/composer.yml

内容为:

name: composeron:   push:    branches:       - master # build master branch onlyjobs:  download:    runs-on: ubuntu-latest    steps:    - name: Checkout      uses: actions/checkout@v2      with:        ref: master    - name: Install      run: |        npm install composer        composer install                    - name: Deploy      uses: peaceiris/actions-gh-pages@v3      with:        github_token: ${{ secrets.TOKEN }}        publish_dir: ./

image-20201001074023800

设置Secret,NAME为TOKEN,内容为你的GithubTOKEN.【Token的获取与这篇文章关联不大】,请自行百度。

image-20201001081902077

image-20201001081941932

回到仓库,新建一个空白的.htaccess 文件夹,里面什么都不写[1],并删除根目录底下的.gitignore触发GithubAction

此处必须删除.gitignore【或者你自行修改】,否则接下来出错一律不管

稍后即部署完毕

本地修改配置文件

Clone你的仓库,指定为gh-pages分支

git clone -b gh-pages https://github.com/ChenYFan-Tester/Artalk-API-PHP.git

-b是强制指定分支的意思

速度慢试试github.com.cnpmjs.org,阿里github镜像

image-20201001082653387

打开并修改Config.example.php 具体参照官方文档

完毕后退出,将Config.example.php重命名为Config.php

此时,你的artalk安装终于告一段落,但是,你还没有将他们部署上去.

部署

Gearhost

Gearhost其实是一个小有名气的托管商,Free计划提供了最高一线程、每小时256MB内存、每小时5%CPU周期和每月1GB流量,作为评论托管是完全足够的。并且不需要信用卡。

进入Gearhost注册一个账号,新建一个免费的CloudSite。

image-20201001083231112

image-20201001083258674

构建完毕后稍等片刻,进入面板设置:

image-20201001083743275

PHP版本设置为7.1

image-20201001085602629

Virtual Directories网址设置为/路径设置为site\wwwroot\public\

image-20201001083959803

进入Publish选项卡,勾选Local GitActivate这个方式

注意,我强烈不推荐你使用FTP上传,FTP看起来有图形化很方便,但是请注意,Composer后的文件将近300+,FTP最致命的上传方式是每上传一个文件就会握一次手,这样子会严重浪费你的时间。而是用Github链接的同学我就要考虑你的危险的想法了,如果没有将仓库设置为Private,那么用Github链接是一个非常不明智的选择

image-20201001084334323

绑定git,上传三步走,git init && git add . && git commit -m "OHH" && git push website master

绑定域名什么不多说了,建议套一层CloudFlare。

Gearhost所用的共享ip,来自美国 科罗拉多州 丹佛,三网优化都不好。当然回源CloudFlare还是不错的。

DEMO:https://artalk-pub1.cyfan.top/

SLA:还在测试,大约95%,你可以前往https://status.cyfan.top查看详情

在页面id为12345有几个测试评论,你可以去测试一下,跨域均设置为’*‘。

管理员用户名:admin

管理员邮箱:admin@admin.admin

管理员密码:admin

000webhost

000webhost也是个著名的免费php托管商,虽然早年的种种行为看着十分恶心,但是好歹也是个能白嫖的托管商。000webhost提供了每个账户一个免费的容器,每个容器每月3GB流量、300MB空间、1w个文件和每天25次邮箱发送。

000webhost的部署相对简单些,直接将所有文件拖拽上传【因为它不支持git上传】,稍等即可

image-20201001150747082

上传至public_html子文件夹下:

image-20201001151009729

000webhost不支持设置运行目录,这意味着data文件夹将会被曝光,但是我们可以设置000webhost的但目录密码保护:

image-20201001151719382

这样,当有人试图访问data/comments.data.json 时,就会遭到密码拦截。

绑定域名

由于000webhost域名验证需要一段时间,请先前往域名托管商设置记录。比如我的app名字是XXX.000webhostapp.com,我要绑定的是artalk-pub2.cyfan.top,就应该这样设置:

image-20201001152944348

请注意000webhost验证域名是通过dns记录来验证的,在验证完毕前请不要开启CDN!

绑定域名,请鼠标移至卡片上,点击QuickActions,点击Details

image-20201001152257105

image-20201001152417027

点击My Domains,进入设置界面:

image-20201001153112150

点击Add domain

image-20201001153038499

选择PointDomain【毕竟把ns改到000webhost是不可能的】

image-20201001153224077

如实填写,稍等即可。

DEMO:https://artalk-pub2.cyfan.top/public/

SLA:还在测试,大约90%,你可以前往https://status.cyfan.top查看详情

000webhost默认线路烂的和shit一样,强烈建议套CloudFlare

和另一个demo一样在页面id为12345有几个测试评论,你可以去测试一下,跨域为’*‘。

管理员用户名:admin

管理员邮箱:admin@admin.admin

管理员密码:admin

000webhost对于这些不能展现他的广告徽标的账户可能会存在限制处理,请注意【老恶心了】。

最后

国庆作业有点多,这篇要不是被我亲爱的群友催的要紧,我也不会水这一篇啊呜呜呜。

如果你的artalk卡在了转圈圈的问题上,你可以在html前面加上这一句

<link href="https://XXX.XXX.XXX/index.php" rel="preconnect" crossorigin>

preconnect可以强制在渲染页面试并发一个请求,可以有效解决5s超时问题和并发过多不稳定导致cancel问题。

另外你也可以用我的artalk脚本:

https://cdn.jsdelivr.net/gh/ChenYFan-Tester/Artalk@gh-pages/Artalk.jshttps://cdn.jsdelivr.net/gh/ChenYFan-Tester/Artalk@gh-pages/Artalk.css
  • 修改超时时间为60s
  • 掩盖artalk标识
  • 杰哥提示语

如果你觉得不放心,你可以亲自检查我做了什么,我会尽量保证与原仓库同步。

国庆快乐!写作业去了

  1. 此处不写.htaccess,gearhost就会莫名其妙炸500错误

图床的千层套路

作者 CYF
2020年9月12日 21:27

博客最近在细心打磨终于上95分了,其中我认为图片功劳不可没。

2020年8月9日Jsdelivr发布了一次使用政策:Create Acceptable Use Policy,其中第4条Prohibited Use引起了众多议论:

4. Prohibited UseThe following behavior is prohibited: 1. Hosting or accessing content that:     - contains malware or harmful code in any form,     - violates proprietary rights of others,     - is sexually explicit,     - is potentially illegal in the EU or the USA. 2. Abusing the service and its resources, or using jsDelivr as a general-purpose    file or media hosting service. This includes, for example:     - running an image hosting website and using jsDelivr as a storage for all       uploaded images,     - hosting videos, file backups, or other files in large quantities.    We recognize that there are legitimate projects that consist of a large number    of files, and these are not considered abuse. For example: icons packs, apps,    or games with a large number of assets.

其中running an image hosting website and using jsDelivr as a storage for all uploaded images 这一句相当的有歧义,要多少的图片才能算是图站?博客里面图片放里面算吗?上传的图片怎样才不行?

反观网上流传的白嫖Github做图床,基本点进去都是https://cdn.jsdelivr.net/gh/ 这样子的图床,这种行为,我不敢妄加评论。但是,jsdelivr诞生的意义似乎并不是为了图床而生的,这种行为也很难判断成滥用。

使用政策发布之后,一时间,QQ群、v2ex、知乎上立刻就炸了锅。很多人猜测jsdelivr是不是滥用过度而禁止将其作为图床?免费图床的白嫖日子要结束了吗?更多的人,是在哭诉和询问那里还有像jsd一样优秀的图床可以白嫖,微博炸了,那里还有免费图床啊?

实际上,我一般采用的是BackBlaze+CloudFlare 但是自从八月底移动开始改道,从原先优秀的CMI绕路LAX后,国内CloudFlare访问质量再次暴跌,这不得不使我将博客迁至Vercel。好在八月份我有幸申请到了doegdoge图床使用权限,获得了国内较高速的图床.

但是,对于哪些没有没有图床的人来说,免费图床真的这么难以获得吗?

不好意思,免费图床非常多,只是你不会用而已,这篇文章,就是拯救面前陷入图床危机的你【当然是面向小白,大佬也可以在底下给我提意见鸭】。

公益图床

sm.ms

https://sm.ms

推荐程度:★

首先推荐的是这个图床,loli.net域名经典重现。三年前此图床域名还有备案采用的是国内CDN,可惜后来因为滥用吊销备案号而被迫迁移国外,用的是CloudFlare。实际使用效果面向国内确实不太好,建议备用。

你不需要注册,拖拽直接上传,只要不违反大陆和香港法律,他就能永久保留你的图片

可搭配PicGo

Imgur

https://imgur.com

推荐程度:★★

国外一家牛逼的图片托管服务商,你可以选择注册或不注册,同样的,拖拽上传,永久保留,其SLA有着相当高的保证。

然而很可惜的是,这种网站很早就在国内被DNS域名污染,也就意味着访客无法正常加载你的图片。这也就是被打为两颗星的原因。

当然,你也可以通过#图像缓存服务 从而实现国内访问。

可搭配PicGo【需注册】

去不图床

https://7bu.top/

推荐程度:★★★★

杜老师提供的个人公益图床,存储于阿里和腾讯的COS,官方保证SLA>=99%,是一个不错的选择,当然,7bu毕竟是个人维护的图床,能不能永久撑下去还是个问题,我也没有做过深度评测,无法表明其可用性。

可搭配PicGo。

接口地址:https://7bu.top/api/uploadpost参数:image回调json:data.url

更准确的API文档

而且,就在我上传测试图片的时候,明明已经表明图片已经上传,打开却发现COS提示404,这一点我不得陷入思考,个人维持的公益项目真的能保证SLA吗?

昨天上传的时候撞上服务器维修了,很抱歉做出了不够恰当的评价.7bu采用的是全国腾讯云CDN加速,国内访问速度十分优良。然而请注意,7bu刚开始建立的目的并不是面向全球【仅面对中国大陆游客】,这导致其大陆以外基本解析至国内西藏腾讯,访问效果并不好。并且,这是通过腾讯云的鉴黄,可能会存在误杀行为。具体使用请个人斟酌【不过作为开发环境还是可行的】。

白嫖的

阿里图床

推荐程度:★★★★

我个人搭建的API:https://picbed.cyfan.top 不保证上传SLA

由于小鸡联通国内网络不太好,很有可能无法正常上传,原项目已经开源 ,你完全可以通过在国内的机子或者是本地搭建以获得更佳体验。

如果上传成功了,图片将会托管于阿里云的CDN,无论是速度还是延迟都相当的优秀。

官方大厂,下载SLA有保障。

可搭配PicGo。

接口地址:https://picbed.cyfan.top/update.phppost参数:file回调json:data.url

已失效,切勿使用

DogeDoge图床

推荐程度:★★★★★

TEST

其实很早就看到V2EX的那篇征文了

可是当时我不够优秀啊虽然现在同样不优秀,博客也没满一年啊,于是白嫖的心态搁浅了。

后来突然看到Jalen的博客也用了DogeDoge图床,这才突然意识到原来我已经满一年了。于是抱着试试看的心态向doge官方邮箱发送了邮件,结果真过了。。。

dogedoge拥有着国内相当不错的CDN,国内访问飞快,但是国外的访问质量的确不如人意。【反正此博客面向中国大陆】

而且,DogeDoge拥有着很良心的处理参数:

w:宽h:高mode:模式 - crop 裁剪、clip 缩略fmt:格式 - jpg、png、webp(原图为 gif,且没有 frame 参数时,不做任何裁切、缩略处理)frame:1 - EOF帧,默认为 1 (对动画有效)q:压缩质量 - 1 - 100(默认 90 rect:指定位置裁剪 - top,left,w,h(若与 w / h 参数同时存在,则 会在 rect 裁剪过后,继续按照 w / h 的要求缩略)pos:(配合 w / h )裁剪位置 - top-left、top、top-right、left、center、right、bottom-left、bottom、bottom-right,默认为centerpos 还有一个特殊的值 auto,该值目前为 alpha 状态,可以根据图片重点来进行 pos 的位置取舍。

话说回来,DogeDoge也可以搭配PicGo使用。

接口地址:https://www.dogedoge.com/tools/upload/{Your_Token}post参数:file回调json:data.o_url

当然,现在的Doge图床还是处于免费的试用期【Creater】,不过好在试用期过后价格也比较合理,一般的tester也足够使用,目前看来SLA还是不错的。

不过,申请不到dogedoge图床也没关系,看下去你就会发现,白嫖的路千千万万,何必执着于一条。

BackBlaze

推荐程度:★★★

具体可以看看这篇文章

千奇百怪的

Github+JSDelivr

正如我所说的,这种组合已经被广大博主所采纳,并且网上教程已经泛滥了,在这里不再阐述。

npm+JSDelivr&&Zhimg&&bdstatic&&自定义镜像

推荐程度:★★★★★

为什么很多文章都没有提到用npm做图床?我想其中很大的原因是,白嫖jsd做图床的,很多都是小白【或者不愿花时间在于此的大佬】,同样的,这些文章面向的都是这些人,毕竟,以拖拽方式上传的Github和命令行方式上传,我想,大都数人会选择前者吧。

可是,你们没有想到的是,github文件镜像【github.com.cnpmjs.org是站点镜像】只有jsd一个,npm镜像可远远不止这一个啊!

让我们看看分别镜像在jsd、zhimg、bdstatic的文件怎么样:

【unpkg镜像用的是CloudFlare,国内加速效果不好,暂时不写】

jsd就不必多说了,国内拥有强劲的网宿节点支撑【虽然以前出现过网宿下游投毒】,速度丝滑无比,国外也有强劲的CloudFlare上岗,可谓国内外两不误。而且,jsd对于npm的package单文件没有大小限制,也就是说泡个视频也不是问题。

zhimg是知乎的unpkg镜像,也是一个不错的选择【阿里CDN】,知乎官方也未对此做出限制,日常使用是可以的。

bdstatic是百度的内用npm镜像,速度也很好【百度CDN】,但是请注意,bdstatic作为内用cdn,其拉取频率较慢,经常出现无法及时更新。

啊哈?不会上传?
npm
官网注册个账号去,然后先:

npm login

接着:

npm initnpm publish

请注意,如果你之前用过淘宝镜像,那么请先手动切回源:

npm config set registry https://registry.npmjs.org

每一次发布图片后,你可以将原来的图片删除,更改package.json 版本号【向上增加】,然后npm publish即可

这个似乎可以搭配picgo,不过好像没这个插件,写起来也麻烦。。。

unpkg的国内镜像其实远远不止这些,包括七牛、饿了么、腾讯都有,不过这个就要自己找了。

一些推荐的npm【or unpkg镜像】:

【jsd出品,网宿国内节点】https://npm.elemecdn.com/【知乎出品,网宿国内节点】https://npm.elemecdn.com/【百度出品,网宿国内节点】https://code.bdstatic.com/npm/【饿了么出品,网宿国内节点,回源是Unpkg,建议用这个】https://npm.elemecdn.com/【饿了么出品,网宿国内节点,回源是JSdelivr,貌似可以用github,但是我用的时候大多无法正常回源,只能获取几个已缓存的热门库】https://shadow.elemecdn.com/npm/【怎么都是网宿的】

或者说,你还可以自建unpkg镜像。

啊,你说你没有服务器反向代理unpkg?

其实,七牛的对象存储,腾讯的COS和阿里的OSS都是支持镜像回源的鸭!

七牛http流量每月免费10GB,腾讯的国内免费60GB6个月,作为自用完全足够了!

ipfs

我曾经写过关于ipfs的讲解 ,作为一个去中心化的存储系统拿来做公开图床其实挺不错的。

我个人搭建的ipfs镜像【托管于CloudFlareWorkers】:https://ipfs.cyfan.top

我个人搭建的ipfs上传API:https://ipfsupload.cyfan.top

基于Vercel+CloudFlare【我也不知道worker为什么死活上传不上去】

接口地址:https://ipfsupload.cyfan.top/api/v0/add?pin=truepost参数:file回调json:Hash

此处Hash获得的是文件的Qmhash,你还要依托ipfs镜像,如https://ipfs.cyfan.top/ipfs/{QmHash}

顺便收录一些ipfs网关【可访问】:

【北京 阿里云】https://hashnews.k1ic.com/【香港 阿里云】https://ipfs.jbb.one/【美国 DigitalOcean】https://ipfs.telos.miami/【Amazon】https://ipfs.oceanprotocol.com/

你可以在https://ipfs.github.io/public-gateway-checker/找到更多

图片缓存服务

正如##Imgur所说的,imgur在国内已经无法访问了,但是,图片缓存服务可以啊!

收集了一些图片缓存服务:

【国内网宿节点,只能加载特定图床图片如imgur】https://search.pstatic.net/common/?src=【Akamai节点,没有使用限制】https://imageproxy.pimg.tw/resize?url=【CloudFlare节点】https://images.weserv.nl/?url=【CloudFlare节点】https://pic1.xuehuaimg.com/proxy/

PicGo的搭配使用

PicGo默认已经集成了部分图床,其拖拽上传、自动复制剪贴板实在赢得了无数人的心。但是,对于一些冷门的图床支持似乎就不太好,这时候你需要用自定义web图床实现这一切:

我在上方介绍的图床如果支持web端上传,基本上就会写一个post请求,你可以依葫芦画瓢填写进去

这样子你就可以实现较为丝滑的上传图片了:

【为了压缩方便删除了部分帧】

后言

实际上最保险的莫过于使用各大厂商的对象存储,当然这笔钱不大好使。
你也可以用自己的VPS搭建Chevereto,当然前提是你有VPS

多域名线程并发与DNS预解析

作者 CYF
2020年8月24日 21:08

军训结束,感想不多,就一句话

【这就是个无限循环,每天重复训练一套动作,直到结束为止,毫无意义】

回家之后我帮父母看店,顺便点开了浏览器,浏览器卡死了30s,终于完全加载出了一个花花绿绿的主页,是大名鼎鼎的2345主页.

虽然我很反感这种杂七杂八的傻逼导航,我也试图将主页换成magi或dogedoge,但是父亲就很不习惯,原因很难以接受:

儿子啊,这样我怎么看天气啊。

。。。

于是我顺手右键点开了源代码,闲着无聊看看,结果却刚看看就找到了好看的东西:

<link rel="dns-prefetch" href="https://union2.50bang.org"><link rel="dns-prefetch" href="//h.2345cdn.net"><link rel="dns-prefetch" href="//image.2345.com"><link rel="dns-prefetch" href="//00imgmini.eastday.com"><link rel="dns-prefetch" href="//01imgmini.eastday.com"><link rel="dns-prefetch" href="//02imgmini.eastday.com"><link rel="dns-prefetch" href="//03imgmini.eastday.com"><link rel="dns-prefetch" href="//04imgmini.eastday.com"><link rel="dns-prefetch" href="//05imgmini.eastday.com"><link rel="dns-prefetch" href="//pos.baidu.com"><link rel="dns-prefetch" href="//p.tanx.com"><link rel="dns-prefetch" href="//gma.alicdn.com">

欸,dns-prefetch 是什么?

好吧,看起来你我都不懂.

多并发请求

请问,一号网页和二号网页哪个加载更快?

假设每张图片的大小为无限小,传输速度为无限大,exmple1.comexmple.com 的镜像

一号:

<!doctype html><html><body><img src="https://exmple.com/1.jpg"><img src="https://exmple.com/2.jpg"><img src="https://exmple.com/3.jpg"><img src="https://exmple.com/4.jpg"><img src="https://exmple.com/5.jpg"><img src="https://exmple.com/6.jpg"><img src="https://exmple.com/7.jpg"><img src="https://exmple.com/8.jpg"><img src="https://exmple.com/9.jpg"><img src="https://exmple.com/10.jpg"><img src="https://exmple.com/11.jpg"><img src="https://exmple.com/12.jpg"></body></html>

二号:

<!doctype html><html><body><img src="https://exmple.com/1.jpg"><img src="https://exmple.com/2.jpg"><img src="https://exmple.com/3.jpg"><img src="https://exmple.com/4.jpg"><img src="https://exmple.com/5.jpg"><img src="https://exmple.com/6.jpg"><img src="https://exmple1.com/7.jpg"><img src="https://exmple1.com/8.jpg"><img src="https://exmple1.com/9.jpg"><img src="https://exmple1.com/10.jpg"><img src="https://exmple1.com/11.jpg"><img src="https://exmple1.com/12.jpg"></body></html>

好了公布答案了,很显然号加载更快。

这时候有人就会问了,凭什么啊!不是说图片无限小吗?右边的反而会增加dns解析时间,难道不会更慢吗?

好吧好吧,实际上,你以为的加载时间是这样的

可是实际上是这样子的:

欸这就很有意思了同学们,为什么最后六张会出现Pending状态?

实际上,浏览器是有并发请求数目限制【通常是2-8个】,虽然这种比较坑人,但这也是为了避免同时大量并发导致资源占用过度,并且只针对同一个域名的(例如向cdn.jsdelivr.net发送请求)。即一时间针对同一域名下的请求有一定数量限制。超过限制数目的请求会被阻塞并进入Pending状态。

从用户的角度来说,浏览器不可能无限量的并发请求,因此衍生出来了并发限制和HTTP/1.1的Keep alive。 所以,IE6/7在HTTP/1.1下的并发才2,但HTTP/1.0却是4。 而随着技术的发展,负载均衡和各类NoSQL的大量应用,基本已经足以应对C10K的问题。 但却并不是每个网站都懂得利用domain hash也就是多域名来加速访问。因此,新的浏览器加大了并发数的限制,但却仍控制在8以内。

然而从服务器的角度来讲,我们也可以窥见,无限制的并发请求无异于一次小型CC,对于保护服务器和优化用户体验还是相当重要的。

从网站上入手,实际上这一点相当容易理解,2345一打开就是少则五十多张图片同时加载,甚至那些下拉箭头为了兼容ie都还是图片而非fontawesome,如果不绕过并发限制,岂不是一片空白.

那么问题来了,如何避免堵塞?

答案显然易见,多换几个域名不就行了??这样图片分别从两个域名获取,直接避免恼火的Pending,

2345主页中 00imgmini.eastday.com05imgmini.eastday.com 五个域名均是拿来绕开并发限制的。

Cookie Free

每请求一次资源,就会生成一次新的Cookie。如果网站每个资源cookie有1 KB、网站首页共150个资源时,用户在请求过程中需要发送150 KB的cookie信息,在512 Kbps的常见上行带宽下,需要长达3秒左右才能全部发送完毕。 尽管这个过程可以和页面下载不同资源的时间并发,但毕竟对速度造成了影响。 而且这些信息在js/css/images/html等静态资源上,几乎是没有任何必要的。

解决方案是启用和主站不同的域名来放置静态资源,也就是cookie free。

注意此处所指的是主域名而非子域名,子域名的Cookie会和主域名共存

但是这样,就孪生出一个极其严重的问题,多个域名,必然会加重DNS解析时间SSL的握手时间

避免DNS拥堵

一旦采用多域名的方式绕开并发量限制,就会导致浏览器在请求时必须一个一个解析过去,从00imgmini.eastday.com05imgmini.eastday.com,然而DNS解析时间虽然短,但是多个解析必然导致严重拖慢速度,此时我们就需要进行预解析。

请注意,预解析在部分主流浏览器中支持,但是并不是所有页面和条件下都支持。

正常情况下打开一个网页,浏览器会做出以下动作:

  1. 浏览器向请求网址发起DNS解析

  2. 浏览器向服务器一个GET请求,拉起进入Pending

  3. 浏览器解析html,完成初步CSS渲染【此时标题栏显示标题】

  4. 进行js解析,并请求额外的资源

DNS解析时间的浪费主要阻塞在第四步,为避免解析时间的阻塞,我们采用dns-prefetch进行优化.

在html头添加

<meta http-equiv="x-dns-prefetch-control" content="on" />

向浏览器表明我需要预解析。

接着【建议】在<meta charset="UTF-8"> 后添加

<link rel="dns-prefetch" href="//exmple.com"><link rel="dns-prefetch" href="//exmple1.com">

表明强制解析这些域名。并且以后会一直记住这个域名的解析结果直到关闭为止。

//开始是为了适配 httpshttp 。就是当前请求链接是https ,那么这个//前面自动补充https ,反则补充http 。

建议

切记 dns-prefetch 不能滥用,它虽然能一定提升网页打开速度,但也会对浏览器产生一定负担。

这边的建议是,作为个人,我并不推荐将此方法运用于个人博客。原因很简单,你博客一次能有几张图片?如果你是开图站的,那么这个方法刚好可以运用到你的网站里,只要进行多域名分开并dns欲解析就可以进一步提升网站体验了。

最后上生肉:

DNS prefetching is an attempt to resolve domain names before a user tries to follow a link. This is done using the computer’s normal DNS resolution mechanism; no connection to Google is used. Once a domain name has been resolved, if the user does navigate to that domain, there will be no effective delay due to DNS resolution time. The most obvious example where DNS prefetching can help is when a user is looking at a page with many links to various domains, such as a search results page. When we encounter hyperlinks in pages, we extract the domain name from each one and resolving each domain to an IP address. All this work is done in parallel with the user’s reading of the page, using minimal CPU and network resources. When a user clicks on any of these pre-resolved names, they will on average save about 200 milliseconds in their navigation (assuming the user hadn’t already visited the domain recently). More importantly than the average savings, users won’t tend to experience the “worst case” delays for DNS resolution, which are regularly over 1 second.

打造一个可国内访问的Blogger(Blogspot)方法

作者 CYF
2020年7月29日 10:43

Blog‍‌‍‌​‌‌‌‌​‌​‍‌​‍‌‍‍​‌‌‍‌​‌‍​‍‌​​‍‌‌‌​‌‍‌‌​‍‍‍​‍‍‌​​‍‌‍‌​‍‌‍‍​‌‌‍‌​‌‍​‍‌​​‍​‍‍‍​‌‍‍‌ger,一个干爽、免费的博客发布平台,作为主流的发布,提供免费的托管,免去了Typecho&Wordpress高昂的服务器费用,避免了Hexo&Jekyll静态博客无后台的缺陷,与CSDN、简书相比,可以绑定域名,界面干净,无广告【当然可以自己放自己的广告】。

实际上,当今写博客的软件数不胜数,主要分为一下三类:

  • 服务器部署:典型代表:Wordpress\Typecho
  • 无服务器托管:典型代表:Hexo\Jekyll\Gidea\Hugo\Hola等等
  • 集成型网站:Blogger、简书、CSDN、cnblog、wodemo等等

上面所有软件,优缺点都有,具体看个人选择

当然,个人认为Typecho适合做个人博客,Hexo可以作为要求不高的人。集成性网站最主要的是只要安安心心写文章,不用管后端乱七八糟的代码。当然,最有问题的是大多数都不支持绑定域名,而且经常往网站上塞广告,自定义范围也不够。

接下来,我们扯扯集成博客中的一股清流:Blogger

Blogger.com是由Pyra Labs公司创立,是目前全球用户数量最多的个人网志服务提供商。Pyra Labs和Blogger.com均被Google公司收购,成为其旗下的一项服务内容。
Blogger提供免费主机Blogspot.com存放博客,用户不必写任何代码或者安装服务器软件或脚本,通过所见即所得界面轻松地创建、发布、维护和修改自己的网志。
Blogger允许有经验的用户自行设计博客界面,其模板支持使用HTML和CSS进行编辑

实际上,由于Blogger托管于谷歌,写作域名 www.blogger.com 和托管域名 *.blogspot.com 均被MainLand所Ban。但是接下来来,我会讲讲如何打造一个能在国内大陆访问的Blogger

1. 注册Blogger

众所周知,请善用技术上网。

用谷歌账号登录 (https://www.blogger.com) ,没有?不是⑧不是吧不是扒,都0202年了,还不会去注册一个谷歌账号?

进入控制台,新建一个博客:

绑定域名,这一步随意,因为我们还要绑定一个自定义域名.

点击确定,一个博客就搭建好了!快吧?

这时候,我们善用技术上网访问之前的域名,我这里是: cyfblogger.blogspot.com,打开:

搞定!搞完之后才知道,Blogger真正做到了什么叫1分钟创建一个博客,但这还不够,中国访客实际上还是打不开这个网站1。所以,接下来,我们要让这个网站能在中国访问。

目前我们有一下思路:

  • 选择一个未被官方屏蔽的Blogger节点,此方法几年前还是可用的,最近不大灵光了,被屏蔽的差不多了
  • 使用服务器反向代理Blogger,不推荐,有这个闲工夫你还不如直接用Typecho呢
  • CloudFlareCDN反向代理,这似乎是唯一一个解决的办法.

2.绑定自定义域名

由于 *.blogspot.com 泛域名被屏蔽,所以你必须要有一个自己的域名.

不推荐免费域名,因为免费域名无法自选CF节点,当然你也可以换别的方式。

这里推荐一个买域名的好网站:https://namebeta.com/ namebeta并不是购买域名的地方,但是它可以帮你货比三家,让你跳出更优惠的方案,并且自带各种温馨提示,包括能不能备案,有没有被注册等等,比较适合刚入门想买域名的小白.

购买绑定NS不在谈论范围内,有域名的看下去

以下演示方便在CloudFlare官网里进行,采用免费域名演示,实际用笨牛CNAME解析为好.

点击【设置】-【正在发布】-【自定义域名】

接着会报错:

提示没验证,没关系,去CloudFlare加上CNAME,注意验证过程中切记把橙色云朵点灰2

回去,点击确定,勾选HTTPS可用性。

谷歌SSL验证和办法真的慢,没办法,喝杯茶,写点作业,过了10min,显示颁发成功:

回到CloudFlare,把访客访问的那条记录点亮橙色云朵,作为验证的记录不点亮.

国内访问试试?

这时候我们发现,虽然国内能直连了,但是背景图加载不出来,整个网页加载缓慢。控制台以下,我们就会发现,Blogspot请求了国外谷歌边缘节点的内容,包括背景图片和部分js。

所以,我们还要对博客主题进行加工。

3.更改主题

演示方便,用Contempo Light主题
实际上用其它主题也差不多的,注意替换掉外链即可

点击备份,下载主题:

用Notepad++打开,方便处理

屏蔽加载

<head>替换为&lt;!--<head>--&gt;&lt;head&gt;

</head>替换为&lt;/head&gt;&lt;!--</head>--&gt;

</body>替换为&lt;!--</body>--&gt;&lt;/body&gt;

替换背景

在49行左右,有这么一段代码:

<Variable name="body.background" description="Background"      color="$(body.background.color)"      type="background"      default="$(color) none repeat scroll top left"  value="$(color) url(https://themes.googleusercontent.com/image?id=L1lcAxxz0CLgsDzixEprHJ2F38TyEjCyE3RSAjynQDks0lT1BDc1OxXKaTEdLc89HPvdB11X9FDw) no-repeat scroll top center /* Credit: Michael Elkan (http://www.offset.com/photos/394244) */;"/>

将其中的

https://themes.googleusercontent.com/image?id=L1lcAxxz0CLgsDzixEprHJ2F38TyEjCyE3RSAjynQDks0lT1BDc1OxXKaTEdLc89HPvdB11X9FDw

替换为背景外链.

必要的JS替换

打开F12,我们会发现,有一个资源阻塞了请求:

https://resources.blogblog.com/blogblog/data/res/2629068285-indie_compiled.js

但这个是必须的,我们要保留,搜索 <b:template-script async='true' name='indie' version='1.0.0'/> 并删除,将其替换成:
<script async='async' src='https://cdn.jsdelivr.net/gh/chenyfan/chenyfan.github.io/1468123664-indie_compiled.js'></script>

当然你也可以用自己的链接.

解决缩略图问题

【感谢阿虚同学的博客解决此方法】

将 此JS 代码放置于标签前:

<b:if cond='data:blog.pageType in {"index","searchQuery","searchLabel","archive"}'> <!--如果当前页是首页,搜索页,标签页,那么代码继续执行-->    <script defer='defer'>        //<![CDATA[        var postThumbnails = document.getElementsByClassName("post-thumbnail");        var postContents = document.getElementsByClassName("post-text");        for (var i=0;i<postContents.length;i++)        {            var postContent = postContents[i].innerText;            var imgReg = /<img.*?(?:>|\/>)/gi;            var srcReg = /src=[\'\"]?([^\'\"]*)[\'\"]?/i;            var imgTags = postContent.match(imgReg);            imgSrcs = imgTags[0].match(srcReg);            imgSrc = imgSrcs[1];            postThumbnails[i].setAttribute('src', imgSrc);        }        //]]>    </script></b:if>

修改模板,搜索data:post.featuredImage,在缩略图处改成下代码:

<b:if cond='data:post.featuredImage'>  <!--判断文章内是否有图片,有则代码继续执行-->    <div class='snippet-thumbnail'>  <!--创建一个 div 容器,缩略图放置在这里-->        <img class='post-thumbnail' sizes='(max-width: 800px) 20vw, 128px' src='https://ae01.alicdn.com/kf/HTB1Gb7LUmzqK1RjSZFL5jcn2XXac.gif'/>  <!--预先放置一个加载图片,增强用户体验-->        <textarea class='post-text' style='display:none;'><data:post.body.escaped/></textarea> <!--这里放置文章全文,图片从中提取,样式设置为不显示-->    </div></b:if>

头像、icon设置

搜索 profile-img 约3899行:

<img class='profile-img' expr:alt='data:messages.myPhoto' expr:height='data:authorPhoto.height' expr:src='data:authorPhoto.image' expr:width='data:authorPhoto.width'/>

将其直接改成

<img class='profile-img' src="你的头像外链地址">

字体问题

我测试时没有发现字体相关问题,请求字体的网址gstatic.com在国内可以访问,虽然部分地区莫名其妙解析到澳大利亚facebook,但大多数都正确解析时国内谷翔。

评论问题

默认的Google评论肯定时不行的,留着还拖慢加载,推荐用DiqusJS或Valine.

修改主题,搜索 <b:includable id='comments' var='post'> 约3453行至 </b:includable> 3511行,全部删除,将以下代码填写回原处

<b:includable id='comments' var='post'>                      <div id='disqus_thread'/>                      </b:includable>

在末端 &lt;!--</body>--&gt;&lt;/body&gt; 前添加:

<script>var dsqjs = new DisqusJS({    shortname: '',    siteName: '',    identifier: '',    url: '',    title: '',    api: '',    apikey: '',    admin: '',    adminLabel: ''});</script>

以上代码具体配置前往 [https://github.com/SukkaW/DisqusJS#%E4%BD%BF%E7%94%A8 ] 配置

Demo&&以后的日记本:[https://moe.cyfan.top ]


推荐自选CDN加速.

注意以后写文章图片必须是外链,可以试试sm.ms

白嫖!10GB免流海外BackBlaze对象存储【可套CDN】

作者 CYF
2020年7月9日 09:37

腾讯云的COS就是个暗坑,进去的时候大肆宣扬用户前六个月免费,但实际上免费仅针对于存储于国内的bucket,而存储于国外的阶梯定价又极不合理,我的COS在一个月走了流量1.6GB。按照官方定价

大概就是0.7左右。

但是,腾讯云从来就是不满1GB按1GB计算的jier,我瞟了一下账单:

不是吧啊Sir,一个月一块多,那我为什么要用你的东西啊

很难让人理解,我CDN设置为一个月的超长缓存,但腾讯还是给我计价1GB,和回源没什么区别。

后来F12看了一下,腾讯悄悄地在header里添加max-cache为43200s,12个小时强制清除缓存。Asir,赚钱不带这么玩吧。

最让我憋屈的是,cyfan.top 是没有备案的,使用国内的bucket就不给绑定域名,害的我只能用香港,但是香港的绑定域名是不给SSL的,偏要套一层CDN才行。可***腾讯云默认CDN是亚马逊的,速度渣的很,用来用去还是用回CloudFlare。可是,既然有CloudFlare,那我为什么不用免费的Github服务啊!

这就是逼着我要换一个图床啊么

后来twitter上有人发推推荐 backblaze 的海外存储,使用了一下,发现完全满足需求。

Backblaze

2015年9月,Backblaze推出了新产品B2 Cloud Storage。作为基础架构即服务(IaaS),它的目标是软件集成(尽管也提供Web前端和API)。它直接与类似服务Amazon S3,Microsoft Azure和Google Cloud竞争。在2018年4月,Backblaze宣布了云计算合作伙伴关系,它将直接将Backblaze的数据中心与其合作伙伴Packet和ServerCentral连接起来,为存储在B2 Cloud Storage中的数据提供高性能的云计算,而无需支付任何费用。

B2 Cloud Storage非常客气,有以下优点:

  • 用户永久免费10GB直链存储
  • 每天1GB下行流量
  • 无限量的上传流量
  • 每天下载请求2500次免费
  • 每天上传请求2500次免费
  • 基于CloudFlareCDN

而且超出免费额度的价钱也十分合理【不过我不会往里头冲一分钱的!】 https://npm.elemecdn.com/chenyfan-oss@1.0.0/pic/postpic/2020-07-09%20100931.jpg

然而我偶然得知,Backblaze加入了CloudFlare的 带宽联盟( Bandwidth Alliance) Backblaze与CloudFlare之间的流量直接免费,也就是每天无限量下行流量,配上CloudFlare超长缓存,每天下载请求无限次免费。

而且这样与我用COS的速度是差不多的,那我何必用付费的COS呢?

注册:

B2 云存储注册,可以用Google快捷登录:

注册之后可能会要求你绑定手机号,乖乖的绑定自己的中国手机号吧【可能会产生短信费用,大概0.1¥】,不要想什么歪门邪道
,毕竟很多公开的手机号是不能用的,而且GoogleVoice也被拒绝了。

新建一个桶,设置为公开:

上传一个文件,点击右边的信息按钮,我们要在这里获取一些信息:

看到那个友好链接吗,这就是CloudFlare加速的链接,但这个不是我们想要的链接,我们要自定义域名,毕竟默认的加速相当蛋疼。

自定义域名

这一步需要你有个域名,开个子域给图床:

由于我是bnxb接入的CloudFlare,所以我首先要去 cdn.bnxb.com ,将 assets.cyfan.top 指向 f000.backblazeb2.com 【每个人都可能不一样,自己看情况】,并开启CDN,然后在DNSPOD里CNAME负载均衡一下,这里就不放图了。

缩短链接

默认即使绑定域名后,链接大概是这样滴:

https://assets.cyfan.top/file/CYF-PicBed/pic/postpic/2020-07-09%20102255.jpg

可以看到,中间多了 /file/CYF-PicBed/ ,这并不是我所需要的,所以我们要把它变成这样:

https://img.cyfan.top/pic/postpic/2020-07-09%20102255.jpg

你要知道,我有一大堆图片都是直接以 https://img.cyfan.top 存在底下的,NotePad++可以批量改,但是我在外链也放了很多啊.所以只能改域名。

前往CloudFlare,设置页面规则,进行301转发:

其中 $1 是CloudFlare的匹配符号,此规则意思是将所有的 https://img.cyfan.top/* 跳转向 https://assets.cyfan.top/file/CYF-PicBed/*

cdn.bnxb.com ,将 img.cyfan.top 指向 任意一ip,比如 1.0.0.1 并开启CDN,然后在DNSPOD里CNAME。

此后,所有访问图片都会在里头跳转一下,外面基本看不出来有什么差别.

开启CORS|加长缓存时间

就这样结束了么?没有,你会发现访问的链接里所有的资源都是MISS,这是因为Backblaze默认不缓存

所以,自己设置呗!

点击桶,进入桶设定:

里头写上:

{"cache-control":"max-age=43200000"}

这个意思是强制缓存 43200000 ,大约是50天.然而这里注意一下,时间太长有个问题,你修改一张图片,外面可以能要50天才能更改,这样只能通过手动清除缓存做到了。

点击CORS设置,选择:

与所有HTTPS来源共享此存储桶中的所有内容。

即可.

实测

免费额度基本用不完.

可惜PicGo没有支持Backblaze,我只能通过网页端上传

然而有一件事情非常蛋疼,你丫的根目录上传的时候是不会创建文件夹的,一次上传会把文件夹里的图片全部上传到根目录。所以,我只能手动创建文件夹,这个痛苦我真的是,

嗯,用了一个晚上,感觉还是挺香的,鹅厂的COS收费确实不合理,也要学学外面人家收费啊!


溜了溜了,作业还没写完呢

怎么才能让GoogleAdsense不拖慢速度

作者 CYF
2020年7月6日 14:19

GoogleAdsense嘛,著名的赚钱大师,虽然只给我40美分,但毕竟我没有做很好的优化嘛,这也不怪谷歌。由于以前用的是 .ga 的免费域名,在上一年将近4个月荒凉,基本日PV在没有和1之间徘徊.后来过年的时候买了一个 .top 总算撑起一层牌面,但是由于 COVID19 ,嗯,所以每次提交2星期就给我来这么一出:

嗯,

后来呢,六月初提交了一次,结果显示,到第13天,百度统计才接收到来自台湾的Google流量,一天之后审核完毕.

真的懒啊,

然而呢,GoogleAdsense也是著名的拖慢加载速度的JS。一年前的GoogleAdsense的js获取是链接美国,谷歌嘛,连不上也是正常的,现在基本解析都是上海和北京的谷翔,速度还行,但是加载广告的速度依旧难以忍受【实际也就6s的速度】。

欸,博客快满一周年了,当时建站的时候根本不管速度如何,能加载就成,不像现在,为了几百毫秒的事情纠结。

GoogleAdsense在后台偷偷加载的事情:

可以看到,一个1.1kb的网页(上面的文字是通过js自动生成的),谷歌广告加载,需要将近10s加载完毕,加载大小将近1.5MB。

最夸张的是,我是通过海外代理访问的,如果放在大陆打开,这甚至好几次加载失败。

虽然谷歌拥有所谓的【异步加载】,可仍然会严重拖慢速度,并且,当用户没有打算看广告时,广告仍然会加载:

简单统计了一下,我打开网页用了1s,剩下9s我的浏览器上方一直在转【表示加载】,这种情况非常的讽刺,因为谷歌在PageSpeedLight中口口声声说需要降低js的渲染速度和外部链接加载。

实际上呢,刚刚的广告,谷歌向服务器发送了57次请求,其中26次js加载,总渲染达到3.87秒,接着是图片,总共将近9个,总大小1.4MB。

这种地步,已经让我无法忍耐了,可以想象,在打开博客,最开始跳出来的不是博文的内容,而是毫不相关的广告,这种情况,访客好感度能好才怪呢。

那么,怎么解决?

万物皆可懒加载!

访客在上方浏览时,广告不加载,直到划到最底下,广告才开始加载,这样大大提升好感【虽然总加载速度和时间还是这个样,但是在访客看来就很舒服】

应该给广告挑个好位置,那么在哪里最好呢?就我个人而言,我最希望看完博文和评论之后,在移动鼠标到下一篇的间隙稍微看看别的东西。就比如说那种3.3¥/月的主机广告我就忍不住想点一下【当然,发布者是不能点击自己的广告的】。那么,我就可以把广告代码扔在Valine评论框以下即可。

我们可以顺手拿一个谷歌广告实例开刀,我的博客广告单元是这样的;

<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script><ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-1878991317600808" data-ad-slot="6517667779" data-ad-format="auto" data-full-width-responsive="true"></ins><script>     (adsbygoogle = window.adsbygoogle || []).push({});</script>

可以看到 https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js 即核心js,那么我们只要把这个js压住懒加载,直到划到底下才显示即可

那么问题来了,怎么压?

答: window.addEventListener

 <script type="text/javascript">function downloadJSAtOnload() {var element = document.createElement("script");element.src = "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js";document.body.appendChild(element);}if (window.addEventListener)window.addEventListener("load", downloadJSAtOnload, false);else if (window.attachEvent)window.attachEvent("onload", downloadJSAtOnload);else window.onload = downloadJSAtOnload;</script>

所以,简单的就这么做:

修改 Valine.ejs ,末端填上

 <script type="text/javascript">function downloadJSAtOnload() {var element = document.createElement("script");element.src = "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js";document.body.appendChild(element);}if (window.addEventListener)window.addEventListener("load", downloadJSAtOnload, false);else if (window.attachEvent)window.attachEvent("onload", downloadJSAtOnload);else window.onload = downloadJSAtOnload;</script><!-- ADs-in-Blog-Under-Valine --><ins class="adsbygoogle"     style="display:block"     data-ad-client="ca-pub-1878991317600808"     data-ad-slot="6517667779"     data-ad-format="auto"     data-full-width-responsive="true"></ins><script>     (adsbygoogle = window.adsbygoogle || []).push({});</script>

就比如现在这样【诶呀,顺手捞一把嘛】


好了,我相信很多营销号【如果有】,绝对会把我上面的文章直接抄走。嗯,我非常讨厌营销号,对的,我在这里提前鄙视你们。以下是我关于这件事情的很多想法,如果你就是想简单优化,这就足够了,如果你有兴趣了解,你可以继续:

关于更多的优化

谷歌论坛上有人提到过,懒加载谷歌广告是否合规,标题是这样的:Lazy Load Adsense fine with the policies?

原文如下:

Many have already been asked, but unfortunately they have not received an answer. :-(I like to optimize site speed in the browser, not AMP.My questions are:- Is lazy load for ads below the fold usefull?- Is it fine with the policies? I've seen some pieces of code to implement it (https://css-tricks.com/lazy-loading-responsive-adsense-ads/, https://betterstudio.com/blog/lazy-load-google-adsense-wordpress/, https://gulshankumar.net/setup-lazy-loading-google-adsense-ad-units/). Are such examples allowed code to implement AdSense lazy loading?Thanks!

噫,好,又是生肉.

简单的说,这位用户的担忧确实很值得思考,确实,AMP对于我来说就是个鸡肋,尤其是想我一样面向中国大陆访客,AMP需要你能访问国外谷歌。担忧的理由也写的很清楚,一是能不能起作用,二是违不违反政策。

可惜,所谓的QuickResponse依旧很答非所问,印证了用户的 Many have already been asked, but unfortunately they have not received an answer

嗯, it makes sense NOT to have lazy loading on themThere is no “yes” or “no” answer to this in the policies ,用户问有没有违反,可你只能回答【没有确切答案?】、【不需要懒加载?】

这谷歌客服一事无成,像极了我的人生

不过说回来,有一件事情确实意思:

As such, it could be argued that behaviour draws attention to those elements. Drawing attention to ads is a policy violation.所以,这可以说这种行为吸引了人们对这些要素变化的关注。请注意这种吸引用户关注行为是违反政策。

所以,这就解释了我为何不用 onscrollIntersectionObserver API ,
而采用不那么灵敏的 window.addEventListener

实际上,在刚刚的论坛问答里,提到了很多的lazyload方法,以 [https://css-tricks.com/lazy-loading-responsive-adsense-ads/] 这篇文章为例子:

这种方法在谷歌广告商增加一个遮罩层,加载网页时先加载遮罩,广告不加载;当滚动完成时,遮罩层消失,加载广告.可惜,此方法已失效,加载的时候谷歌检测到有遮罩层就会拒绝加载。

无论怎么说,懒加载广告,速度一下上去了,口袋里零花钱也多了。钱速双收,何为不乐乎?

最后,我瞟了一眼,看到了广告:

阿里支付宝什么时候沦落到要打广告了?

利用Travis-CI实现在线更新Hexo

作者 CYF
2020年6月29日 13:31

Hexo作为静态博客,好处相当明显,开销少,并且对于那种DDosS和CC套上CDN毅然不动。当然,最蛋疼的莫过于更新了,每次在自己电脑上辛辛苦苦码好字,一个push,hexo绿色光芒在命令提示符上闪烁着光芒,突然发现把仓库名字 ChenYFan 打成 CehnYFan 真实事件 ,异或着是用手机查看自己的博客,突然发现:

由于hexo基于nodejs+git,手机无法更新;同时如果换了台电脑,hexo就要重装。这种事情hexo用户应该体会得到,我也就不多说了。

那么,hexo用户如何进行在线更新呢?

正常来讲,服务器法 最直接,但也是最没用的。用服务器就意味着丧失了hexo的最优点-节省开支。当然,Hexo+Nginx+HexoAdmin确实可以实现很棒的书写环境,但是与其这么麻烦你还不如直接用Typecho&Wordpress呢。

曾经在 Hexo官方 上看过 利用Travis-ci自动部署GithubPages 不过我一看到这么多步骤直接 萎缩

后来,促使我改变主意的,是我得知中考之后放假三天接着上课[高中],我***,然后突然想起来博客不方便更新,接着手一抖,把博客的Repo删掉了.

既然删了,那么就这么干吧。

其实后来发现这并不困难,只是我刚开始想多了而已。

简介

Travis CI是在软件开发领域中的一个在线的,分布式的持续集成服务,用来构建及测试在GitHub托管的代码。这个软件的代码同时也是开源的,可以在GitHub上下载到,

实际上你会发现,当你把博客Push到Github上时,你的计算机会在NodeJS环境下生成静态文件,然后push到Github。这些步骤其实完全可以用Travis CI做到。

最好在 source 下新建一个 CNAME 文件,并将绑定的域名写进去,不然直接在 gh-page 分支里弄Travis-ci会覆盖掉的.

开始

本地搭建环境

这一步必不可少,额具体方法网上一搜一大堆,这里就不说了。请注意,最好事先选好主题和插件,配置完成后自己测试一下。完毕后进入下一步。这里不在演示了【毕竟搭建环境与此博文几乎无关】

上传

默认情况下,直接在hexo博客根目录上链接repo上传,不会把 node_modules/ 上传上去,因为 .gitignore 中包含这么一行:

但是当时以为我拓展是不能上传的,于是手一滑,删掉那一行,上传上去了。

结果后来发现这就是个错误的选择。

  1. node_modules/ 中,文件比较小

  2. node_modules/ 中,文件比较多

所以:

  1. node_modules/ 中,文件比较碎

嗯,

git add . 了一下,它运算了半小时, git commit 又花了半小时,幸好git push 是打包上去的,不然我估计又要花半个小时.

结果戏剧性的是,当我去看travis-ci部署记录时,我发现:

所以,还没用Travis-ci的同学,请不要手贱删掉 .gitignore 中的 node_modules/

部署

这里暂时不说私有怎么部署,这里讲的是PublicRepo.

1.注册travis-ci.org

前往 [https://travis-ci.org] 用Github账号注册 注意了啊,注意了啊,.org 而不是 .com ! 鬼知道我在这上面浪费了多少时间! travis-ci.org 是免费给公开repo部署的,travis-ci.com 是收费的,但是 travis-ci.com 却是可以绑定并免费部署公开Repo的.最有问题的是这两个网站其中一个绑定repo后,另一个就不能绑定了!! 害得我以为是缓存搞出来的事,搞了半天没解决,一看地址栏人都傻了.

2.绑定travis

前往 [https://github.com/marketplace/travis-ci] 绑定travis-ci到你的github后 继续前往 GitHub 的 Applications settings ,点击 Travis CI配置你的repo能被TravisCI访问

3.新建Token

前往 GitHub 新建 Personal Access Token

新建一个Token:

然而请注意,官方文档里说只需勾选 repo 全部权限即可,但是据我测试,只勾选则会导致401验证错误.似乎还要勾选 read:public_keyread:user ,当然如果你足够懒,你也可以全勾上, 但请不要把Token泄露出去,否则你的Github就不太好使了.

点击生成Token:

记得复制下来保存!不然下次你就看不到你的Token了!

4.修改Repo

进入Repo的 Master 分支,新建一个 .travis.yml ,里面塞上:

sudo: falselanguage: node_jsnode_js:  - 10 # use nodejs v10 LTScache: npmbranches:  only:    - master # build master branch onlyscript:  - hexo generate # generate static filesdeploy:  provider: pages  skip-cleanup: true  github-token: $GH_TOKEN  keep-history: true  on:    branch: master  local-dir: public

对,改都不要改,就这么塞进去.

5.Token导入Travis-ci

Token很重要,你必须要告诉Travis-ci,因为它要获取对你的repo的写入权,但你也不能明文写在Repo里面,因为别人看得到.

所以,在 [### 4.修改Repo] 中,github-token: 后面跟着的不是明文,而是变量 $GH_TOKEN.

进入 [https://travis-ci.org/github/{用户名}/{仓库名}/settings] 中,看到 Environment Variables ,Name选择 GH_TOKEN ,Value把[### 3.新建Token] 中的Token粘贴到里面去.BRANCH直接默认.

特别注意!,将后面DISPLAY VALUE IN BUILD LOG弄成灰色,不然你的Token将会公开!,如果你不慎公开Token,请前往GithubPersonalToken删除并重新生成一个Token!

最终应该是这样子的:

在日志里面,搜索Token,应该是这样子的:

$ export GH_TOKEN=[secure]

6.打开触发器

前往 https://travis-ci.org/account/repositories,打开目标Repo后面的按钮:

7.触发Travis-ci

修改文件或新建Readme,让Travis-ci触发并开始工作.

比如说我更新 留言板.md ,Github上一更新,Travis-ci自动开始工作:

麻烦无视左上角的亮度调节

本次日志地址 https://api.travis-ci.org/v3/job/703061869/log.txt

8.以后

更新博客直接在Github上更改,或者写好之后直接上传,或者pull到本地写好后push到Github,此后操作用户无需本地使用Hexo,也不用调整Travis-ci,安心写博客吧!

草稿问题

其实这个比较简单,在修改时新建一个branch,名字叫 drafts ,由于 .travis.yml 规定只捕获 master ,草稿分支不会触发,修改的时候全部在drafts上修改,修改好了直接PullRequest,完事!

*这么干以后,一定要注意,以后所有修改无论大小,都必须先在 drafts 里修改,然后发起PR,然后合并.不然直接在 master 里修改有可能会导致无法合并! *

后记

总之,这样就可以安心用手机或者在学校更新Blog了!

通过CloudFlareAPI获取用户侧信息

作者 CYF
2020年6月28日 08:30

噫,中考结束了,心中一块大石头总算碎了。虽然说考上提前批中考考不考无所谓,但是回去一次模拟考直接把我考傻了,太烂了,以前初一不学习的时候都没有这么烂。啊么学习两星期,中考一考,今年理科超级简单,欸,这样理科分数拉不开了,啧啧啧,理科生的末日。

因为人大多数时间在学校,不太方便用自带hexopush到Github,一是博客文件同步不方便,二是如果有个小错误就很抓狂,所以呢,以前的打算是部署到vps,通过hexo+nginx实现访问,不过可能是人比较傻,一直没搞好,而且这种免费小鸡说炸就炸,不保险,还是跟着hexo官方文档 用Travis-ci+Github实现在线自动部署,以后更新也方便点

好了好了,不说了,今天简单讲一下如何用CloudFlare自带的API获取用户信息。

/cdn-cgi/trace

刚开始建立博客的时候,也是想过显示用户ip地址的,但网上的教程大多都是用搜狐新浪的js,而且很早就过期了。所以也就搁着迟迟没有解决。

后来偶然间知道CloudFlare有个比较神奇的技术,在部署在CloudFlare上的网站,域名后面加上/cdn-cgi/trace就可以获得用户侧信息,例如https://blog.cyfan.top/cdn-cgi/trace

fl=23f2**h=blog.cyfan.topip=39.182.***.***ts=15933078**.***visit_scheme=httpsuag=Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36colo=HKGhttp=http/2loc=CNtls=TLSv1.3sni=plaintextwarp=off

[对于部分隐私替换掉,请见谅]

差不多都全了,ipuagcololoctls这些数据都是我们所需要的,那么,怎么把这些数据迁移到网页上呢?

答案是:JavaScript

首先要引入Jquery,如果你的网页上已经有Jquery,那么就不必再次引入:

<script src="https://npm.elemecdn.com/jquery@3.2.1/dist/jquery.min.js"></script>

接着加入JS[需在Jquery以下,否则会报错]:

<script>getCDNinfo = function() {$.ajax({url: "https://cdn.cyfan.top/cdn-cgi/trace",success: function(data, status) {let areas = "Antananarivo, Madagascar - (TNR);Cape Town, South Africa - (CPT);Casablanca, Morocco - (CMN);Dar Es Salaam, Tanzania - (DAR);Djibouti City, Djibouti - (JIB);Durban, South Africa - (DUR);Johannesburg, South Africa - (JNB);Kigali, Rwanda - (KGL);Lagos, Nigeria - (LOS);Luanda, Angola - (LAD);Maputo, MZ - (MPM);Mombasa, Kenya - (MBA);Port Louis, Mauritius - (MRU);Réunion, France - (RUN);Bangalore, India - (BLR);Bangkok, Thailand - (BKK);Bandar Seri Begawan, Brunei - (BWN);Cebu, Philippines - (CEB);Chengdu, China - (CTU);Chennai, India - (MAA);Chittagong, Bangladesh - (CGP);Chongqing, China - (CKG);Colombo, Sri Lanka - (CMB);Dhaka, Bangladesh - (DAC);Dongguan, China - (SZX);Foshan, China - (FUO);Fuzhou, China - (FOC);Guangzhou, China - (CAN);Hangzhou, China - (HGH);Hanoi, Vietnam - (HAN);Hengyang, China - (HNY);Ho Chi Minh City, Vietnam - (SGN);Hong Kong - (HKG);Hyderabad, India - (HYD);Islamabad, Pakistan - (ISB);Jakarta, Indonesia - (CGK);Jinan, China - (TNA);Karachi, Pakistan - (KHI);Kathmandu, Nepal - (KTM);Kolkata, India - (CCU);Kuala Lumpur, Malaysia - (KUL);Lahore, Pakistan - (LHE);Langfang, China - (NAY);Luoyang, China - (LYA);Macau - (MFM);Malé, Maldives - (MLE);Manila, Philippines - (MNL);Mumbai, India - (BOM);Nagpur, India - (NAG);Nanning, China - (NNG);New Delhi, India - (DEL);Osaka, Japan - (KIX);Phnom Penh, Cambodia - (PNH);Qingdao, China - (TAO);Seoul, South Korea - (ICN);Shanghai, China - (SHA);Shenyang, China - (SHE);Shijiazhuang, China - (SJW);Singapore, Singapore - (SIN);Suzhou, China - (SZV);Taipei - (TPE);Thimphu, Bhutan - (PBH);Tianjin, China - (TSN);Tokyo, Japan - (NRT);Ulaanbaatar, Mongolia - (ULN);Vientiane, Laos - (VTE);Wuhan, China - (WUH);Wuxi, China - (WUX);Xi'an, China - (XIY);Yerevan, Armenia - (EVN);Zhengzhou, China - (CGO);Zuzhou, China - (CSX);Amsterdam, Netherlands - (AMS);Athens, Greece - (ATH);Barcelona, Spain - (BCN);Belgrade, Serbia - (BEG);Berlin, Germany - (TXL);Brussels, Belgium - (BRU);Bucharest, Romania - (OTP);Budapest, Hungary - (BUD);Chișinău, Moldova - (KIV);Copenhagen, Denmark - (CPH);Cork, Ireland -  (ORK);Dublin, Ireland - (DUB);Düsseldorf, Germany - (DUS);Edinburgh, United Kingdom - (EDI);Frankfurt, Germany - (FRA);Geneva, Switzerland - (GVA);Gothenburg, Sweden - (GOT);Hamburg, Germany - (HAM);Helsinki, Finland - (HEL);Istanbul, Turkey - (IST);Kyiv, Ukraine - (KBP);Lisbon, Portugal - (LIS);London, United Kingdom - (LHR);Luxembourg City, Luxembourg - (LUX);Madrid, Spain - (MAD);Manchester, United Kingdom - (MAN);Marseille, France - (MRS);Milan, Italy - (MXP);Moscow, Russia - (DME);Munich, Germany - (MUC);Nicosia, Cyprus - (LCA);Oslo, Norway - (OSL);Paris, France - (CDG);Prague, Czech Republic - (PRG);Reykjavík, Iceland - (KEF);Riga, Latvia - (RIX);Rome, Italy - (FCO);Saint Petersburg, Russia - (LED);Sofia, Bulgaria - (SOF);Stockholm, Sweden - (ARN);Tallinn, Estonia - (TLL);Thessaloniki, Greece - (SKG);Vienna, Austria - (VIE);Vilnius, Lithuania - (VNO);Warsaw, Poland - (WAW);Zagreb, Croatia - (ZAG);Zürich, Switzerland - (ZRH);Arica, Chile - (ARI);Asunción, Paraguay - (ASU);Bogotá, Colombia - (BOG);Buenos Aires, Argentina - (EZE);Curitiba, Brazil - (CWB);Fortaleza, Brazil - (FOR);Guatemala City, Guatemala - (GUA);Lima, Peru - (LIM);Medellín, Colombia - (MDE);Panama City, Panama - (PTY);Porto Alegre, Brazil - (POA);Quito, Ecuador - (UIO);Rio de Janeiro, Brazil - (GIG);São Paulo, Brazil - (GRU);Santiago, Chile - (SCL);Willemstad, Curaçao - (CUR);St. George's, Grenada - (GND);Amman, Jordan - (AMM);Baghdad, Iraq - (BGW);Baku, Azerbaijan - (GYD);Beirut, Lebanon - (BEY);Doha, Qatar - (DOH);Dubai, United Arab Emirates - (DXB);Kuwait City, Kuwait - (KWI);Manama, Bahrain - (BAH);Muscat, Oman - (MCT);Ramallah - (ZDM);Riyadh, Saudi Arabia - (RUH);Tel Aviv, Israel - (TLV);Ashburn, VA, United States - (IAD);Atlanta, GA, United States - (ATL);Boston, MA, United States - (BOS);Buffalo, NY, United States - (BUF);Calgary, AB, Canada - (YYC);Charlotte, NC, United States - (CLT);Chicago, IL, United States - (ORD);Columbus, OH, United States - (CMH);Dallas, TX, United States - (DFW);Denver, CO, United States - (DEN);Detroit, MI, United States - (DTW);Honolulu, HI, United States - (HNL);Houston, TX, United States - (IAH);Indianapolis, IN, United States - (IND);Jacksonville, FL, United States - (JAX);Kansas City, MO, United States - (MCI);Las Vegas, NV, United States - (LAS);Los Angeles, CA, United States - (LAX);McAllen, TX, United States - (MFE);Memphis, TN, United States - (MEM);Mexico City, Mexico - (MEX);Miami, FL, United States - (MIA);Minneapolis, MN, United States - (MSP);Montgomery, AL, United States - (MGM);Montréal, QC, Canada - (YUL);Nashville, TN, United States - (BNA);Newark, NJ, United States - (EWR);Norfolk, VA, United States - (ORF);Omaha, NE, United States - (OMA);Philadelphia, United States - (PHL);Phoenix, AZ, United States - (PHX);Pittsburgh, PA, United States - (PIT);Port-Au-Prince, Haiti - (PAP);Portland, OR, United States - (PDX);Queretaro, MX, Mexico - (QRO);Richmond, Virginia - (RIC);Sacramento, CA, United States - (SMF);Salt Lake City, UT, United States - (SLC);San Diego, CA, United States - (SAN);San Jose, CA, United States - (SJC);Saskatoon, SK, Canada - (YXE);Seattle, WA, United States - (SEA);St. Louis, MO, United States - (STL);Tampa, FL, United States - (TPA);Toronto, ON, Canada - (YYZ);Vancouver, BC, Canada - (YVR);Tallahassee, FL, United States - (TLH);Winnipeg, MB, Canada - (YWG);Adelaide, SA, Australia - (ADL);Auckland, New Zealand - (AKL);Brisbane, QLD, Australia - (BNE);Melbourne, VIC, Australia - (MEL);Noumea, New caledonia - (NOU);Perth, WA, Australia - (PER);Sydney, NSW, Australia - (SYD)".split(";");let area = data.split("colo=")[1].split("\n")[0];for (var i = 0; i < areas.length; i++) {if (areas[i].indexOf(area) != -1) {document.getElementById("cdn").innerHTML = areas[i];document.getElementById("ip").innerHTML = data.split("ip=")[1].split("\n")[0];document.getElementById("httpos").innerHTML = data.split("visit_scheme=")[1].split("\n")[0];document.getElementById("uag").innerHTML = data.split("uag=")[1].split("\n")[0];document.getElementById("http").innerHTML = data.split("http=")[1].split("\n")[0];document.getElementById("loc").innerHTML = data.split("loc=")[1].split("\n")[0];document.getElementById("tls").innerHTML = data.split("tls=")[1].split("\n")[0];document.getElementById("warp").innerHTML = data.split("warp=")[1].split("\n")[0];break;}}}})}$(document).ready(function() {getCDNinfo();    //页面加载完毕就获取CDN信息});</script>

这个脚本会获取大部分用户信息并解析所链接节点位置,默认链接到cdn.cyfan.top,已通过CORS,接着在网页需要添加的地方增加html代码:

<p>当前CDN节点: <span id="cdn">【未知】</span></br>你的ip: <span id="ip">【未知】</span></br>你当前以: <span id="httpos">【未知】</span>形式访问我们的网站</br>你的User-Agent: <span id="uag">【未知】</span></br>你以: <span id="http">【未知】</span>形式访问本网站</br>你的所在国家/地区: <span id="loc">【未知】</span></br>你以: <span id="tls">【未知】</span></br>你是否以Warp访问我们: <span id="warp">【未知】</span></p>

[可以根据需要增减],一般情况下简单获取写成这样:

<p>当前CDN节点: <span id="cdn">【未知】</span> |你的ip: <span id="ip">【未知】</span> |你的所在国家/地区: <span id="loc">【未知】 </span></p>

结果应该类似这样:

划到页底即可看到。

End

OHHH,又水了一篇,下次讲讲如何部署博客到Travis-ci上。溜了溜了

这一个半月,我干了什么

作者 CYF
2020年5月30日 16:06

啊,期中考试终于考完了,现在心里想的都是司马脸,

距离上一次更新已经将近1个半月了,赶在5月小尾巴发一篇博文。当然,这个月维护还是做到了,只不过太忙没更新而已。

过来扯扯这个月发生了什么。

计算机方面

网站方面

VPS

入手了一台德国VPS,还得感谢这位老兄:

簞純-EUserv 德国永久免费VPS申请

试了一下,性能略差,连IPV4都没有,就当是学习吧,现在就是拿来玩玩的,毕竟我大多数情况下基本是ServerLess。

以后打算全站迁移到一台VPS上,当然习惯用Hexo了,毕竟Hexo-Admin还是很给力的。

图床:

我当场裂开。

之前全放在Github上,但是,但是这会导致多线程并发是Worker抛出异常,速度还很慢。而且很大,Github那么恐怖的大小:

三个星期前开始迁移,刚开始采用了GoogleDrive+
GDIndex,上学去的时候,加载速度还不错,结果一到学校,自己打开,爆一大堆404.

Workers自然也出大问题

后来了解到,谷歌网盘在每每输出一个文件,都会来一次销魂的杀毒,一张10kb的图片,杀毒将近10s,Worker超时30s直接返回404!!!???,我当时心里就开始表演天皇meta的showtime了。

所以后来又采用腾讯云免费SCF额度+OneDrive那可怜的5GB制作图床,好歹也能正常加载,但是OMp的工作原理和GI不一样,GoogleDrive在国内那是不可访问的,所以最终还是采用了反向代理,但是OneDrive是可以滴,所以OMp采用的是301跳转。燃鹅,直连速度和延迟相当的感人,在多线程并发时经常超时。

当时整个人都裂开了。

所以,最终解决办法就是,氪金!!!!!!

腾讯云COS(相当于阿里云OSS)+CDN,当然因为没备案所以放在Hongkong,但是腾讯云有个暗坑,COS绑定自定义域名md居然不给直接开SSL,非得要套层CDN才行,这不是明摆着坑钱么。计算下来,每天平均支出0.03¥.

肉疼。

好在腾讯COS也有客户端,上传文件至少没那么麻烦。

不过话说回来,最近香港局势确实很不稳定,我现在根本无法直连Hongkong的COSBucket,CDN套就套吧,只不过神奇地绕道美国都是什么奇葩玩意,害得我只能A到日本,出口居然是Amazon。

评论系统

又是一个当场炸裂的东西。

Gitalk本身链接api.github.com就是一个相当蛋疼的事情。

我也尝试着做过类似于DiqusJS的反向代理的尝试,可是到最后一步Github回调地址又给我强制跳到api.github.com,我当时人都傻了。最后实在头疼,换成了Valine。当然找了个魔改版本,看起来还不错。【这一次再也不会造成30天无访问自动归档这种奇异的事情了】

两个魔改后的JS地址

https://npm.elemecdn.com/chenyfan-oss@1.0.0/js/av-min.jshttps://npm.elemecdn.com/chenyfan-oss@1.0.0/js/valine.min.js

编程方面

艹,VB轮到我这一届居然不考,,害得我只能硬啃C++。考试还行,就那附加题做不出来,一道高精乘法,居然忘记了strlen()这个函数,当时想撞墙的心态都有了。

学习方面

考完了,我完了。

数学150分扣46分是什么鬼???????语文五道选择题错三道又是什么鬼????????

还好物理只扣10分,化学一分没扣,计算机也只是附加题最后一道不会写而已

欸欸欸欸欸欸欸,感觉我要垫底。

后言

好好学习,天天向上!

好了好了,继续潜水

滑稽

Pandownload:愿你归来仍是英雄

作者 CYF
2020年4月17日 17:19

PanDownload 21世纪的英雄,在几天前,死了。

4月15日下午,扬州网警巡查执法官方微博发布通报称:“Pandownload软件开发嫌疑人已经被捕。”

刘某:PanDownload有窃取用户隐私行为。

嗯,来,你的证据呢?

人性的毁灭

百度网盘迟迟不对PanDownload限制,之前很奇怪,现在明白了,原来是等到“盈利”到30万,有了个证据,才死死咬住不放。百度阴险,由此可见。

百度网盘限速,我就只能说呵呵了,如果你杠百度网盘硬盘资源有限,那么开始你可以调小硬盘大小,尽量不限速,那还行。自己拼命张开嘴说自己吃的多,到头来发现吃不了又悄悄闭嘴,什么意思?

刘某,一个人就可以把一个开发者抓到牢里,呵,我现在相当有理由怀疑这是个幌子,百度人性,可见一斑。哦不,百度没有人性。

想当年,550KB的运用程序硬是给我弄成大文件。我???

要推车出去的停车场

百度网盘就好比停车场吧,刚开始假惺惺说这里空间比谁都大,好吧这确实赢得了我的好感,于是我也存了很多东西上去,就好比我把很多车开进来。直到垄断了整个停车场事业。百度终于展现自己的吃相了。

开车进来?没问题!2T空间,随便你!对啊,2T空间,谁不想要呢?我也停了。附近的停车场用户太多没能力,倒闭了。

现在,当我要开车出去的时候,你突然告诉我,不能开车出去,要用手推出去,理由是出去的通道很窄?要开车要付费,几十块一个月?

PanDownload,形象点,就是把你分身成几个,一起推,现在,总算能正常出去了吧。

PanDownload窃取用户隐私?你可能没搞懂,PanDownload,作用就是把你分身(Aria2多线程下载),窃取用户隐私,我可以肯定地说,就是个幌子。幕后百度,我只能说呵呵了。

漏洞百出的被捕声明

upload successful

首先,PanDownload核心就是获取个直链,你需要反编译解剖软件结构,一盯几个小时?民警办案效率挺高的嘛。

黑客攻击计算机系统,哦,获取直链就算是黑客啦?我觉得我好屌哦。

隐私照片和文件泄露:证据呢?

以非会员权限突破百度网盘设定:你还是没脸说自己限速是吧,百度?

侵入?非法控制?:PanDownload唯一的作用就是获取直接链接(百度网盘客户端的正常操作)以及多线程下载(官方客户端没有),我用多线程就算是违法咯?

欸,行了行了,我都不想说了;

未来,何去何从

互联网上分享个文件真的很难吗?我想,对于众多小白来说,真的,互联网分享文件确实很难。

百度一家,我已经彻底失去最后一点希望了,

贴吧的风控我也是服了,认认真真敲好的字被删除,我还不如用知乎;

搜索引擎?呵呵,营销号的风格,一目了然,国内我现在用magi 一个人工智能搜索引擎,自动从网上拉取文章研究,AI把营销号和同类文章精准度真的高,而且搜索一些学习单词也很正常;或者使用DogeDoge(感觉颇似DuckDuckGo),精准的不误导不追踪;Bing,说实话不太习惯;国外的我就不必多说了;

网盘?OneDrive15GB储存个人资料不香么?普通大文件分享像我这样的穷B Workers+GoogleDrive用户体验完爆百度,为什么要百度资本?Mega还行,实际上Mega15GB也就刚好吧(那个50GB中35GB是新用户临时体验,一个月后失效)。再不济,iCloud!苹果有国内加速节点,虽然5GB真的够呛,不过同步一些个人资料还行吧?开源项目直接扔Github,反正也没多大。

所有百度,我只有用百度统计需求,原因么,Google统计国内加载真的很拖速度,迫不得已使用百度统计,虽然功能差很多,不过勉强吧;

PanDownload能死而复生么?我不清楚,但是,我衷心祈愿!

IPFS+CloudFlare=ServerLessWebPage

作者 CYF
2020年4月7日 16:29

IPFS,这个2018年诞生出来的小东西,似乎掀起了一阵热潮,然后被忘性极大的互联网吃瓜群众所抛弃,但不得不说这个东西可玩度非常高。

IPFS是什么

网上一大堆文绉绉气昂昂极富学术气息的营销号整天吹嘘着ipfs是跨时代下一时代的比特币,我只能说呵呵,真的想要了解ipfs是什么的,建议左转维基百科或百度百科。

抄一下wikipedia上的介绍:

(InterPlanetary File System,缩写IPFS)是一个旨在创建持久且分布式存储和共享文件的网络传输协议。它是一种内容可寻址的对等超媒体分发协议。在IPFS网络中的节点将构成一个分布式文件系统。

其实按我的话来说,ipfs就是一个神奇的东西,你放上去一个文件,就会立刻被瓜分成数个文件碎片,每一台运行着ipfs的电脑和矿机就会争先恐后来抢夺你的碎片,并且抢到的人就会获得系统的奖励,而你却一分钱也不用付出.

是不是听起来很神奇?对的,你没有付出任何东西,别人却能获得奖励,而且你既满足储存文件的需求,储存你文件的人也能得到金钱.这种机制,叫做FileCoin.

你可以把它想象成Bittorrent+BitCoin的结合物,实际上他就是这样的.当然不完全是,但核心理念就是这样的.只不过,BitCoin付出的是算力,而FileCoin付出的是硬盘.

IPFS的主要目标是取代HTTP,说是取代,那刚开始就必须兼容,所以目前ipfs可以通过官网ipfs.io获取.

工作原理

与bt下载类似,我们需要先介绍BT.

Bittorrent:分布式哈希表技术

先来一个故事

很久很久以前,有一位老头,叫做刘XX。

他是一位很有资格的老师,为了顽固学生们的学习效果,他发送给所有学生一部视频:

图片

(注:老刘的带宽是8Mbps,即最大带宽1MB/s,每一个学生的带宽也是8Mbps)

我们假设有5位学生需要这部视频,则可以画这样一幅图:

图片

这只视频是88.8MB大小,按照这样的速度,同学需要花费1024*88.8MB/(204.8kb/s)=444s7分钟24秒

这还往往只是最理想状况.现在,我们来假设一下现象的出现:

  1. 由于视频过于好看,引起广泛关注.现在有200个同学要求下载.则单个人下载完需要17760s即296min也就是约5h!
  2. 刘老师觉得单个视频教学质量不够好,决定上传一个8880GB的超大视频.(下载时间大于一天)
  3. 几个同学与刘老师搞好了关系,刘老师给他们较高的下载速度,导致带宽分配不均匀,没有关系的学生下载速度更慢了.(百度网盘既视感)

图片

  1. 由于刘老师给的视频过于激烈,在下载了一段时间后,FBIopen the door!敲上门来带走了刘老师,视频被查封了!!!(下载速度0)

图片

可是,学习是必须的,这可怎么办呢???

这时….

刘老师突然想起来,为什么不让每一个学生互相连接,贡献自己已经下好的部分,以来获得最大的下载速度呢??

请注意,在上述下载中,每一个人都没有充分利用自己的最大带宽,对不对?

那,解决办法就来了:

P2P:

以256KB为一包,我们就可获得356块文件

图片

回到下载界面,我们看到,所有人都与其他人获得连接,那么,连接完成后:

图片

(为了方便观察,我们将不必要的连接全部去掉)

现在,我们重新开始下载:

我们假设,A1下载了第一块,A2下载了第二块…

两秒后,所有人手上都拥有了一块文件:

图片

接下来,在不影响下载的情况下,A1与A2交换下载好的部分,同时与A3、A4、A5交换

图片

看到了吗,所有人都得到了更高的速度,所有人的带宽都得到了最充分的利用,包括原先看起来毫无用处的上传带宽。

接着4秒后,所有的贡献分块都下载好了,而每一个人都从文件发布者身上又得到新的文件碎片。

图片

所以,这种情况回一直持续下去,直到每一个人都下载完成,每一个人都只需下载149秒即2分29秒!相对于单点时间大大减少!

图片

人数多&文件大 问题

现在,我们来假设第一个问题,200人下载怎么办?

我们可以明白,人数越多,文件交换越密集,对不对?

显然会见的,这个问题显得异常白痴,可以体会的是,在最佳条件下,每个人的带宽都达到了最大,如果每个人都下载了不同的分块,同时每个人都在贡献。

那么,每个人的下载速度,根本不会受到很大影响。

这对于传统下载(http/https/ftp)不同,传统下载仅仅由一台服务器贡献,人数多对于这台服务器压力极大。

但是,在这个环境里,服务器不仅仅是一台,而且,每当加入一个人,每一个人都可以当作服务器,这样,下载速度反而会大大提升。

反而文件很大是个硬梗,不过相对于传统下载,这种下载方式还是有一定能力解决这个问题的。

回到这里

IPFS的原理

ipfs的世界有个东西叫做cid,大概类似于这样一串: QmZCvMHrE56VqsejmG53xd9bW4RZjtFpLz46QMQjA81orL ,前面的 Qm 是固定的,后面是SHA256密钥.cid又分为用户密钥和文件密钥,这个暂时不讨论.SHA256强度目前看来基本不会碰撞,毕竟64位十六进制的字符串,能实现16^64个文件的存储,也就是1.1579208923731619542357098500869e+77个id,有生之年能看到它碰撞也不是一件容易的事情

上传

如同BT,在短短几秒内,需要分享的文件被分块完毕,但此时,文件还是乖乖的窝在硬盘里,没有分享出去.

当有任何一个人试图获取资源时,你的ipfs会联系距离最近的节点,询问他们有没有意愿存东西.请注意此处的 距离 ,这可不是物理距离,而是逻辑距离.

节点大都都会很高兴的同意,当然有些节点可能就是混口饭吃不想存,于是这些节点就会帮你联系离他最近的节点要不要,直到所有碎片瓜分完毕.这种算法,像极了DHT.

几分钟内,全球数个硬盘里就会出现你的文件的碎片,只要一声令下,这些文件就会调出.

此时,纵使文件发布者下线,文件照样流传.重复上传的文件,拆碎后被校验到与存在的文件相同则不会被上传.

上传完毕,文件就会拥有自己的id,也称为指纹,取回这些文件则需要id.

下载

你可以通过客户端下载,这个下载方式和btDHT其实原理是一样的;当然IPFS为了向下兼容http,自己也有网关,不过网关由于是公开的,一方面是速度,另一方面是已经引起了G||F||\/\/の注意,目前并不推荐使用网关下载,当然对于一些小文件比如网页和图片,这些都是随意的。

IPFS的作用

与一些网盘相反,ipfs反而非常鼓励你往里面塞东西,越多越好,但是作为个人网盘并不适合,一方面ipfs的资源会随着下载的人越多,缓存的机器也会越多,速度更快,私人文件反而速度不佳;另一方面ipfs一旦上传,任何人包括你自己永远无法彻底删除这些文件,即使你在ipfs客户端删除了你分享的文件,这些文件仍然会得到传播。所以ipfs其实可以作为公开下载,甚至是图床,网页托管等等。

在中国,ipfs实际上并没有得到很大的限制,分享文件和下载文件其实很容易做到,麻烦的只是不能从ipfs网关获取罢了。

下面是我放在ipfs上的一张图片,即使我下线,这个文件依旧流传于网络之间。

但是,ipfs反感在网关下载大文件,因为这样会造成不必要的带宽浪费,下载大文件请使用ipfs客户端+ipfs伴侣

搭建无服务器网页

很多人网页放在Github+CloudFlare,其实换个思路,为什么不用IPFS+CloudFlare呢?

开始吧!

下载ipfs

官网因为自带网关已经封锁了,请自带梯子访问。

写html

请注意,在ipfs上写网页时,请尽量不要使用外链,对于js和css请直接写在网页中,图片请使用base64或上传至ipfs后使用相对链接。

上传

upload successful

点击添加按钮,上传

upload successful

接下来,重点来了!

ipfs分享文件有两种方式,是ipfs和ipns,前者采用文件hash辨别文件,文件内容一旦改变,原来的链接无法更新,链接格式为 https://ipfs.io/ipfs/QmZCvMHrE56VqsejmG53xd9bW4RZjtFpLz46QMQjA81orL ;后者采用用户id辨别,内容允许更新,但是用户在线时间过短会导致无法同步,并且有可能暴露用户信息,后者格式为: https://ipfs.io/ipfs/QmQQKZphgJdEGhTp18NRvVdSJ3RJArRst2keKk3tZvmfPz?filename=index.html .

如果你只是单个文件网页,此处比较建议使用ipfs,ipns可能离线时间过长导致无法下载。具体看个人所好。

ipfs链接获取:点击 ··· ,选择复制哈希,在前面加上网关域名即可。

ipns连接获取:点击 ··· ,选择分享,复制链接即可。

在善用技术上网的前提下访问 https://ipfs.io/ipfs/QmZCvMHrE56VqsejmG53xd9bW4RZjtFpLz46QMQjA81orL 显示目标界面

请注意,每次上传后一定要先访问一遍资源,否则文件是不会上传到ipfs服务器的!

CloudFlare设置

不知道为什么CloudFlare的ipfs服务器有点问题,使用官方说明一直爆404 page not found,官方地址在这里https://www.cloudflare.com/distributed-web-gateway/ 反正我是折腾失败了。

↑以上为放屁,现在来讲一下怎么正确绑定:

dnslink绑定

这个方法无论你的dns服务商在哪都能绑定,只不过不在CloudFlare托管的用户要多一步。

1.将需要绑定的域名,以CNAME形式指向 www.cloudflare-ipfs.com ,比如我需要让 showtime.cyfan.top 成为ipfs出口,则这么设置。

upload successful

2.使用txt记录绑定ipfs hash,新建txt记录,名字是 _dnslink.yourwebsite 一定要加上yourwebsite!比如我绑定的是 showtime.cyfan.top ,则名称一行填上 _dnslink.showtime !接着内容是 dnslink=/ipfs/ 后面接上hash,比如showtimehtml的hash是: QmWAvNck7QBhUAYAEgBFvbvvsMxDC9s55NXVJXeJTjTM1Y 则大概这么填:

upload successful

3.如果你本来就是托管在CloudFlare上的,到此为止就可以了,如果不是托管在Cloudflare上,类似dnspod\alicdn之类的,那还要获取证书,进入https://www.cloudflare.com/distributed-web-gateway/ 拉到最底下

upload successful

输入域名,获取证书即可。

但问题是,www.cloudflare-ipfs.com 已经被dns污染了,你用cname绑定是无法正常访问的啦!所以只能用Workers绕路啦!

JSProxy反代ipfs.io

老办法,Workersjsproxy反向代理ipfs网关,解决

网址:https://showtime.cyfan.top

其他的用处

我在Github上看了很多奇奇怪怪的用法,有的拿来做博客,有的拿来做网盘,有的拿来做图床,欸,真的是脑洞大开,幸好ipfs经得起折腾,越折腾他们赚的越多

在不支持ipv6的路由器下使用ipv6

作者 CYF
2020年4月5日 15:34

从奶奶家回到城里,立刻有个问题困扰着我,我将处于没有ipv6的环境下。

拜托,都0202年了,你怎么连ipv6都没有?

因为家里情况有点小特殊,有三个路由器:

upload successful

移动路由器网关:楼下。100Mbps。通常使用LAN口直接网线插进去,一般不用无线功能,实际上老早支持ipv6了。

小米路由器:楼下。有线连接移动网关,因为支持5GHz的WiFi,所以在楼下一般手机都是接入小米路由器。

水星路由器:楼上。有线链接移动网关,不支持5GHzWiFi,不过2.4GHz一般满足日常使用,峰值可达44Mbps,对等网速4MB/s保持的住,也就不计较速度慢了。扯淡的是由于生产日期过早,居然不支持ipv6!!!

我在楼上,身边有个2008年买来的水星路由器,这个路由器通过百兆网线链接楼下的移动网关,也就是套了两层路由器。

恼火的是,由于是2008年的产物,这破玩意是不支持ipv6的。

upload successful

问题是,没有公网ipv6就意味着我的BTSYNC将处于疯狂的中继服务器,欸,不可忍耐。

以至于百度的ipv6都上不去。

upload successful

甚至连自己ipv6站点都上不去。

这谁忍得住啊。

坐下来好好想想

之前去问过姨父,姨父是个非常屌的男人。

我问:“阿姨丈(方言称呼),怎样在不支持ipv6的路由器下使用ipv6啊,把局域网下电脑做代理可以吗?”

姨父:(一脸鄙夷地瞟了我一眼)“硬件解决不了的的事情还想用软件解决?”

好,那我就用软件解决硬件解决不了的事情!

刷固件?我不知道这种行不行的通,刚才问了一下 winkiller 刷硬件的大佬,回答是应该不行.

upload successful

不行就不行吧,反正我也没指望刷硬件.

想一想,对了,父母那台电脑不是挺适合拿来做代理服务器吗?

思考一下:

  • 我是笔记本,他们是台式电脑,他们电脑每天开机,早6:00到网上11:00一直开着,也就是我开机的时间他们绝对开着.
  • 父母根本就没有ipv6这个需求,他们最多上爱奇艺央视看视频,ipv4和ipv6是啥都不知道,不过那台机子ipv6常开.
  • 父母网速需求不大,我因为路由器一层限制速度最多4MB/s,而家里带宽是12.5MB/s,基本不会影响.
  • 楼下那台绝大多数情况是空闲的╮(╯▽╰)╭
  • 楼下那台为了方便我把它设置成静态ip了

那就把父母那台机子做代理吧!

安装

首先要思考拿什么做代理.

VPN?算了吧,配置太麻烦,还是SS好.

欸你没看错,没错,把父母电脑作为SS服务端.

查了查,有一个东西叫做libQtShadowsocks

不过最新版本有点问题,于是我下载了1.10.0版

那就下载吧,为了防止楼下Github连不上去,我打包好了

>>去网盘下载

解压,我的版本里已经预置了一下内容,可以修改,如果是用原版的同学请继续.

shadowsocks-libqss.exe 同目录的文件夹下新建 config.json ,里面填上:

{      "server":"0.0.0.0",      "server_port":8023,      "local_address":"127.0.0.1",      "local_port":1080,      "password":"password",      "timeout":600,      "method":"aes-256-cfb",      "http_proxy": false,      "auth": false  }
  • server 表示监听的ip, 0.0.0.0 表示监听来自局域网的ip,保持默认即可
  • server_port 表示监听的端口,随意,只要不被占用即可
  • password 密码
  • method 加密方式.

上面的配置看情况修改,我这里就这样.

运行,出现个黑色框框碍眼,还报错?

upload successful

不要紧,因为没有指定配置,在底下新建 Start.vbs 里面填上:

Set ws = CreateObject("Wscript.Shell")ws.run "shadowsocks-libqss.exe  -c config.json -S",vbhide

双击解决,隐藏运行.

开机自启

难道每天早上我都要手动去运行?

不可能.

所以要设置开机自启动.

注册表 HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run,里头新建一个字符串值,名字随意,值为vbs的绝对路径.

测试

upload successful

添加浏览器代理.

于是出现封面如此不可思议的景象.

upload successful

upload successful

完成!

后言

实际上原理是这样的:

upload successful

另外,不知道是不是楼下360杀毒的锅,http链接经常不能跳转成https,导致一些网页没办法好好运行,emmm,加个HTTPSEverywhere解决问题。

>>去私有云下载HTTPSEverywhere

因为代理在局域网内,速度损耗和延迟基本不计入在内,也就是全天挂在这里都没问题,想要全局笼罩建议加个Proxifier

>>去网盘下载Proxifier

AV?BV!

作者 CYF
2020年4月2日 12:54

:(

Bilibili在2020/3/23公布要将av号全面换成BV号:公告地址

尊敬的各位用户:
一直以来,AV号都是B站视频稿件的重要标识,在视频的传播和分享中起到了关键作用。
为了保护稿件信息安全,容纳更多投稿,维护UP主的权益,自2020年3月23日起,AV号将全面升级为BV号。与纯数字的AV号不同,BV号是一段由数字和大小写字母组成的字符串,经过算法自动生成。未来将统一使用BV号作为稿件标识。
同时,2020年3月23日前生成AV号的相关功能保持不变。例如,已分享的稿件链接,AV号搜索,以及动态、评论、私信中的高亮跳转。
此外,用户在复制BV号或者包含BV号的链接后,打开B站APP的同时会自动跳转至该视频。
更多详细规则说明请见链接FAQ:https://www.bilibili.com/blackboard/activity-BV-PC.html
BV号将继续见证UP主们在这个舞台上创造无限的可能。陪伴大家的每一次灵感迸发,为每一个创意而干杯喝彩!

这就意味着AV号将永远没落于网络世界的角落,50年后b站的AV号将成为这个年代所有人的回忆.

我当时就觉得这是扯淡,本来AV号弄的好好的为什么又要换成BV号??

简介

AV号

AV 指的是AcgVideo(动漫视频)而不是AdultVideo ,目的是为了和niconico的 sm 号(SmileVideo)相对应。

通常AV号是按顺序的,比如前一个视频是AV10491,下一个视频如果审核通过了那一定是AV10492,绝对不可能是AV10493或AV17001,即使被删了那也是永远占了这个坑,想填都填不了, 排除后台操作可能性

AV号已经持续了将近12年的时间,具有了非常悠久的历史,b站老用户永远都不会忘记的数字。

BV号

BV:“BilibiliVideo”,感觉和油管接轨了一样,也是一堆看似乱码的玩意

反人类设计:10位固定设计,字母数字混合,最难受的是还要区分大小写,这让线下传播视频就变得异常难受啊!!!

区分大小写意味着如果你忘记按CapsLock打bv,那么你就会进入一个全新世界

坏处

难记难打

AV号很简洁,稿件是 av 前缀 加 若干位数字,一般人大都是八到十位,当然一些陈年老梗: av17001 av10492 av10388 往往就更加简洁了。

以前

A: 看看这个沙雕视频,香蕉哥,哈哈哈哈哈哈哈,AV号10198539

B: 1…噼里啪啦…9…噼里啪啦…3…噼里啪啦

B: 噗,哈哈哈哈哈哈哈,笑死我了

A&B: 哈哈哈哈哈哈

结果现在

A: 看看这个沙雕视频,香蕉哥,哈哈哈哈哈哈哈,BV号是17小写x411大写S7小写d大写Y

B: 什么沙雕玩意,那么难打,懒得看。

老是有关键字

比如说av57336629(这个是虚拟的),转成BV是BV17x411R18Y,然后因为有个R18被禁了….

[当时心情真的无语了]

换汤不换药

AV与BV其实是互通的,只是算法极其nb,base58(这辈子就听说过base64)加一堆高大上算法,结果还是可逆的,也就是说AV号实际上永远保留下来,但是永远从外部转向内部了.

分享视频必须进入b站复制bv号或者直接告知标题。

因为记不住。

著名的视频早晚会淡出视野。

因为记不住。

新用户则会对一些老视频的梗一头雾水。

因为他们不知道,AV已经成为历史。

邻居问题

很多up主都有这样的经历,将自己视频的av号加个1或减个1,看看邻居的视频怎么样.对于自己稿件通过审核的时间也把握的清楚

搞颜色

upload successful

upload successful

注:BlueVideo是美国黄片的俚语

当然也有唯一好处

孩子:妈妈过来看看B站上的AV!

啪!

妈妈:小小年纪不学好, 和隔壁老王一样 看AV!


孩子:妈妈过来看看B站上的BV!

妈妈:欸,宝贝,来了~~~~

究竟是为什么,让B站决定要换BV呢?

网上众说纷纭,不外乎关于这几点:

AV号有限,装不下了

目前我觉得可能性不大,一般性int值能达到2147483647,全中国一人发个视频也勉强装得下,而且最主要的AV号和BV号是互通的,以BV1874111715为例

upload successful

换成av号av99307985

upload successful

这说明,av只是从明面转向暗面,实际还是存在的.

当然,我也不清楚av什么时候会永远被清除,不过我可以肯定,av在最近10年估计都不会消失,因为B站也不想让以前网上流传的b站av链接点进去变成404.

反爬虫

反百度爬虫这种沙雕问题不在讨论范围内,b站估计反的是从av1到av100000000获取所有标题简介之类的爬虫.

可问题是,谁会那么蛋疼去遍历10万个视频的简介,大多数爬虫也就是遍历热搜榜的视频,这些换不换都是一样的啊.

保护安全和利益

?????我觉得我语文阅读理解有点小问题,没读懂,有大神在评论区指导一下吗?

宣扬品牌文化

其实这个解释有一定道理,但我觉得有点不必要,毕竟av号一贴别人就屁颠屁颠去b站粘贴了,换成bv还导致复制的位数更大了

模仿油管

虽然经常上油管鬼混,但我非常厌恶这种行为,av号是b站独有的文化之一,说难听点,现在随意跟youtube屁股,我觉得b站有点变了味.

常用油管的朋友应该能看到,油管内部链接都是直接复制的链接,而不是一串神秘代码,这使传播视频的同时,强制向所有观众传播了油管的品牌。同样常用/s/xxx神秘代码+密码的朋友,也知道百度网盘、百度盘、百度云是一个多么不清晰的品牌概念。

以前是AV加一串数字,很是没牌面。

现在是BV加一串字母数字,看起来像一串密码,不容易记住,跟国际接轨,很有牌面。

故意避免线下传播

没什么,五星好评,这个结论我能送上天

名字难听

…好像确实是这样的

转换

之前提了很多次,av与bv是互通的,接下来随便说几个avbv互相转换的方法,估计有效期能很长.

原生自带转换

以BV1874111715

upload successful

ChromeF12直接开挂开发者调试,选择 Console 控制台,输入aid

upload successful

返回一串数字,就是av号

upload successful

同理,对于AV视频,可以输入bvid获取那 F**king BV

upload successful

缺点

  1. 手机无法使用,当然vConsole那就不再是原生浏览器原汁原味了.
  2. 对于一些已删除和不存在(这两个是有区别的)的视频无法正常返回.

官方接口

AV to BV

接口: https://api.bilibili.com/x/web-interface/archive/stat?aid=

后面跟av号码,比如99307985,输入

https://api.bilibili.com/x/web-interface/archive/stat?aid=99307985

返回json格式:

{"code":0,"message":"0","ttl":1,"data":{"aid":99307985,"bvid":"BV1874111715","view":59702,"danmaku":524,"reply":1291,"favorite":1550,"coin":649,"share":134,"like":5068,"now_rank":0,"his_rank":0,"no_reprint":1,"copyright":1,"argue_msg":"","evaluation":""}}

其中bvid即为bv号.

BV to AV

接口: https://api.bilibili.com/x/web-interface/archive/stat?bvid=

后面跟bv号码,比如1874111715,输入

https://api.bilibili.com/x/web-interface/archive/stat?bvid=1874111715

返回json格式

{"code":0,"message":"0","ttl":1,"data":{"aid":99307985,"bvid":"BV1874111715","view":59704,"danmaku":524,"reply":1291,"favorite":1551,"coin":649,"share":134,"like":5068,"now_rank":0,"his_rank":0,"no_reprint":1,"copyright":1,"argue_msg":"","evaluation":""}}

其中aid即为av号

缺点

对于一些特♂殊♂の♀视♂频,av10492,则返回

{"code":-403,"message":"访问权限不足","ttl":1,"data":null}

直接数据库

这个,我真的服了,方法有点傻,但我还是佩服,大家可以去知乎上看看这位大仙,在这里就不贴链接了。

算法

知乎mcfx的回答,欸我是真的服了,下面直接照搬.

table='fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF'tr={}for i in range(58):tr[table[i]]=is=[11,10,3,8,4,6]xor=177451812add=8728348608def dec(x):r=0for i in range(6):r+=tr[x[s[i]]]*58**ireturn (r-add)^xordef enc(x):x=(x^xor)+addr=list('BV1  4 1 7  ')for i in range(6):r[s[i]]=table[x//58**i%58]return ''.join(r)print(dec('BV17x411w7KC'))print(dec('BV1Q541167Qg'))print(dec('BV1mK4y1C7Bz'))print(enc(170001))print(enc(455017605))print(enc(882584971))

互相转换脚本,如果算法没猜错,可以保证在 av 号 upload successful 时正确,同时应该在upload successful 时也是正确的。

此代码以 WTFPL 开源。
UPD:之前的代码中,所有数位都被用到是乱凑的,实际上并不需要,目前只要低 6 位就足够了。
(更大的 av 号需要 64 位整数存储,但是 b 站现在使用的应该还是 32 位整数,所以应该还要很久)
发现的方法:首先从各种渠道的信息来看,应该是 base58 编码的。设 x 是一个钦定的 av 号,查询upload successful这些 av 号对应的 bv 号,发现 bv 号的第 12、11、4、9、5 位分别会变化。所以猜测这些是 58 进制下的相应位。但是直接 base58 是不行的,所以猜测异或了一个大数,并且 base58 的字符表可能打乱了。经过实验,bv 号最低位相同的数,av 号的奇偶性相同,这一定程度上印证了之前的猜想。接下来找了一些 av 号 x,满足 x 和 x+1 对应 bv 号的第 11 位不同。设异或的数为 X,那么upload successfulupload successful表示异或)。由于 av 号(除了最新的少量视频)最多只有 27 bits,所以可以设upload successful。然后可以发现X只和upload successful和b有关,那么可以枚举这两个值(一共upload successful种情况)然后使用上面的式子检查,就能得到若干可能的upload successful和b。这里我得到的可能值如下:(左边是upload successful,右边是b)

22 9098364222 9098364350 4323408450 43234085

有奇有偶是因为异或 1 之后也能找到轮换表。而upload successful则使得模 58 的余数刚好变成upload successful减它。我取了 b=43234084,然后处理最低位,可以得到一个字符表,即 fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF。对于更高位,实际上还需要知道upload successful^2,upload successful^3这些值也可以 枚举 58 次得到,最后我得到的值是upload successful^4=1749968。这时我发现,每一位的字符表是相同的(实际上只对 b=43234084 是这样的),然后再微调一下参数(上面代码中的两个 magic number 就相当于这里的a,b),最后处理了一下upload successful的情况就得到了这份代码。

讲句大实话,我根本没看懂,不过看起来好牛逼的样子.

在线工具转换

既然python能做到,那么js也能做到!

工具很多,知乎上一搜一大把,这里懒得贴了.

插件

Give me AV not BV

upload successful

upload successful

作者估计是个暴躁老哥,骂人骂的挺顺口的,不过脚本不错,脚本检测到地址栏的BV号会自动无刷新替换为AV号,同时会在稿件页标题下方显示原始av号。.

安装前前请安装Tampermonkey或暴力猴脚本宿主.

后言

也许很多人认为这没什么大不了的,不要小题大做,没意思,那你还不是照样要看b站吗?

我王境泽就是饿死,死外边,从这里跳下去,不会吃你们一点东西! 欸,真香

对啊,可是这样就给我们b站的老用户一种错觉(其实我是2017入坑的),认为b站没有重视过老用户的感受,似乎就没了这种情怀.

AV转BV,似乎暗示着B站已经逐渐去ACG化了,这很难令人不落泪,还记得当时b站发的公告吗,大用户与普通用户无异,b站可能会倒闭,但不会变味,b站永远不会添加广告.

这几条与其它氪金为主的中短视频平台的差异广告,成为了绝大多数用户入坑的原因.

现在转头看来,大用户似乎有点越权,b站可能会在我们这辈子人的孙子的孙子的孙子死后倒闭,b站的味道依旧真香,但是以前和现在总感觉多了一些孜然味,原来的味道还有,但没有那么重了.唯一不变的是没有添加添加广告,这是我依旧占坑不爬出来的原因.

AV号历史很悠久,成为b站用户必不可缺的玩意,av梗也很多,很多up辛辛苦苦等在电脑前排个号av号,如今已成为泡影;像av10492,av10388,av17001这些老梗在未来就变成无人能理解的事物,有趣而神秘.

如果BV是高管随随便便提出来没有考虑过用户的体验而是盲目跟油管风的产物,那我可以毫不犹豫骂这个东西垃圾反人类,甚至很担心从前的niconico将成为b站的翻版,希望这是也只是担心.

从此,AV号将成为我们这辈子的纪念,50年后提起AV号就变得像如今提起抗日战争,听起来刺激又有意思,但不亲身体会永远没有这种失落感.

PHPnow-Windows下最轻巧的PHP运行软件,没有之一

作者 CYF
2020年3月29日 08:20

虽然作为一个静态博客的博主,但是还是有调试PHP的需求,(搭建静态是因为静态博客省钱),之前用过PHPStudy,不得不说功能真的非常强大,但我很多功能都不需要 ,而且相当吃内存,我一个笔记本主要是追求轻巧,PHPStudy一个安装包将近50MB。最头疼的是,我的80端口已经被系统监听了,无法终结,PHPStudy即使改了端口也没用,导致MySQL服务刚启动就停止的奇葩现象,害的我一年都没有好好调试过PHP。

至于这个安装包哪里来的,讲个笑话,在植物大战僵尸贴吧个人网址flash试玩版里提到:

upload successful

然后我就把PHPnow提取出来了,一个只有20MB的PHP运行包。

>>去网盘下载

请注意,PHPnow已经终止开发,最后一版的更新时间是2012-02-03 ,默认官网是http://phpnow.org,现在已经重定向至http://servkit.org/?from=phpnow.org,最高PHP是5.2.14,最高MySQL是5.1.50,框架只有Apache,最高2.2.16,并且与Windows10存在一定的bug,请仔细阅读下面的文档!

安装

upload successful

解压:

upload successful

有个7z.exe说明这是个解压Package.7z包,

Win10因为有UAC,直接双击Setup.cmd有问题,右键管理员也不行,要手动启动管理员命令行,cd到相应文件夹,输入 Setup 安装!否则会安装失败!

upload successful

upload successful

输入22

upload successful

输入51

接着是一番解压,不要管。

upload successful

输入y

upload successful

upload successful

Windows10家庭版不知道为什么80端口被禁用了,所以只能选1,我这里端口为4001

upload successful

安装,设置root密码

upload successful

任意键后,出现以下

upload successful

安装完成!

控制面板

安装完成后输入 pncp ,进入管理界面

upload successful

下次启动输入20,关闭输入30,其它的具体看情况

放入

将php放入 htdocs 即可!

测试Typecho

由于版本真的很古老,安装Typecho之类的请安装0.9,安装最新版1.*会导致错误!

upload successful

upload successful

一路确认,数据库填写test,安装完成

upload successful

upload successful

毕竟是古老的php,至少笔记本跑起来真的毫无压力,作为测试环境也完全足够了.

讲讲2020/3/26Github遭中间人攻击事件

作者 CYF
2020年3月28日 08:29

原标题:世界上最大的男同性恋GayHub遭到443端口劫持,是人性的毁灭还是道德的扭曲?

本博文上半篇转载【 https://blog.qwqdanchun.cn/archives/807 】的博文,转载如下:



转载

GitHub今晨遭遇大规模中间人攻击

昨晚6,7点左右,国内访问所有的 github pages 页面开启 HTTPS 的话证书都变成下面这个

upload successful

今晨,github.com也遭受了相同的中间人攻击

upload successful

谷歌浏览器提示不安全

upload successful

证书与昨晚的相同
疑似GitHub等网站遭到了大规模中间人攻击 。

中间人攻击(英语:Man-in-the-middle attack,缩写:MITM)在密码学和计算机安全领域中是指攻击者与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。在中间人攻击中,攻击者可以拦截通讯双方的通话并插入新的内容。在许多情况下这是很简单的(例如,在一个未加密的Wi-Fi 无线接入点的接受范围内的中间人攻击者,可以将自己作为一个中间人插入这个网络)。

一个中间人攻击能成功的前提条件是攻击者能将自己伪装成每一个参与会话的终端,并且不被其他终端识破。中间人攻击是一个(缺乏)相互认证的攻击。大多数的加密协议都专门加入了一些特殊的认证方法以阻止中间人攻击。例如,SSL协议可以验证参与通讯的一方或双方使用的证书是否是由权威的受信任的数字证书认证机构颁发,并且能执行双向身份认证。

简而言之,所谓的中间人攻击就是通过拦截正常的网络通信数据,并进行数据篡改和嗅探,而通信的双方却毫不知情。

奇特的是,可以实现此攻击需要劫持运营商或者dns才有可能实现,但是这种公然留qq邮箱的做法就十分迷惑

upload successful

本账号疑似真实姓名:张勇 ,黑龙江人???
经搜索,该用户曾在 https://blog.csdn.net/yhyhyhy/article/details/51248497#comments 帖子下评论,csdn账户: https://me.csdn.net/blog/qq_29158525 ;引出qq: 29158525

upload successful

本账号疑似真实姓名:周言諭 ,台北人???

upload successful

本账号疑似真实姓名: 谢邵 ,河南人???
据不可靠消息来源: https://www.hottg.com/liyuans/p31809.html 此邮箱可能归属于某三位数公司???

upload successful

另有v2ex老哥声称之前就看到过这个邮箱劫持其他域名

upload successful

证书生成疑似参照此文章:https://www.lagou.com/lgeduarticle/52972.html(是否参照存疑,不过原理确实相同,仅加密算法有所更改)

曾在贴吧提问: https://tieba.baidu.com/p/400626957?red_tag=3454236974

从论坛其他人评论可以得到其qq群关系:

群号:4823518 昵称:张勇群名:建三江一中同学群介绍:三江一中89级92届同学,,本届的加入,加入必须写名字!谢群号:66136842 昵称:张勇群名:亲友群群介绍:沟通群号:69386774 昵称:帅哥5号  张勇群名:帅哥靓女对对碰群介绍:命运负责洗牌,但是玩牌的是我们自己!群号:72876767 昵称:张勇群号:10456040 昵称:张勇群名:synjones群介绍:http://www.synjones.com群号:13602636 昵称:灵山-D3群名:★城东新居—D区★群介绍:哈尔滨城东新居,群策群力,共建美好家园。(有好的主张,可以发到群论坛)群号:15116517 昵称:灵山-D3群名:城东新居群介绍:小区业主维权的专家,溪畔家园业主委员会的李主任,联系电话是13069708074。大家回家群号:15376336 昵称:心即灵山群名:金龙卡服务群群介绍:内部专用群号:32068923 昵称:心即灵山群名:城东新居高级群群介绍:希望大家都能为本群和小区做一些贡献。都想一想办法,怎样能解决我们小区现存的问题。群号:19466772 昵称:我想回到以前群名:张兴庄吧群号:52260534 昵称:我想回到以前群名:巧群号:1363550 昵称:(り Remote、群名:都四班的群号:4250637 昵称:(り Remote、群名:QQ群群介绍:http://www.167cq.com/群号:70152084 昵称:谢绍。群名:国专08三班群介绍:国际经济与贸易专科0803班群号:84351791 昵称:帅气G★Remote群名:荣华09汽三群介绍:-群号:92050039 昵称:(り Remote、群名:v1p{會員}倲.总群群号:92118193 昵称:(り Remote、群名:高楼庄⒐⑥界黄金一班群号:92637291 昵称:(り Remote、群名:小伙伴群号:95128769 昵称:(り Remote、群名:藝术学院体育部群介绍:发掘、培养体育特长学生;做好对班级的考核工作;协助其他部门开展工作。群号:6888129 昵称:(り Remote、群号:9496270 昵称:(り Remote、群名:21中群号:11604443 昵称:収惢養鮏.┊群名:安阳铁路中学高06群介绍:班级群外人勿进群号:22739863 昵称:(り Remote、群名:华□豫□学□院。群号:29121641 昵称:(り Remote、群名:乀闭丄眼﹋⒑指紧筘群号:30215035 昵称:(り Remote、群名:看群公告!好消息!群介绍:免费拿話費兩百块!群里的朋友们用手机拨打1259064212参加侣友在线答题就可以了,我今群号:32591681 昵称:(り Remote、群名:高三一班群群介绍:创建此群的目的:仅仅是为了提供朋友之间的交流。在此群里,拒绝一切色情粗俗群号:34421223 昵称:(り Remote、群名:魭滴系鲥緔群介绍:IC,IP.IQ卡,通通告诉我密码!!群号:38607494 昵称:(り Remote、群名:ωǒ錯了?群介绍:莪們都昰好孩孓。异想’兲開徳孩孒︶ ̄~°群号:56483925 昵称:谢邵。群名:国专0803班群介绍:国专0803

总结:其人曾用qq:346608453,29158525,23853637;地址等不明,有代码基础,有能力实现本次攻击,疑似qq都是盗来的,无法确定本人身份

最后提醒:

《中华人民共和国刑法》第二百八十六条 违反国家规定,对计算机信息系统功能进行删除、修改、增加、干扰,造成计算机信息系统不能正常运行,后果严重的,处五年以下有期徒刑或者拘役;后果特别严重的,处五年以上有期徒刑。

违反国家规定,对计算机信息系统中存储、处理或者传输的数据和应用程序进行删除、修改、增加的操作,后果严重的,依照前款的规定处罚。

千万要知法守法,技术无罪,但是拿来做坏事可是要被抓进去的哦

11:03前来更新

攻击者换了一个证书,新的邮箱为 1396060845037@mymail.com ,截图如下

upload successful

第一个qq里的网址可能是因为这个qq是盗的,如果这样的话,几个qq之间没有联系也就正常了

upload successful

可能是最后的更新(2020/3/28 9:38)

昨天文章写的有些急,也受了一些言论的误导,出现了一处错误,csdn的昵称后缀是随机生成的,因此推出来的后两个qq号与此无关。而在昨晚,该用户修改签名为:“QQ号码被盗,现已恢复”,并在随后关闭空间(确实很多老哥前去轰炸),这一波貌似洗白的操作却是疑点重重。

首先,大家基本已经认为这个号是被盗的了,但是号主现身就有些奇妙,作为一个 从事软件开发工作近 16 年( CSDN 介绍 )的人,被长时间盗号,却不找回或无法找回,突然出事了便轻松找回,可能性???

而且,这样的话,另一个昨天没用到的点就有意思了,928天 qq达人,如果是盗号,那盗号的人也是把这个号当大号用了叭。这样我们可以假设确实被盗号,那么如果明天达人就该断了叭。找回了一个三年多没用的号,发完解释还会占着手机那一个qq登录的位置吗,大概率不会。而如果不是盗号,咳咳,那这位张勇兄弟大概就是真的勇了叭,哈哈。

其实说了这么多,就是我们这些GitHub重度用户,想要一个合理的解释,不管是有些人猜测的G||F||//,还是一线小兵误操作,抑或是黑客在家闲极无聊等等。如果是误操作或被黑,大家就当吃瓜一场,就算是墙的问题,我们也好提前搬好仓库吧。



好的,视线转回来,我要开始bb了。

刚开始知道这件事还是我的好奥秘Sir QQ空间里的转载:

upload successful

哦,发生了什么?

于是拿着QQ号去网上一找,冷汗直流:

这个QQ号的主人在2020/3/26通过手段劫持了Github、CloudflareCDN、京东网站,不过证书有问题被截下来了。

为什么冷汗直流?因为我全站绑定在Github+CloudFlare上,那岂不是我也被影响了吗?

至于为什么我没有察觉到,是因为3/26-3/27我在认真学习,没有上github

后来查找了一下,这次攻击只是影响了国内的互联网,CloudFlare劫持也只是针对美国那几个节点。

正好,上次嫌美国节点慢,通过自代理的方式节点到香港线,也就是我和攻击玩了个擦边球。

于是我就放心了.

但这次攻击不是一般的诡异,有很多疑点,当我松了一口气后又觉得不对,请听我慢慢讲解:

1.本次攻击并非DNS劫持,而是中间人劫持

DNS劫持那是G||F||\/\/的老把戏了,如果这一次这三家遭到DNS劫持,也许我也就懒得说了,毕竟这已经见怪不怪了,手动绕开来就可以了。

但是这一次,是七层精准劫持,也许这个名词不懂,那就是中间人攻击特定端口.

什么是中间人?

MITM,全称Man-in-the-middle attack,这种攻击在一定程度上能突破HTTPS,通俗的来讲是这样的:

先说正常https传输:

upload successful

这种加密方式叫非对称加密,应用的很广泛,至于为什么要用非对称加密而不用对称加密,这不是这里该讨论的.

假设有个黑客,想要窃取他们之间的信息:

upload successful

为什么毫无卵用?因为,非对称加密有个特点,用公钥加密的东西就不能用公钥解密了,而公钥是公开的.

但是,这样还有攻击的方式:

upload successful

这种黑客卡中间的方式,叫中间人攻击.

而且这种攻击,做的好就可以神不知鬼不觉窃听通话甚至篡改内容,最惨的是你都不知道被窃听了,用户和服务器都以为自己和对方直接通话,这才是最阴险的地方.解决办法只有HSTS。

至于用户是怎么被误导到黑客的,这就相当有意思,这就是路由污染,我们所说的ip查找,其实没有说有ip就可以直接找到对方了,还是要经过路由的.

比如说我第一次上百度(假设沿途完全没有缓存):baidu.com,真正完整的经过是这样的

  1. 电脑查找hosts,没有baidu
  2. 电脑于是向DNS(假如是8.8.8.8)发送请求包,问baidu.com在哪?
  3. 8.8.8.8这台服务器查了查,没有缓存,于是回头和我说稍等,自己去联系.com服务器
  4. .com服务器查了查,和8.8.8.8说,你去北京I根域名服务器找,我这里没有缓存
  5. 于是8.8.8.8又屁颠屁颠去北京I根域名服务器找NS记录,I根查了查,你去ns1.baidu.com找,它的ip是202.108.22.220
  6. 于是8.8.8.8回来跟我说,nameserver是202.108.22.220
  7. 于是我去202.108.22.220,向上一级路由(我的路由器)问202.108.22.220在哪?
  8. 路由器不知道,于是向上一级询问
  9. 上一级返回给我的路由器稍等,自己还要去上上级查询,我这里是117.149.*.*
    (此处省略17步路由查询)
  10. 最终,层层路由查询告诉我,ip202.108.22.220在北京联通,我帮你找到了
  11. 发个握手包,连接,返回baidu.com的ip 39.156.69.79
  12. 又一次经历7-10步,我与百度连接成功
    ||完了?嘿嘿,其实还没完
  13. 百度301重定向将我从 baidu.com 定向到 www.baidu.com
  14. 又一次经历1-10步,最终,百度界面华丽丽地展开来.

那么,中间的路由查询变得很有意思,如果我在骨干网那么干,故意把 github.io 路由到我的服务器上,是不是所有人都被劫持了?

很不巧,这一次真的就是那么干的.

2.只劫持443端口,没有劫持80端口

有人curl一下github.io,80端口TTL是51,443就变成56了

更nb是,有人ping了一下github.io:80和github.io:443,前者延迟280ms,丢包率25%,后者延迟20ms,不丢包.

所以这玩意还能降低延迟?

我笑了.

更厉害的是,由于是中间人攻击,有网友说如果信任证书还能上github,不过速度很慢.

也对,谁叫他手贱修改了全国路由,现在他的服务器正在遭受全国的DDoS.

敢情难道是他想给全国人民一个惊喜,反向代理gihub以提高访问速度?

3.留下QQ邮箱

【以一己之力攻击国家骨干网污染三个大网站的路由】和【留下自己的QQ邮箱】,干这两件事情的真的是同一个人吗?????

有人说这是仿照一篇文章伪造的,文章什么我就懒得贴了,反正伪造证书这种事情我也不会( ﹁ ﹁ ) ~→

但是,能攻击国家骨干网却拿了个自签名的证书攻击,这种事情都可以上诡异录了,唯一的解释可能是混淆视听,或者是大攻击前的试水,亦或是真的QQ被盗了,来当个替罪羊。

4.声称自己QQ被盗

这是QQ对应的空间,3/27晚上已无法正常访问,这是之前截图的:

upload successful

5.同时攻击了京东和CloudFlare

如果说只攻击了Github,那我能肯定,多半是G||F||\/\/技术人员手动失误,但你把京东给搞了,我觉得这……没搞懂啊,所以这真的可能是试水咯?

6.初学者?

网上有很多人说这是个初学者,想测试一下,没想到惹出那么大的事情.

好啊,初学者随随便便搞塌国内Github京东CF,再学一点签个真实证书,下次干脆把gov.cn全站劫持了,是不是这样才能让zf了解到https的重要性.

如果真的是初学者,那么从侧面就可以讽刺我国网络安全堪忧啊.

教练,我想学这个.

7.这么值得报道的新闻,官媒毫无反应

我觉得这就很有意思了,我不说,大家应该知道为什么

后言

关于这次被攻击,为什么Github错误页面会被显示呢?

1.Github开了HSTS,匹配的头字符不对,浏览器直接拦截,连忽略警告,继续访问这样的字符都没有,吓得我赶紧给我全站开了HSTS.
2.证书是个自签名,按理说能攻击骨干网的大佬伪造个证书应该很容易,为什么用了这个呢?

另外,以国内的环境,大家上Github扔代码一定要用SSH,因为SSH双方是强加密的,可以预防中间人攻击!

❌
❌