SpringBoot+Freemarker模板生成Word:告别POI手拼报表合同
简介面向后端Java开发者的Spring Boot整合FreeMarker生成Word文档示例工程解决在项目中按模板动态输出报告、合同等文档并将图片嵌入Word的需求。资源包共30个文件涵盖Java源码与编译后的class文件、FreeMarker的ftl模板、XML配置、properties和yml配置以及pom.xml与Maven Wrapper等构建文件整体仅77KB结构紧凑下载后可快速对照学习。核心代码包含WordService服务类、模板渲染逻辑与自定义HtmlToWordConverter转换器其中通过cid方式引用图片并以内联对象写入docx的处理步骤尤其值得参考能帮助绕过常见的图片丢失问题。模板本身采用HTML结构便于通过变量控制标题、正文内容工程还保留了target目录和已编译的class文件可对照源码查看编译结果。目前已有1638人学习或下载适合希望掌握Spring Boot、FreeMarker与Apache POI三者集成方式的开发者借助该Demo可打通从模板渲染到Word导出的完整链路并在此基础上扩展表格、样式等更复杂的文档需求。1. 报表和合同别再用 POI 手拼了SpringBootFreemarker 模板生成 Word 的正确打开方式实际业务里导出合同、验收单、巡检报告这类带固定样式的 Word 文档时很多团队第一反应是用 POI 一行一行去拼段落和表格。拼到后期你就会发现模板里的某个字号或表格宽度一改Java 代码就得跟着挪改完还得担心漏了哪个单元格。换成 Spring Boot Freemarker 生成 Word 的方案思路会彻底不一样把 Word 另存成 XML 格式把它改造成一份 ftl 模板后端只负责往模板里填数据模板归模板代码归代码。这样做的好处是模板可以独立维护表格循环、图片占位都能在模板层处理适合报表导出、自定义合同这类需求也适合被 XWPF API 折腾过的开发者快速切换思路。2. 模板设计原理与转换流程为什么 docx 要转成 XML 再改造成 FTL2.1 选 Word 2003 XML 作为中间格式的技术原因docx 文件本质上是一个 zip 压缩包里面装着多份 XML、样式表和图片资源。Freemarker 模板引擎只认纯文本如果直接拿一个 zip 包做文本替换必须先解压、再修改 document.xml、最后重新打包这个过程的复杂度并不比用 POI 写代码低。Word 2003 XML 则把整份文档平铺成一个 XML 文件段落、文字、样式、表格、页眉页脚全都按顺序落在一个文本文件里正好是 Freemarker 最喜欢的输入形状。另存成 Word 2003 XML 也有代价。文档里的宏、域代码、复杂控件在转换时会丢失或被打散所以这个方案更适合样式固定、以变量替换和循环填充为主的文档。如果你导出的文档包含大量动态书签或域建议先评估一下模板改造的边界不要硬上。另存的时候还要注意不要把格式选成单一网页 HTML。网页格式里会塞满无意义的 class 和样式层叠占位符会被拆分到好几个标签节点里后面替换时不是漏改就是错改。Word 2003 XML 的标签结构更稳定普通文字一般落在w:rw:t节点内定位和替换都很直接。2.2 转换前的文档整理占位符规范与编码检查打开 Word 另存为 XML 之前先做三件事。第一把文档内容定为终稿。别把待替换的文字直接写在正文里否则搜索替换时容易误伤正式内容。建议先用显眼的占位符文本比如【日期】、【客户名称】、【金额大写】。第二清理修订与批注。如果文档经过多人编辑先在审阅模式里接收全部修订并删除批注。带着修订痕迹导出的 XML 会多出w:ins、w:del这类节点渲染时会让段落结构多出冗余层级。第三确认编码是 UTF-8。另存为 XML 时会写入?xml version1.0 encodingUTF-8?声明后续用文本编辑器修改模板时也要保持 UTF-8否则渲染出来的中文很容易乱码。整理完文档之后建议先画一张占位符和数据字段的对应表避免模板里写的名称和数据模型里的 key 对不上。Freemarker 做变量替换时不会给拼写错误兜底一旦 key 不匹配轻则输出空串重则直接抛异常中断渲染。Word 占位符模板写成数据类型实际使用位置【日期】${reportDate}String正文首段或页眉【客户名称】${customerName}String合同首段【项目清单】${itemList}List表格数据行循环这张表也是后端和模板维护者之间的契约。后面启动渲染时只要对照这张表把数据模型填完整模板基本不用反复试错。2.3 XML 层面的 FTL 改造占位符、循环与转义规则用文本编辑器打开另存出来的 XML能看到整份文档的标签结构。下面是一段典型的段落文本w:p w:r w:t报告日期/w:t /w:r w:r w:t2024年01月01日/w:t /w:r /w:p改成 Freemarker 模板之后直接把日期文字替换成变量w:p w:rw:t报告日期/w:t/w:r w:rw:t${reportDate}/w:t/w:r /w:p这里有一条关键约束${}必须落在完整的w:rw:t结构里。如果你把占位符贴到w:r标签中间办公软件打开时会出现文本断行甚至内容丢失。如果数据里可能带着、这类特殊字符模板里要写成${customerName?xml}让 FreeMarker 在输出前做 XML 转义否则生成的文件会被判定为损坏。表格循环是另一个重点。Word 2003 XML 中的表格由w:tbl包裹行是w:tr单元格是w:tc。先做出表头再在数据行外层套#listw:tbl w:tr w:tcw:pw:rw:t序号/w:t/w:r/w:p/w:tc w:tcw:pw:rw:t设备名称/w:t/w:r/w:p/w:tc w:tcw:pw:rw:t巡检结果/w:t/w:r/w:p/w:tc /w:tr #list itemList as item w:tr w:tcw:pw:rw:t${item.seq}/w:t/w:r/w:p/w:tc w:tcw:pw:rw:t${item.deviceName}/w:t/w:r/w:p/w:tc w:tcw:pw:rw:t${item.status}/w:t/w:r/w:p/w:tc /w:tr /#list /w:tbl注意/#list闭合标签必须放在整行w:tr之后不能只包住某个单元格。凡是循环后出现表格错位的情况大概率是闭合标签位置放错了。模板里也可以嵌套#if做条件显示但一个#if最好只管一个单元格内容不要跨单元格写判断。提示改造 XML 时建议保留一份原始 XML 备份。如果模板改坏拿原始文件重新替换比在几千行标签里逐个排查要省事得多。模板文件命名成 report.ftl放到 src/main/resources/templates 目录后面 Spring Boot 配置会直接消费这个目录。3. Spring Boot 集成实现从依赖配置到渲染下载的完整链路3.1 Maven 依赖与 Freemarker 配置使用 Spring Boot 时不需要手工引入 Freemarker 核心包直接加官方 starter 就够了。pom.xml 里加入两个依赖dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-freemarker/artifactId /dependency /dependenciesspring-boot-starter-freemarker已经把 FreeMarker 引擎和 Spring Boot 的自动配置打包好了。版本统一由父工程管理不需要写具体 version避免升级时连带改模板语法。接着在 application.yml 里配置模板加载路径和字符集spring: freemarker: template-loader-path: classpath:/templates suffix: .ftl charset: UTF-8 cache: false settings: classic_compatible: true逐个说明参数的作用template-loader-path模板根目录对应 src/main/resources/templates。suffix模板文件后缀调用 getTemplate(report.ftl) 时要写全文件名。charset模板读取编码必须和文件实际保存编码一致。cache开发阶段设 false避免每次改模板都要重启。生产环境记得改回 true。classic_compatible设为 true 后模板里漏传的变量不会直接抛异常而是输出空字符串。这个开关救过不少只改了模板字段却忘了同步模型的现场。3.2 数据模型组装用 Map 而不是 POJOFreemarker 渲染的本质是拿数据模型和模板变量名做绑定数据模型可以是任意 Java 对象。这里更推荐嵌套 MapString, Object因为模板版本更新频繁时改模板不用改 Java 类模型可以按接口返回结构灵活组装。MapString, Object model new HashMap(); model.put(reportDate, 2024-12-18); model.put(customerName, 某公司); ListMapString, Object itemList new ArrayList(); for (int i 1; i 5; i) { MapString, Object item new HashMap(); item.put(seq, i); item.put(deviceName, 巡检设备- i); item.put(status, i % 2 0 ? 正常 : 待复检); itemList.add(item); } model.put(itemList, itemList);三处对应关系要特别注意变量 reportDate、customerName、itemList 必须和 FTL 模板里的 key 完全一致itemList 里的每一项都是一个 Map模板循环里用 item.seq 取下层字段循环变量名可以随便起但优先叫 item读模板的人一眼能看出意图。这段代码里故意不用实体类。常见误用是把数据库实体整个丢进 model模板里直接绑实体字段实体属性一改模板输出就静默少值排查起来效率很低。3.3 渲染方法封装一次完整的渲染与下载链路有了模板和 model写一个公用的导出服务Service public class WordExportService { private final freemarker.template.Configuration freemarkerConfig; public WordExportService(freemarker.template.Configuration freemarkerConfig) { this.freemarkerConfig freemarkerConfig; } public void renderWord(String templateName, MapString, Object model, HttpServletResponse response) throws Exception { Template template freemarkerConfig.getTemplate(templateName); response.reset(); response.setContentType(application/msword); response.setCharacterEncoding(UTF-8); String fileName URLEncoder.encode(巡检报告_ LocalDate.now(), UTF-8); response.setHeader(Content-Disposition, attachment; filename\ fileName .doc\); try (Writer writer new OutputStreamWriter( response.getOutputStream(), StandardCharsets.UTF_8)) { template.process(model, writer); writer.flush(); } } }controller 调用示例RestController RequestMapping(/export) public class ExportController { private final WordExportService exportService; public ExportController(WordExportService exportService) { this.exportService exportService; } GetMapping(/report) public void downloadReport(HttpServletResponse response) throws Exception { MapString, Object model new HashMap(); model.put(reportDate, LocalDate.now().toString()); exportService.renderWord(report.ftl, model, response); } }三个细节要特别留意下载文件名放在 Content-Disposition 头里时中文要先执行 URLEncoder.encode否则浏览器端会得到乱码文件名。Content-Type 用 application/msword。文件实际内容是 XML但这个类型会让浏览器弹出下载框并由办公软件关联打开。渲染方法直接抛出 Exception。模板加载失败或变量不匹配时调用方能尽快看到失败原因而不是在 service 里吞掉异常后返回一个空文件。渲染成功后可以用文本编辑器打开下载到的 doc 文件确认第一行还是 XML 声明。这个习惯能挡掉很多“生成成功但打不开”的问题。4. 模板渲染常见问题排查表格、图片、编码与模板缓存四个坑4.1 表格行循环后只剩表头数据行消失或错位现象模板里写了#list运行后下载的文件用办公软件打开表格只有表头一行下面数据全没了有些场景还会弹窗提示文件损坏需要恢复。原因最常见的是没有把/#list闭合标签放在整行w:tr之后。Freemarker 渲染时只处理被#list和/#list夹住的片段如果这段范围不完整生成的 XML 就是一个残缺的w:tbl结构。另一个隐蔽原因是从 Word 复制的行内容在 XML 里夹着多余的空文本节点这些节点被 Freemarker 输出成空白行破坏了表格行的连续性。解决回到原始 XML 模板从w:tr开始选中一整行先复制到记事本确认w:tr和/w:tr闭合完整再套进#list。如果发现行内夹着类似w:r/w:r的空段落可以整体删掉。循环体外不要放任何多余的注释或文本标记避免文本节点插到表格结构里。4.2 图片替换成红叉或直接空白现象模板里放好的静态图片能正常导出一旦想用代码动态替换图片比如合同里盖电子章渲染结果却是红叉或整块空白。原因Word 2003 XML 中的图片不是简单的一张图片文件二进制数据通常放在w:binData标签里再通过v:imagedata r:idrIdX关联。直接替换文本节点时如果只改了 rId 引用却没有替换w:binData里的内容渲染后图片数据还是旧图。解决在模板里把图片对应的w:binData内容改成${imageBase64}渲染前把图片文件读成 base64 字符串放进 modelprivate String toBase64(byte[] imageBytes) { return Base64.getEncoder().encodeToString(imageBytes); }base64 字符串很长模板里这一行不要加入人工换行。渲染后如果有空白先确认传入的 base64 是否完整再确认v:imagedata的 rId 值和对应的 binData 名称是否一致。4.3 中文乱码与 XML 特殊字符破坏文档结构现象模板和代码都标了 UTF-8运行后中文变成一串问号有些模板能渲染成功但数据里带“设备AB”这种特殊字符时渲染直接中断。原因中文乱码多半是模板文件本身不是 UTF-8 编码。用系统记事本另存 XML 时默认编码可能是 ANSISpring Boot 读取模板时按 UTF-8 解码中文自然错乱。特殊字符问题是因为 XML 文本节点中出现了未转义的或直接把文档结构打断了。解决打开模板文件后先确认右下角编码是 UTF-8不带 BOM 更好。如果模板要输出用户填写的文本使用?xml内建函数强制转义w:t${customerName?xml}/w:t?xml会把、、等字符转成 XML 实体。另外循环里做#if item.status 正常这种判断时尽量把判断结果拆成独立的布尔字段避免类型不匹配带来的模板异常。4.4 改了模板不生效或启动时报找不到模板现象本地改了 report.ftl刷新页面导出内容还是旧版本。另一些情况是启动后调用导出接口控制台提示找不到 report.ftl。原因不生效是模板缓存问题。Spring Boot 默认会缓存模板改文件后不重启应用template 对象还是旧内容。找不到模板则是目录不对模板没有随构建复制到 classpath或者 template-loader-path 配置与实际目录不一致。解决开发阶段在 application.yml 里把 spring.freemarker.cache 设为 false再不行就把spring.freemarker.settings.template_update_delay0一起设上。项目日志里显示的模板路径如果指向 target/classes核对一下实际文件是否在模板目录下。执行mvn clean compile重新构建能同步大部分资源文件不一致问题。提示template_update_delay0 只建议在开发环境使用上生产前必须把 cache 恢复为 true否则每次请求都会重新读磁盘并发高了响应时间会明显变差。5. 验证与进阶用法给导出的文档加一道防御性自检在把导出功能交给测试之前我会做三个动作。第一用文本编辑器打开下载回来的 doc 文件确认第一行是?xml version1.0 encodingUTF-8?只要看到这一行就说明渲染没有把错误堆栈塞进文件。第二把同一个文件拷贝到办公软件里打开看表格、图片、页边距是否都保留。第三在模板里临时改一个标题文本重新导出比较两次文件大小和内容差异。进一步做文件存档时可以把渲染结果从 response 里拿出来存成字节数组再转存到文件服务器或作为邮件附件ByteArrayOutputStream bos new ByteArrayOutputStream(); try (Writer writer new OutputStreamWriter(bos, StandardCharsets.UTF_8)) { template.process(model, writer); } byte[] docBytes bos.toByteArray(); // 这里接文件上传或附件逻辑这段代码就是把第 3 章里的渲染逻辑从响应输出挪到内存输出改动很小。存下来的 byte[] 可以继续写本地文件、上传对象存储或者打包发送。这里有一个隐藏参数模板内嵌图片越大整份文档体积越大生成速度也会被明显拖慢。建议导出前把图片尺寸压缩到合理范围实测对接口响应时间影响很大。从那以后我每次提交 Word 导出功能都强制走一遍“文本编辑器验证 XML 头、办公软件打开、改模板再对比导出”的流程三个动作全过才敢发测试。宁可多花两分钟做检查也不想等测试同事拿着旧模板反问为什么内容没更新。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

JDK 1.8 下载安装全攻略:从选型到环境变量配置避坑指南

JDK 1.8 下载安装全攻略:从选型到环境变量配置避坑指南

简介:Java JDK 1.8完整开发工具包,面向需要搭建Java 8开发环境或学习该经典版本的开发者与初学者。资源内置标准JDK组件,包括编译器、运行虚拟机、核心类库以及常用命令行工具,能够满足企业级应用开发、课程实验、项目维护等多种场…

2026/10/17 0:40:04 阅读更多 →
google cache接口实战:缓存快照原理、批量抓取与降级方案

google cache接口实战:缓存快照原理、批量抓取与降级方案

1. 从一个被忽视的接口说起:google cache接口到底能做什么第一次接触“google cache接口”这个概念,是在做一个内容聚合类项目的时候。当时的需求很朴素:给定一批URL,需要快速拿到这些页面的历史快照,用来做内容比对和…

2026/10/17 0:40:04 阅读更多 →
Electron入门实战:从ZIP解压到可安装桌面应用

Electron入门实战:从ZIP解压到可安装桌面应用

简介:本资源是一份基于 Electron 框架的跨平台桌面应用开发入门实践包,面向前端开发者、全栈初学者及希望快速掌握桌面端开发的 Web 技术学习者。它以 JavaScript、HTML 和 CSS 为核心技术栈,帮助用户摆脱原生平台限制,实现一套代…

2026/10/17 0:40:04 阅读更多 →

最新新闻

从数字分身到AI员工:企业级Agent的可靠、可控与可审计实践

从数字分身到AI员工:企业级Agent的可靠、可控与可审计实践

OpenClaw 最近确实带起了一波“数字分身”热潮。我前后折腾了两周,最大的感受是:让 AI 模仿我的知识库、口吻和日常动作,确实能把不少琐碎工作自动化,还能用自然语言指挥它干活。但个人版玩得越爽,越能感觉到一个反差—…

2026/10/17 4:04:00 阅读更多 →
多轮强化学习训练LLM智能体:从环境设计到长程决策实战

多轮强化学习训练LLM智能体:从环境设计到长程决策实战

如果你已经试过让一个大模型智能体去完成"查资料→对比→写报告→发邮件"这样四段式的任务,大概率会遇到同一个现象:前几步做得漂漂亮亮,到了第三步开始跑偏,最后一步直接给你一个格式全错的附件。我去年在重构内部智能…

2026/10/17 4:04:00 阅读更多 →
本地部署Openclaw接入飞书:搭建AI代理自动生成PPT的完整实战

本地部署Openclaw接入飞书:搭建AI代理自动生成PPT的完整实战

折腾了一个多星期,总算是把本地部署的Openclaw和飞书串起来了。现在团队群里的PPT汇报基本都被它包了,大家只需要在群里喊一句“龙虾,帮我做个季度汇报PPT,8页,市场风格”,过个两三分钟,一个像模…

2026/10/17 4:04:00 阅读更多 →
工业智能体架构设计与落地实操:从感知决策到边缘协同

工业智能体架构设计与落地实操:从感知决策到边缘协同

1. 工业智能体到底是什么,为什么突然成了香饽饽工业智能体这个词,最近一两年在制造业圈子里被提得越来越多。我最早接触这个概念是在一次工厂数字化改造的交流会上,当时有个做产线优化的朋友跟我说,他们厂里上了一套系统&#xff…

2026/10/17 4:04:00 阅读更多 →
基于CNN的图像分类在杏仁损伤检测中的应用与工程实现

基于CNN的图像分类在杏仁损伤检测中的应用与工程实现

每年到毕设季,总有人跑来找我,说想做个深度学习视觉方向的项目。与其随大流做猫狗分类、垃圾分拣这种已经被做过无数遍的题目,不如挑一个看起来冷门但工程链条完整、能讲清楚故事的课题。这次我带的这个题目就是这样来的——基于CNN的图像分类…

2026/10/17 4:04:00 阅读更多 →
Python计算机二级题库全解析:考纲、刷题技巧与备考误区

Python计算机二级题库全解析:考纲、刷题技巧与备考误区

简介:面向全国计算机二级Python备考人群,这份题库将近年常考知识点与真题题型整理为可直接刷练的练习集,覆盖选择题、操作题等常见考核模块,并配套参考答案与解析,帮助考生在考前快速定位薄弱环节、掌握得分要点。压缩…

2026/10/17 4:03:00 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 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/16 21:54:38 阅读更多 →
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/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练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/16 14:23:36 阅读更多 →