iText PDF 实战:创建、签章、斜字水印与文本替换避坑指南

简介:这是一份面向Java开发者的iText操作PDF实战源码包,聚焦创建PDF、电子签章、斜字水印与文本替换四类常见需求,适合需要快速上手PDF处理的初中级工程师参考。压缩包共32个文件,约8.77MB,包含7个java源码、9个class编译文件、5个jar依赖(itextpdf、itext-asian、bcprov等)、2个p12证书及png、jpg示例图片,另附readme说明与Eclipse工程配置,结构完整可直接导入运行。目前已有1571人学习下载。通过阅读源码,读者可掌握PdfWriter创建文档、PdfStamper签章与文本替换、PdfFormXObject绘制旋转水印等核心API用法,并了解证书配置、版本兼容与性能优化思路,是学习iText处理PDF的实用参考。

1. 从一份“看着简单”的 PDF 需求说起

前阵子有个做企业文档系统的朋友找我,说他们客户提了个需求:合同 PDF 要能自动生成、盖电子章、加斜字水印,还要把模板里的占位符替换成真实数据。他一开始觉得用现成工具拼一拼就行,结果卡在“斜字水印”和“文本替换”上——水印要么是正的,要么旋转后位置飘了;文本替换更头疼,PDF 里的文字不是按你看到的顺序存的,直接搜字符串经常找不到。

这类需求在电子合同、报表导出、票据生成场景里非常常见。iText 就是干这个的:它是一套 Java 操作 PDF 的库,能创建文档、加签章、画水印、做文本替换。适合谁?做后端文档服务、需要批量生成 PDF 的开发者,或者想从零搭一套合同签署流程的团队。下面我按实际拆过的顺序,把创建、签章、斜字水印、文本替换四块讲透,顺带把踩过的坑标出来。

2. iText 环境搭建与 PDF 创建:从依赖到第一个文档

2.1 选 iText 5 还是 iText 7

这是第一个要做的决定。iText 5 和 iText 7 的 API 差异很大,网上搜到的代码经常混着用,直接抄会编译不过。

iText 5 的包名是com.itextpdf.text,写法偏“流式”,Document+PdfWriter组合,老项目多。iText 7 是com.itextpdf.kernel和com.itextpdf.layout,模块拆得更细,链式调用多,新项目建议直接上 7。但要注意:iText 7 的文本替换能力比 5 弱一些,很多替换场景反而要回到 5 的PdfReader+PdfStamper思路。我一般建议:新建项目用 7,维护老项目或做复杂替换用 5。

依赖方面,Maven 里 iText 7 核心包是kernel和layout,签章还要加sign。iText 5 就一个itextpdf包,但版本别用太老的,5.5.13 之后对中文支持好很多。

<!-- iText 7 核心依赖,创建和布局 --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>kernel</artifactId> <version>7.2.5</version> </dependency> <dependency> <groupId>com.itextpdf</groupId> <artifactId>layout</artifactId> <version>7.2.5</version> </dependency> <!-- 签章模块,后面数字签名要用 --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>sign</artifactId> <version>7.2.5</version> </dependency>

版本号别乱升,7.2.x 和 7.1.x 之间有些类名变了。如果项目里已经有其他 PDF 库,注意bcprov这类加密包的冲突,签章那块尤其容易撞。

2.2 创建第一个 PDF 并写入中文

创建本身不难,难的是中文。iText 默认字体不含中文字形,直接写中文会输出空白或方块。必须显式指定中文字体文件。

// iText 7 创建 PDF 并写入中文 import com.itextpdf.kernel.font.PdfFont; import com.itextpdf.kernel.font.PdfFontFactory; import com.itextpdf.kernel.pdf.PdfDocument; import com.itextpdf.kernel.pdf.PdfWriter; import com.itextpdf.layout.Document; import com.itextpdf.layout.element.Paragraph; public class CreatePdf { public static void main(String[] args) throws Exception { String dest = "output.pdf"; PdfWriter writer = new PdfWriter(dest); PdfDocument pdf = new PdfDocument(writer); Document document = new Document(pdf); // 关键:加载中文字体,路径换成你系统里的字体文件 PdfFont font = PdfFontFactory.createFont("C:/Windows/Fonts/simsun.ttc,0", PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED); document.setFont(font); document.add(new Paragraph("这是一段中文测试文本")); document.close(); } }

逻辑说明:PdfWriter负责写字节流,PdfDocument是 PDF 对象模型,Document是高层布局。createFont第二个参数控制字体嵌入策略,PREFER_EMBEDDED会把字体子集嵌进 PDF,保证别的机器打开不乱码。参数上,simsun.ttc,0里的,0表示取 TTC 集合的第一个字体,Linux 下常用NotoSansCJK-Regular.ttc。

注意:字体文件路径不要写死成开发机路径,生产环境用 classpath 资源或配置项。字体嵌入会让文件变大,但中文场景基本躲不掉。

3. 电子签章与数字签名:把“盖章”这件事做对

3.1 签章的原理和两种做法

PDF 签章分两种:一种是“画个章图”,就是视觉上盖个红章,没有密码学意义;另一种是数字签名,用证书对文档做哈希签名,能验证文档有没有被改过。实际合同场景通常两者都要:先画章图,再加数字签名。

数字签名的流程是:对 PDF 字节范围算摘要,用私钥加密摘要,把签名值和证书一起写进 PDF 的签名域。验证时用公钥解密摘要,重新算文档摘要对比。iText 的sign模块封装了这套流程,但证书和密钥库要自己准备。

视觉章图就简单:准备一张透明背景的 PNG,用Image对象画到指定坐标。难点在坐标定位——PDF 坐标系原点在左下角,和很多图像库相反,算位置时容易翻车。

3.2 用 iText 7 做数字签名

下面是一个可复现的签名示例,用 PKCS12 密钥库。

// iText 7 数字签名示例 import com.itextpdf.kernel.pdf.PdfReader; import com.itextpdf.kernel.pdf.PdfWriter; import com.itextpdf.kernel.pdf.StampingProperties; import com.itextpdf.signatures.*; import java.io.FileInputStream; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.Certificate; public class SignPdf { public static void main(String[] args) throws Exception { String src = "input.pdf"; String dest = "signed.pdf"; String keystorePath = "keystore.p12"; char[] password = "123456".toCharArray(); // 加载密钥库 KeyStore ks = KeyStore.getInstance("PKCS12"); ks.load(new FileInputStream(keystorePath), password); String alias = ks.aliases().nextElement(); PrivateKey pk = (PrivateKey) ks.getKey(alias, password); Certificate[] chain = ks.getCertificateChain(alias); PdfReader reader = new PdfReader(src); PdfWriter writer = new PdfWriter(dest); PdfDocument pdfDoc = new PdfDocument(reader, writer, new StampingProperties()); // 创建签名外观和签名器 SignatureUtil signUtil = new SignatureUtil(pdfDoc); PdfSigner signer = new PdfSigner(reader, writer, new StampingProperties()); signer.setFieldName("Signature1"); // 签名外观:位置、页码、章图 Rectangle rect = new Rectangle(100, 100, 200, 100); signer.setReason("合同签署"); signer.setLocation("某公司"); // 执行签名,SHA256 + RSA IExternalSignature pks = new PrivateKeySignature(pk, "SHA-256", "BC"); IExternalDigest digest = new BouncyCastleDigest(); signer.signDetached(digest, pks, chain, null, null, null, 0, PdfSigner.CryptoStandard.CMS); } }

逻辑说明:PdfSigner是核心类,signDetached做分离签名。参数里digest是摘要算法封装,pks是私钥签名器,chain是证书链,CryptoStandard.CMS是签名格式。setFieldName指定签名域名称,重复签名时不能重名。

参数坑点:PrivateKeySignature第三个参数是 provider,用 BouncyCastle 就写"BC",但要在代码里提前注册Security.addProvider(new BouncyCastleProvider()),否则报找不到算法。signDetached最后那个0是估算签名长度,设太小会报缓冲区不足,一般给 0 让它自动算。

注意:密钥库密码、别名这些别硬编码,走配置中心或环境变量。签名后的 PDF 再被修改,验证会失败,这是特性不是 bug。

4. 斜字水印与文本替换:两个最容易翻车的点

4.1 斜字水印的坐标与旋转

水印本身是画文字或图片,斜字就是加旋转。iText 里用setRotationAngle或变换矩阵。坑在于:旋转是绕原点转的,直接设角度,文字会跑到页面外。

正确做法是先算好旋转后的位置,或者用PdfCanvas配合saveState/restoreState做局部变换。

// iText 7 添加斜字水印 import com.itextpdf.kernel.pdf.canvas.PdfCanvas; import com.itextpdf.kernel.geom.Rectangle; import com.itextpdf.kernel.colors.ColorConstants; public void addWatermark(PdfDocument pdfDoc, String text) { int pages = pdfDoc.getNumberOfPages(); PdfFont font = PdfFontFactory.createFont("C:/Windows/Fonts/simsun.ttc,0", PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED); for (int i = 1; i <= pages; i++) { Rectangle pageSize = pdfDoc.getPage(i).getPageSize(); PdfCanvas canvas = new PdfCanvas(pdfDoc.getPage(i)); canvas.saveState(); // 设置透明度和颜色 canvas.setFillColor(ColorConstants.LIGHT_GRAY); canvas.setExtGState(new PdfExtGState().setFillOpacity(0.3f)); // 平移到页面中心,再旋转,再画文字 canvas.beginText(); canvas.setTextMatrix(1, 0, 0, 1, pageSize.getWidth() / 2 - 100, pageSize.getHeight() / 2); canvas.setFontAndSize(font, 40); // 旋转 45 度 canvas.setTextMatrix((float) Math.cos(Math.PI / 4), (float) Math.sin(Math.PI / 4), (float) -Math.sin(Math.PI / 4), (float) Math.cos(Math.PI / 4), pageSize.getWidth() / 2 - 100, pageSize.getHeight() / 2); canvas.showText(text); canvas.endText(); canvas.restoreState(); } }

逻辑说明:saveState和restoreState保证变换不影响后续内容。setTextMatrix的六个参数是仿射变换矩阵:前四个控制旋转缩放,后两个是平移。旋转 45 度时,cos和sin都是 0.707 左右。平移量要减去文字宽度的一半,否则文字中心不在页面中心。

参数说明:setFillOpacity(0.3f)控制透明度,太深会盖住正文,太浅打印看不清。字号 40 到 60 之间比较合适,具体看页面尺寸。旋转角度一般 30 到 45 度,正负都行,负角度是另一个方向。

4.2 文本替换为什么直接搜不到

PDF 里的文本不是按阅读顺序存的,而是按绘制指令存的。一段“甲方:____”可能被拆成多个Tj操作,中间还夹着定位指令。直接PdfTextExtractor提取再替换,位置对不上。

常见做法是:用PdfReader读内容流,遍历PdfDictionary和PdfArray,找到Tj或TJ操作符,替换其中的字符串。但字体编码是个大坑——如果 PDF 用了子集字体,字符串是字形索引不是 Unicode,替换会乱码。

// iText 5 文本替换思路(iText 7 类似但 API 不同) PdfReader reader = new PdfReader("input.pdf"); PdfStamper stamper = new PdfStamper(reader, new FileOutputStream("output.pdf")); AcroFields fields = stamper.getAcroFields(); // 如果是表单域,直接 setField fields.setField("name", "张三"); // 如果是普通文本,要操作内容流 PdfDictionary page = reader.getPageN(1); PdfArray contentArray = page.getAsArray(PdfName.CONTENTS); // 遍历 contentArray,找 Tj 操作符,替换字符串 stamper.close();

逻辑说明:表单域替换最简单,setField直接搞定。普通文本替换要操作内容流,PdfName.CONTENTS拿到页面内容,遍历操作符。替换时注意字符串编码,用PdfString的toUnicodeString和setHexWriting控制。

参数坑点:setField对中文表单域要确保字体已嵌入,否则显示空白。内容流替换时,如果原字符串长度和新字符串不一致,后面的定位指令会错位,需要重新计算Td或Tm的偏移量。这也是为什么很多替换方案只适合等长替换。

注意:文本替换没有银弹,复杂 PDF 建议用模板引擎先生成,而不是事后替换。如果非要替换,先确认字体编码方式,再决定用表单域还是内容流。

5. 避坑与排查:签章、水印、替换的常见翻车记录

5.1 签名后 PDF 打不开或提示损坏

现象:签名完成,文件能生成,但用阅读器打开提示“文档已损坏”或“签名无效”。

原因:最常见的是PdfReader和PdfWriter同时操作同一个文件流,或者StampingProperties没设对。另一个原因是签名长度估算为 0 时,iText 预留的空间不够,写入时截断了。

解决:确保PdfReader和PdfWriter操作不同文件,或者用StampingProperties().useAppendMode()追加模式。签名长度给一个合理估算值,比如 8192 字节。如果还不行,检查 BouncyCastle 版本和 JDK 版本是否匹配。

5.2 水印旋转后位置偏移

现象:水印加了旋转,但不在页面中心,偏到角落去了。

原因:setTextMatrix的平移参数是在旋转后的坐标系里生效的,直接给页面中心坐标,旋转后自然偏。

解决:先平移到目标位置,再旋转,或者把平移量按旋转矩阵反算。简单办法是用canvas.transform做复合变换,顺序是:平移 → 旋转 → 画文字。别在setTextMatrix里同时塞旋转和平移,容易算错。

5.3 中文替换后变成乱码或问号

现象:替换后的中文显示为乱码、方块或问号。

原因:PDF 原字体不含中文字形,或者替换时用了错误的编码。iText 默认用PdfString的 Latin 编码,中文会丢。

解决:替换前确认字体已嵌入,用PdfFontFactory.createFont加载中文字体并setFont。如果是表单域,setFieldProperty里指定字体。内容流替换时,用PdfString的setHexWriting(true)写十六进制 Unicode。

5.4 多页文档只有第一页有水印

现象:给多页 PDF 加水印,结果只有第一页有。

原因:水印代码只操作了pdfDoc.getPage(1),没遍历所有页。

解决:用getNumberOfPages()拿总页数,循环处理每一页。注意每页的PdfCanvas要重新创建,不能复用。

5.5 签章图片背景不透明

现象:盖的章图有白色背景,盖上去把正文遮住了。

原因:章图是 JPG 或没有 alpha 通道的 PNG。

解决:用带透明通道的 PNG,iText 的Image对象会保留透明度。如果只有 JPG,可以用PdfCanvas手动做透明处理,但麻烦,不如换图。

6. 进阶技巧:用模板 + 数据绑定替代硬替换

文本替换做多了会发现,与其事后替换,不如一开始就用模板。iText 7 的pdfHTML模块能把 HTML 转 PDF,配合模板引擎(如 FreeMarker)做数据绑定,比操作内容流稳得多。

具体做法:用 FreeMarker 定义 HTML 模板,占位符写成${name},渲染成 HTML 字符串,再用pdfHTML转 PDF。中文、样式、分页都好控制。签章和水印再在这个基础上叠加。

// FreeMarker + pdfHTML 生成 PDF Configuration cfg = new Configuration(Configuration.VERSION_2_3_31); cfg.setDirectoryForTemplateLoading(new File("templates")); Template template = cfg.getTemplate("contract.ftl"); String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, data); ConverterProperties props = new ConverterProperties(); props.setFontProvider(new DefaultFontProvider(true, true, true)); HtmlConverter.convertToPdf(html, new PdfWriter("output.pdf"), props);

逻辑说明:data是 Map,里面放业务数据。DefaultFontProvider第三个参数true表示加载系统字体,中文能正常显示。ConverterProperties还能设 base URI,处理图片相对路径。

参数上,DefaultFontProvider的三个布尔值分别是:标准字体、扩展字体、系统字体。中文场景必须开系统字体。如果模板里有 CSS,pdfHTML支持大部分常用样式,但 flex 和 grid 支持有限,布局别写太花。

验证方法:生成后随机抽几页,用PdfTextExtractor提取文字,对比模板数据是否一致。签章用 Adobe Reader 验证签名状态,水印看透明度和位置。我一般还会用qpdf --check做一次结构校验,确认没有损坏。

从那以后我每次做 PDF 生成,都强制先走一遍“模板渲染 → 结构校验 → 签名验证”三步,不再事后硬替换。希望帮到你。

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

相关新闻

Codex中转站配置核心:config.toml详解与协议网关实践

Codex中转站配置核心:config.toml详解与协议网关实践

1. 项目概述&#xff1a;为什么 config.toml 是 Codex 接中转站的“心脏文件”Codex 接中转站——这个说法在当前技术社区里已经不是新鲜词&#xff0c;但真正能把它跑通、调稳、用熟的人&#xff0c;远比想象中少。我接触过不下二十个团队&#xff0c;其中超过一半卡在第一步&…

2026/10/21 8:05:16 阅读更多 →
地震反演培训教案编写指南:从软件操作到可复现处理链路

地震反演培训教案编写指南:从软件操作到可复现处理链路

简介&#xff1a;这份专业课件面向地质勘探、储层预测方向的工程师与高校师生&#xff0c;系统讲解JASON地震反演软件的原理与实操。内容从反演技术发展历程切入&#xff0c;梳理递推反演、测井约束反演到非线性反演、叠前反演的演进脉络&#xff0c;并区分叠前与叠后、线性与非…

2026/10/21 8:05:16 阅读更多 →
.NET基础类库(BCL)演示工程:字符串到异步性能优化实战

.NET基础类库(BCL)演示工程:字符串到异步性能优化实战

说实话&#xff0c;我在本地养成整理 Demo 仓库的习惯&#xff0c;已经有年头了。刚开始纯属偷懒——有些 API 用法调完就忘&#xff0c;每次都得重新翻文档、找别人的代码仓库。后来干脆把这些零散尝试收拢到一个项目里&#xff0c;取名就叫 BasicDemo&#xff0c;定位也一下子…

2026/10/22 10:42:11 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年&#xff0c;我大部分时间都在跟浏览器标签页打交道&#xff0c;直到第一次把桌面应用跑起来&#xff0c;才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词&#xff0c;最开始是在某段时间频繁出现在同事的讨论里&#xff0c;一个用HTML…

2026/10/22 10:18:38 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介&#xff1a;亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档&#xff0c;主要面向智能锁安装人员、终端用户及售后维护者&#xff0c;帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件&#xff0c;大小4.37MB&#xff0c;内容结构清晰&#xff…

2026/10/21 9:32:40 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统&#xff0c;整车要求空载跑到8m/s&#xff0c;电机转速已经拉到额定点&#xff0c;驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令&#xff0c;转速纹丝不动&#xff0c;三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/22 9:44:47 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年&#xff0c;用它写过嵌入式驱动、做过游戏服务端&#xff0c;也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词&#xff0c;我其实挺感慨的——这门语言的难点从来不是语法多&#xff0c;而是知识点之间的耦合太深…

2026/10/22 10:42:00 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介&#xff1a;亚太天能T109滑盖智能锁产品说明书以PDF格式发布&#xff0c;适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开&#xff0c;先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能&#xff0c;再给出完整安装步骤&…

2026/10/22 10:40:08 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历&#xff1a;翻开源码注释、论文摘要或者一份专业报告&#xff0c;每个字都认识&#xff0c;连在一起却像天书。想找人聊聊&#xff0c;身边要么没人懂&#xff0c;要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型&#xff0c;几秒钟之后&a…

2026/10/21 9:31:32 阅读更多 →

日新闻

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/22 1:47:45 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/21 21:32:28 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/22 5:12:39 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:50:56 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:51:55 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 16:24:48 阅读更多 →