企业级 npm 私有仓库部署方案

本文作者系360奇舞团前端开发工程师

淘宝 NPM 镜像站切换新域名时,放了一张知乎博主·天猪·的图片,如下:

6adb1118bfb5e42deaaade2b897c3fe6.jpeg

_图片来源:https://zhuanlan.zhihu.com/p/432578145

看着逐年增长的访问量,不禁让人感慨,npm 的出现,对 JavaScript 生态产生了深远影响。npm 包下载量的变迁,也是 JavaScript 生态体系发展的一个缩影,同时也是国内公司前端领域发展的一个缩影。

npm 的出现,让我们可以轻松使用第三方模块,也让我们可以轻松发布自己的模块。

随着企业的发展,公司内部对私有的 npm 源的需求越来越多,下面就来介绍一下,企业级 npm 私有仓库部署方案。

一、 为什么需要 npm 私有仓库

我们从基本要求和扩展需求两个方面来分析。

  1. 基本要求

  • 安装速度快

  • 内网访问

  • 对接公司内部登录权限系统

  • 支持私有包

  • 避免公网 npm 服务不稳定

扩展需求

  • 安全审计,追踪依赖,及时发现安全问题

  • 使用 AI 相关技术,自动产出项目老旧依赖升级建议

  • 支持内部 npm cli 工具扩展更多功能

  • 前端工程数字化建设,定制化数据统计分析功能

基本要求,是搭建企业级 npm 私有仓库的核心诉求;扩展需求,则是企业数字安全和团队工程化的诉求。当然,不同的团队有不同的需求,本质上就是为了安全和稳定,能依托 npm 服务做定制化服务。

二、开源项目方案

既然要搭建企业级 npm 私有仓库,那么首先就要考虑开源项目。

有 nexus-public、verdaccio(sinopia已停止更新)、cnpmcore(cnpmjs已废弃)等,其中 nexus-public 是 java 项目,其他都是 nodejs 项目。另外有收费的有 JFrog Artifactory,是通用通用存储库管理器,支持许多流行仓库包括 npm。

基本上,能做到私有化部署npm服务 + 对接公司内部权限系统,就能满足基本要求。

  • nexus-public 是 java 项目,能满足基本要求,如果公司有同步管理 maven、docker等需求,可以考虑。

  • verdaccio 界面简洁,功能齐全,社区活跃,拥有 15k 的 star数,是一个不错的选择。verdaccio 非常轻量,安装和配置都非常简单,可以通过插件,定制化包括身份验证、存储和通知等功能。

  • cnpmcore 是淘宝 NPM 镜像站服务 npmmirror.com 背后的核心,是基于 eggjs 开发的。

三、选择 cnpmcore

企业服务,要求数据和服务分离,既要保证数据的安全和可靠性,又要保证服务的持续升级迭代。

既然是内网私有化部署,不用面对公网的流量,更多的关注点在于,除了满足内网 npm 基本服务要求,更多的是企业定制化需求。

综合考虑各方需求后,我认为 cnpmcore 对于企业级 npm 私有仓库部署方案来说,是一个不错的解决方案。

d079999f1e32393d8679ff7a5e8f3ae7.png

图片来源: https://juejin.cn/post/7132723239813382151

相比于前身 cnpmjs,cnpmcore 进行了重大的重构,cnpmcore 对我来说,看中的就是它的二次研发能力。

cnpmcore 部署比较简单,文档介绍的已经很详细了,主要步骤简单描述:

  1. 依赖服务准备,MySQL 数据服务、Redis 缓存服务,包存储默认是本地文件系统,推荐使用对象存储服务

  2. 源码下载,安装依赖,修改配置文件,启动服务

比起直接源码部署,我更推荐创建 tegg 项目,集成 cnpmcore 使用, cnpmcore 官方有提供详细的 tegg 集成示例。当前有个前提,你对于 eggjstegg 有一定的了解。

了解 cnpmcore 项目架构

首先放一下 cnpmcore 文档里的架构分层依赖图

605ad033ddd0d9f87d7220a6c336c70b.png

简单总结一下文档内容:

  • 项目结构:项目按照功能进行了分层,包括 common(通用工具和服务调用)、core(核心业务逻辑)、repository(数据存储和查询)、port(HTTP 控制器)、infra(基于 PaaS 的基础设置实现)等。

  • Controller:主要处理 HTTP 请求,继承自 AbstractController 和 MiddlewareController。AbstractController 封装了一些基础的数据 Entity 访问方法,MiddlewareController 主要负责编排中间件的加载顺序。

  • 请求合法性校验:请求合法性校验包括请求参数校验、用户认证和资源操作权限校验。请求参数校验使用 egg-typebox-validate,用户认证和资源操作权限校验通过 UserRoleManager 进行。

  • Service:依赖 Repository,然后被 Controller 依赖。PackageManagerService 管理所有包以及版本信息。

  • Repository:依赖 Model,然后被 Service 和 Controller 依赖。Repository 类方法命名规则包括 findSomething(查询一个模型数据)、saveSomething(保存一个模型数据)、removeSomething(移除一个模型数据)和 listSomethings(查询一批模型数据)。

由于 tegg 的存在,后续在定制化开发中,我们可以按需导出所需要的模块,然后进行二次开发。

四、定制化开发

如何在 tegg 中集成 cnpmcore 是官方提供的集成示例,完整代码可以参考 这里。

准备工作

依赖的外部服务这里不多做赘述,既然是企业级,可以自行对接公司内部的 MySQL、Redis 服务。另外,准备对象存储服务 s3, 用于存储包文件。

注意: cnpmcore 是渐进式开发,mysql 数据结构会随着版本发生变动,升级 cnpmcore 时,一定要注意这里 https://github.com/cnpm/cnpmcore/tree/master/sql, 提前做好数据库表结构变更准备。

项目初始化

$ mkdir tegg-cnpm
$ cd tegg-cnpm
$ npm init egg --type=ts

初始化后的项目,是一个 tegg 项目,集成了 eggjs 和 tegg,这里先更新一下依赖。

然后,我们需要安装 cnpmcore 依赖,以及需要的一些依赖,如 @eggjs/tegg-orm-plugin、egg-typebox-validate、s3-cnpmcore 等。

配置文件

  1. 修改 config/config.default.ts 配置文件,添加 cnpmcore 配置。

config.cnpmcore = {
    name: 'mynpm',
    sourceRegistry: 'https://registry.npmmirror.com',
    ...
    // sync mode
    //  - none: don't sync npm package, just redirect it to sourceRegistry
    //  - all: sync all npm packages
    //  - exist: only sync exist packages, effected when `enableCheckRecentlyUpdated` or `enableChangesStream` is enabled
    syncMode: SyncMode.admin,
    syncDeleteMode: SyncDeleteMode.delete,
    ...
    registry: process.env.CNPMCORE_CONFIG_REGISTRY || 'http://localhost:7001', // 填写自己的域名
    // white scope list
    allowScopes: [
      '@myscope', // 这里添加自己的 scope
    ],
    ...
    // 默认的系统管理员
    admins: { 
      // name: email
      xxx: 'xxx@my.cn',
    },
    ...
  };
  1. 添加 nfs 配置,用于存储包文件

import S3Client from 's3-cnpmcore';
...
// 默认是本地文件系统
config.nfs = {
  client: null,
  dir: join(config.dataDir, 'nfs'),
};

// 这里使用 s3 对象存储, s3-cnpmcore 是 cnpmcore 官方提供的 s3 对象存储实现, 当然可以不用这个,自己实现也可以
config.nfs.client = new S3Client({
    region: 'xx',
    endpoint: 'http://xxx.com',
    credentials: {
      accessKeyId: '',
      secretAccessKey: '',
    },
    bucket: 'cnpmcore',
    forcePathStyle: false,
    disableURL: false,
  });
  1. 修改 config/plugin.ts 配置文件,开启我们需要的插件,如下:

teggOrm: {
    enable: true,
    package: '@eggjs/tegg-orm-plugin',
  },
  typeboxValidate: {
    enable: true,
    package: 'egg-typebox-validate',
  },
  1. 添加 module.json

[
  {
    "path": "../app/service"
  },
  {
    "path": "../app/infra"
  },
  {
    "package": "cnpmcore/common"
  },
  {
    "package": "cnpmcore/core"
  },
  {
    "package": "cnpmcore/port"
  },
  {
    "package": "cnpmcore/repository"
  }
]

其中 package 可以导出 cnpmcore 的模块,也可以用 path 导出自己的本地模块,具体参考 tegg 文档。

  1. 写业务代码

44b194b82cf69033c14482523530851c.png
文件结构

如图所示,在 infra 中,我们可以实现自己的 adapter,如 nfs 为例,这里实现了自己的 nfs adapter,用于存储包文件。如果在默认配置里,没有配置 nfs.client,那么就会使用默认的本地文件系统存储包文件。

以 AuthAdapter 为例,这里实现了自己的 AuthAdapter,用于对接公司内部的登录权限系统。

import { AccessLevel, EggContext, SingletonProto } from '@eggjs/tegg';
import { AuthAdapter } from 'cnpmcore/infra/AuthAdapter';
import { randomUUID } from 'crypto';
import { AuthUrlResult, userResult } from 'node_modules/cnpmcore/dist/app/common/typing';

const ONE_DAY = 3600 * 24;

@SingletonProto({
  name: 'authAdapter',
  accessLevel: AccessLevel.PUBLIC,
})
export class MyAuthAdapter extends AuthAdapter {
  async getAuthUrl(ctx: EggContext): Promise<AuthUrlResult> {
    const sessionId = randomUUID();
    await this.redis.setex(sessionId, ONE_DAY, '');

    const registry = ctx.app.config.cnpmcore.registry;
    const ssoLoginUrl = ctx.app.config.ssoLoginUrl;
    const ref = encodeURIComponent(`${registry}/cli/login/sso/${sessionId}`);
    return {
      loginUrl: `${ssoLoginUrl}?ref=${ref}`,
      doneUrl: `${registry}/-/v1/login/done/session/${sessionId}`,
    };
  }

  async ensureCurrentUser(): Promise<userResult | null> {
    if (this.user) {
      return this.user;
    }
    return null;
  }
}

添加登录验证Controller

import {
  HTTPController,
  HTTPMethod,
  HTTPMethodEnum,
  Context,
  EggContext,
  HTTPParam,
  HTTPQuery,
  HTTPBody,
  Middleware,
  Inject,
} from '@eggjs/tegg';
import { EggLogger, EggAppConfig } from 'egg';
import { traceMethod } from '../middleware/trace_method';
import { LoginService } from '../service/LoginService';
import { CacheAdapter } from 'cnpmcore/common/adapter/CacheAdapter';
import { UserService } from 'cnpmcore/core/service/UserService';

@HTTPController()
@Middleware(traceMethod)
export class LoginController {
  @Inject()
  private readonly loginService: LoginService;
  @Inject()
  protected userService: UserService;
  @Inject()
  private cacheAdapter: CacheAdapter;
  @Inject()
  protected config: EggAppConfig;
  @Inject()
  private readonly logger: EggLogger;

  @HTTPMethod({
    method: HTTPMethodEnum.GET,
    path: '/cli/login/sso/:sessionId',
  })
  async cliLogin(@Context() ctx: EggContext, @HTTPParam() sessionId: string, @HTTPQuery() sid: string) {
    
    if (!sessionId) {
      return { success: false, data: { message: 'need sessionId' } };
    }
  
    // 验证 sessionId 是否有效
    const sessionData = await this.cacheAdapter.get(sessionId);
    if (sessionData !== '') {
      return { success: false, data: { message: 'invalid sessionId' } };
    }

    // 通过自己实现的 loginService 获取用户信息
    const user = await this.loginService.getUser(ctx, sid);
    if (!user?.name || !user?.email) {
      return { success: false, data: { message: 'invalid user info' } };
    }

    // 通过 cnpmcore UserService 保存 token
    const { token } = await this.userService.ensureTokenByUser({ name: user.name, email: user.email, ip: ctx.ip });
    await this.cacheAdapter.set(sessionId, token!.token!);

    // return { success: true, data: { message: 'login success' } };
    // 跳转到登录成功页
    ctx.redirect(`${this.config.cnpmcore.registry}/-/v1/login/request/success`);
    return;
  }
}

前端部分

上面讲了服务端定制化开发,对于前端部分,cnpm 提供了 https://github.com/cnpm/cnpmweb,可以直接下载源码使用,也可以根据自己的需求进行二次开发,这里就不多做赘述了。

五、总结

cnpmcore 对于企业级 npm 私有仓库部署方案来说,是一个不错的选择。通过在 tegg 中集成 cnpmcore,我们可以很方便的进行 npm 服务的二次开发,扩展自己的业务代码,满足企业定制化需求。

参考资料

  1. https://juejin.cn/post/7132723239813382151

  2. https://github.com/cnpm/cnpmcore

  3. https://github.com/cnpm/cnpmcore/blob/master/INTEGRATE.md

  4. https://github.com/verdaccio/verdaccio

- END -

关于奇舞团

奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

2a1979be3c2703adf5d467c08afda184.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/293253.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

并发编程大杀器,京东多线程编排工具asyncTool

一、简介 并发编程大杀器&#xff0c;京东多线程编排工具asyncTool&#xff0c;可以解决任意的多线程并行、串行、阻塞、依赖、回调的并行框架&#xff0c;可以任意组合各线程的执行顺序&#xff0c;带全链路执行结果回调。多线程编排一站式解决方案。 二、特点 多线程编排&am…

GPT/GPT4科研应用与AI绘图技术及论文高效写作(建议收藏)

详情点击链接&#xff1a;GPT/GPT4科研实践应用与AI绘图技术及论文高效写作 一OpenAI 1.最新大模型GPT-4 Turbo 2.最新发布的高级数据分析&#xff0c;AI画图&#xff0c;图像识别&#xff0c;文档API 3.GPT Store 4.从0到1创建自己的GPT应用 5. 模型Gemini以及大模型Clau…

客服系统接入FastGPT

接入FastGPT 点击【应用】【外部使用】【API访问】【新建】新建一个KEY&#xff0c;同时也可以看到我们的API根地址 这个根地址和Key可以填入任何支持OpenAI接口的应用里&#xff0c;这个接口是兼容OpenAI格式。 在客服系统【知识库AI配置】里填上接口地址和接口密钥。这样我…

图像分割实战-系列教程10:U2NET显著性检测实战2

&#x1f341;&#x1f341;&#x1f341;图像分割实战-系列教程 总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在Pycharm中进行 本篇文章配套的代码资源已经上传 U2NET显著性检测实战1 U2NET显著性检测实战2 5、残差Unet模块 class RSU7(nn.Module):#UNet07DRES…

04.MySQL的基本操作

MySQL的基本操作 一、连接和断开MySQL服务器1、通过系统服务器启动、停止MySQL服务器2、通过命令提示符&#xff08;DOS&#xff09;启动、停止MySQL服务器2.1 启动 MySQL 服务器&#xff1a;2.2 停止 MySQL 服务器&#xff1a;2.3 登录和退出mysql 二、创建和管理数据库2.1 创…

k8s---pod基础下

k8s的pod与docker重启策略的区别 k8s的重启策略 always deployment的yaml文件只能是always&#xff0c;pod的yaml三种模式都可以。不论正常退出还是非正常退出都重启。OnFailure&#xff1a;正常退出不重启&#xff0c;非正常退出会重启Never&#xff1a;正常退出和非正常退出…

奇技淫巧:如何给项目中的RabbitMQ添加总开关

本文主要分享了如何给项目中的RabbitMQ添加总开关&#xff0c;通过简单配置开/关RabbitMQ。 一、需求背景 SpringBoot项目里使用了RabbitMQ&#xff0c;但某些场景下&#xff0c;不希望项目启动时自动检查RabbitMQ连接 例如&#xff1a; 在开发不需要RabbitMQ的功能过程中&…

Prometheus插件安装(cadvisor)

简介 当docker服务数量到一定程度&#xff0c;为了保证系统的文档&#xff0c;我们就需要对docker进行监控。一般情况下我们可以通过docker status命令来做简单的监控&#xff0c;但是无法交给prometheus采集&#xff0c;因此谷歌的cadvisor诞生了。cadvisor不仅可以轻松收集到…

【Spring进阶系列丨第六篇】Spring的Bean管理(基于注解)

文章目录 一、说明二、用于创建对象的2.1、Component注解2.1.1、定义Bean2.1.2、主配置文件配置扫描注解2.1.3、测试2.1.4、Component注解总结 2.2、Controller注解2.3、Service注解2.4、Repository注解 三、用于注入数据的3.1、Autowired注解3.1.1、定义Bean3.1.2、主配置文件…

Selenium-java元素等待三种方式

第二种方式需要写在创建driver时的代码下面 第三种则是对每个定位元素进行配置

探索Commons Exec管理外部进程

第1章&#xff1a;引言 咱们在日常的Java开发中&#xff0c;经常会遇到需要调用外部进程或命令的场景。比如说&#xff0c;可能需要在Java程序中启动一个外部的脚本&#xff0c;或者执行一个系统命令。Java虽然提供了Runtime和ProcessBuilder类来处理这类需求&#xff0c;但说…

Docker Linux快速安装及Nginx部署

前言 最近正在部署一套新的Linux服务器环境&#xff0c;基于Docker来部署所有的应用&#xff0c;顺便整理了一套经过验证的操作手册&#xff0c;以便大家遇到类似需求时&#xff0c;可以直接拿来用。 本文会涉及以下知识点&#xff1a;Docker的Linux安装和卸载、Docker用户组…

【网络安全】Nessus部署自动更新和端口权限开放

文章目录 Nessus 自动更新配置Nessus服务端口开放Nessus profession 版本需要开放端口Sensor ProxyTenable Security Center (TSC)Tenable OT Security (TOT)Tenable OT Security Enterprise Manager (IEM)Tenable OT Security Industrial Core Platform (ICP)Tenable OT Secur…

基于卷积神经网络的回归分析

目录 背影 卷积神经网络CNN的原理 卷积神经网络CNN的定义 卷积神经网络CNN的神经元 卷积神经网络CNN的激活函数 卷积神经网络CNN的传递函数 卷积神经网络的回归分析 完整代码:卷积神经网络的回归分析(代码完整,数据齐全)资源-CSDN文库 https://download.csdn.net/download/…

基于深度学习大模型实现离线翻译模型私有化部署使用,通过docker打包开源翻译模型,可到内网或者无网络环境下运行使用,可以使用一千多个翻译模型语言模型进行翻译

基于深度学习大模型实现离线翻译模型私有化部署使用,通过docker打包开源翻译模型,可到内网或者无网络环境下运行使用,可以使用一千多个翻译模型语言模型进行翻译,想要什么语种直接进行指定和修改就行。 环境要求,电脑内存低于8G建议不要尝试了,有无GPU都可以运行,但是有…

秋招复习之栈与队列

前言 1 栈 「栈 stack」是一种遵循先入后出逻辑的线性数据结构。 我们可以将栈类比为桌面上的一摞盘子&#xff0c;如果想取出底部的盘子&#xff0c;则需要先将上面的盘子依次移走。我们将盘子替换为各种类型的元素&#xff08;如整数、字符、对象等&#xff09;&#xff0c…

释放创造力:可视化页面渲染引擎在低代码开发平台的应用

本文由葡萄城技术团队发布。转载请注明出处&#xff1a;葡萄城官网&#xff0c;葡萄城为开发者提供专业的开发工具、解决方案和服务&#xff0c;赋能开发者。 什么是页面渲染引擎? 页面渲染引擎是低代码开发平台的核心组件之一&#xff0c;它负责将开发者设计的页面布局和用户…

Docker 存储卷管理

一、存储卷简介 存储卷是一种方便、灵活、高效的Docker容器内数据存储方式。存储卷可以在容器内的不同进程间共享数据&#xff0c;并且可以在容器之间共享和重用。 二、存储卷的优点 可以在容器之间共享和重用&#xff0c;避免了在不同容器之间复制数据的繁琐。对数据卷的修…

Flume基础知识(七):Flume 事务与 Flume Agent 内部原理

1. Flume 事务详解 2. Flume Agent 内部原理 重要组件&#xff1a; 1&#xff09;ChannelSelector ChannelSelector 的作用就是选出 Event 将要被发往哪个 Channel。其共有两种类型&#xff0c; 分别是 Replicating&#xff08;复制&#xff09;和 Multiplexing&#xff08;多…

linux 的直接direct io

目录 什么是 Direct IO java 支持 使用场景 数据库 反思 在之前的文章零拷贝基础上&#xff0c;有一个针对那些不需要在操作系统的 page cache 里保存的情况&#xff0c;即绕过 page cache&#xff0c;对于 linux 提供了 direct io 的功能。 https://blog.csdn.net/zlpzl…