做报表工具选型的时候,Jaspersoft Studio 社区版 7.0.3 经常被提起,但真正把它用明白的人不多。很多人下载安装之后就卡在"模板设计"这一步,不知道怎么连数据源、怎么处理参数、更不知道导出的 PDF 为什么中文字体乱成一团。这篇文章我会从实际应用的角度出发,把这套工具从安装到部署的完整链路捋一遍,专门针对 community edition 7.0.3 这个版本,把那些文档里没写清楚、论坛里翻了半天才找到答案的问题集中讲透。无论你是刚接触报表开发的程序员,还是需要自己捣鼓报表的运维、实施人员,这篇内容都能帮你少走弯路。
1. 什么时候你需要Jaspersoft Studio:从需求场景说起
1.1 它到底解决什么问题
如果你所在的项目需要频繁产出格式固定的业务报表——比如财务月结单、销售日汇总、物流对账单——又不想每次都在代码里手动拼 HTML 或者用 POI 去画 Excel,那 Jaspersoft Studio 就是用来解决这个问题的。它的核心价值在于:把报表的"视觉设计"和"数据填充"彻底分离。
设计人员在 Studio 里画好模板,定义好表格列、分组、汇总行、图表位置,这个模板是静态的;程序运行时通过传参和数据集把真实数据填进去,动态生成 PDF、HTML、DOCX、XLSX 等格式的输出文件。这种设计思路和前端模板引擎(比如 Thymeleaf、FreeMarker)非常像,区别在于 Jaspersoft Studio 面向的是复杂的打印级版式,比如精确到毫米的对齐、跨页重复表头、金额大写转换这些企业报表刚需。
1.2 社区版 7.0.3 和商业版的差异,选型前必须知道
很多第一次接触 Jaspersoft 的开发者会陷入一个误区:以为社区版(Community Edition)只是少了技术支持,功能上应该差不多。实际用下来差异还是明显的。
社区版 7.0.3 基于 Eclipse 内核构建,你可以免费使用完整的报表可视化设计器,支持数据源连接、数据集编写、元素拖拽、参数和变量管理、子报表、图表等核心功能,也能通过 JasperReports 库(Java 类库)在应用服务端编译和填充报表。这些能力已经覆盖了绝大多数企业内部报表需求。
但商业版(JasperReports Server 商业授权)额外提供的可视化报表调度、用户权限体系、数据审计、定时推送、多租户隔离等能力,在社区版里是完全没有的。如果你需要的是"用户在浏览器里自助查看和订阅报表",那社区版还得靠你自己开发前端方案,搭配开源的 JasperReports Server 社区版(注意,这个也有社区版)来做基础的管理和预览。
提示:如果你的项目场景只是"Java 程序里生成 PDF 报表",那直接用 JasperReports Library(Maven 坐标 net.sf.jasperreports:jasperreports)就够了,连 Studio 都可以不装。Studio 主要是用来做模板设计和调试的,两者配合使用才是完整工作流。
1.3 7.0.3 这个版本值得关注的几个变化
Jaspersoft Studio 7.x 系列相比 6.x 最大的变化是底层迁移到了较新的 Eclipse 平台,UI 响应速度有所提升,对高分辨率屏幕(Windows 高分屏、Mac Retina)的支持明显改善。7.0.3 在 7.0.x 系列里属于比较稳定的补丁版本,修掉了一些 7.0.0 里新增 JSON 数据集和可视化查询编辑器相关的崩溃问题。
如果你之前用的是 6.20.x 或更早版本,打开 7.0.3 后要注意:旧模板文件(.jrxml)可以正常导入,但项目配置文件和数据适配器(Data Adapter)的设置可能需要重新调整,尤其是自定义 JDBC 驱动的方式有了变化。这个我后面会详细说。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 7.0.3社区版安装前的几个关键决断
2.1 JDK版本和安装包选择:这里踩坑的人最多
Jaspersoft Studio 7.0.3 官方要求 Java 17 及以上运行环境。这里有个很常见的坑:很多开发机装的是 JDK 8,因为老项目在用;直接双击启动 Jaspersoft Studio 会报错或者闪退,错误日志指向 Java 版本不兼容。
解决方案有两种。第一种是单独下载一个 JDK 17(建议用 Eclipse Temurin 发行版),安装后在 Jaspersoft Studio 的启动配置文件 JaspersoftStudio.ini 里显式指定 -vm 参数,指向 JDK 17 的 bin\javaw.exe。注意这个参数必须放在 -vmargs 之前,否则不生效。第二种方法更省事:直接下载 TIBCO 官方提供的包含 JRE 的 Windows 安装包,安装完自带运行时,不依赖系统 Java。
Linux 环境下解压 tar.gz 包后,同样需要确认 JAVA_HOME 环境变量指向的是 JDK 17 或更高版本。用 java -version 检查一下,不要想当然。
2.2 下载渠道和版本校验
搜索"jaspersoft studio 下载"会出来一堆第三方站点,很多还捆绑流氓软件。官方下载地址是 SourceForge 上的 JaspersoftStudio 项目页面,以及 TIBCO 官网的社区版下载入口。SourceForge 上的 7.0.3 安装包文件名一般是 JaspersoftStudio-7.0.3-windows-x86_64.exe 或 JaspersoftStudio-7.0.3-linux-x86_64.tar.gz。
下载后建议核对一下 SHA-256 校验值,官方页面会提供。这一步虽然很多人嫌麻烦,但对于从非官方渠道下载的情况,这是最基本的自我保护。
2.3 汉化到底要不要做:我的实际建议
热词里有个"jaspersoft studio 汉化包下载",展开说一下。Jaspersoft Studio 官方没有提供中文语言包,社区里流传的汉化方式本质上是往 Eclipse 的 dropins 目录塞语言插件,或者是直接修改 configuration 下的文本资源。这些方式在 6.x 版本上还能凑合用,到了 7.0.3 这个基于新 Eclipse 平台的版本,很多汉化包会失效,强行安装反而会导致界面半英文半中文,菜单错位。
我的建议是不要折腾汉化。Jaspersoft Studio 的界面术语是固定的那一两百个单词,配上一张中英对照表,一周内就能完全适应。而且你迟早要面对英文的报错信息、英文的官方文档,报表模板里的字段名和表达式也都是英文的。在报表开发这个场景里,真正的障碍不是界面语言,而是对 JasperReports 表达式语法的理解。
如果你实在想要中文界面,可以尝试安装 Eclipse 的 Babel 语言包项目,选择对应 Eclipse 版本的简体中文包,但请做好兼容性测试,不要在生产开发环境里直接覆盖。
3. 第一张能跑的报表:数据源、数据集与模板的串联逻辑
3.1 数据适配器(Data Adapter)的正确创建方式
打开 Jaspersoft Studio 7.0.3,新建项目时选 "JasperReports Project",然后在项目资源管理器里右键新建 "Data Adapter"。这里 Supported 的类型包括:JDBC 数据库连接、JSON、XML、CSV、Excel 文件、JavaBean 集合(通过 JRDataSource)、空数据源等。
如果连数据库,注意选择 "Database JDBC Connection",在向导里填 JDBC URL、用户名、密码。7.0.3 的驱动管理方式和之前不太一样:老版本里常见的 "Driver classpath" 直接添加 JAR 的方式仍然保留,同时新增了 Maven 依赖解析方式。如果你用的是 PostgreSQL 或 MySQL,直接在驱动下拉选内置的即可;如果是国产数据库(达梦、人大金仓等),需要手动添加驱动 JAR 到 jaspersoft_studio 安装目录的 modules 下,或者通过数据适配器里的驱动管理按钮添加。
注意:JDBC URL 里的
serverTimezone、useSSL、characterEncoding这些参数在建立连接时也要一并配好,否则后面在数据集里写 SQL 查询时,中文条件可能查不出数据。
3.2 数据集(Dataset)和数据集查询:不只是写 SQL
报表模板里的数据源可以有多个,但归根结底每个数据集都必须有一个查询语句。在 "Dataset and Query" 对话框里,语言可以选择 SQL、JSON、XML、XPath 等。最常用的是 SQL。
写 SQL 的时候有个 Studio 特别方便的特性:可以直接点击 "Read Fields",Studio 会执行一次查询并自动把结果集的字段结构读取出来,生成 Field 列表。这样你在模板上拖拽字段的时候就不用手动定义每一个 Field 的名字和类型。
这里分享一个实用技巧:如果你的业务表字段特别多,但报表只需要其中几列,建议在 SQL 里显式列出需要的列,而不是用 SELECT *。原因不只是查询性能,更重要的是字段列表的可读性和后续维护性。Studio 的字段读取也会更精准。
3.3 模板布局中的基本元素:Static Text、Text Field、Band 和其含义
报表设计区竖向分为 Title、Page Header、Column Header、Detail、Column Footer、Page Footer、Summary 等带区(Band)。刚上手的人最困惑的是:这些带区到底有什么区别?为什么我的字段放错带区之后,显示位置完全不对。
- Title:整个报表的第一页顶部只显示一次,适合放报表大标题、主 LOGO。
- Page Header:每一页顶部都会显示,适合放页标题、查询条件摘要。
- Column Header:在每一页中,数据列表的列头会显示在 Detail 上方,适合放表格表头。
- Detail:数据多少行就重复渲染多少次,是报表的主体循环区。
- Column Footer:列底部的汇总区,常放小计。
- Page Footer:每页底部,常放页码、打印人。
- Summary:整个报表最后显示一次,常放总计、签名区域。
理解 Band 的含义很简单:把 Detail 想象成表格的一行,Column Header 就是表头,Page Header/Footer 是每页的页眉页脚。这个设计在分页打印场景下极其好用,因为 JasperReports 运行时会自动处理跨页时的表头重复,不需要你自己判断当前页有没有表头。
3.4 运行一张临时报表:预览视图的三种模式
设计完模板之后,点击预览标签页可以直接看到输出结果。预览工具栏里有几个重要选项:Data(选择使用哪个数据源)、Language(输出语言,选 Java 或 Groovy)、Format(预览成 PDF、HTML、XLSX 等)。
我建议首次预览用 PDF 模式,因为 PDF 是最接近打印效果的表现形式,字体问题、宽度溢出问题在 PDF 下最容易暴露。HTML 预览时很多 CSS 渲染特性和 PDF 不同,不能完全作为最终效果参考。
4. 报表设计中的参数、变量与子报表:从静态到动态的关键一跃
4.1 参数(Parameter)的声明与默认值处理
报表参数是外部传入模板的口子。在模板大纲里右键 "Parameters" 新建,指定参数名、类型(String、Integer、Date 等)。注意:如果参数可能为空,记得设置 isForPrompting 为 false,或者在表达式里写默认值。
比如你要做一个按时间范围查询的报表,定义 startDate 和 endDate 两个参数,类型都是 java.util.Date,在 SQL 里这样写:
sql复制SELECT ... FROM orders WHERE order_date BETWEEN $P{startDate} AND $P{endDate}
这里有个语法坑:SQL 里引用参数用 $P{参数名},不是 ? 占位符,也不是 :参数名。Studio 在数据集查询里会把 $P{} 替换成 JDBC 的 ? 并做安全绑定。如果你是自己拼 SQL 字符串传字符串参数,一定要在 Java 代码里手动做防注入处理,Studio 本身不做这个。
4.2 变量(Variable)与内置函数:从零开始写累加逻辑
变量的作用类似于 SQL 里的窗口函数,是在报表渲染过程中逐行计算的。JasperReports 内置了 sum、count、average 等常用聚合函数,可以直接定义变量用。
以"销售明细表带总金额汇总"为例,新建一个变量 totalAmount,计算类型选择 Sum,变量表达式填 $F{orderAmount},重置类型按报表级别选 Report,那么 Detail 每次渲染时都会把 orderAmount 累加到这个变量上。在 Summary 或者 Column Footer 里的 Text Field 表达式直接写 $V{totalAmount} 就能显示总金额。
比内置变量更灵活的是自定义计算逻辑:变量值表达式里可以写完整的 Java 表达式,比如格式化金额、判断空值后累加。注意一个关键点:变量的计算顺序和 Detail 行的渲染顺序一致,不要在变量表达式里引用尚未计算出来的其他变量,否则会拿到 null。
4.3 子报表(Subreport)为什么会成为很多人的噩梦
子报表适合处理"主报表中的一个区块自身也是一个完整报表"的场景,比如订单主表 + 订单明细多个子项的嵌套结构。
最标准的做法是:在主报表中新建一个 Subreport 元素,通过子报表向导创建子报表文件,并且把需要传递给子报表的参数(比如主报表里的订单号)通过 Subreport Parameter 传递过去。子报表的数据源表达式可以选择通过 new JRBeanCollectionDataSource(...) 传入一个 Java 集合,或者直接让子报表自己查询数据库。
最大的坑出现在参数连接上:如果子报表设置成"使用主报表的连接"(Connection Expression 留空),那子报表查询里用的参数必须每项都通过 Subreport Parameter 显式传递,否则子报表数据集在填充时会报 "Parameter not found" 或拿到 null。排查这类问题的时候,建议先给子报表设置一个"空数据源"来测试子报表本身是否能独立运行,确认子报表没问题,再回到主报表里检查参数传递链路。
4.4 表达式中的 Java 类使用与类型转换技巧
JasperReports 的表达式本质上就是 Java 表达式,只是用 $F{}、$P{}、$V{} 来引用字段、参数、变量。所以你在表达式里可以调用任何类的方法,但必须在表达式编辑器里手动引入类的全限定名,或者在模板的 import 节点里加 import。比如:
java复制new java.text.DecimalFormat("#,##0.00").format($F{amount})
类型转换的坑主要集中在 BigDecimal 和 Double 之间的比较、求和。报表里涉及金额的字段,建议在数据集里就把 SQL 类型映射为 java.math.BigDecimal(JDBC 驱动一般会自动映射),避免浮点数精度问题导致汇总金额差几分钱。
5. 中文环境的三个老大难:字体、导出与预览不一致
5.1 模板里中文显示正常,预览 PDF 却乱码或方块
这个问题几乎每个中文用户都会遇到。原因在于 JasperReports 渲染 PDF 时,默认使用的字体是 Helvetica,这套字体不包含中文字形。解决思路并非在 Studio 里换一个 Windows 中文字体名称(比如设为"宋体")就完事——模板到服务器端渲染时,服务器上没有宋体,一样会乱。
正确做法是配置字体扩展(Font Extension)。推荐用 noto sans cjk 或 思源黑体(Source Han Sans)。做法如下:
- 下载思源黑体的 TTF/OTF 文件。
- 在项目里新建一个字体(右键项目 -> Properties -> Jaspersoft Studio -> Fonts),设置字体名称、Regular 和 Bold 的路径。
- 模板中的 Text Field 字体设置为该字体,
pdfFontName和pdfEncoding会在导出时自动带上。 - 如果是部署到服务器,把字体扩展打成 JAR 包,放到应用的 classpath 里;最好把字体文件注册到操作系统里也可以。
另外一个和字体相关的参数是 net.sf.jasperreports.pdf.font.name,这个在全局配置里可以指定默认 PDF 字体。但注意,pdfEncoding 必须设置为 Identity-H(或使用 Unicode 编码),否则就算指定了中文字体,导出也可能乱码。
5.2 "所见即所得"的预览和实际渲染之间的差异
Studio 里的预览标签页(Preview)有两种:一种是 JR 渲染后的预览,一种是设计时的拖拽预览。很多人会混淆这两者。设计界面上显示的字体大小、行宽,并不等于最终 PDF 里的效果,因为设计界面走的是 Eclipse SWT 的字体渲染机制,PDF 走的是 JasperReports 布局引擎。
排查预览不一致问题,我习惯先切到 PDF Preview 作为基准;如果 PDF 预览对了、但部署到服务器后不对,那就查服务端字体和环境配置;如果 PDF 预览本身就有问题,优先查字体扩展和数据适配器。
5.3 导出 Excel 时中文正常,数字变成文本或科学计数法
JasperReports 导出 XLSX 时,Text Field 的数值如果没设置正确的 Pattern,Excel 里会默认按文本存储,这类问题在财务数据交接时容易引发二次处理麻烦。解决办法是给 Text Field 设置 Pattern,比如 #,##0.00,并在导出时配置 net.sf.jasperreports.export.xls.detect.cell.type 为 true,让导出器根据值类型自动判断单元格类型。
6. 从Studio到服务器:部署集成的隔离与踩坑
6.1 项目打包:.jrxml 编译成 .jasper 的几种方式
报表模板开发完成后,要部署到应用服务器。你可以在 Studio 里右键项目直接 Build,生成 .jasper 编译文件;也可以把 .jrxml 打包进应用,在运行时用 JasperCompileManager.compileReport() 动态编译。
这两种方式各有适用场景。静态编译(.jasper)的好处是:模板语法错误在开发期就能暴露,服务器上不需要有编译环境,启动速度更快;动态编译的好处是:模板可以放在数据库或外部文件系统里,修改模板后不用重启应用。
我个人的推荐是:稳定运行的项目用静态编译;需要频繁调整报表格式、又不想经过完整发版流程的情况,把 .jrxml 存数据库,做模板管理功能,用动态编译。
6.2 Java 应用集成最简示例:从 File 到 PDF
如果你用的是 Spring Boot 项目,最简集成方式如下:
java复制JasperReport jasperReport = JasperCompileManager.compileReport(inputStream);
JasperPrint jasperPrint = JasperFillManager.fillReport(
jasperReport,
parameters,
dataSource // 可以是 JRBeanCollectionDataSource 或 java.sql.Connection
);
byte[] pdfBytes = JasperExportManager.exportReportToPdf(jasperPrint);
参数 parameters 是一个 Map<String, Object>,key 必须和模板里定义的参数名严格一致。dataSource 如果传的是 JDBC Connection,那模板里数据集查询语句会直接在这个连接上执行;如果传的是 JRBeanCollectionDataSource,那模板里各字段是通过 getter 方法从 JavaBean 里取值(字段名匹配属性名)。
这里有个常见错误:用了 JRBeanCollectionDataSource 之后,模板数据集里还写着 SELECT ... 的 SQL,运行时报 "Result set not found"。原因是你混淆了两种数据源模式。用 Bean 数据源时,数据集查询类型必须是 "No query" 或者返回 List 的表达式,不能在查询里写 SQL。
6.3 单独使用 JasperReports Server 社区版:本地搭建和数据源管理
如果有内网报表门户需求,可以部署 JasperReports Server 社区版(现在已经改名叫 Jaspersoft Community Project)。它自带 Web 界面,可以管理数据源、上传 JRXML 模板、分配用户权限。社区版和 Studio 的连接方式是:Studio 里 "Repository Explorer" 视图可以连接到 Server 的 REST API。
需要注意:Server 的版本和 Studio 的版本最好保持一致的大版本,比如都用 7.x,避免 JRXML 版本兼容性问题。另外 Server 社区版默认使用内嵌的 HSQLDB 数据库保存仓库元数据,生产环境建议更换为 PostgreSQL 或 MySQL,否则数据量一大就有锁库风险。
6.4 高并发场景下,填充报表的服务端内存设置
JasperReports 填充报表是一个 CPU 密集和内存密集的过程。如果你在一个请求线程里同步生成几百页的大 PDF,服务端内存会瞬间飙升。我自己遇到过 OOM 的情况,排查后原因就是没有限制单次导出最大页数。
建议在服务端做如下控制:
- 对输入参数的数据范围做前置校验,比如时间跨度超过 1 年直接拒绝。
- 用
JRExporter导出流式输出到 HTTP 响应,不要先填充成一个超大JasperPrint对象再输出。 - 必要时设置 JVM 参数:
-XX:MaxDirectMemorySize和堆内存上限,根据报表复杂度调整。
7. 实际问题排查链路:从报错到定位根因
7.1 建立一套稳定的排查方法,而不是到处乱猜
很多人遇到 JasperReports 报错,第一反应是全栈贴到搜索引擎里找,这样效率很低。我建议建立这样一套排查顺序:
- 先看异常类型。
JRException通常都是模板或数据源层面的问题;ClassCastException通常是字段类型映射错了;NullPointerException优先查参数和变量是否为空。 - 用 Studio 本地预览复现问题。Studio 内置了完整的运行环境,能在设计阶段暴露 90% 的问题。
- 开启详细日志。JasperReports 的日志级别调到 DEBUG,重点看
org.jasperreports.engine.fill.JRFillDataset和JRFillSubreport这两个 logger。 - 分模块隔离测试。比如一个报表在 Server 上跑失败,先在 Studio 里用同样的数据源跑一遍。
7.2 高频报错一:net.sf.jasperreports.engine.JRException: Errors were encountered when compiling report expressions class
这是最常见的编译错误,模板里的表达式有语法问题,比如多余的括号、引用了不存在的字段、类型不匹配。Studio 报错面板里会列出具体哪一行表达式编译失败,双击错误信息会自动跳到对应的 Text Field。
排查时特别注意:表达式里的字符串常量必须用双引号,不能像 JS 那样用单引号;BigDecimal 和 Integer 做比较时要显式转换。
7.3 高频报错二:Unable to load class: com.mysql.jdbc.Driver 或驱动类找不到
这种报错一般只出现在部署环境,Studio 里连接正常,但应用里跑不了。原因是应用运行时的 JDBC 驱动不在 classpath 里。Studio 里通过数据适配器配的驱动不自动打包到应用里,你必须自己把 MySQL Connector/J 或 PostgreSQL 驱动加进项目的 Maven 依赖。
如果你用的数据库是达梦,在 Spring Boot 里除了加驱动,还要注意数据源类型是 dm.jdbc.driver.DmDriver,分页 SQL 语法和 MySQL 略有不同。
7.4 高频报错三:Resource not found at : /xxx.jrxml
模板文件路径写错。JasperCompileManager.compileReport() 里如果是相对路径,是相对于 classpath 的根目录。建议用类加载器方式读取:
java复制InputStream is = getClass().getResourceAsStream("/reports/MyReport.jrxml");
这里 /reports/ 前加斜杠表示从 classpath 根路径开始找。如果模板放在文件系统,尽量用绝对路径并做好路径配置管理,不要硬编码。
7.5 高频报错四:PDF 导出的日期字段显示为 null 或不对
日期参数传入时是 java.util.Date,但模板字段或参数类型定义成了 java.sql.Date,两者转换不匹配就会显示 null。解决办法是统一用 java.util.Date,在表达式里调用 new java.sql.Date($P{dateParam}.getTime()) 做显式转换。
另外一个隐蔽问题是时区。如果应用服务器和数据库服务器时区不一致,直接查询出来的日期可能差 8 小时。最好在 JDBC URL 里显式设置时区参数,比如 PostgreSQL 写 currentSchema=...&options=-c timezone=Asia/Shanghai。
8. 进阶一点:让报表模板更健壮的设计习惯
8.1 元素布局尽量用矩形和相对定位,而不是硬编码坐标
Jaspersoft Studio 是老派的拖拽式设计,但硬编码坐标的报告在参数长度变化时特别容易错位。比较好的做法是:文本字段的宽度设成自适应(Stretch With Overflow),容器元素用 Float 模式设置对齐关系,不要频繁用绝对定位。
对于字段内容可能很长的情况,比如"备注"列,要在 Text Field 属性里打开 Stretch With Overflow,并且保证它所在行的其他元素也设置了相同的拉伸策略,否则内容会被截断。
8.2 报表格式尽量从外部配置读取,不要写死在模板里
比如公司 LOGO 路径、导出版本号、底部免责声明,这些高频变动的内容建议做成参数传入,而不是在模板里写死。这样运营人员改了一次配置,所有报表都能生效,不需要重新发布模板。
8.3 建立一套模板命名和版本管理习惯
模板文件命名建议遵循 模块_业务场景_版本号.jrxml 的规则,比如 sales_daily_report_v2.jrxml。一个项目里有几十个报表后,没有命名规范完全不可维护。模板文件也放到 Git 里管理,每次修改都留 commit 记录,后面排查某个报表从哪天开始变了会很方便。
8.4 预留一份模板字段清单文档
每个 .jrxml 里用到的字段、参数、变量,如果有条件,维护一份 Excel 清单,写清楚字段来源表和业务含义。这个工作看似繁琐,实际项目里帮你省的时间远比投入多——尤其是接手别人做的报表时,一份字段文档能让你少看半天源码。
根据我个人这几年维护报表系统的经验,Jaspersoft Studio 这套东西上手不难,但用得深不深完全取决于你对模板和数据源关系的理解。社区版 7.0.3 把很多底层的细节封装得相对友好,但最终决定报表稳定性和可维护性的,还是设计时的数据模型规划、参数合理性,以及部署时对字体和环境的把控。最开始我被子报表参数传递和中文 PDF 字体问题来回折腾了两周,后来把排查链路梳理清楚后,绝大多数问题都能在半小时内定位。希望这篇内容能帮你把这条路走得更顺畅。
