今天把我压箱底的web-replayer公开发布了
在开源Anys之后,我再次把自己的压箱底作品公开发布。在此之前,我一直的想法是密而不发,毕竟现在的伸手党实在是太多了,白嫖过去用,一分钱不掏,最后还可能被喷。虽然开源本身是非常好的,可以促进技术的发展,相互学习,一起创造出一些有意思的东西出来。然而,由于环境不好,好好的一件事,最后搞的不愉快。因此,我现在开源也好,公开发布新东西也好,也都留了一个心眼,避免卷入这些内耗纷争。
今天发布的是我在后台用来进行日志回放的播放器,取名web-replayer,见名知意。
https://www.npmjs.com/package/web-replayer
简单讲它就是一个用代码来进行演示的播放器。什么意思呢?就是它让你的代码执行,像播放器播放视频一样进行执行。我们使用anys的一个能力,就是录制前端页面的变更,用户的行为等等。那么当这些数据进入数据库之后,通过web-replayer,就可以把这些日志数据取出来进行回放,这样我们就可以在web-replayer中,看到用户的操作,以及当时的情况。那么具体怎么把一条条日志数据进行播放呢?这里就需要引入视频里面的“帧”的概念,理论上,1条日志就是1帧。因此,我们只需要把这些一帧一帧的过程,在web环境下给它呈现出来。怎么呈现呢?当然是操作DOM了。因此,我们只需要提供每一帧,如何操作DOM即可。如何操作DOM,则是依赖日志本身,当我们看到是一条mousemove的日志时,我们就操作DOM里面用来模拟鼠标的元素进行移动;当我们看到是一条snapshot的日志时,我们就直接用HTML镜像覆盖当前的DOM。这样,我们把对每一条日志对应的DOM操作都写好之后,我们只需要按照时间的流逝,不断去执行这些动作即可。这就是web-replayer的底层思路。当然,它自己还有一些其他方面的设计和考虑,但是核心思路就在这里。
基于这一思路,web-replayer不单单可以用来回放日志,你甚至可以用来实现一段视频,因为你可以在帧上操作图片、声音等素材,通过代码来编写这些东西,就可以实现一种神奇的“用代码做视频”的效果。
好了,如果你有兴趣,不妨通过上面的链接去下载和使用web-replayer。最后,我使用了较为严格的license,主要是防止某些不良服务商白嫖。如果你是个人,且不需要对源码进行修改,也不需要部署自己的服务,那么可以随便使用。
我们看似不规律的事情,反而是规律的正弦波在时域上的投影,而正弦波又是一个旋转的圆在直线上的投影。……我们眼中的世界就像皮影戏的大幕布,幕后有无数的齿轮在旋转。我们只看到屏幕上毫无规律的小人在表演,无法预测它的下一步。而幕后的齿轮却永远一直那样旋转,永不停歇。
技术本身的发展并不是核心,编程思想的发展才是进步
我最近一直在思考一个问题,大概9年前,我使用sea.js进行前端编程,那个时候觉得它很神奇,但于此同时,AMD/CMD带来的心智负担也很重,在编程中往往遇到一些不符合预期的结果。而我最近写了一个框架PHC,就开始反思,从技术层面讲,在sea.js的时代,似乎也是可以实现PHC的,为什么时隔那么多年,我才能写出这样的一个框架来呢?经过反思,我想这里面最大的原因,在于编程思维的进步。
我为什能写出PHC呢?因为我这么多年在前端的开发经验和尝试,让我自然而然的写出来。而我的这些尝试,往往又是在整个大的技术背景下被驱动的,如果没有这些年在sea.js, angularjs, vue, react等等框架的影响下,没有nodejs, 前端工程化, SSR, 微前端等等技术风潮的熏陶下,我也没有意识去写一个这样的框架。当随着技术的洪流随波逐行时,我们这一批的程序员的思维也在变迁,以前想不到的东西,在通过几个代际的更迭之后,也逐渐有了新想法,就有了PHC的尝试。
这一经历中,我发现,在sea.js的时代,我们完全可以做到PHC的同等效果,但是我们又做不到,因为那个时候没有UI组件化思想、微前端思想、沙盒思想,这些思想都是近几年才出现的,因此,在那个时代,我们无法做到。同样的道理,我现在能写出PHC,就像当年玉伯写出sea.js一样,他在模块化思潮的影响下写出的优秀框架,却无法超越时代写出更精简的实现。借古推今,再几年后,我自己回头看,也会感叹,自己当初怎么只能写出PHC这样的框架呢?历史时点上的思维限制,是我们技术发展本身无关的,虽然在技术发展中,新技术可以为我们提供可以用更少代码实现更强能力的工具,但是我们还是要看到,技术思维的发展,才是我们突破技术局限的核心根源。
这让我想起《三体》小说中的一句忠告:你要想,多想!
如何彻底杀死child_process.exec子进程
最近在尝试使用child_process来跑一些子任务时,调用了yarn的命令来启动一个服务,但是发现怎么调接口都无法kill,子任务还在跑。后来灵光一闪,spawn在运行yarn命令的时候,是不是再在它的上面包了一层。顺藤摸瓜,发现网上也有同样的说法。原来,spawn在运行子命令的时候,首先要选一个shell来跑,默认是使用sh,也可以使用bash或cmd.exe,这个需要在调用spawn的时候传入它的第三个参数里面的配置来确定。这也就意味着,child.pid返回的是调用sh的进程,而在sh里面执行命令产生的子进程的pid不是默认返回的child.pid,而且,在sh里面,可能还会再起其他的子进程,也就是说,实际上,我们看似只跑了一个spawn,但是实际上它可能起了一堆子进程,而我们想要kill的目标进程只是这一堆里面的其中一个。
默认情况下,我们通过ctrl+c可以退出process,而nodejs会把SIGINI传递给所有子进程,进而关闭全部子进程。但是,如果我们自己手动调用child.kill来杀死子进程,就会导致只杀掉了一个,而且可能是最不重要的(因为跑的是sh)。
有了这个前置知识,那么问题就比较好解决了。我们只要封装一个自己的kill函数,传入child.pid,然后把其全部子进程杀掉即可。具体做法可以参考ps-tree里面的示例代码。在我们自己实现的时候,也可以直接引用ps-tree这个包来把所有子进程pid查出来,然后再通过exec遍历它们执行一个kill -9 {pid}即可。如此就可以真正把子进程杀掉了。
时间沉淀,不问世人问自己
今天发现,当下非常火的视频剪辑软件剪映是有深圳市脸萌科技有限公司开发的,“脸萌”是多年前突然爆火的一款APP,在那段时间,几乎所有人都将自己的头像换成了用脸萌软件组合而成的头像。我至今仍在用的开通头像也是用脸萌生成的。虽然脸萌不是最早的捏人软件,但是确实那时最简单有趣的。不过,在爆火之后,很快这款app就退烧了。相信在互联网流速这么快的时代,很少有人还记得这款app,除了像我这种还在使用曾经制作的卡通头像的人以外。后来,我在一些视频网站看到媒体对它创始人的采访,他说到,随着app退烧,公司的业务也开始转型,可能很大程度上会作为技术服务公司,说的难听一点,就是外包。本质上,脸萌app没有盈利模式,所以逐渐褪去色彩也是很正常的,只是作为一家公司,如何在爆款过期后持续运营,是很难的一件事。不少公司或团队不断探索新的爆款,采用低成本高数量输出,博一个中奖概率。但是,我相信脸萌的团队在这些年的沉寂中,没有这走条路,或者说想走但最终条件不足,选择了技术沉淀的道路。我能想象到他们经历了公司从几个人到上百人的发展过程中,经历很多的消耗、迷茫、懊恼等等,但是,我也相信他们在不断的为其他项目提供技术支持的同时,不断的总结了自己的技术经验,形成了一套不错的技术护城河,最终通过技术获得字节这样头部公司的青睐。出品剪映,可能仅仅是因为他们拥有这样的技术沉淀,比别人走的早走的快,现在能把产品做的这么好,算是对这些年默默无声的回报。我相信,就两款软件而已,剪映更有价值,无论是使用的场景,还是未来的商业价值,然而,我们却并没有看到他们团队像曾经一样作为明星团队到处接受采访。这或许就是成长,同样一家公司,不同时期,不同阅历,不同心境。
windows11 资源管理器 该文件没有与之关联的应用来执行该操作
关于win11任务栏的文件资源管理器出现打不开,没有与之关联的应用.....,可以尝试以下方法,亲测有用。
新建txt文档,将以下代码复制进去。
Windows Registry Editor Version 5.00 [HKEY_CLASSES_ROOT\Folder] @="Folder" "ContentViewModeForBrowse"="prop:~System.ItemNameDisplay;~System.LayoutPattern.PlaceHolder;~System.LayoutPattern.PlaceHolder;~System.LayoutPattern.PlaceHolder;System.DateModified" "ContentViewModeForSearch"="prop:~System.ItemNameDisplay;System.DateModified;~System.ItemFolderPathDisplay" "ContentViewModeLayoutPatternForBrowse"="delta" "ContentViewModeLayoutPatternForSearch"="alpha" "EditFlags"=hex:d2,03,00,00 "FullDetails"="prop:System.PropGroup.Description;System.ItemNameDisplay;System.ItemTypeText;System.Size;System.HomeGroupSharingStatus" "NoRecentDocs"="" "ThumbnailCutoff"=dword:00000000 "TileInfo"="prop:System.Title;System.HomeGroupSharingStatus" [HKEY_CLASSES_ROOT\Folder\DefaultIcon] @=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,00,74,00,25,\ 00,5c,00,53,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,73,00,68,00,\ 65,00,6c,00,6c,00,33,00,32,00,2e,00,64,00,6c,00,6c,00,2c,00,33,00,00,00 [HKEY_CLASSES_ROOT\Folder\shell\explore] "LaunchExplorerFlags"=dword:00000018 "MultiSelectModel"="Document" "ProgrammaticAccessOnly"="" [HKEY_CLASSES_ROOT\Folder\shell\explore\command] "DelegateExecute"="{11dbb47c-a525-400b-9e80-a54615a090c0}" [HKEY_CLASSES_ROOT\Folder\shell\open] "MultiSelectModel"="Document" "LegacyDisable"=- [HKEY_CLASSES_ROOT\Folder\shell\open\command] @=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,00,74,00,25,\ 00,5c,00,45,00,78,00,70,00,6c,00,6f,00,72,00,65,00,72,00,2e,00,65,00,78,00,\ 65,00,00,00 "DelegateExecute"="{11dbb47c-a525-400b-9e80-a54615a090c0}" [HKEY_CLASSES_ROOT\Folder\shell\opennewprocess] "ExplorerHost"="{ceff45ee-c862-41de-aee2-a022c81eda92}" "Extended"="" "LaunchExplorerFlags"=dword:00000003 "MUIVerb"="@shell32.dll,-8518" "MultiSelectModel"="Document" "LegacyDisable"=- [HKEY_CLASSES_ROOT\Folder\shell\opennewprocess\command] "DelegateExecute"="{11dbb47c-a525-400b-9e80-a54615a090c0}" [HKEY_CLASSES_ROOT\Folder\shell\opennewwindow] "LaunchExplorerFlags"=dword:00000001 "MUIVerb"="@windows.storage.dll,-8517" "MultiSelectModel"="Document" "OnlyInBrowserWindow"="" "LegacyDisable"=- [HKEY_CLASSES_ROOT\Folder\shell\opennewwindow\command] "DelegateExecute"="{11dbb47c-a525-400b-9e80-a54615a090c0}" [HKEY_CLASSES_ROOT\Folder\shell\pintohome] "AppliesTo"="System.ParsingName:<>\"::{679f85cb-0220-4080-b29b-5540cc05aab6}\" AND System.ParsingName:<>\"::{645FF040-5081-101B-9F08-00AA002F954E}\" AND System.IsFolder:=System.StructuredQueryType.Boolean#True" "MUIVerb"="@shell32.dll,-51377" [HKEY_CLASSES_ROOT\Folder\shell\pintohome\command] "DelegateExecute"="{b455f46e-e4af-4035-b0a4-cf18d2f6f28e}" [HKEY_CLASSES_ROOT\Folder\ShellNew] "Directory"="" "IconPath"=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,00,\ 74,00,25,00,5c,00,73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,73,\ 00,68,00,65,00,6c,00,6c,00,33,00,32,00,2e,00,64,00,6c,00,6c,00,2c,00,33,00,\ 00,00 "ItemName"="@shell32.dll,-30396" "MenuText"="@shell32.dll,-30317" "NonLFNFileSpec"="@shell32.dll,-30319" [HKEY_CLASSES_ROOT\Folder\ShellNew\Config] "AllDrives"="" "IsFolder"="" "NoExtension"=""
保存文档到桌面,修改文档名称为folder fix w11,后缀reg格式。
双击reg文件,点击是,添加到注册表中。
另外win10系统也可以将文件名称改为w10,其他步骤相同。
PM2 + Nginx 502 Bad Gateway
用PM2起node服务,再nginx反向代理,结果遇到502 Bad Gateway问题。我自己猜测,502就是node端报错或者什么原因,导致代理没有找到目标。后来研究很久之后,发现是PM2 --watch模式的原因。
当PM2开启--watch之后,目录下有文件变动的情况下,会重启服务,这就会导致会有短暂的空窗期,这期间nginx反向代理找不到目标,九会报502.
解决办法是将数据库、日志等会被操作的文件移除PM2对应的项目目录,确保项目下的文件,在运行期间不会被代码修改。