离线 Maven 仓库包配置与商城项目构建排障

发布时间:2026/10/29 20:31:51
离线 Maven 仓库包配置与商城项目构建排障

简介:gpmall-repo.rar 是面向云计算与分布式电商实践者的离线 YUM 源仓库包,服务于 GPMall 电商项目在 CentOS 7 下的依赖安装与部署,可解决内网无法访问外网时软件包无法在线拉取、版本不一致导致环境搭建受阻的问题,适合具备 Linux 基础、正在做项目实训或课程设计的学习者。压缩包共 173 个文件、约 304MB,以 166 个 rpm 为主体,涵盖 Elasticsearch、Logstash、MongoDB、OpenJDK 等中间件与运行时组件;另有 3 个 gz、3 个 bz2 压缩文件,对应 primary、filelists、other 等仓库元数据,1 个 xml 用于仓库描述,使本地源能被 yum 直接识别。目前已有 187 人学习下载。读者可据此搭建可复现的离线软件源,省去逐个检索下载依赖的时间,快速完成搜索、日志采集、数据库与 Java 环境的版本对齐,为后续集群部署与排错打下稳定的包基础。

1. 先看清楚:这份离线 Maven 仓库包到底替你省掉了什么

源码拉下来,mvn一敲,屏幕开始一行行Downloading,然后卡在某个 jar 上十分钟不动——这套商城系统的第一次构建,有较大可能会这样失败。这份 repo 包做的事很单一:把一套已经下载完整的本地 Maven 仓库打包给你,解压、指向、离线编译,三步之后你面对的就只剩业务代码。它适合两类人:一类是想在断网机器、内网构建机或者云上临时开的编译节点上把项目跑起来的开发者;另一类是想把整套依赖一次性固化下来、以后不再受中央仓库不稳定影响的人。

放到云计算的语境里看,这就是一种基础但有效的依赖快照做法。你可能换了镜像源、换了构建机、换了办公网,但只要这份仓库还在硬盘上,mvn -o就永远能跑出同样的结果。依赖版本不随上游变化,构建结果才可复现,这比任何“一键部署脚本”都实在。

2. 拆包:目录结构和 settings.xml 的对应关系

2.1 解压后先确认三样内容

拿到压缩包不要急着往 IDEA 里拖,先解压到一个路径里没有中文、没有空格的目录。这一点在这里听起来像废话,但它是后面一半报错的根源:Maven 在拼file://路径时对空格的处理很不友好,而 Windows 默认下载目录常常带中文用户名。

解压完之后,正常会看到这样的层级:

repo-root/ ├── settings.xml # 配套的 Maven 配置,已写好 localRepository └── repository/ # 真正的本地仓库根目录 ├── org/springframework/boot/... ├── org/apache/dubbo/... ├── com/alibaba/... ├── io/netty/... └── ...

真正要配进localRepository的是repository这一层,不是外层的repo-root。这是第一个高频错误:指向错一层,Maven 会认为仓库是空的,然后老老实实去联网重下一遍,你等了半小时才发现它根本没读本地文件。

进去之后,用最直接的方式看一眼规模:

# 统计 jar 数量,判断包是否完整(完整包通常是数千个 jar) find ./repository -name "*.jar" | wc -l # 找出被标记为"下载失败"的残留文件,这些是真正的坑 find ./repository -name "*.lastUpdated" | wc -l # 找出整包搬运后常见的 _remote.repositories 标记文件 find ./repository -name "_remote.repositories" | wc -l

*.lastUpdated是 Maven 记录“这个坐标我下过但失败了”的书签,数量不为 0 说明打包时就有缺失。_remote.repositories记录的是“这个 jar 从哪个远程仓库 ID 下下来的”,一旦你换了settings.xml、换了镜像 ID,Maven 会认为来源对不上,在离线模式下直接报错。这两个文件在后面第五章还会出现,现在先记住它们的位置。

文件/目录作用异常时的典型表现
repository/本地仓库根配错层,构建全量重新下载
*.lastUpdated下载失败书签离线构建报 “resolution will not be reattempted”
_remote.repositories来源仓库 ID 记录换镜像后报 “not available in offline mode”
*.pom依赖元数据缺 pom 时依赖树断裂、传递依赖丢失

2.2 settings.xml 里必须改的几个值

包里自带的settings.xml通常写的是打包者本机的绝对路径,比如D:\maven\repository,到你机器上必然失效。打开它,重点看三处:

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"> <!-- 1. 改成你解压后的真实路径,指向 repository 这一层 --> <localRepository>D:/dev/repo-root/repository</localRepository> <!-- 2. 离线开关:调试阶段建议显式打开,命令行就不用每次加 -o --> <offline>true</offline> <mirrors> <!-- 3. mirrorOf 不要写 *,否则连插件仓库也一起被劫持 --> <mirror> <id>internal-mirror</id> <mirrorOf>central</mirrorOf> <url>http://your-nexus/repository/maven-public/</url> </mirror> </mirrors> <profiles> <profile> <id>jdk8</id> <activation> <activeByDefault>true</activeByDefault> <jdk>1.8</jdk> </activation> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> </profile> </profiles> </settings>

几处参数值得单独说。<localRepository>用正斜杠/或者转义的反斜杠都行,别直接抄 Windows 风格的单反斜杠,D:\dev\repo里的\r会被当成回车。<offline>true</offline>是全局离线开关,效果等价于每条命令都加-o,好处是忘记加参数时也不会偷偷联网卡住,坏处是后面你想临时下一个新依赖时必须先改回来,我一般调试期打开、加完依赖就关。

<mirrorOf>central</mirrorOf>这一项是最容易出错的地方。很多人习惯写*,意思“所有仓库请求都走这个镜像”,本身没错,但它会把pluginRepositories也一起改写。当你后面用mvn -o的时候,Maven 发现插件来源和本地记录不一致,就会反复告警甚至拒绝解析。写成central或者*,!your-repo-id这种精确形式,问题会少很多。

提示:改完settings.xml之后,一定在 IDEA 里确认 “User settings file” 指向的是这个文件,而不是默认的~/.m2/settings.xml,否则前面全部白改。

2.3 一条命令验证仓库完整性

配置完成之后不要直接mvn package,先用一个轻量目标试探一下仓库能不能被正确读取:

# 不编译、不下载,只解析自身的父 pom 和插件配置 mvn -o validate # 打印依赖树,观察有没有 MISSING 标记 mvn -o dependency:tree -Dverbose=false > tree.txt

validate是最早的生命周期阶段,能跑通说明settings.xml、本地仓库路径、父 pom 继承链这三层逻辑都是通的。如果这里就报Non-resolvable parent POM,不用往下查了,问题 100% 在路径或仓库内容上。

dependency:tree的输出重定向到文件,是因为商城类项目的依赖树动辄两三千行。你只需要在里面搜MISSING和omitted for conflict两个关键词:前者是仓库缺件,后者是版本冲突。前者靠补仓库解决,后者靠<dependencyManagement>锁版本解决,两件事不要混为一谈。

3. 跑起来:从解压到一次编译通过

3.1 环境对齐:JDK、Maven 版本不能太自由

这类基于 Spring Boot 的商城系统,父 pom 里基本会写死spring-boot-starter-parent的版本,而不同代的 Spring Boot 对 JDK 和 Maven 有硬性要求。老版本的编译插件在 Maven 3.9 上会报插件解析错误,新版本又要求 JDK 17,混搭起来就是排查耗时且难以定位的问题。

先确认现状:

# 两个都必须指向同一个 JDK,否则编译期和运行期行为会不一致 java -version mvn -v # Windows 下确认 JAVA_HOME 没有多余引号,这是经典坑 echo %JAVA_HOME%

mvn -v会同时打印 Maven 版本和它使用的 JDK 路径。如果java -version是 1.8 而mvn -v显示 11,说明JAVA_HOME指向了别的 JDK,IDEA 里编译能过、命令行打包却报invalid target release,就是这里出的问题。

注意:项目里如果有多个模块分别用不同 JDK 编译(老项目常见),不要在系统环境变量里来回切,改用~/.mavenrc或者 IDE 的 per-module SDK 设置,否则你会陷入“改了 A 模块 B 模块失败”的循环。

3.2 IDEA 里需要动的三处配置

命令行能通之后,IDEA 里还有一组独立配置,它不会自动继承系统环境。三处都在Settings → Build, Execution, Deployment → Build Tools → Maven:

配置项填什么不填的后果
Maven home path你自己装的 Maven 目录,别用 IDEA 内置 Bundled内置版本可能和settings.xml语法不兼容
User settings file解压包里的settings.xml,勾上 Override走默认~/.m2,本地仓库指向错误
Local repository自动读取即可,但要肉眼确认路径对显示~/.m2/repository说明上一条没生效

填完点一次Reload All Maven Projects。工程如果一直转圈显示Resolving dependencies,先看右下角进度,再看Help → Show Log in Explorer里的idea.log,十有八九是在尝试联网。这时候在 Maven 面板里勾上Work offline(那个小飞机图标旁边的开关),能立刻止住。

还有一个隐藏项:Settings → Build → Compiler → Annotation Processors,必须勾选Enable annotation processing。这套工程大量使用 Lombok,不勾选的话所有@Data生成的方法都会在编译期报“找不到符号 getXxx”,看起来像仓库缺 jar,其实是注解处理器没开。

3.3 编译顺序:父 pom 一定要先 install

多模块工程有一个必须遵守的规则:父 pom 和 common 模块必须先进本地仓库,否则子模块解析不到它们的坐标。很多人直接在最外层mvn package,然后看到子模块报Could not find artifact xxx-common:jar,其实不是仓库缺件,是编译顺序问题。

# 第一次构建:先装父 pom,再装公共模块,最后全量打包 mvn -o -N install -DskipTests # -N 表示只处理当前 pom,不递归子模块 mvn -o -pl common -am install -DskipTests mvn -o clean package -DskipTests # 之后增量构建只编某个模块,-am 会顺带编它依赖的模块 mvn -o -pl order-service -am install -DskipTests

-N是--non-recursive,只 install 根 pom 本身,速度最快。-pl指定模块,-am是--also-make,自动把依赖模块一起构建。这两个参数组合起来是日常开发的主力,比每次全量打包快一个数量级。

编译通过之后再谈启动。这类微服务商城的启动顺序有依赖关系,乱序启动会看到一堆Connection refused,其实不是代码问题:

# 建议固化成脚本,顺序不能乱 # 1. 注册中心 / 缓存 / 数据库 start zookeeper-server start redis-server start mysqld # 2. 消息中间件、搜索引擎 start rocketmq-namesrv start rocketmq-broker start elasticsearch # 3. 基础服务(商品、用户、库存) java -jar product-service/target/product-service.jar java -jar user-service/target/user-service.jar # 4. 聚合层(网关、前台、后台) java -jar gateway/target/gateway.jar java -jar portal-web/target/portal-web.jar

判断标准很简单:前一步的服务端口能telnet通,再启动下一步。Dubbo 服务提供者启动时会向注册中心写节点,如果注册中心还没起来,提供者会重试若干次然后放弃注册,进程看着是活的,但消费者永远调不到——这是“服务起来了,但接口 503”的常见来源。

4. 核心模块拆解:仓库里的 jar 决定了运行时的行为

4.1 先看依赖树,弄清哪些版本被固定

这份仓库包的价值不只是“有 jar”,而是“有一组互相兼容的 jar”。商城系统里最容易出问题的是网络层和序列化层:Dubbo 版本和 Netty 版本强绑定,Fastjson、Jackson 又同时被多个框架引用,随便升一个就可能出现NoSuchMethodError。

# 只看几个关键组件,输出更清晰 mvn -o dependency:tree -Dincludes=org.apache.dubbo,io.netty,com.alibaba # 找出所有被依赖的 fastjson 版本,判断是否存在多版本共存 mvn -o dependency:tree -Dincludes=com.alibaba:fastjson -Dverbose=true

-Dincludes接受groupId:artifactId形式的过滤条件,支持通配。-Dverbose=true会把“被哪个模块引入、被哪个版本覆盖”一并打印出来,输出长但信息全。

一个典型商城工程的依赖分布大致是这样:

组件作用版本不匹配的后果
Dubbo + NettyRPC 通信启动报NoSuchMethodError,或连接握手失败
RocketMQ Client下单异步消息消费端收不到消息,事务消息不生效
Elasticsearch Rest Client商品搜索查询 DSL 报 400,或反序列化失败
Redis Client(Jedis/Lettuce)购物车、库存缓存连接池泄漏,压测时连接数暴涨

这张表的用法是反着的:当你遇到NoSuchMethodError的时候,先回来查这一行,看仓库里实际装了哪个版本,再看 pom 里声明的是哪个版本。九成的运行时异常都能在这一步定位。

4.2 Dubbo 与注册中心的配置对齐

仓库里装的是 Dubbo 的 jar,配置文件决定它怎么用。这类工程的 RPC 配置一般集中在一个dubbo.yml或者application.yml里:

dubbo: application: name: order-service # 服务名,注册中心里显示的就是它 qos-enable: false # 生产环境关掉 QoS 端口,避免多实例抢 22222 registry: address: zookeeper://127.0.0.1:2181 timeout: 10000 # 注册中心连接超时,内网可以调小 check: false # 启动时不检查注册中心,避免启动顺序死锁 protocol: name: dubbo port: 20880 # 多实例部署时用 -Ddubbo.protocol.port 覆盖 consumer: check: false # 消费者启动时不做可用性检查 timeout: 3000 retries: 0 # 写操作必须为 0,否则重复下单

check: false这一项在单体调试时非常关键:它允许服务在依赖的提供者还没起来时也能启动,方便你按任意顺序起服务。但retries必须设成 0,这是需要特别注意的地方——默认重试 2 次,一次下单请求超时后会被重发三次,库存扣三次,这就是超卖。

qos-enable: false也值得说一句。Dubbo 的 QoS 默认监听 22222 端口,本机跑多个提供者实例时会端口冲突,日志里报Address already in use,但服务本身还能起来,问题在于它已经变成只读状态了,你后续的运维命令全部无效。

4.3 下单链路:消息与缓存的配合

商城核心链路是“校验 → 锁库存 → 建订单 → 发消息 → 扣库存”。这段代码最能体现仓库里那些 jar 是怎么协同工作的:

@Service public class OrderServiceImpl implements OrderService { @Reference(check = false) private StockService stockService; // Dubbo 远程调用,走 Netty @Autowired private RedisTemplate<String, Object> redisTemplate; @Autowired private RocketMQTemplate rocketMQTemplate; @Override @Transactional(rollbackFor = Exception.class) public String createOrder(OrderRequest request) { // 1. 幂等:同一用户同一商品 5 秒内只允许提交一次 String lockKey = "order:lock:" + request.getUserId() + ":" + request.getSkuId(); Boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, "1", Duration.ofSeconds(5)); if (Boolean.FALSE.equals(locked)) { throw new BizException("请勿重复提交"); } // 2. 预扣库存,失败直接抛异常,事务回滚 boolean deducted = stockService.deduct(request.getSkuId(), request.getQuantity()); if (!deducted) { throw new BizException("库存不足"); } // 3. 落订单主表与明细表 Order order = buildOrder(request); orderMapper.insert(order); // 4. 发延迟消息,30 分钟后未支付则关单回滚库存 rocketMQTemplate.syncSend("order-delay-topic", MessageBuilder.withPayload(order.getOrderNo()) .setHeader("delayLevel", 16) // RocketMQ 预置延迟级别 .build()); return order.getOrderNo(); } }

逐段说。第 1 步的setIfAbsent对应 Redis 的SET NX EX,是分布式锁里最轻的一种实现,够用但有边界:锁在业务执行完不会主动释放,靠 5 秒过期兜底,所以它防的是“手抖连点”,不是严格互斥。

第 2 步的stockService.deduct是跨进程调用,走的是仓库里那套 Dubbo + Netty。这里有个隐含前提:库存服务的deduct方法内部必须是update stock set num = num - ? where num >= ?这种带条件的原子更新,否则扣减本身就会有并发问题,重试机制反而会放大它。

第 4 步的delayLevel是 RocketMQ 的预置级别,不是毫秒数,16 对应 30 分钟。写错成时间戳会导致消息立即投递,关单任务在订单刚创建时就跑了,用户还没付款订单就没了——这种 bug 在测试环境很难复现,因为测试时你总是先付款。

4.4 搜索模块与客户端的版本关系

商品搜索走 Elasticsearch,这一块对版本尤其敏感。ES 7 之后废弃了type,客户端 API 也从TransportClient换成了RestHighLevelClient。如果仓库里装的是 7.x 的客户端而代码还是 6.x 的写法,编译期就会报找不到类。

@Autowired private RestHighLevelClient esClient; public PageResult<Product> search(String keyword, int page, int size) { SearchRequest request = new SearchRequest("product"); SearchSourceBuilder builder = new SearchSourceBuilder(); // 关键词匹配 + 过滤上架状态,bool 查询是最常用的组合 builder.query(QueryBuilders.boolQuery() .must(QueryBuilders.matchQuery("name", keyword)) .filter(QueryBuilders.termQuery("status", 1))); builder.from((page - 1) * size).size(size); builder.highlighter(new HighlightBuilder().field("name")); request.source(builder); try { SearchResponse resp = esClient.search(request, RequestOptions.DEFAULT); return toPageResult(resp); } catch (IOException e) { // 搜索降级:ES 不可用时回落到数据库模糊查询 return fallbackSearch(keyword, page, size); } }

这段代码的关键点是catch里的降级。搜索服务挂掉不应该让整个商品列表页白屏,所以我在所有 ES 调用外都包了一层 fallback。RequestOptions.DEFAULT是超时控制的位置,默认没有超时,ES 卡住时线程会一直挂着,压测时能直接把 Tomcat 线程池打满——后面我统一改成了一个 2 秒超时的自定义RequestOptions。

5. 避坑与排查:本地仓库最容易出问题的五个地方

5.1 报 “was cached in the local repository”

现象:mvn -o package时对某个明明存在的 jar 报The POM for xxx is missing或者resolution will not be reattempted until the update interval of central has elapsed,去repository目录下看,jar 文件确实在那儿。

原因:同目录下存在xxx.jar.lastUpdated文件。Maven 优先读这个“失败书签”,判定该坐标不可用,压根不去看旁边的 jar。整包搬运、断点续传失败、打包时网络抖动,都会留下这个文件。

解决:批量清掉,再重新构建。

# 清理所有失败书签,Linux/macOS find ./repository -name "*.lastUpdated" -delete # Windows PowerShell Get-ChildItem -Path .\repository -Recurse -Filter *.lastUpdated | Remove-Item

清完立刻重跑mvn -o validate,如果能过,说明仓库本身是好的,问题纯粹是书签污染。

5.2 换镜像后报 “not available in offline mode”

现象:在你自己的机器上跑得好好的,同事拿过去配了他自己的settings.xml,一构建就报某个包is not available in the local repository,可那个包明明在。

原因:每个 jar 目录下有个_remote.repositories,里面记着xxx.jar>central=这样的来源标记。同事的settings.xml里镜像 ID 叫aliyun或者nexus,和central对不上,Maven 在离线模式下判定“来源不匹配”,拒绝使用。

解决:两种方式,看你要不要保留来源信息。

# 方式一:删除所有来源标记,最彻底,适合纯离线环境 find ./repository -name "_remote.repositories" -delete # 方式二:启动时启用 legacy 本地仓库模式,不校验来源 mvn -o -llr clean package -DskipTests

-llr是--legacy-local-repository的缩写,效果等价于-Dmaven.legacyLocalRepo=true。我一般用方式一,一次清理永久生效,代价是以后想追溯某个 jar 从哪来就查不到了。

5.3 IDEA 一直卡在 Resolving dependencies

现象:命令行mvn -o package三分钟跑完,IDEA 里却一直在右下角转圈,External Libraries里一片红色。

原因:IDEA 的 Maven 导入走的是自己的进程,用的是Settings里配的settings.xml,不一定和命令行一致。更常见的是它在后台尝试解析SNAPSHOT依赖,而离线模式下 SNAPSHOT 永远解析不到最新版,于是一直重试。

解决:三步走。先确认 IDEA 的User settings file指向正确的文件;再打开 Maven 面板的Work offline;最后强制刷新一次索引,File → Invalidate Caches → Just Restart。如果还不行,看idea.log里最后一条 Maven 相关日志,它会明确写出在解析哪个坐标。

注意:SNAPSHOT依赖在离线仓库里是“某一天的快照”,不是“最新快照”。团队协作时要约定好,要么统一用 release 版本,要么把快照包一起提交进仓库。

5.4 编译报“找不到符号”,但类明明存在

现象:@Data标注的实体类,调用getName()报cannot find symbol,点进去看源码一切正常。清理重编、删target、重启 IDEA 全试过,无效。

原因:Lombok 是编译期注解处理器,靠maven-compiler-plugin的 annotation processor 机制工作。仓库里如果有多个版本的 Lombok,或者 IDEA 没开注解处理,处理器就不会运行,字节码里自然没有getName()。

解决:先在命令行验证,排除 IDE 干扰。

# 命令行能过说明是 IDE 配置问题,过不了才是依赖问题 mvn -o clean compile -DskipTests # 查看实际使用的 Lombok 版本 mvn -o dependency:tree -Dincludes=org.projectlombok

命令行能过 → 去 IDEA 勾Enable annotation processing。命令行也过不了 → 检查pom.xml里 Lombok 的<scope>是不是被写成了provided之外的奇怪值,或者父 pom 里锁了一个和 JDK 不兼容的版本。

5.5 同一坐标存在多个版本,运行时报 NoSuchMethodError

现象:编译一切正常,启动后调用某个工具类报java.lang.NoSuchMethodError: com.xxx.util.JsonUtil.parse(Ljava/lang/String;)Ljava/lang/Object;,反编译看方法签名确实存在。

原因:依赖树里有同一个 artifact 的两个版本,Maven 按“最近优先”选了一个,但仓库里两个版本都在,编译期和运行期可能加载到不同的那份。典型受害者是 Fastjson、Guava、Netty 这三个被到处引用的库。

解决:先定位冲突来源,再在<dependencyManagement>里锁定。

# 找出所有引入 fastjson 的路径 mvn -o dependency:tree -Dincludes=com.alibaba:fastjson -Dverbose=true

输出里带omitted for conflict with x.x.x的那几行就是被覆盖的。统一在父 pom 的<dependencyManagement>里声明一个版本,所有子模块继承,不要在子模块里各写各的。

6. 二次开发:往离线仓库里加依赖、接私服、进 CI

仓库包不是一次性用品,你迟早要往里加东西。最常见的场景是:业务要用一个新的 SDK,mvn一跑发现本地没有,离线模式直接失败。这时候不要关掉离线开关让它自己下——它会顺带把你锁好的版本全刷新一遍。正确做法是精确地只取那一个坐标:

# 在能联网的机器上,只下载指定坐标到指定目录 mvn dependency:get \ -Dartifact=com.example:some-sdk:1.2.3 \ -Ddest=./offline-drop/ \ -Dtransitive=true # 把 offline-drop 里的内容按 groupId/artifactId/version 结构复制进仓库 # 例如 com/example/some-sdk/1.2.3/

-Dtransitive=true会把它自己的传递依赖一起拉下来,省得你手动补。-Ddest指定落地目录,避免污染你本机的~/.m2。复制进仓库之后,记得同步在父 pom 的<dependencyManagement>里加一行版本声明,否则子模块各自引各自的版本,三个月后又是一次NoSuchMethodError。

团队规模上来之后,离线包应该退化成“冷启动兜底”,正式方案是内网私服。做法是settings.xml里配一个mirrorOf指向私服,pom 里加distributionManagement,mvn deploy的时候把内部模块推上去。这一步做完,新同事入职只需要一个空的settings.xml,不用再传几个 G 的压缩包。

容器化构建是另一个值得提前处理的点。把仓库打进镜像的构建层,可以让 CI 每次构建都不依赖外网:

FROM maven:3.6-jdk-8 AS builder # 先拷仓库再拷源码,利用 Docker 层缓存:源码变了不用重传仓库 COPY repo-root/repository /root/.m2/repository COPY settings.xml /root/.m2/settings.xml COPY . /build WORKDIR /build RUN mvn -o clean package -DskipTests

顺序很关键。仓库在前、源码在后,只要仓库不变,这一层就命中缓存,构建时间从十分钟降到一分钟。反过来写的话,每次改一行代码都要重传几个 G 的仓库层,CI 会被拖死。

验证改造是否成功,我固定跑三条命令:mvn -o validate确认配置链没问题,mvn -o dependency:tree | grep MISSING确认没有缺件,mvn -o clean package -DskipTests确认全量能过。三条都绿了才提交,比事后在 CI 上翻日志省事得多。

有一段时间我不信邪,觉得离线仓库这种“土办法”迟早要被更好的方案替代,结果在一个客户现场断网三天,全靠它救回来。从那以后我每次接手新项目,第一步都是把依赖仓库整包备份一份放到自己的移动硬盘里,再在settings.xml里把那行<offline>注释写清楚备注——只有类似需求才打开。这个习惯看着笨,但它是我这些年唯一没后悔过的做法。希望帮到你。

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