Maven多模块项目实战:用JaCoCo插件一键生成聚合覆盖率报告(含完整配置)
Maven多模块项目实战:用JaCoCo插件一键生成聚合覆盖率报告(含完整配置)
如果你正在管理一个包含多个子模块的Maven项目,每次跑完单元测试后,是不是经常被分散在各处的覆盖率报告搞得头疼?每个模块单独生成一份报告,查看时需要来回切换,汇总整体覆盖率还得手动计算,既费时又容易出错。这种体验在微服务架构或大型企业级项目中尤为明显——十几个甚至几十个模块的覆盖率数据,想要一目了然地掌握整体质量状况,简直是一场噩梦。
实际上,JaCoCo作为Java生态中最主流的代码覆盖率工具,早就为多模块项目提供了优雅的聚合报告解决方案。但很多团队在配置时要么停留在单模块使用,要么被复杂的父子模块依赖关系绕晕,最终放弃了聚合报告这个能极大提升效率的功能。今天我就结合自己最近在一个中型微服务项目中的实践,详细拆解如何配置JaCoCo插件,实现真正的一键生成聚合覆盖率报告。整个过程不需要复杂的脚本,也不需要手动合并数据,只需要合理的Maven配置就能搞定。
1. 为什么你需要聚合覆盖率报告?
在深入配置细节之前,我们先明确一下聚合覆盖率报告到底解决了什么问题。想象一下这样一个场景:你的项目有5个核心业务模块、3个公共工具模块、2个接口定义模块。每个模块都有自己的测试套件,运行mvn test后,每个模块的target/site/jacoco目录下都会生成独立的覆盖率报告。
这时候你想回答几个简单的问题:
- 整个项目的整体代码覆盖率是多少?
- 哪个模块的覆盖率最低,需要重点加强?
- 新增的代码是否达到了团队要求的覆盖率门槛?
如果只有分散的报告,你需要:
- 打开10个HTML报告页面
- 手动记录每个模块的覆盖率百分比
- 根据代码行数加权计算整体覆盖率
- 对比各个模块的数据找出短板
这个过程不仅繁琐,而且容易出错。更糟糕的是,在持续集成环境中,你很难设置一个统一的覆盖率阈值来卡点——因为每个模块的阈值需要单独配置,维护成本极高。
聚合覆盖率报告的核心价值就在于统一视图和集中管理。它会把所有子模块的覆盖率数据收集起来,生成一个综合性的报告,在这个报告里你可以:
- 看到整个项目的汇总覆盖率数据
- 点击进入任意子模块查看详情
- 设置统一的覆盖率质量门禁
- 与SonarQube等质量平台无缝集成
注意:聚合报告并不是要取代模块级别的详细报告,而是提供了一个更高维度的视角。模块级别的报告对于定位具体的未覆盖代码行仍然不可或缺。
2. 项目结构与配置策略
在开始配置之前,我们先规划一下项目的结构。一个典型的多模块Maven项目通常采用这样的布局:
parent-project/ ├── pom.xml (父POM,packaging为pom) ├── module-a/ │ ├── pom.xml │ └── src/ ├── module-b/ │ ├── pom.xml │ └── src/ ├── module-common/ │ ├── pom.xml │ └── src/ └── module-report/ ├── pom.xml └── (通常没有或很少源代码)这里有几个关键点需要注意:
- 父POM的packaging必须是
pom,这是多模块项目的基础 - 所有子模块都在父POM的
<modules>中声明 - 专门创建一个report模块用于聚合报告生成,这个模块本身可能不包含业务代码,或者只包含一些配置类
为什么需要单独的report模块?因为JaCoCo的report-aggregate目标需要在一个能够“看到”所有其他模块的上下文中执行。这个模块通过依赖其他所有需要统计覆盖率的模块,获得了访问它们覆盖率数据的能力。
在实际项目中,我通常这样分配职责:
| 模块类型 | 包含内容 | 是否需要JaCoCo配置 |
|---|---|---|
| 父模块 | 公共依赖、插件管理、属性定义 | 配置prepare-agent和report目标 |
| 业务模块 | 业务代码、单元测试 | 继承父模块配置,无需额外配置 |
| 公共模块 | 工具类、常量定义 | 继承父模块配置,无需额外配置 |
| 报告模块 | 无或极少代码,依赖其他所有模块 | 配置report-aggregate目标 |
这种结构的好处是职责清晰:父模块负责基础配置,业务模块专注业务实现,报告模块专职报告聚合。当项目规模扩大时,这种分离能让配置保持可维护性。
3. 父模块的完整配置详解
父模块的配置是整个聚合报告体系的基石。这里不仅要配置JaCoCo插件,还要考虑与其他测试相关插件的协作。下面是一个生产可用的完整配置示例:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>multi-module-demo-parent</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>pom</packaging> <modules> <module>order-service</module> <module>user-service</module> <module>payment-service</module> <module>common-utils</module> <module>coverage-report</module> </modules> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <jacoco.version>0.8.8</jacoco.version> <surefire.version>3.0.0-M7</surefire.version> <junit.version>5.9.2</junit.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> </dependencies> </dependencyManagement> <build> <pluginManagement> <plugins> <!-- 配置Surefire插件用于运行测试 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>${surefire.version}</version> <configuration> <argLine>@{argLine} -Dfile.encoding=UTF-8</argLine> <includes> <include>**/*Test.java</include> <include>**/*Tests.java</include> </includes> </configuration> </plugin> <!-- JaCoCo核心配置 --> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>${jacoco.version}</version> <executions> <!-- 绑定到initialize阶段,准备代理 --> <execution> <id>prepare-agent</id> <goals> <goal>prepare-agent</goal> </goals> </execution> <!-- 绑定到test阶段后,生成各模块独立报告 --> <execution> <id>generate-module-report</id> <phase>test</phase> <goals> <goal>report</goal> </goals> </execution> <!-- 可选的:绑定到verify阶段进行覆盖率检查 --> <execution> <id>check-coverage</id> <goals> <goal>check</goal> </goals> <configuration> <rules> <rule> <element>BUNDLE</element> <limits> <limit> <counter>LINE</counter> <value>COVEREDRATIO</value> <minimum>0.80</minimum> </limit> </limits> </rule> </rules> </configuration> </execution> </executions> </plugin> </plugins> </pluginManagement> <!-- 所有子模块都会继承的插件配置 --> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> </plugin> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> </plugin> </plugins> </build> </project>这个配置有几个值得注意的细节:
使用
pluginManagement而不是直接在build/plugins中定义:这样可以让子模块选择性地继承,而不是强制应用所有配置。对于JaCoCo,我们通常希望所有业务模块都继承,所以同时在build/plugins中也进行了声明。Surefire插件的
argLine配置:@{argLine}是JaCoCo代理参数的占位符,这个配置确保了测试运行时JaCoCo代理能够正确附加。多个execution的phase配置:
prepare-agent默认绑定到initialize阶段,在测试开始前准备代理report绑定到test阶段之后,生成模块级别的报告check可以绑定到verify阶段,用于在构建过程中强制执行覆盖率标准
版本统一管理:所有插件和依赖的版本都在
properties中定义,便于统一升级和维护。
在实际使用中,我建议将覆盖率检查(check目标)单独配置,而不是在每个构建中都执行。因为开发阶段频繁的构建如果因为覆盖率不达标而失败,会影响开发效率。可以在持续集成服务器的构建任务中单独执行mvn verify来触发覆盖率检查。
4. 报告模块的聚合配置
报告模块是整个配置中最关键的部分,它负责收集所有子模块的覆盖率数据并生成聚合报告。这个模块的pom.xml需要精心设计:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.example</groupId> <artifactId>multi-module-demo-parent</artifactId> <version>1.0.0-SNAPSHOT</version> </parent> <artifactId>coverage-report</artifactId> <packaging>jar</packaging> <!-- 关键:依赖所有需要统计覆盖率的模块 --> <dependencies> <dependency> <groupId>com.example</groupId> <artifactId>order-service</artifactId> <version>${project.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.example</groupId> <artifactId>user-service</artifactId> <version>${project.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.example</groupId> <artifactId>payment-service</artifactId> <version>${project.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.example</groupId> <artifactId>common-utils</artifactId> <version>${project.version}</version> <scope>compile</scope> </dependency> </dependencies> <build> <plugins> <!-- 聚合报告配置 --> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <executions> <execution> <id>aggregate-reports</id> <phase>verify</phase> <goals> <goal>report-aggregate</goal> </goals> <configuration> <title>多模块项目聚合覆盖率报告</title> <footer>生成时间: ${maven.build.timestamp}</footer> <outputDirectory>${project.reporting.outputDirectory}/jacoco-aggregate</outputDirectory> <!-- 可选:排除某些包或类 --> <excludes> <exclude>**/generated/**</exclude> <exclude>**/*Test.class</exclude> <exclude>**/*Tests.class</exclude> </excludes> <!-- 可选:设置数据文件包含模式 --> <dataFileIncludes> <dataFileInclude>**/jacoco.exec</dataFileInclude> </dataFileIncludes> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>这个配置有几个技术要点需要理解:
依赖的作用:报告模块必须依赖所有需要统计覆盖率的模块,这是因为report-aggregate目标在执行时,会查找所有依赖模块的target/jacoco.exec文件。如果没有依赖关系,Maven不会构建那些模块,也就无法收集到覆盖率数据。
执行阶段的选择:我将report-aggregate绑定到了verify阶段,而不是test阶段。这是因为:
test阶段在各模块中并行执行,此时其他模块的.exec文件可能还未生成verify阶段在test之后,确保所有模块的测试都已执行完毕- 聚合报告生成相对耗时,放在最后阶段更合理
输出目录定制:通过<outputDirectory>配置,我将聚合报告输出到jacoco-aggregate子目录,这样就不会和模块自身的报告冲突。生成的报告结构如下:
coverage-report/target/site/jacoco-aggregate/ ├── index.html # 聚合报告首页 ├── jacoco.csv # CSV格式的原始数据 ├── jacoco.xml # XML格式的原始数据 ├── order-service/ # 子模块详细报告 │ └── index.html ├── user-service/ │ └── index.html ├── payment-service/ │ └── index.html └── common-utils/ └── index.html排除配置的重要性:在大型项目中,经常会有生成的代码(如Lombok生成的代码)、测试类本身等不需要统计覆盖率的文件。通过<excludes>配置可以过滤这些文件,让覆盖率数据更准确反映业务代码的测试情况。
5. 实战中的高级配置与优化
基础配置能工作,但在实际生产环境中,我们还需要考虑更多细节。下面分享几个我在项目中实际用到的进阶配置技巧。
5.1 多环境差异化配置
在开发、测试、生产不同环境中,对覆盖率的要求可能不同。我通常使用Maven的profile来实现差异化配置:
<!-- 在父POM中定义profiles --> <profiles> <profile> <id>ci</id> <activation> <property> <name>env</name> <value>ci</value> </property> </activation> <build> <plugins> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <executions> <execution> <id>check-coverage</id> <goals> <goal>check</goal> </goals> <configuration> <rules> <rule> <element>BUNDLE</element> <limits> <!-- CI环境要求更高的覆盖率 --> <limit> <counter>LINE</counter> <value>COVEREDRATIO</value> <minimum>0.85</minimum> </limit> <limit> <counter>BRANCH</counter> <value>COVEREDRATIO</value> <minimum>0.70</minimum> </limit> </limits> </rule> </rules> </configuration> </execution> </executions> </plugin> </plugins> </build> </profile> <profile> <id>local</id> <activation> <activeByDefault>true</activeByDefault> </activation> <build> <plugins> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <executions> <execution> <id>check-coverage</id> <goals> <goal>check</goal> </goals> <configuration> <rules> <rule> <element>BUNDLE</element> <limits> <!-- 本地开发环境要求较低 --> <limit> <counter>LINE</counter> <value>COVEREDRATIO</value> <minimum>0.60</minimum> </limit> </limits> </rule> </rules> </configuration> </execution> </executions> </plugin> </plugins> </build> </profile> </profiles>这样配置后,在本地开发时使用默认的宽松标准,而在CI服务器上通过mvn verify -Denv=ci启用更严格的标准。
5.2 集成测试覆盖率合并
单元测试覆盖率很重要,但集成测试的覆盖率同样不可忽视。JaCoCo支持合并多个.exec文件,我们可以这样配置:
<!-- 在报告模块的pom.xml中添加 --> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <executions> <execution> <id>merge-reports</id> <phase>verify</phase> <goals> <goal>merge</goal> </goals> <configuration> <fileSets> <!-- 合并单元测试覆盖率 --> <fileSet> <directory>${project.basedir}/../order-service/target</directory> <includes> <include>jacoco.exec</include> </includes> </fileSet> <!-- 合并集成测试覆盖率 --> <fileSet> <directory>${project.basedir}/../order-service/target</directory> <includes> <include>jacoco-it.exec</include> </includes> </fileSet> <!-- 其他模块类似配置 --> </fileSets> <destFile>${project.build.directory}/jacoco-merged.exec</destFile> </configuration> </execution> <execution> <id>generate-merged-report</id> <phase>verify</phase> <goals> <goal>report</goal> </goals> <configuration> <dataFile>${project.build.directory}/jacoco-merged.exec</dataFile> <outputDirectory>${project.reporting.outputDirectory}/jacoco-merged</outputDirectory> </configuration> </execution> </executions> </plugin>这个配置会先合并单元测试和集成测试的覆盖率数据,然后基于合并后的数据生成报告,得到更全面的覆盖率视图。
5.3 与SonarQube集成
如果团队使用SonarQube进行代码质量管理,需要将JaCoCo的覆盖率数据传递给Sonar。配置如下:
<!-- 在父POM中配置 --> <properties> <sonar.coverage.jacoco.xmlReportPaths> ${project.basedir}/coverage-report/target/site/jacoco-aggregate/jacoco.xml </sonar.coverage.jacoco.xmlReportPaths> <sonar.coverage.exclusions> **/generated/**, **/*Test.java, **/*Tests.java, **/test/**, **/target/** </sonar.coverage.exclusions> </properties> <!-- 或者使用jacoco.xml的聚合路径 --> <sonar.coverage.jacoco.xmlReportPaths> ${project.basedir}/coverage-report/target/site/jacoco-aggregate/jacoco.xml </sonar.coverage.jacoco.xmlReportPaths>然后在SonarScanner执行时,就能读取到聚合的覆盖率数据了:
mvn clean verify sonar-scanner \ -Dsonar.projectKey=my-project \ -Dsonar.sources=. \ -Dsonar.host.url=http://sonarqube-server:9000 \ -Dsonar.login=your-token5.4 大型项目的性能优化
当项目模块数量很多(比如超过20个)时,生成聚合报告可能会比较慢。我通过以下方式优化:
并行构建:在父POM中启用Maven的并行构建
<properties> <maven.build.parallel>true</maven.build.parallel> <maven.build.parallel.threadCount>4</maven.build.parallel.threadCount> </properties>增量报告:只对变更的模块重新计算覆盖率
<configuration> <append>true</append> <!-- 追加模式,而不是覆盖 --> </configuration>排除不需要的模块:有些模块如文档模块、客户端SDK模块可能不需要覆盖率统计
<excludes> <exclude>**/documentation/**</exclude> <exclude>**/client-sdk/**</exclude> </excludes>
6. 常见问题与解决方案
在实际配置和使用过程中,我遇到过不少问题,这里总结几个最常见的:
问题1:聚合报告为空或缺少某些模块的数据
症状:运行mvn clean verify后,聚合报告生成了,但某些模块的数据是空的。
可能原因:
- 报告模块没有正确依赖那些模块
- 那些模块的测试没有执行(可能被skip了)
- 那些模块的
.exec文件生成路径不标准
解决方案:
# 首先检查依赖关系 mvn dependency:tree -pl coverage-report # 然后检查各模块是否生成了jacoco.exec文件 find . -name "jacoco.exec" -type f # 最后检查测试是否真的执行了 mvn test -pl missing-module如果发现某个模块没有生成.exec文件,检查该模块的pom.xml是否继承了父模块的JaCoCo配置。
问题2:覆盖率数据不一致
症状:聚合报告中的覆盖率与各模块单独报告的总和不一致。
可能原因:
- 重复统计了某些代码(如公共模块被多个业务模块依赖)
- 测试执行顺序影响了覆盖率数据
- 使用了静态代码块或初始化器
解决方案:
<!-- 在报告模块中配置去重 --> <configuration> <includes> <include>com/example/**</include> </includes> <!-- 使用相同的classIdRoot确保类识别一致 --> <sessionId>my-project</sessionId> </configuration>问题3:内存不足或构建超时
症状:生成聚合报告时Maven崩溃或长时间无响应。
可能原因:
- 项目太大,覆盖率数据过多
- JaCoCo插件内存配置不足
解决方案:
# 增加Maven可用的内存 export MAVEN_OPTS="-Xmx4g -XX:MaxPermSize=512m" # 或者调整JaCoCo的堆内存 mvn verify -Djacoco.dumpOnExit=true -Djacoco.address=localhost -Djacoco.port=6300问题4:与Spring Boot的集成问题
症状:Spring Boot项目中使用内嵌容器运行测试时,覆盖率数据不准确。
解决方案:
<!-- 在Spring Boot项目的pom.xml中添加 --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <jvmArguments> -javaagent:${settings.localRepository}/org/jacoco/org.jacoco.agent/${jacoco.version}/org.jacoco.agent-${jacoco.version}-runtime.jar=destfile=${project.build.directory}/jacoco.exec </jvmArguments> </configuration> </plugin>这个配置确保Spring Boot应用启动时也加载JaCoCo代理。
7. 自动化与持续集成
配置好本地环境后,下一步就是将其集成到CI/CD流水线中。我在Jenkins和GitLab CI中都实践过,下面分享两个典型的配置。
Jenkins Pipeline配置:
pipeline { agent any stages { stage('Checkout') { steps { checkout scm } } stage('Build and Test') { steps { sh 'mvn clean compile test' } } stage('Generate Coverage Report') { steps { sh 'mvn verify -pl coverage-report' // 发布聚合报告 publishHTML([ reportDir: 'coverage-report/target/site/jacoco-aggregate', reportFiles: 'index.html', reportName: 'JaCoCo聚合覆盖率报告', keepAll: true ]) // 检查覆盖率阈值 sh ''' # 解析jacoco.xml获取整体覆盖率 COVERAGE=$(xmllint --xpath "string(//report/counter[@type=\'LINE\']/@covered)" coverage-report/target/site/jacoco-aggregate/jacoco.xml) TOTAL=$(xmllint --xpath "string(//report/counter[@type=\'LINE\']/@missed)" coverage-report/target/site/jacoco-aggregate/jacoco.xml) TOTAL=$((COVERAGE + TOTAL)) RATIO=$((COVERAGE * 100 / TOTAL)) if [ $RATIO -lt 80 ]; then echo "覆盖率低于80%: ${RATIO}%" exit 1 fi ''' } } } post { always { // 清理工作空间 cleanWs() } } }GitLab CI配置:
stages: - test - coverage variables: MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN" cache: paths: - .m2/repository - target/ test: stage: test script: - mvn clean test -B artifacts: paths: - "*/target/jacoco.exec" - "*/target/surefire-reports" expire_in: 1 week coverage: stage: coverage script: - mvn verify -pl coverage-report -B artifacts: paths: - coverage-report/target/site/jacoco-aggregate/ expire_in: 1 month coverage: '/Total.*?([0-9]{1,3})%/'在GitLab CI中,coverage正则表达式会自动从构建日志中提取覆盖率百分比,并在Merge Request中显示。
8. 实际项目中的最佳实践
经过多个项目的实践,我总结了一些最佳实践,能让JaCoCo聚合报告发挥最大价值:
1. 分层设置覆盖率目标不要对所有模块一刀切。我通常这样分层:
- 核心业务模块:85%行覆盖率
- 工具类模块:90%行覆盖率
- 接口定义模块:60%行覆盖率(主要是DTO)
- 集成测试:70%分支覆盖率
2. 定期清理历史数据.exec文件会累积,定期清理可以避免磁盘空间问题:
# 在CI脚本中添加 find . -name "jacoco*.exec" -mtime +7 -delete3. 与代码审查结合在Merge Request流程中,要求新增代码的覆盖率不低于整体项目覆盖率。可以使用GitLab的API或GitHub Actions自动检查。
4. 可视化趋势将每次构建的覆盖率数据存储起来,生成趋势图。我用的InfluxDB + Grafana方案:
# 提取覆盖率数据并写入InfluxDB COVERAGE=$(xmllint --xpath "string(//report/counter[@type='LINE']/@covered)" jacoco.xml) TOTAL=$(xmllint --xpath "string(//report/counter[@type='LINE']/@missed)" jacoco.xml) curl -i -XPOST 'http://influxdb:8086/write?db=coverage' \ --data-binary "coverage,project=my-project value=$((COVERAGE*100/(COVERAGE+TOTAL)))"5. 排除合理的低覆盖率文件有些文件确实难以测试或不需要测试,合理排除能让数据更真实:
<excludes> <!-- 配置类 --> <exclude>**/config/**</exclude> <!-- 自动生成的代码 --> <exclude>**/generated/**</exclude> <!-- 只有getter/setter的DTO --> <exclude>**/dto/**</exclude> <!-- 第三方库的适配器 --> <exclude>**/adapter/**/*Impl.java</exclude> </excludes>6. 教育团队成员最后也是最重要的:工具只是手段,提升代码质量才是目的。我定期在团队内部分享:
- 如何阅读覆盖率报告
- 哪些情况下的低覆盖率是合理的
- 如何编写可测试的代码
- 单元测试的最佳实践
这些实践让我们的覆盖率从最初的40%提升到了现在的75%,而且更重要的是,团队对测试的态度从"不得不写"变成了"主动要写"。
配置过程中最让我头疼的是模块间的依赖关系问题——某个模块的测试依赖另一个模块的类,导致测试顺序影响覆盖率数据。后来我们通过重构测试代码,使用Mockito等工具解耦测试依赖,才彻底解决了这个问题。另一个教训是关于性能的:当项目有30多个模块时,生成聚合报告需要5分钟以上。我们通过拆分聚合报告(按业务域分组)和增量生成优化到了1分钟内。
