跟我一起部署和定制 CNPM——自定义包存储层

597 查看

CNPM 的自定义包存储层文件系统简称 NFS,我猜是 NPM File System 的意思。

在之前《跟我一起部署和定制 CNPM——基础部署》中提到过,CNPM 配置项里面有一项配置 nfs,它所对应的是一个 NFS 对象。

在同步 package 的时候,CNPM 会把源站的包下载到本地,然后传给 NFS 对象相应的函数交予去处理,由 NFS 对象返回处理结束之后该包在我们自己部署的 CNPM 对应的包下载链接。

上面的这一套流程就给我们自定义包存储提供了可能,比如我们可以把包同步到又拍云存储、阿里云 OSS 等地方去,也可以以二进制的形式存入我们自己的数据库(不推荐),甚至可以什么都不用做直接放在本地,然后把本地文件对外网暴露即可。

NFS 接口

NFS 的接口是实现定义好的,我们如果要写一个自己的 NFS 类,只需要按照约定的接口实现他们的逻辑即可。

虽然我自己不喜欢,但是 NFS 的所有函数需要在菊花函数中被实现。

下面给出接口的定义:

  • function* upload(filepath, options)
    • filepath:文件路径。
    • options
      • key:待上传文件的标识
      • size:待上传文件大小
  • function* uploadBuffer(fileBuffer, options)
    • fileBuffer:待上传文件的 Buffer
    • options
      • key:待上传文件的标识
      • size:待上传文件的大小
  • function* remove(key)
    • key: 文件标识
  • function* download(key, savePath, options)(可选实现)
    • key:文件标识
    • savePath:保存路径
    • options
      • timeout:超时时间
  • function* createDownloadStream(key, options)(可选实现)
    • key: 文件标识
    • options
      • timeout:超时时间
    • 返回一个 ReadStream
  • function[*] url(key)(可选实现,可以不是菊花函数)
    • key: 文件标识

OSS-CNPM 解析

这里拿出一个 NFS 的官方实现阿里云 OSS 版来作为解析。它的 Repo 是https://github.com/cnpm/oss-cnpm

打开 index.js 我们能看到,的确 OssWrapper 实现了上面的一些接口。

构造函数

function OssWrapper 里面我们看到它 newali-oss 对象。

也就是说在各种上传等函数里面都是以这个 client 为主体做的事情的。

upload 和 uploadBuffer

首先我们看看 upload 函数,从外部传进来文件的 key,NFS 对象将该文件以 key 为名传到 OSS 去,并返回该文件上传之后在 OSS 上的地址。

uploadBuffer 其实也一样,参数第一个 fileBuffer 是一个文件二进制 Buffer 对象,而 ali-oss 包的 put 函数第二个参数既可以传一个文件路径,也可以传一个 Buffer,所以相当于把 upload 这个函数直接拿过来就能用了,于是就有了:

remove、download 和 createDownloadStream

这两个函数实际上也是直接调用了 ali-oss 的函数,并没有什么好讲的,大家自己看看就好了。

url

这个函数无非就是判断下有没有自定义的 CDN 域名什么的,根据不同的返回不同的网址而已。

trimKey

key 里面带的最前面的斜杠去掉。

我的 OSS-CNPM 随意改造

上面一节解析了 oss-cnpm 这个包的代码,如果官方出的几个 NFS 包不能满足,大家也能自己去写一个 CNPM 存储层的包了。

我们公司的包是直接在 OSS 上面的,所以用 oss-cnpm 并没有什么不妥。

不过对于阿里系本身的公司门来说,OSS 并不是什么大事儿,对于我们来说,OSS 的 bucket 资源还是蛮稀缺的,上次就达到上限了。所以我们目前的 NPM 包跟公司别的测试业务用的是同一个 bucket。

那么问题来了:

oss-cnpm 直接把所有文件放在根目录下建文件夹,太乱了,而且的确是有小可能冲突的。而这个包又不能让人自定义前缀什么什么的。

于是我就自己 Fork 小小改装了一下这个包,让它适合我们公司自己。

改装很简单,在上传的目录中加一个文件夹前缀。

动的是 trimKey 函数:

这下所有在我们内部 CNPM 里面的包的链接都多了个 _snpm_/ 的前缀了。

CNPM 调用解析

上面解析了接口之后,我们来扒一扒什么时候会调用上面实现的接口们吧,这样就知道 CNPM 对于 NFS 使用的工作原理了。

controllers/registry/package/download.js

源码参考

对于包下载来说,它的路由是:

然后在里面判断一下如果 NFS 对象有实现 url() 函数的话,先用 url() 函数生成对该包而言的真实下载链接。

读出这个包的 registry 信息,里面如果没有 dist 等参数的话直接 302 到刚生成的地址去。

接下去是涉及到上一章没有提到过的一个配置参数,叫 downloadRedirectToNFS,默认为 false。如果该值为 true 的话并且刚才由 url() 函数生成了下载链接的话,也是直接 302 到真实下载链接去。

不过如果本身 registry 里面就没 key 这个选项的话也会直接用 url() 生成的链接给跳过去。如果没有 url() 的链接,那么直接用 registry 里面的 tarball 字段。

上面如果都跳过去了,那么说明要开始调用事先写好的 download 那两个函数了,把文件读到 Buffer 里面,然后把 Buffer 放到 Response 里面传回去。

controllers/registry/package/remove.js

源码参考

对于删除包来说,除了把包从数据库删掉之外,还要循环遍历一遍这个包的所有版本,把所有版本的这个包都从 NFS 里面删除。

这里就调用了你事先写好的 remove 了。当然你不实现也没关系,最多是包的压缩文件不删除而已。

controllers/registry/package/remove_version.js

源码参考

这里跟上一小节差不多,之前是删除整个包,这里是删除包的某一个版本,所以就不用循环删除了。

controllers/registry/package/save.js

源码参考

然后就是用户 $ npm publish 用的路由了,在一堆判断之后,发布传过来的包被放在二进制 Buffer 内存里面: