2587 字
约 8 分钟
6
S01-J08 · 制品发布:GPG 签名与中央仓库发布

适用读者:要把内部 common 库、SDK、starter 发给别人用的人;发布到 Maven Central 卡在签名/元数据校验的人。
前置:S01-J07(依赖治理)、S01-B03(Maven 基础)。
实测环境:Maven 3.8.7 / GnuPG 2.4.4 / Ubuntu 24.04.1;GPG 签名与本地 staging 部署为 2026-11-05 实测。
覆盖旧文:A-1399(Maven Central 微服务包发布管理,849 个点位,本系列第二大篇)。
新增扩展:E-S01-026(Central Portal 迁移)、E-S01-027(云效制品库 vs 中央仓库的边界)。
上一篇:S01-J07 · 依赖治理:依赖树、冲突、BOM 与私服
下一篇:S01-A01 · 团队工具链标准化与新人 Onboarding(进入架构师层)。

待核提示:中央仓库的注册入口与发布流程在 2024 年后发生变化(OSSRH 新项目注册迁移到 Central Portal)。本文按"机制不变、入口可变"的原则写,具体点击路径标「待核」,机制部分给依据链接。


1. 一句话结论

发布分三级,先想清楚"给谁用",再选仓库:

级别 仓库 谁在用 代价
1 本地仓库(~/.m2,mvn install) 只有你自己验证 零
2 公司私服(Nexus / Artifactory / 阿里云效制品库) 同公司、跨团队 需要私服与权限
3 Maven Central 全世界 必须 GPG 签名 + 元数据合规 + 不可撤回

90% 的人只需要第 1、2 级。 上中央仓库不是"更专业",而是"承担长期维护义务"(版本不可删除、元数据永久可查)。


2. 版本规范:SNAPSHOT 与 release

类型 版本写法 特性 能不能覆盖
开发包 0.0.0-SNAPSHOT(或 1.2.3-SNAPSHOT) 同版本可反复覆盖发布 ✅
正式包 1.0.0 不可变 ❌(只能发新版本)

为什么不能"改一下再发同一个版本号":依赖方已经缓存了 1.0.0,你覆盖后:

  • 有人拿到新的,有人拿到旧的(本地仓库缓存不刷新);
  • 构建不再可复现;
  • 中央仓库直接拒绝(release 版本重复发布是硬性错误)。

多模块统一改版本(旧文提到的做法,正确):

mvn versions:set -DnewVersion=1.1.0 -DgenerateBackupPoms=false
mvn versions:commit
# 或按模块批量
mvn -pl module-a,module-b versions:set -DnewVersion=1.1.0

手工改多个 pom.xml 一定会漏,漏的那个模块发出去版本号不一致——排查成本极高。


3. 第一级与"第二级的形态":install 与 staging(实测)

3.1 本地 install

mvn -B install
# 产物落到 ~/.m2/repository/<groupId 路径>/<artifactId>/<version>/

3.2 部署到"类 staging"仓库(真实验证发布产物形态)

中央仓库要求的东西,可以先在本地模拟一遍:

mvn -B deploy -DaltDeploymentRepository=staging::default::file:///tmp/staging

实测生成的目录结构:

/tmp/staging/demo/s01-b03/1.0.0/
├── s01-b03-1.0.0.jar
├── s01-b03-1.0.0.jar.sha1
├── s01-b03-1.0.0.jar.md5
├── s01-b03-1.0.0.pom
├── s01-b03-1.0.0.pom.sha1
└── s01-b03-1.0.0.pom.md5
/tmp/staging/demo/s01-b03/maven-metadata.xml

maven-metadata.xml 实测内容:

<metadata>
  <groupId>demo</groupId>
  <artifactId>s01-b03</artifactId>
  <versioning>
    <release>1.0.0</release>
    <versions><version>1.0.0</version></versions>
    <lastUpdated>20261105164256</lastUpdated>
  </versioning>
</metadata>

注意缺了什么:没有 .asc(签名)、没有 -sources.jar、没有 -javadoc.jar。这三样正是中央仓库强制要求的——本地这次"成功部署"在中央仓库会直接被拒。

顺带一个真实教训:我第一次跑这条命令失败,原因是 pom.xml 同目录的 .mvn/maven.config 里残留了 --offline(S01-B03 §8.2 警告过的坑)。删掉后成功。推广配置时要检查默认参数。


4. GPG 签名:不可省略的一步(实测)

4.1 生成密钥

# 交互式(推荐,可设密码)
gpg --full-generate-key            # 选 RSA 4096 或 ed25519,设有效期

# 批量化(CI 场景,注意密钥安全)
gpg --batch --pinentry-mode loopback --passphrase '' \
    --quick-generate-key "Your Name <[email protected]>" ed25519 sign 1y

gpg --list-secret-keys --keyid-format LONG

实测输出(节选):

sec   ed25519/51074FF1BEF011C5 2026-11-05 [SC] [expires: 2026-11-06]
      BCDF60A705431057F46EEB2251074FF1BEF011C5
uid   [ultimate] S01 Test <[email protected]>

注意:uid 里的邮箱/姓名会进入公钥,将来要上传到 keyserver(keys.openpgp.org)。

4.2 签名与验证(实测)

$ echo hello > file.txt
$ gpg --armor --detach-sign -o file.txt.asc file.txt     # 生成 228 字节的 .asc
$ gpg --verify file.txt.asc file.txt
gpg: Signature made Thu Nov  5 16:41:10 2026 UTC
gpg: Good signature from "S01 Test <[email protected]>" [ultimate]

Good signature 是发布前的自检:签名链不通,中央仓库校验必然失败。

4.3 上传公钥

gpg --keyserver keys.openpgp.org --send-keys <KEY_ID>
# 服务器会发验证邮件到 uid 里的邮箱,验证后才公开

这一步经常被跳过:本地签名没问题,但仓库侧验证时拿不到你的公钥 → 校验失败。

4.4 pom.xml 里的签名插件

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-gpg-plugin</artifactId>
  <version>3.2.4</version>
  <executions>
    <execution><id>sign-artifacts</id><phase>verify</phase>
      <goals><goal>sign</goal></goals>
    </execution>
  </executions>
  <configuration>
    <!-- 让 gpg 从 settings.xml 的 server 里取密码,避免明文 -->
    <gpgArguments><arg>--pinentry-mode</arg><arg>loopback</arg></gpgArguments>
  </configuration>
</plugin>

关键设计:密码放在 ~/.m2/settings.xml 的 <servers> 里(旧文也是这么做的),绝不写进 pom.xml——pom 会进仓库,等于公开密码。


5. 中央仓库的硬性要求(缺一项就失败)

要求 为什么 怎么满足
-sources.jar 使用者要能看源码 maven-source-plugin
-javadoc.jar API 文档 maven-javadoc-plugin(JDK 21 的 doclint 较严,可加 -Xdoclint:none)
.asc 签名 防篡改 maven-gpg-plugin
POM 元数据 可追溯、可展示 name、description、url、licenses、developers、scm —— 六项全要
groupId 与 namespace 一致 域名所有权验证 com.example 需验证 example.com;io.github.xxx 走 GitHub 验证
release 版本不可重复 不可变原则 只能递增版本

最小可用 POM 片段:

<name>My Library</name>
<description>What it does in one sentence.</description>
<url>https://github.com/you/my-library</url>
<licenses><license><name>Apache-2.0</name>
  <url>https://www.apache.org/licenses/LICENSE-2.0.txt</url></license></licenses>
<developers><developer><id>you</id><name>Your Name</name>
  <email>[email protected]</email></developer></developers>
<scm><connection>scm:git:[email protected]:you/my-library.git</connection>
  <url>https://github.com/you/my-library</url></scm>

旧文的问题:A-1399 给了完整操作流程(GPG 安装、命名空间、settings.xml、手动上传、自动发包),但对"元数据六项"没有作为校验清单列出——而这正是首次发布最常被打回的项。


6. 发布入口的变更(E-S01-026,待核细节)

时期 入口 状态
2010s OSSRH(Nexus staging + JIRA 工单) 老项目仍可用;新项目注册已迁移
2024 起 Central Portal(central.sonatype.com) 新项目走这里;mvn deploy 直接推 Portal bundle

机制不变的部分(可放心依赖):

  • 仍然需要 GPG 签名与 .asc;
  • 仍然需要 sources/javadoc;
  • release 仍不可撤回(发错了只能发新版本并在元数据里标注废弃)。

要按当前文档核对的部分(待核):命名空间验证的具体步骤、Portal 的 bundle 上传方式、发布后同步到 Central 的时延(旧文写 4–12 小时)。

依据:https://central.sonatype.org/publish/publish-portal-guide/(旧文引用的 /publishing 页已并入此流程)。


7. 云效制品库 vs 中央仓库(E-S01-027)

维度 阿里云效制品库(私有) Maven Central
使用者 公司/团队内部 全世界
签名 可选(内部信任) 必须
元数据 宽松 六项必填
版本覆盖 SNAPSHOT 可覆盖 release 不可覆盖
访问控制 有(账号/权限) 公开
同步延迟 秒级 小时级
适用 内部 common、starter、SDK 开源库、对外 SDK

判据一句话:"这段代码会不会被公司外的人依赖?" 会 → 中央仓库;不会 → 私服,别给自己找签名的麻烦。


8. 发布失败:看哪一行

报错关键词 含义 处置
Invalid signature / no public key 签名对不上 / 公钥未上传 重签 + --send-keys 后等邮件验证
Missing required metadata 元数据缺项 补 name/description/url/licenses/developers/scm
Sources jar is missing 没生成 sources 加 maven-source-plugin
Javadoc jar is missing / Javadoc errors 缺 javadoc 或 doclint 报错 加插件,必要时 -Xdoclint:none
Version already released 版本重复 递增版本号
401 / 403 settings.xml 的 server id 与 pom 的 distributionManagement 不匹配 核对 id 一致

id 不匹配是最隐蔽的一条:settings.xml 里 <server><id>central</id>,而 pom 里 <repository><id>ossrh</id> —— Maven 按 id 找凭据,找不到就 401,但报错不会告诉你"是 id 写错了"。


9. 旧文对照

旧文章节 内容 本篇处理
一 "为什么报错"(引不到微服务包 → 本地 install) §1、§3.1 保留,归纳为"三级发布"
二 微服务包管理(本地 → 公司镜像 → 中央) §1 保留并给"给谁用"判据
三 发包规范(SNAPSHOT vs release、versions 插件) §2 保留,补"不可覆盖"的后果
四 阿里云效发包(制品库、配置、本地发包、查看) §7 抽象为"私服 vs 中央"对照;具体账号步骤不再展开(会过期)
五.1 GPG 安装(Windows/macOS) §4 重写为命令行流程 + 实测签名验证
五.2 命名空间配置 §5 保留(groupId = namespace 一致性),标注入口待核
五.3 settings.xml 凭据 §4.4 保留"密码放 settings,不放 pom"的原则
五.4–5.5 手动上传 / 自动发包 §4.4 给插件配置;补 sources/javadoc/元数据六项校验清单
— 无失败排查 新增 §8(含 id 不匹配这条隐蔽坑)
— 无"发到哪里"的决策依据 新增 §1、§7

10. 自测

  1. 内部 common 库为什么不该发到中央仓库?
  2. release 版本为什么不能覆盖?会引发什么具体问题?
  3. 本地 mvn deploy 成功、但在中央仓库被拒,最可能缺哪三样文件?
  4. settings.xml 的 <server> 与 pom 的 distributionManagement 靠什么关联?
  5. 为什么签名密码不能写在 pom.xml?

11. 下一篇

S01-A01 · 团队工具链标准化与新人 Onboarding

进阶层到此结束。架构师层的第一篇换视角:不再问"我怎么配好环境",而是问"怎么让十个人在一天内都配好环境,且不靠口口相传"。下一篇给出版本矩阵、devcontainer 草案、Onboarding 检查单,以及可度量的准入条件。


本篇属于系列《S01 · 开发环境与工具链》。旧文来源:A-1399;扩展点 E-S01-026/027。GPG 签名、staging 目录结构为 2026-11-05 实测。

S01-J08 · 制品发布:GPG 签名与中央仓库发布
https://www.clxhxhhr.top/posts/5173/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。