springBoot+itext处理富文本转pdf

该博客介绍了如何在SpringBoot项目中利用iText库将包含HTML的富文本转换为PDF文件。文章详细展示了所需的pom.xml依赖,并提供了处理中文字符、避免换行问题的方法。此外,还给出了一个名为Breaker.java的类,用于处理文本的断行逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

pom文件引入依赖

 <!-- itext5 start -->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.13.2</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext-asian</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf.tool</groupId>
            <artifactId>xmlworker</artifactId>
            <version>5.5.11</version>
        </dependency>
        <dependency>
            <groupId>org.xhtmlrenderer</groupId>
            <artifactId>flying-saucer-pdf-itext5</artifactId>
            <version>9.0.3</version>
        </dependency>
        <dependency>
            <groupId>org.xhtmlrenderer</groupId>
            <artifactId>core-renderer</artifactId>
            <version>R8</version>
        </dependency>
<!-- itext5 end -->

html转PDF方法

import com.itextpdf.text.pdf.BaseFont;
import com.lowagie.text.DocumentException;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;

import java.io.*;

/**
 * @auth zhang
 * @date 2021年06月06日:14:10
 */
public class HtmlToPdf {
    /**
     * 生成 PDF 文件-保存为流
     * @param out 输出流
     * @param html HTML字符串
     * @throws IOException IO异常
     * @throws DocumentException Document异常
     */
    public static InputStream createPDF(OutputStream out, String html) throws IOException, DocumentException {
        ITextRenderer renderer = new ITextRenderer();

        html=html.replace("&nbsp;","");
        html=html.replace("&shy;","");
        html=html.replace("&ldquo;","");
        html=html.replace("&rdquo;","");
        html=html.replaceAll("font-family:(.*?);","font-family: SimSun;");
        System.out.println("html:::"+html);
        renderer.setDocumentFromString(html);
        // 解决中文支持问题
        ITextFontResolver fontResolver = renderer.getFontResolver();

        if (System.getProperty("os.name").contains("Window")) {
            try {
                fontResolver.addFont("C:/Windows/Fonts/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
            } catch (com.itextpdf.text.DocumentException e) {
                e.printStackTrace();
            }
        } else {
            try {
                fontResolver.addFont("/usr/share/fonts/win/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
            } catch (com.itextpdf.text.DocumentException e) {
                e.printStackTrace();
            }
        }
        renderer.layout();
        try {
            renderer.createPDF(out);
        } catch (com.itextpdf.text.DocumentException e) {
            e.printStackTrace();
        }
        //转换流
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byteArrayOutputStream = (ByteArrayOutputStream) out;
        InputStream resultInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        return resultInputStream;
    }

    /**
     * 生成 PDF 文件-保存到本地
     * @param out 输出流
     * @param html HTML字符串
     * @throws IOException IO异常
     * @throws DocumentException Document异常
     */
    public static void createPDFByLocal(OutputStream out, String html) throws IOException, DocumentException {
        ITextRenderer renderer = new ITextRenderer();

        html=html.replace("&nbsp;","");
        html=html.replace("&shy;","");
        html=html.replace("&ldquo;","");
        html=html.replace("&rdquo;","");
        System.out.println("html:::"+html);
        renderer.setDocumentFromString(html);
        // 解决中文支持问题
        ITextFontResolver fontResolver = renderer.getFontResolver();

        if (System.getProperty("os.name").contains("Window")) {
            try {
                fontResolver.addFont("C:/Windows/Fonts/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
            } catch (com.itextpdf.text.DocumentException e) {
                e.printStackTrace();
            }

        } else {
            try {
                fontResolver.addFont("/usr/share/fonts/win/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
            } catch (com.itextpdf.text.DocumentException e) {
                e.printStackTrace();
            }
        }
        renderer.layout();
        try {
            renderer.createPDF(out);
        } catch (com.itextpdf.text.DocumentException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException, DocumentException {
        String fileName="D:\\text\\"+"测试"+System.currentTimeMillis()+".pdf";
        File file = new File(fileName);
        FileOutputStream outputStream = new FileOutputStream(file);
//        OutputStream outputStream=new ByteArrayOutputStream();
        String customHtml="<p style=\"text-align: center; margin: 0cm; font-size: 10.5pt; font-family: 'Times New Roman', serif;\" align=\"center\"><strong><span style=\"font-size: 22pt; font-family: 宋体; color: #c00000;\">&shy;&shy;&shy;&shy;&shy;Xx</span></strong><strong><span style=\"font-size: 22pt; font-family: 宋体; color: #c00000;\">测试一下</span></strong></p>\n" +
                "<p style=\"margin: 0cm 0cm 15.6pt; text-align: center; font-size: 10.5pt; font-family: 'Times New Roman', serif;\" align=\"center\"><strong><span style=\"font-size: 22pt; font-family: 宋体; color: #c00000;\">20xx</span></strong><strong><span style=\"font-size: 22pt; font-family: 宋体; color: black;\">XXXXXXXXXXXXXXXXXX书</span></strong></p>\n";
        customHtml=customHtml.replaceAll("font-family:(.*?);","font-family: SimSun;");
        String html = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<!DOCTYPE html[\n" +
                "<!ENTITY nbsp \" \">\n" +
                "]><html>\n" +
                        "<head>\n" +
                        "<style type=\"text/css\">\n" +
                        "body {\n" +
                        "\tfont-family: SimSun;\n" +
                        "}\n" +
                        "</style>\n" +
                        "</head>\n" +
                        "<body>\n" +
                        customHtml
                        +
                        "</body>\n" +
                        "</html>\n";


        createPDFByLocal(outputStream,html);

    }
}

处理不换行

创建包

新增Breaker.java类

package org.xhtmlrenderer.layout;

/*
 * Breaker.java
 * Copyright (c) 2004, 2005 Torbj�rn Gannholm,
 * Copyright (c) 2005 Wisconsin Court System
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 */

import org.xhtmlrenderer.css.constants.IdentValue;
import org.xhtmlrenderer.css.style.CalculatedStyle;
import org.xhtmlrenderer.render.FSFont;

/**
 * A utility class that scans the text of a single inline box, looking for the
 * next break point.
 *
 * @author Torbj�rn Gannholm
 */
public class Breaker {

    public static void breakFirstLetter(LayoutContext c, LineBreakContext context,
                                        int avail, CalculatedStyle style) {
        FSFont font = style.getFSFont(c);
        context.setEnd(getFirstLetterEnd(context.getMaster(), context.getStart()));
        context.setWidth(c.getTextRenderer().getWidth(
                c.getFontContext(), font, context.getCalculatedSubstring()));

        if (context.getWidth() > avail) {
            context.setNeedsNewLine(true);
            context.setUnbreakable(true);
        }
    }

    private static int getFirstLetterEnd(String text, int start) {
        int i = start;
        while (i < text.length()) {
            char c = text.charAt(i);
            int type = Character.getType(c);
            if (type == Character.START_PUNCTUATION ||
                    type == Character.END_PUNCTUATION ||
                    type == Character.INITIAL_QUOTE_PUNCTUATION ||
                    type == Character.FINAL_QUOTE_PUNCTUATION ||
                    type == Character.OTHER_PUNCTUATION) {
                i++;
            } else {
                break;
            }
        }
        if (i < text.length()) {
            i++;
        }
        return i;
    }

    public static void breakText(LayoutContext c,
                                 LineBreakContext context, int avail, CalculatedStyle style) {
        FSFont font = style.getFSFont(c);
        IdentValue whitespace = style.getWhitespace();

        // ====== handle nowrap
        if (whitespace == IdentValue.NOWRAP) {
            context.setEnd(context.getLast());
            context.setWidth(c.getTextRenderer().getWidth(
                    c.getFontContext(), font, context.getCalculatedSubstring()));
            return;
        }

        //check if we should break on the next newline
        if (whitespace == IdentValue.PRE ||
                whitespace == IdentValue.PRE_WRAP ||
                whitespace == IdentValue.PRE_LINE) {
            int n = context.getStartSubstring().indexOf(WhitespaceStripper.EOL);
            if (n > -1) {
                context.setEnd(context.getStart() + n + 1);
                context.setWidth(c.getTextRenderer().getWidth(
                        c.getFontContext(), font, context.getCalculatedSubstring()));
                context.setNeedsNewLine(true);
                context.setEndsOnNL(true);
            } else if (whitespace == IdentValue.PRE) {
                context.setEnd(context.getLast());
                context.setWidth(c.getTextRenderer().getWidth(
                        c.getFontContext(), font, context.getCalculatedSubstring()));
            }
        }

        //check if we may wrap
        if (whitespace == IdentValue.PRE ||
                (context.isNeedsNewLine() && context.getWidth() <= avail)) {
            return;
        }

        context.setEndsOnNL(false);

        String currentString = context.getStartSubstring();
        int left = 0;
//    int right = currentString.indexOf(WhitespaceStripper.SPACE, left + 1);
        int right = getStrRight(currentString, left);
        int lastWrap = 0;
        int graphicsLength = 0;
        int lastGraphicsLength = 0;

        while (right > 0 && graphicsLength <= avail) {
            lastGraphicsLength = graphicsLength;
            graphicsLength += c.getTextRenderer().getWidth(
                    c.getFontContext(), font, currentString.substring(left, right));
            lastWrap = left;
            left = right;
//      right = currentString.indexOf(WhitespaceStripper.SPACE, left + 1);
            right = getStrRight(currentString, left + 1);
        }

        if (graphicsLength <= avail) {
            //try for the last bit too!
            lastWrap = left;
            lastGraphicsLength = graphicsLength;
            graphicsLength += c.getTextRenderer().getWidth(
                    c.getFontContext(), font, currentString.substring(left));
        }

        if (graphicsLength <= avail) {
            context.setWidth(graphicsLength);
            context.setEnd(context.getMaster().length());
            //It fit!
            return;
        }

        context.setNeedsNewLine(true);

        if (lastWrap != 0) {//found a place to wrap
            context.setEnd(context.getStart() + lastWrap);
            context.setWidth(lastGraphicsLength);
        } else {//unbreakable string
            if (left == 0) {
                left = currentString.length();
            }

            context.setEnd(context.getStart() + left);
            context.setUnbreakable(true);

            if (left == currentString.length()) {
                context.setWidth(c.getTextRenderer().getWidth(
                        c.getFontContext(), font, context.getCalculatedSubstring()));
            } else {
                context.setWidth(graphicsLength);
            }
        }
        return;
    }

    private static boolean isChinese(char c) {
        Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
        if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
                || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
                || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
                || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION
                || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION
                || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS) {
            return true;
        }
        return false;
    }

    private static int getStrRight(String s, int left) {
        if (left >= s.length())
            return -1;
        char[] ch = s.toCharArray();
        for (int i = left; i < ch.length; i++) {
            if (isChinese(ch[i]) || ' ' == ch[i]) {
                return i == 0 ? i + 1 : i;
            }
        }
        return -1;
    }

}

ps:如果遇到大小写等生成失败的错误时,在标签上添加:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">```

<think>嗯,用户这次想了解如何搭建一个标书编写系统,并明确提到了需要技术架构、工具和平台推荐。从对话历史看,用户之前可能已经接触过一些招投标系统的资料(引用1-3),但那些引用主要是描述现有系统的功能,而非搭建方法。用户的需求其实很清晰——不是想了解现成系统怎么用,而是想自己从零开始构建一套标书编写系统。这显然是个企业级需求,可能是用户所在公司需要自建招投标平台。考虑到标书涉及复杂的格式要求和合规性检查,系统设计需要特别注重文档处理能力和流程管控。关于标书编写系统的关键点需要梳理清楚:首先是核心模块应当包括模板管理(应对不同招标要求)、协作编辑(标书通常由多人完成)、合规校验(避免废标)这三个基本功能。技术选型上,文档处理最好选POI或PDFBox这种成熟库——直接生成Word/PDF能避免格式错乱。在架构设计方面,微服务比较合适,比如把模板管理、权限控制拆成独立服务,这样后期扩展评标模块也方便。用户提到过的SpringBoot确实是个好选择,搭配Redis做缓存能提升文档生成速度。用户隐藏的痛点可能是多人协作时的版本混乱问题,所以得推荐GitLab这类带版本控制的工具。测试环节需要重点考虑,毕竟标书错一个字可能导致流标,应该建议用户用Selenium做自动化的完整性检查。安全也不能忽视,尤其是投标文件包含敏感信息。系统需要SSL加密传输,商业项目最好上Vault管理密钥。用户提到过“技术方案模板”(引用1),说明需要预设条款库功能,这块可以用Elasticsearch实现智能推荐,减少重复劳动。另外引用3强调资质文件管理,系统需要集成OCR识别证件信息并自动归档。这些增强功能点可以作为下一步迭代建议。</think> 基于标书编写的核心需求(格式规范、多人协作、资质管理、版本控制),以下是完整的系统搭建方案: --- ### 一、技术架构方案(推荐组合) **1. 基础架构** ```mermaid graph TD A[前端] --> B[业务层] B --> C[数据层] C --> D[存储系统] ``` - **前端**:Vue.js/LayUI(响应式界面) + CKEditor(富文本编辑) - **业务层**:Spring Boot(主框架) + Shiro(权限控制) - **数据层**:MyBatis Plus + MySQL(结构化数据) + MongoDB(非结构化文档) - **存储**:MinIO(文件存储) + Redis(缓存加速) **2. 核心功能模块** | 模块 | 技术方案 | 关键能力 | |-------------|-----------------------------------|----------------------------| | 模板引擎 | Apache POI + Freemarker | Word/PDF动态生成 | | 协同编辑 | Operational Transformation算法 | 多人实时编辑冲突解决 | | 版本控制 | GitLab集成 + 自定义版本树 | 历史追溯/差异对比 | | OCR识别 | Tesseract+OpenCV | 资质文件自动识别 | **3. 安全架构** - HTTPS + JWT令牌验证 - 文件水印 + 操作审计日志 - 阿里云KMS密钥管理(敏感数据加密) --- ### 二、工具链推荐 1. **开发工具** - IDE:IntelliJ IDEA(后端)/ VSCode(前端) - 接口测试:Postman + Swagger 2. **DevOps支撑** ```bash # 部署示例(Docker化部署) docker compose up -d mysql redis minio docker build -t bid-system . ``` 3. **文档处理库** - 表格处理:Apache POI(Java) - PDF生成:iText 7(商业授权)/ Apache PDFBox - 格式换:LibreOffice Headless模式 --- ### 三、现成平台方案 | 类型 | 推荐方案 | 适用场景 | |------------|-----------------------------------|-------------------------| | SaaS | 泛微OA招投标模块 | 快速部署的中大型企业 | | 开源框架 | OpenProposal(GitHub 2.4k star) | 二次开发需求 | | 云服务 | 阿里云「智能招投标解决方案」 | 政府/国企项目 | --- ### 四、关键实现示例(标书生成) ```java // Spring Boot标书生成核心逻辑 @PostMapping("/generate") public ResponseEntity<byte[]> generateBid(@RequestBody BidData data) { // 1. 获取模板 File template = templateService.getTemplate(data.getBidType()); // 2. 数据填充 XWPFDocument doc = new XWPFDocument(new FileInputStream(template)); TableRenderPolicy.render(doc, data.getCompanyQualification()); // 3. 生成加密PDF OutputStream out = new ByteArrayOutputStream(); PdfConverter.getInstance().convert(doc, out, PdfOptions.create().watermark("CONFIDENTIAL")); return ResponseEntity.ok() .header("Content-Disposition", "attachment; filename=bid.pdf") .body(((ByteArrayOutputStream) out).toByteArray()); } ``` --- ### 五、实施路线图 1. **基础搭建**(2周) - 安装JDK17/MySQL8/MinIO - 初始化Spring Boot项目 2. **核心功能**(6周) - 模板管理模块(Freemarker集成) - 版本控制功能(GitLab API对接) 3. **增强功能**(4周) - 资质OCR识别(Tesseract训练) - 智能错漏检查(规则引擎Drools) --- ### 六、风险规避建议 1. **格式兼容性** - 严格遵循《GBT 9704-2012 公文格式》 - 使用``docx4j``替代Apache POI处理复杂表格 2. **合规性要求** - 国密算法支持(SM4加密招标文件)[^3] - 投标截止时间自动熔断机制
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值