Maven离线仓库gpmall-repo.rar构建与微服务部署避坑

发布时间:2026/10/29 20:31:51
Maven离线仓库gpmall-repo.rar构建与微服务部署避坑

简介:gpmall-repo.rar 是一份面向云计算与 Linux 服务部署学习场景的离线软件仓库资源包,主要服务于需要搭建 gpmall 相关服务、补齐系统依赖、复现实验环境或进行内网部署演练的读者。包内共 173 个文件,以 166 个 rpm 安装包为主体,覆盖 Java 运行环境、数据库、日志检索与搜索服务等常见组件;同时包含 3 个 gz、3 个 bz2 压缩文件及 1 个 xml 文件,用于提供仓库索引与元数据信息,便于在离线条件下完成依赖检索、版本核对和批量安装。压缩包整体约 304.19MB,文件类型集中,目录组织偏向服务端部署资料。已有 187 人学习下载,适合用作云服务组件安装、环境复现和依赖排错时的本地软件源参考,也能帮助读者理解 rpm 仓库的基本构成、元数据作用与组织方式,对高校学生、运维初学者和开发人员较友好。

1. gpmall-repo.rar 到底解决什么问题:一次离线构建的现场还原

上周把一套微服务电商系统往云主机上搬,代码拉下来不到两分钟,mvn clean package跑了四十多分钟还卡在Downloading from central,最后超时失败。在云计算场景里按量计费的构建节点最怕这种场面——机器空转,钱照扣,依赖还没齐。gpmall-repo.rar 就是冲这个环节来的:它是一份按 Maven 目录规范码放好的离线仓库包,把一套电商微服务在编译、打包、启动阶段要用到的 jar、pom、插件全塞进 groupId/artifactId/version 的层级里,解压后直接喂给 Maven 或导入私服,构建就能在断网机器上跑完。

它适合三类人:要在内网或隔离环境里交付 Java 微服务的人、构建机带宽常年吃紧的团队、以及想把整套电商后端跑起来看链路的学生和转岗开发者。不适合只想读代码不想编译的人,也不适合把它当成"免安装一键启动包"的人——它只解决依赖,不解决中间件。

2. 拆包与清点:目录结构、坐标规范与依赖缺口判断

拿到一个几百兆的仓库包,第一反应不该是解压,而是先看一眼顶层结构。压缩包里最怕的是"套娃"——解开后是repo/repository/,或者干脆是一堆散落在根目录的 groupId 目录。前者你得多剥一层,后者说明打包的人是用tar直接压缩了目录内容,路径前缀丢了。先确认再动手,能省掉后面 settings 里路径配错的来回折腾。

2.1 目录层级与坐标的对应关系

Maven 本地仓库的物理布局是死的:groupId里的每个点号换成一级目录,然后artifactId、version,最后是同名前缀的 jar 加 pom。搞清这个规则,你就能用find直接判断某个依赖在不在,不用等构建报错。

# 云主机默认不一定装了 rar 解压工具,先探一下 command -v unrar || echo "unrar 未安装,需要先补" # 列出压缩包头部内容,确认顶层是不是以 groupId 开头 unrar l gpmall-repo.rar | head -n 40 # 解压到独立目录,-o+ 表示同名覆盖、不问询,x 表示保留内部路径 mkdir -p /data/maven-repo unrar x -o+ gpmall-repo.rar /data/maven-repo/

-o+这个参数别省。离线包动辄几万个文件,中途弹一次"是否覆盖"交互,脚本就卡死在那儿,CI 里表现为任务超时,排查半天才发现是等待输入。解压完立刻做一次结构体检:

# 看前 20 个二级目录,正常应该都是 org、com、cn、io 这类顶级域名反写 find /data/maven-repo -maxdepth 2 -type d | head -n 20 # 统计 jar 数量,几百个以下基本可以判定是残缺包 find /data/maven-repo -name "*.jar" | wc -l # 看有没有 _remote.repositories 和 *.lastUpdated 这类"脏文件" find /data/maven-repo \( -name "_remote.repositories" -o -name "*.lastUpdated" \) | wc -l

最后那行的数字很关键。_remote.repositories是 Maven 3 用来记录"这个构件是从哪个仓库 id 下载来的"的元数据文件,*.lastUpdated则是下载失败留下的痕迹。这两个东西从别人的机器上打包带过来,到了你的环境里极容易引发"jar 明明躺在本地,Maven 却坚持去网上找"的怪事,后面第 5 章会专门讲怎么处理。

2.2 依赖清单核对:别等构建才验证完整性

肉眼数目录不现实,写个几十行的脚本做预检更靠谱。思路是解析工程里所有 pom 的<dependency>节点,抽出 groupId/artifactId/version,拼成路径去离线仓库里比对目录是否存在。缺失的列出来,一次性补,别让 Maven 一条一条地报。

# 离线仓库预检:解析 pom 依赖坐标,逐个比对本地目录是否存在 import os import sys import xml.etree.ElementTree as ET NS = {"m": "http://maven.apache.org/POM/4.0.0"} REPO = "/data/maven-repo" # 离线仓库解压根目录 SKIP_SCOPE = {"test", "provided"} # 按需保留:测试和容器提供的依赖可放宽 def coord_exists(g, a, v): """Maven 布局:groupId 点号换斜杠 / artifactId / version""" path = os.path.join(REPO, *g.split("."), a, v) return os.path.isdir(path) def parse_pom(pom): root = ET.parse(pom).getroot() out = [] for dep in root.findall(".//m:dependency", NS): g = dep.findtext("m:groupId", default="", namespaces=NS) a = dep.findtext("m:artifactId", default="", namespaces=NS) v = dep.findtext("m:version", default="", namespaces=NS) s = dep.findtext("m:scope", default="compile", namespaces=NS) # 跳过属性占位 ${xxx.version},这类要由父 pom 解析后才有真值 if not (g and a and v) or "${" in v or s in SKIP_SCOPE: continue out.append((g, a, v)) return out missing = [] for root_dir, _, files in os.walk(sys.argv[1]): for f in files: if f == "pom.xml": for g, a, v in parse_pom(os.path.join(root_dir, f)): if not coord_exists(g, a, v): missing.append(f"{g}:{a}:{v}") print(f"缺失 {len(missing)} 个依赖") for m in missing[:50]: print("MISS", m)

三个参数说明一下。REPO指向解压后的根,如果你的包多套了一层repository/,这里要跟着改。SKIP_SCOPE把 test 和 provided 排除掉是实用主义做法,这类依赖只在特定阶段用,缺了不影响主流程跑通,先让核心链路起来再说。跳过${...}占位符是必须的,父 pom 里的属性没解析前拿到的是字符串,直接比对会刷出一屏假缺失,反而掩盖真问题。

跑完这个脚本,你会得到一份真实缺口清单。实践里最常见的缺失不是业务依赖,而是构建插件和它们的依赖:maven-compiler-plugin、maven-resources-plugin、spring-boot-maven-plugin、maven-shade-plugin,还有 Lombok 的 annotation processor、protobuf 的编译插件。这些走的是pluginRepositories,很多离线包只覆盖了repositories,结果依赖齐了、编译还是失败。

2.3 SNAPSHOT 与多版本共存:包里的隐形地雷

离线包里混进 SNAPSHOT 版本是常态,尤其是内部公共模块。SNAPSHOT 的语义是"随时可能变",Maven 默认会按updatePolicy去远程检查更新,在-o离线模式下虽然不会真的联网,但如果本地存的是带时间戳的快照(1.0.0-20240101.120000-1),而 pom 里写的是1.0.0-SNAPSHOT,解析路径就可能对不上。稳妥做法是进包前把所有 SNAPSHOT 统一重打包成 release 版本,或者确认maven-metadata-local.xml齐全。

另一个隐形问题是同一个 artifactId 存在多个 version 目录。这在离线包里不算错,但意味着你的工程实际用的哪个版本,取决于 pom 和依赖调解的结果。真要排查冲突,mvn dependency:tree比翻目录高效得多,只是这一步得等依赖能解析之后才能跑,所以顺序上还是先补包、再查冲突。

3. 接进构建链:settings.xml 的三种挂法与离线模式验证

清点完就该把仓库接进 Maven 了。这里有三种接法,成本、隔离性和可复用性差别很大,选错了后面全是返工。我一般按"单机临时验证用第一种,多机复用上第二种,容器化构建用第三种"来分。

3.1 本地模式:用 -Dmaven.repo.local 直接指过去

最省事的做法是让 Maven 把解压目录当成自己的本地仓库。不要用cp -r往~/.m2/repository里灌——一旦灌进去,原始目录和你机器上的私有构件就混在一起,下次想复用这份干净的基线就得重新解压一遍。

# 用参数临时指定本地仓库,不污染 ~/.m2/repository mvn -Dmaven.repo.local=/data/maven-repo -o clean package -DskipTests # 长期使用可以写进环境变量,避免每次敲一长串 export MAVEN_OPTS="-Dmaven.repo.local=/data/maven-repo"

-o是离线模式的开关,它的价值不只是"不联网",更是"快速失败"。任何缺失的构件都会立刻抛Cannot access ... in offline mode,而不会挂在那里等三十秒超时再换下一个仓库。用离线模式跑一遍,能在一两分钟内把离线包的完整性验证个七七八八,比逐个find高效。

提示:-Dmaven.repo.local指向的目录会被写入,包括构建过程中产生的_remote.repositories和下载失败的.lastUpdated。如果这份仓库要共享给多台机器,务必先复制副本再指过去,或者干脆只读挂载——第 3.3 节会讲。

本地模式的副作用是多用户场景下权限容易出问题。A 用户解压的目录,B 用户跑构建会因为写不了仓库而报Permission denied。简单的处理是chmod -R a+rX /data/maven-repo并配合只读挂载,复杂点就上私服,别在文件权限上耗时间。

3.2 镜像模式:把离线目录当成 file 协议仓库

多台构建机共享一份基线时,正确姿势是把离线目录声明成一个file://协议的远程仓库,让 Maven 从它那里"下载"到各自的本机缓存。这样每台机器有独立的可写本地仓库,共享的基线目录保持只读,互不干扰。

<!-- /data/settings-offline.xml --> <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"> <localRepository>${user.home}/.m2/repository</localRepository> <mirrors> <!-- mirrorOf 用 external:* 表示除 localhost/file 外的所有外部仓库都被接管 --> <mirror> <id>offline-file-repo</id> <url>file:///data/maven-repo</url> <mirrorOf>external:*</mirrorOf> </mirror> </mirrors> <profiles> <profile> <id>offline</id> <repositories> <repository> <id>offline-file-repo</id> <url>file:///data/maven-repo</url> <releases><enabled>true</enabled></releases> <snapshots><enabled>true</enabled></snapshots> </repository> </repositories> <!-- 插件仓库必须单独声明,否则插件解析还是走默认路径 --> <pluginRepositories> <pluginRepository> <id>offline-file-repo</id> <url>file:///data/maven-repo</url> <releases><enabled>true</enabled></releases> <snapshots><enabled>true</enabled></snapshots> </pluginRepository> </pluginRepositories> </profile> </profiles> <activeProfiles> <activeProfile>offline</activeProfile> </activeProfiles> </settings>

mirrorOf的取值是这段配置的核心。写central只接管中央仓库,工程 pom 里如果还声明了别的仓库(很多脚手架默认带一个),那些还是走原地址。写*接管全部,包括你本地的 file 仓库,可能造成循环。写external:*是折中:接管所有非 localhost 和非 file 协议的仓库,既覆盖了外网地址,又不会把自己这个 file 仓库再镜像一遍。

pluginRepositories这段别漏。我见过不止一次"依赖全下来了,编译却报插件找不到"的情况,原因就是只配了repositories。Maven 对插件和普通依赖走的是两条解析路径,插件仓库要单独声明。

另外,mirrors和profile里的repository同时配其实是冗余的,实际生效以 mirror 优先。写全是为了让配置文件在别人机器上、在不同 Maven 版本下都能稳定工作,代价是多几十行。真正讲究的话,团队里应该只保留一种,别两套并存。

3.3 容器化构建:只读挂载与 CI 缓存层

云上 CI 通常跑在容器里,这时候把仓库塞进镜像会撑大体积,正确做法是运行时挂载。挂载必须带:ro,理由很实在:并发构建时多个容器同时写同一个基线目录,出现过 jar 写了一半被另一个进程读到的情况,报出来是zip file is empty,排查方向完全跑偏。

docker run --rm \ -v /data/maven-repo:/data/maven-repo:ro \ -v /data/settings-offline.xml:/root/.m2/settings.xml:ro \ -v "$PWD":/build -w /build \ maven:3.8-eclipse-temurin-8 \ mvn -o -s /root/.m2/settings.xml clean package -DskipTests

-v ...:ro保证容器内无法改写基线;-w /build把工作目录定在挂载进来的工程根上,否则mvn会在镜像的默认目录找不到 pom;-s显式指定 settings,别指望容器里预置的那份跟你的一致。镜像 tag 里的 JDK 版本要和工程 pom 里的maven.compiler.source对齐,这套电商微服务多数还停在 JDK 8,用 JDK 17 的镜像去编会撞上模块化裁剪,报package javax.xml.bind does not exist之类。

如果 CI 平台支持缓存目录,把~/.m2/repository做成缓存能显著加快二次构建。但注意别把/data/maven-repo也做成缓存——它是基线,应该由制品库统一下发,缓存一份固定版本反而会在基线更新后继续用旧的。

4. 云主机上跑通:中间件对齐、模块编译顺序与启动探活

依赖齐了不等于能跑。这套电商微服务是典型的多模块结构,商品、订单、用户、库存、网关各自独立,靠注册中心和 RPC 串起来。把编译、启动、探活这三步走顺,才算真正落地。

4.1 中间件版本对齐:编译期和运行期是两回事

第一个高频坑是把"编译通过"当成"能启动"。编译期只校验 jar 里的类和签名,运行期还要看服务端真实版本。举几个具体例子。

数据库驱动。工程 pom 里如果是mysql:mysql-connector-java:5.1.x,驱动类是com.mysql.jdbc.Driver;而云上装的 MySQL 8 实例要求用com.mysql.cj.jdbc.Driver,连接串还得带serverTimezone、useSSL。版本不对的典型报错是Loading class 'com.mysql.jdbc.Driver' is deprecated后紧跟一个Communications link failure,看着像网络问题,实际是驱动和协议对不上。

注册中心。Zookeeper 3.4 和 3.5 的客户端行为差异不小,尤其是curator-framework的版本对应。离线包里带的是哪一版,服务端就得配到能兼容的那一档,否则启动日志里会刷一堆ConnectionLoss,然后服务起不来也不报明确原因。

缓存客户端。Redis 的lettuce和jedis在连接池参数上完全不同,离线仓库里如果两个都在,依赖调解选哪个不一定符合预期。稳妥做法是显式exclusion掉不用的那个,别赌 Maven 的调解结果。

JDK 版本也得对齐。JDK 8 编译的字节码在 JDK 11 上跑,如果没有用到被移除的模块(javax.xml.bind、javax.annotation),一般没事;一旦用到,运行时报NoClassDefFoundError,而编译阶段完全正常。这是最典型的"编译期与运行期断层"。

4.2 多模块编译顺序与 -pl / -am 的用法

父 pom 必须先install到本地仓库,子模块才能引用。离线模式下这一步更容易失败,因为父 pom 本身也在被解析。

# 第一步:把父 pom 装进本地仓库,-N 表示不递归子模块 mvn -o -N install -DskipTests # 第二步:只编某个模块及其依赖链,-pl 指定模块,-am 表示同时构建它依赖的模块 mvn -o -pl order-service -am clean package -DskipTests # 第三步:全量构建,用 install 而不是 package,让模块间能互相引用 mvn -o clean install -DskipTests

-N(--non-recursive)的作用常被忽略。父 pom 通常只有dependencyManagement和pluginManagement,不产出代码,但子模块解析版本号时要读它。不先装进去,子模块会在本地仓库里找不到父 pom,报Non-resolvable parent POM。

-pl加-am是日常开发最有用的组合。改了一个模块,只想验证它和它依赖的模块,没必要全量重编。注意-am是"also make",只向上游走,不包含下游。要连带编下游用-amd。这两个参数在离线模式下尤其值钱,能把构建时间从二十分钟压到两分钟。

package和install的区别也要清楚:package只把产物放到模块的target/下,其他模块引用不到;install会装进本地仓库。多模块工程里模块间有依赖关系,必须用install,否则报Could not resolve dependencies for project ...: The following artifacts could not be resolved。

4.3 启动验证:日志、端口、健康检查三件套

服务起来了没有,别靠"看进程还在不在"判断。Java 进程崩了可能被守护脚本立刻拉起,看着一直在,实际每次启动都失败。按下面三条查。

# 1) 端口监听:确认服务真的在 listen,而不是启动到一半卡住 ss -lntp | grep -E ':(8081|8082|8083|2181|6379|3306)\b' # 2) 健康端点:Spring Boot Actuator 有就查,没有就退化到首页状态码 curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8081/actuator/health # 3) 关键日志:找启动完成标志和异常栈,别只看最后一行 grep -nE "Started .*Application|APPLICATION FAILED TO START|Caused by" logs/*.log | tail -n 30

端口检查用ss而不是netstat,后者在新一点的发行版上默认没装。-lntp里-l只看监听态,-n不做域名解析避免卡顿,-p显示进程号,能立刻看出端口是不是被别的进程占了——这在微服务部署里太常见了,本地残留一个旧进程,新进程起不来,日志只报Address already in use,不说谁占的。

健康端点返回503说明进程活着但依赖没通,多半是数据库或注册中心连不上。这时候别急着重启,先把日志里的Caused by抓出来,按第 5 章的分类对号入座,比反复重启省时间。

5. 排查与避坑:五个真实翻车现场的「现象 → 原因 → 解决」

离线仓库这套东西,配对了就是一次性的,配错了能折腾一整天。下面这五条是我在不同环境里反复撞见的,按依赖解析、编译打包、运行三个阶段排。

5.1 依赖解析阶段的两个坑

现象:jar 文件明明躺在/data/maven-repo/com/xxx/foo/1.0.0/下面,ls能看到,Maven 却报Could not find artifact com.xxx:foo:jar:1.0.0,或者报The POM for ... is missing然后继续去连远程仓库。

原因:十有八九是_remote.repositories在作祟。这个文件记录了构件当初是从哪个仓库 id 下载的,Maven 3 会校验"当前解析上下文里的仓库 id"和它记录的是否匹配。离线包从别的机器打过来,记录的 id 是central或某个私服 id,而你现在的 settings 里 mirror 的 id 是offline-file-repo,两边对不上,Maven 就认为"这个本地文件不属于我这个来源",直接忽略。

解决:进包前清一遍元数据文件,让 Maven 把本地构件当成"本来就是本地构建的"来处理:

# 清掉来源标记和失败残留,让本地构件被视为本地产物 find /data/maven-repo -name "_remote.repositories" -delete find /data/maven-repo -name "*.lastUpdated" -delete # 如果不想删,也可以在构建时启用 legacy 本地仓库模式 mvn -o -llr clean package -DskipTests

-llr(--legacy-local-repository)是 Maven 3.0 之后保留的兼容开关,行为上等于告诉 Maven 别校验来源。长期方案还是清文件,因为开关在不同 Maven 版本上的行为不完全一致,配置文件比命令行参数更容易被下一个接手的人看见。

现象:补完全部缺失依赖后,构建报Could not resolve dependencies但列出的坐标明明存在,且版本号看着一样。

原因:版本号里有不可见字符,或者 pom 里写的是${project.version}这类表达式,解析出来的实际值和目录名差一个后缀,比如1.0.0和1.0.0.RELEASE。离线包里两种都有,你比对的是前者,工程要的是后者。

解决:用mvn help:evaluate -Dexpression=project.version -q -DforceStdout打印真实版本,别靠眼睛看 pom。再把find /data/maven-repo -type d -name "1.0.0*"的结果拿出来对一遍,缺哪个补哪个。这一步看着笨,但比猜快。

5.2 编译打包阶段的两个坑

现象:mvn compile报找不到符号,指向的是 Lombok 生成的 getter/setter,或者报cannot find symbol: method builder()。

原因:Lombok 走的是 annotation processor 路径,离线包里如果只有lombok.jar本体、缺了对应版本的maven-compiler-plugin或者 plugin 的依赖,处理器就不会被加载。另一种情况是 Lombok 版本和 JDK 不匹配——老版本 Lombok 在 JDK 11 以上会因为反射访问限制直接失效,编译期不报 Lombok 本身的问题,只报符号找不到。

解决:先确认插件在不在包里:ls /data/maven-repo/org/projectlombok/lombok/,再看maven-compiler-plugin的版本目录。两者都在还报错,就把编译插件的annotationProcessorPaths显式写进 pom,让处理器路径不依赖 classpath 推断:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>8</source> <target>8</target> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> </path> </annotationProcessorPaths> </configuration> </plugin>

source/target要和运行环境的 JDK 对齐,annotationProcessorPaths里的版本要和仓库里实际存在的版本一致。这两处对不上,报错信息会很误导。

现象:打包成功,java -jar启动报no main manifest attribute。

原因:spring-boot-maven-plugin的repackage目标没执行。离线模式下如果这个插件缺失,Maven 不会报错,只是跳过 repackage,打出来的是普通 jar。这类"静默跳过"最坑,因为构建是绿的。

解决:构建日志里搜spring-boot-maven-plugin,看有没有repackage的执行记录。没有就说明插件没解析到,去/data/maven-repo/org/springframework/boot/spring-boot-maven-plugin/下确认版本目录。补包之后重新install,别只package。

5.3 运行阶段的坑

现象:三四个微服务一起在 4 GB 云主机上启动,跑到第三个进程被系统杀掉,日志里没有任何异常栈,只有Killed。

原因:容器或云主机的内存上限被 JVM 默认堆突破。JDK 8 在容器里读不到 cgroup 限制的场景很常见,默认按物理机内存算,每个进程都开一个不小的堆,几个加起来直接触发 OOM Killer。

解决:启动参数里显式限定堆,并给元空间留余量:

# 单实例限制 512 MB 堆 + 256 MB 元空间,剩余留给系统和其他进程 java -Xms256m -Xmx512m -XX:MaxMetaspaceSize=256m -jar order-service.jar

-Xms和-Xmx设成一样能避免运行期扩容抖动,MaxMetaspaceSize不设上限的话,动态代理多的服务会慢慢把元空间吃光。按服务重要性分配,网关和订单给大一点,后台管理给小一点,分批启动,每起一个等健康检查通过再起下一个。

6. 沉淀成私服基线:把离线仓库变成团队可复用的 hosted 仓库

离线包用一次是救急,用十次就该升级成基础设施了。文件挂载的方式有几个绕不过去的限制:没法审计谁拉了什么、没法增量更新、并发读写在 NFS 上表现不稳。把这些包导进一个 hosted 仓库,团队就有一份带版本记录的基线,新人入职不用再传压缩包。

6.1 用 hosted 仓库接管离线包

思路很简单:在私服上建一个 hosted 类型的 Maven 仓库,然后把离线目录里的每个构件按相对路径 PUT 上去。路径结构不变,所以curl --upload-file就够了,不需要额外的客户端工具。

#!/usr/bin/env bash # 把离线仓库逐件推送到 hosted 仓库,路径保持不变 set -u REPO=/data/maven-repo URL=http://127.0.0.1:8081/repository/maven-offline USER=deployer PASS='change-me' find "$REPO" -type f \( -name "*.jar" -o -name "*.pom" -o -name "*.module" \) \ -print0 | while IFS= read -r -d '' f; do rel=${f#"$REPO"/} # 去掉根前缀,得到 groupId/.../file code=$(curl -sS -o /dev/null -w '%{http_code}' \ -u "$USER:$PASS" --upload-file "$f" "$URL/$rel") # 201 新建成功,204 已存在被覆盖,其他都算失败 if [ "$code" != "201" ] && [ "$code" != "204" ]; then echo "FAIL $code $rel" fi done

-print0配read -d ''是为了处理带空格的文件名,离线仓库里偶尔会出现带空格的构件名。rel的截取用${f#"$REPO"/},只剥前缀不动后面的层级,保证路径和 Maven 布局完全一致。HTTP 状态码判断里,201 和 204 都算成功,前者是首次上传,后者是覆盖——重跑脚本时大量 204 是正常的,不用当成冲突处理。

只传 jar 和 pom 就够了吗?多数情况够,但如果有maven-metadata.xml(SNAPSHOT 相关),也要一起推,否则客户端解析 SNAPSHOT 会失败。另外,私服如果开了 Redeploy 策略禁止覆盖,204 不会出现,重跑会返回 400,这时候要么开策略,要么先删后传。

6.2 校验脚本与版本锁:让基线可验证

导入完成不等于基线正确。真正的验证方式是拿一台干净机器,配置只指向这个私服,跑一次mvn -o clean install。离线模式会强制所有构件都必须从私服命中,任何缺口都会当场暴露,比人工核对清单可靠得多。

版本锁这件事我一般做成强制项:把父 pom 里所有依赖的版本号从${xxx.version}属性改成固定值,属性集中放在<properties>里并在 CI 里校验属性值不允许是LATEST或RELEASE。用过LATEST的人应该都体会过那种"昨天能编今天不能编"的玄学,问题出在构建不可重现,而离线仓库的全部价值恰恰建立在可重现上。

还有个小习惯值得保留:每次基线更新,把清点脚本跑一遍,把缺失清单和构件总数记到一个文本文件里,跟私服版本号绑在一起。两个月后有人问"这个版本能不能支持新加的那个依赖",翻文件比翻聊天记录快得多。

血泪经验是那次急着交付,直接拿一份来源不明的离线包铺到构建机上,_remote.repositories没清,构建机又刚好能连外网,结果一半构件走本地、一半走远程,打出来的包里两个版本的公共模块混着用,运行期报的错跟依赖八竿子打不着。从那以后我每次接离线仓库,都强制走一遍"清元数据 → 清单预检 →-o离线全量构建 → 干净机器复验"这四步,一步不跳。希望帮到你。

本文还有配套的精品资源,点击获取