适用读者:要把内部 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. 自测
- 内部 common 库为什么不该发到中央仓库?
- release 版本为什么不能覆盖?会引发什么具体问题?
- 本地
mvn deploy成功、但在中央仓库被拒,最可能缺哪三样文件? settings.xml的<server>与pom的distributionManagement靠什么关联?- 为什么签名密码不能写在
pom.xml?
11. 下一篇
S01-A01 · 团队工具链标准化与新人 Onboarding
进阶层到此结束。架构师层的第一篇换视角:不再问"我怎么配好环境",而是问"怎么让十个人在一天内都配好环境,且不靠口口相传"。下一篇给出版本矩阵、devcontainer 草案、Onboarding 检查单,以及可度量的准入条件。
本篇属于系列《S01 · 开发环境与工具链》。旧文来源:A-1399;扩展点 E-S01-026/027。GPG 签名、staging 目录结构为 2026-11-05 实测。