企业级 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 enabledsyncMode: SyncMode.admin,syncDeleteMode: SyncDeleteMode.delete,...registry: process.env.CNPMCORE_CONFIG_REGISTRY || 'http://localhost:7001', // 填写自己的域名// white scope listallowScopes: ['@myscope', // 这里添加自己的 scope],...// 默认的系统管理员admins: { // name: emailxxx: '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 保存 tokenconst { 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

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

opencv006 绘制直线、矩形、⚪、椭圆

绘制图形是opencv经常使用的操作之一&#xff0c;库中提供了很多有用的接口&#xff0c;今天来学习一下吧&#xff01; &#xff08;里面的函数和参数还是有点繁琐的&#xff09; 最终结果显示 函数介绍 直线 line(img, pt1, pt2, color, thickness, lineType, shift) img: 在…

React Hook 原理,及如何使用Hook

一、 Hook使用规则 只在最顶层使用Hook 不要在循环&#xff0c;条件或嵌套函数中调用Hook&#xff1b; 只在组件函数和自定义hook中调用Hook Q1 &#xff1a; 为什么 hook 不能 在循环&#xff0c;条件或嵌套函数中调用Hook &#xff1f; A1&#xff1a; 因为这跟React的…

Gin 框架介绍与快速入门

Gin 框架介绍与快速入门 文章目录 Gin 框架介绍与快速入门一、Gin框架介绍1. 快速和轻量级2. 路由和中间件3. JSON解析4. 支持插件5. Gin相关文档 二、基本使用1.安装2.导入3.第一个Gin 应用 三、应用举例四、Gin 入门核心1.gin.Engine2.gin.Context 一、Gin框架介绍 Gin是一个…

LLM Agent之再谈RAG的召回多样性优化

1. Query多样性 2019 Query Expansion Techniques for Information Retrieval: a Survey 传统搜索Query的扩展&#xff0c;有基于用户搜索日志挖掘的相似Query&#xff0c;有基于相同召回文档关联的相似Query&#xff0c;也有基于SMT的Query改写方案。那和大模型时代更搭配的自…

YOLOv8改进 | 2023Neck篇 | 利用Gold-YOLO改进YOLOv8对小目标检测

一、本文介绍 本文给大家带来的改进机制是Gold-YOLO利用其Neck改进v8的Neck,GoLd-YOLO引入了一种新的机制——信息聚集-分发(Gather-and-Distribute, GD)。这个机制通过全局融合不同层次的特征并将融合后的全局信息注入到各个层级中,从而实现更高效的信息交互和融合。这种…

ssl证书(https/wss)内网测试

前言 一般后端部署到外网&#xff0c;可以去申请免费的SSL 证书&#xff0c; 但在内网测试时&#xff0c;需要自己生成证书 本章主要讲述ssl证书生成 1:环境 生成证书 openssl &#xff08;windows or linux 都行) 2:生成证书 1>生成私钥 pkcs#1私钥 openssl genrsa -out…

有详细一些的考研数学真题解析吗?

考研数学真题解析可以写得很详细&#xff0c;但是纸质资料可能受限于篇幅与排版等原因&#xff0c;没有把过程写得很详细。 但是&#xff0c;如果解析步骤不够详细的话&#xff0c;可能读者在看的时候就会因为其中某一个被省略的步骤而“卡壳”&#xff0c;进而需要花费很多额…

SpringBoot学习(三)-整合JDBC、Druid、MyBatis

注&#xff1a;此为笔者学习狂神说SpringBoot的笔记&#xff0c;其中包含个人的笔记和理解&#xff0c;仅做学习笔记之用&#xff0c;更多详细资讯请出门左拐B站&#xff1a;狂神说!!! 一、整合JDBC使用&#xff08;理解&#xff09; 创建项目 勾选依赖启动器 查看依赖 …

ElasticSearch集群(Windows)

文章目录 ElasticSearch集群&#xff08;Windows&#xff09;1. 修改配置文件2. 启动ES集群3. 使用cerebro查看集群4. 使用postman查看集群5. 测试集群添加和查询索引5.1 在9201节点添加product索引5.2 在9203查看product索引 ElasticSearch集群&#xff08;Windows&#xff09…

使用Apache POI将数据写入Excel文件

首先导入依赖 <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>3.16</version> </dependency> <dependency><groupId>org.apache.poi</groupId><artifactId>po…

简单多状态dp问题(打家劫舍Ⅱ)

通过分类谈论&#xff0c;将环形的问题&#xff0c;转化成两个线性的 “ 打家劫舍Ⅰ ” 1.状态表示 2.状态转移方程 3.初始化 f[ 0 ] nums[ 0 ] g[ 0 ] 0 4.填表顺序 从左往右填表&#xff0c;两个表一块填 5.返回值 max( f[ n-1 ] , g [ n - 1 ] )

DRF从入门到精通七(频率源码分析、接口文档、JWT介绍、构成原理、Base64编码与解码、jwt的开发重点)

文章目录 一、频率源码分析APIView的频率源码分析SimpleRateThrottle 频率类源码分析 二、项目自动生成API接口文档1.CoreAPI定义文档接口描述信息 2.Swagger3.接口文档规范 三、Cookie、Session、Token介绍1.Cookie介绍2.Session介绍3.Cookie和Session的区别4.Token介绍 四、J…