发布管理(npm 与 Maven Central)
个人开发者向 npm 与 Maven Central 两大公共仓库发布包的完整链路:账号注册、命名空间验证、发布凭证、首次发布与撤回规则。
将 npm scope 指向私有仓库的配置不在本文范围,见作用域包与私有仓库配置。
0. 两仓库规则速览
| 规则 | npm | Maven Central |
|---|---|---|
| 命名空间 | @scope/pkg,scope 与域名无关 | groupId 为域名反写或托管账号,需验证所有权 |
| 撤回发布 | 发布后 72 小时内可 unpublish | 一经发布永久不可修改、不可删除 |
| 发布凭证 | Access Token(Granular) | User Token + GPG 签名 |
| 命名空间迁移 | scope 可随时新建 | groupId 发布过构件即永久绑定 |
一、npm 发布
1.1 注册账号
- 打开 npmjs.com/signup
- 选择以下方式之一:
- GitHub / Google 账号登录(推荐 GitHub,与代码托管身份打通)
- 邮箱 + 用户名 + 密码注册
- 邮箱注册方式需到邮箱点击验证邮件完成激活
账号即发布身份。用户名会出现在你发布的所有包页面和维护者列表中。
1.2 开启 2FA
发布包要求开启双因素认证:
- 头像 → Account → Security → Enable 2FA
- 选择 Authenticator App(Microsoft Authenticator / Google Authenticator 均可)
- 扫码绑定,保存恢复码
2FA 级别选 Authorization and writes 时,每次 npm publish 都需要输入一次性验证码。
1.3 创建 Organization(拿到 @scope)
scope 是 npm 的包命名空间,格式 @scope/package-name。要使用独立 scope(而非 @用户名),需创建 Organization:
- 点右上角头像 → Add an Organization
- 名字填目标 scope,如
cloudomni- 可先访问
npmjs.com/org/<名字>探测是否已被占用
- 可先访问
- 选择 Free 方案:公开包无限发布,个人开发者够用;私有包才按席位收费
- 创建完成后即可发布
@cloudomni/xxx包
npm 的 scope 与域名无关,只看 npmjs.com 上是否被占用。即使没有对应域名也可以用任意 scope 名。
1.4 本地登录
npm login
命令会打开浏览器完成授权,成功后终端显示 Logged in on https://registry.npmjs.org/。
1.5 首次发布
package.json 中使用 scope 命名:
{
"name": "@cloudomni/your-package",
"version": "0.1.0"
}
发布命令:
npm publish --access public
scoped 包默认按私有包处理,个人免费账号必须显式加 --access public,否则直接报错。
撤回规则:发布后 72 小时内、且无其他包依赖、下载量极低时可 unpublish;超过后只能 deprecate。发版前确认版本号和内容。
1.6 CI 自动发布用 Access Token
交互式登录不适合 CI,改用令牌:
- 头像 → Access Tokens → Generate New Token
- 选 Granular Access Token(可限定包和权限范围,不要再用即将淘汰的经典 token)
- 将令牌配到 CI 环境变量(如
NPM_TOKEN),.npmrc中引用:
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
二、Maven Central 发布
Sonatype 已退役 OSSRH(oss.sonatype.org + JIRA 工单那套老流程),新发布者统一走 Central Portal(central.sonatype.com)。网上大量 OSSRH + Nexus Staging 的旧教程已不适用。
2.1 注册账号
- 打开 central.sonatype.com,右上角 Sign In → 注册
- 支持 GitHub / Google 登录或邮箱 + 用户名 + 密码(需邮箱验证)
推荐 GitHub 登录:GitHub 账号自带 github.io 域(GitHub Pages),Sonatype 大多数情况下会自动开通并验证 io.github.<你的GitHub用户名> 命名空间,跳过手动验证步骤。
账号注意事项:
- 用户名注册后不可修改,想要新用户名只能注册新账号
- 忘记用户名且丢失邮箱,账号无法找回——注册邮箱必须是长期可控的邮箱
- Sonatype 只通过 central.sonatype.com 站内链接管理凭证,不会索要密码
2.2 命名空间(groupId)选型
命名空间即 Maven 坐标中的 groupId 前缀,两种来源:
| 方案 | 命名空间 | 验证方式 | 适用场景 |
|---|---|---|---|
| 代码托管账号 | io.github.<用户名> | GitHub 登录自动发放 | 无域名的个人开发者 |
| 代码托管账号 | io.gitee.<用户名> 等 | 建以验证 Key 命名的公开仓库 | Gitee / GitLab / Bitbucket 用户 |
| 自有域名反写 | cn.cloudomni | 域名 DNS 加 TXT 记录 | 长期维护的开源项目 |
groupId 一旦发布过构件就永久绑定,不可迁移:io.github.xxx 意味着工件坐标永远挂着 GitHub 用户名。自有域名是自己控制的命名空间,一年几十块的域名换终身独立身份,长期项目建议走域名方案。域名必须持续续费——过期被他人注册后,命名空间归属就说不清了。
同一个账号可以同时验证多个命名空间(如 io.github.xxx 和 cn.cloudomni 共存),以后随时追加。
2.3 免费命名空间验证(GitHub / Gitee)
- GitHub:用 GitHub 登录 Central Portal,多数情况自动发放
io.github.<用户名>,无需任何操作 - Gitee / GitLab / Bitbucket:添加命名空间后,按提示在对应平台创建一个以 Verification Key 命名的临时公开仓库(如
gitee.com/<用户名>/<verification-key>),验证通过后删掉该仓库即可
2.4 域名命名空间验证(DNS TXT)
以 cn.cloudomni(对应域名 cloudomni.cn)为例:
添加命名空间:登录 Portal,点右上角用户名 → View Namespaces → Add Namespace,输入 cn.cloudomni,Submit,此时状态为 Unverified。复制页面给出的 Verification Key。
添加 DNS TXT 记录:到域名 DNS 控制台添加记录:
| 字段 | 值 |
|---|---|
| 记录类型 | TXT |
| 主机记录 | @(根域) |
| 记录值 | 粘贴 Verification Key |
- 阿里云:云解析 DNS → 添加记录
- 腾讯云:DNSPod → 添加记录
新注册的 .cn 域名必须先完成域名实名认证,否则解析不生效。
确认生效后提交:先在本地确认 TXT 记录已全球生效:
dig -t txt cloudomni.cn
输出中能看到 Verification Key 后,回到 Portal 点 Verify Namespace → Confirm,几分钟内状态变为 Verified。验证通过后,cn.cloudomni 开头的所有 groupId(如 cn.cloudomni.core)都允许发布。
TXT 记录未生效就点确认,Sonatype 查到的 NXDOMAIN 会被缓存,验证会卡住并显著拖延。另外系统只检查精确域名(cn.cloudomni 只查 cloudomni.cn,不查任何子域名),命名空间和域名必须严格对应。
2.5 GPG 签名密钥
Central 要求所有构件附带 GPG 分离签名(.asc 文件),且公钥必须上传到公开 keyserver。
安装 GPG(macOS):
brew install gnupg pinentry-mac
生成密钥:
gpg --full-generate-key
交互选择:
| 提示 | 选择 |
|---|---|
| 密钥类型 | RSA and RSA(默认,选项 1) |
| 密钥长度 | 4096 |
| 有效期 | 0(永不过期;过期密钥会导致多年后无法再签名发版) |
| Real name | 公开的身份名 |
| 与 pom 中 developers 一致为宜 | |
| Passphrase | 设置强口令并记牢——每次发版都要输入,忘了等于密钥作废 |
查看密钥 ID:
gpg --list-secret-keys --keyid-format long
输出形如 sec rsa4096/3ABCDEF123456789 2026-08-19 [SC],/ 后面的 3ABCDEF123456789 就是密钥 ID(后续 -Dgpg.keyname 用它,或直接用密钥邮箱)。
上传公钥到 keyserver(关键步骤):
gpg --keyserver keyserver.ubuntu.com --send-keys <密钥ID>
Central 校验签名时从 keyserver.ubuntu.com 拉取公钥,所以必须传这里(可再传一份到 keys.openpgp.org 做冗余)。上传后验证是否可查:
gpg --keyserver keyserver.ubuntu.com --recv-keys <密钥ID>
keyserver 传播有延迟(分钟级到小时级)。发布时若报 signature could not be verified,多半是公钥尚未传播,等一段时间重试即可。
本地自测签名(可选但建议):
echo test > /tmp/gpg-test.txt
gpg --detach-sign --armor /tmp/gpg-test.txt # 生成 /tmp/gpg-test.txt.asc 即成功
备份私钥(强烈建议):私钥丢失 = 该身份永远无法再给新构件签名。导出后离线保存(密码管理器/加密盘):
gpg --armor --export-secret-keys <密钥ID> > private-key-backup.asc
gpg --armor --export <密钥ID> > public-key-backup.asc
2.6 Portal 令牌与 settings.xml
发布认证不用登录密码,而是专用令牌:
-
Portal 右上角头像 → View Account
-
Generate User Token,弹出的 XML 直接就是 settings.xml 需要的内容:
<server><id>central</id><username>xxxxxxxx</username> <!-- 一串随机用户名,不是登录邮箱 --><password>xxxxxxxx</password> <!-- 一串随机口令 --></server> -
写入
~/.m2/settings.xml(文件不存在则新建)。<id>central</id>必须与 pom 中 central-publishing-maven-plugin 的publishingServerId完全一致:<settings><servers><server><id>central</id><username>令牌用户名</username><password>令牌口令</password></server></servers></settings>
2.7 项目侧要求
Central 对构件有一组强制要求,缺一不可:
- pom 元数据:
name/description/url/licenses/developers/scm必须齐全(多模块项目配在根 pom,子模块自动继承) - sources 与 javadoc jar:
maven-source-plugin、maven-javadoc-plugin(建议关 doclint,避免注释不规范阻断发布) - GPG 签名:
maven-gpg-plugin对每个构件生成.asc,keyname 通过-Dgpg.keyname传入 - 发布插件:
org.sonatype.central:central-publishing-maven-plugin直接上传 Central Portal(老教程里的distributionManagement+ Nexus release 插件已不适用);autoPublish=true时上传通过校验后自动发布 - 版本:Central 不接受 SNAPSHOT,发版前确认是正式版本号
通常把后三项插件放进 release profile,日常构建不带签名开销。
2.8 执行发布
mvn -P release deploy -Dgpg.keyname=<密钥ID或密钥邮箱>
过程说明:
- 先输 passphrase(pinentry 弹窗或终端提示)
- 全模块构建 + 打 source/javadoc 包 + GPG 签名 + 上传,多模块工程预计 10~20 分钟
autoPublish=true:Portal 校验(签名可验、pom 规范、校验和一致)通过后自动转为 Published;校验失败会在 Portal 的 Publishing 页列出每个构件的错误明细
2.9 发布验证
三处确认(Portal 显示 Published 后约 30 分钟内同步到仓库):
# 1. 仓库目录直查
open https://repo1.maven.org/maven2/<groupId路径>/
# 2. Portal 搜索
open https://central.sonatype.com/search?q=<groupId>
# 3. 消费端真实拉取(最硬的验证)
cd 某个引用该依赖的工程 && mvn dependency:resolve
2.10 常见问题排查
| 症状 | 原因与处理 |
|---|---|
signature could not be verified / key not found | 公钥未传播到 keyserver.ubuntu.com。gpg --keyserver keyserver.ubuntu.com --recv-keys <ID> 自查,未查到就重传并等待。 |
Failed to authenticate / 401 | settings.xml 未配置、<id> 与 publishingServerId 不一致、或用了登录密码而非 User Token。 |
validation failed,提示缺 license/developers/scm | 该模块 pom 未继承到根 pom 元数据(独立于主仓 reactor 的工程不会继承),需自带这套元数据。 |
| macOS 上 gpg 签名卡住无反应 | pinentry 配置问题。brew install pinentry-mac,并在 ~/.gnupg/gpg-agent.conf 写入 pinentry-program /opt/homebrew/bin/pinentry-mac,然后 gpgconf --kill gpg-agent。 |
| javadoc 生成失败 | release profile 已设 doclint=none;若仍失败多为非法 HTML 字符(如裸 < >),修注释或确认走的 release profile。 |
| 发布了但 repo1 搜不到 | Portal Published → repo1 同步最长 30 分钟,先在 Portal 搜到即算成功。 |
| 误发布/需要下架 | Portal → Publishing → 对应版本 → Drop(发布前可撤);已 Published 的无法删除,只能发新版本覆盖(语义化版本前进)。 |
2.11 安全清单
- GPG 私钥与 passphrase 分开离线存放
- Central Portal 令牌只存
~/.m2/settings.xml,不进任何 git 仓库(该文件永远只在本地) - 泄露应对:Portal 重新 Generate User Token(旧令牌立即失效);GPG 密钥泄露则吊销并换新