跳到主要内容

发布管理(npm 与 Maven Central)

个人开发者向 npm 与 Maven Central 两大公共仓库发布包的完整链路:账号注册、命名空间验证、发布凭证、首次发布与撤回规则。

将 npm scope 指向私有仓库的配置不在本文范围,见作用域包与私有仓库配置

0. 两仓库规则速览

规则npmMaven Central
命名空间@scope/pkg,scope 与域名无关groupId 为域名反写或托管账号,需验证所有权
撤回发布发布后 72 小时内可 unpublish一经发布永久不可修改、不可删除
发布凭证Access Token(Granular)User Token + GPG 签名
命名空间迁移scope 可随时新建groupId 发布过构件即永久绑定

一、npm 发布

1.1 注册账号

  1. 打开 npmjs.com/signup
  2. 选择以下方式之一:
    • GitHub / Google 账号登录(推荐 GitHub,与代码托管身份打通)
    • 邮箱 + 用户名 + 密码注册
  3. 邮箱注册方式需到邮箱点击验证邮件完成激活
备注

账号即发布身份。用户名会出现在你发布的所有包页面和维护者列表中。

1.2 开启 2FA

发布包要求开启双因素认证:

  1. 头像 → AccountSecurityEnable 2FA
  2. 选择 Authenticator App(Microsoft Authenticator / Google Authenticator 均可)
  3. 扫码绑定,保存恢复码

2FA 级别选 Authorization and writes 时,每次 npm publish 都需要输入一次性验证码。

1.3 创建 Organization(拿到 @scope)

scope 是 npm 的包命名空间,格式 @scope/package-name。要使用独立 scope(而非 @用户名),需创建 Organization:

  1. 点右上角头像 → Add an Organization
  2. 名字填目标 scope,如 cloudomni
    • 可先访问 npmjs.com/org/<名字> 探测是否已被占用
  3. 选择 Free 方案:公开包无限发布,个人开发者够用;私有包才按席位收费
  4. 创建完成后即可发布 @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,改用令牌:

  1. 头像 → Access TokensGenerate New Token
  2. Granular Access Token(可限定包和权限范围,不要再用即将淘汰的经典 token)
  3. 将令牌配到 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 注册账号

  1. 打开 central.sonatype.com,右上角 Sign In → 注册
  2. 支持 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.xxxcn.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 NamespacesAdd 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 NamespaceConfirm,几分钟内状态变为 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公开的身份名
Email与 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

发布认证不用登录密码,而是专用令牌:

  1. Portal 右上角头像 → View Account

  2. Generate User Token,弹出的 XML 直接就是 settings.xml 需要的内容:

    <server>
    <id>central</id>
    <username>xxxxxxxx</username> <!-- 一串随机用户名,不是登录邮箱 -->
    <password>xxxxxxxx</password> <!-- 一串随机口令 -->
    </server>
  3. 写入 ~/.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 jarmaven-source-pluginmaven-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或密钥邮箱>

过程说明:

  1. 先输 passphrase(pinentry 弹窗或终端提示)
  2. 全模块构建 + 打 source/javadoc 包 + GPG 签名 + 上传,多模块工程预计 10~20 分钟
  3. 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 / 401settings.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 密钥泄露则吊销并换新