itextpdf7 使用之 html转pdf,生成目录可跳转、添加页眉页脚

这篇具有很好参考价值的文章主要介绍了itextpdf7 使用之 html转pdf,生成目录可跳转、添加页眉页脚。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

最近有个需求,生成信用报告。

需求:

1、生成pdf有页眉页脚
2、生成目录
3、目录加锚点可跳转。

难点:

1、生成的目录不能实时读取页码
2、目录是后生成的,属于两份pdf拼接的,不能添加锚点跳转

思路:

1、freemaker进行html页面布局及动态变量替换
2、生成一份pdf文档,用于关键字查询,获取所在页码
3、再生成一份目录+内容的pdf,目录的页码从刚生成的文档中查询,由于此文档目录和内容是同一个文档,所以可以添加锚点

实现:

1、引入 freemaker

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>

2、freemaker 默认 文件路径是 resources/tempates
itext7 html转换为pdf生成页码、页眉、页脚,java,html,pdf,前端
3、读取freemaker页面布局及变量替换

	Map<String,Object> appendix = new HashMap<>();
	appendix.put("iprTrademarkList", iprTrademarkList);
	appendix.put("iprPatentList", iprPatentList);

	Template  appendixtemp = configurer.getConfiguration().getTemplate("risk_appendix.html");
	content += FreeMarkerTemplateUtils.processTemplateIntoString(appendixtemp, appendix);

4、html转为pdf,添加页眉页脚

	public void html2Pdf(String content, String outPath, String type) {
		try {
			BufferedOutputStream outputStream = FileUtil.getOutputStream(outPath);
			if ("head".equals(type) || "content".equals(type)) { // 封面、临时content文件   无页眉,无水印,无页脚
				PdfUtils.convertHtmlToPdf(content, reportLogoUrl, "", "", false,PageSize.A4, outputStream);
			} else if ("directAndContent".equals(type)) {  // 目录+内容  有页眉、有水印,有页脚
				PdfUtils.convertHtmlToPdf(content, reportLogoUrl, reportHeadText, reportWaterText, true,PageSize.A4, outputStream);
			} else { // 说明、概览     有页眉、有水印,无页脚
				PdfUtils.convertHtmlToPdf(content, reportLogoUrl, reportHeadText, reportWaterText, false,PageSize.A4, outputStream);
			}
		} catch (Exception e) {
			System.out.println("生成模板内容失败"+e.fillInStackTrace());
		}
	}

/**
 * Itext7转换pdf工具类
 */
@Slf4j
public class PdfUtils {

    // 字体文件路径
    private static final String fontPath = "pdf/font/MSYH.TTF";
    // 字体文件 列表
    private static final Set<String> fontSet = new HashSet<>();

    static {
		ClassPathResource classPathResource = new ClassPathResource(fontPath);
        fontSet.add(classPathResource.getPath());
    }
	
	public static void convertHtmlToPdf(String htmlStr, String logoUrl, String headerText, String waterMark, boolean hasFooter, PageSize pageSize, OutputStream outputStream) {
		convertHtmlToPdf(new ByteArrayInputStream(htmlStr.getBytes(StandardCharsets.UTF_8)), logoUrl, headerText, waterMark, hasFooter, pageSize, fontSet, outputStream);
	}

    /**
     * html转 pdf
     * @param inputStream  输入流
     * @param headerText    页眉
     * @param waterMark    水印
     * @param pageSize 纸张大小
     * @param fontPathList  字体路径列表,ttc后缀的字体需要添加<b>,0<b/> 例:C:\front\msyh.ttc,0
     * @param outputStream 输出流
     */
    public static void convertHtmlToPdf(InputStream inputStream, String logoUrl, String headerText, String waterMark, boolean hasFooter,PageSize pageSize,
                                        Set<String> fontPathList, OutputStream outputStream) {

        // 验空
        Objects.requireNonNull(inputStream);
        Objects.requireNonNull(outputStream);

        PdfWriter pdfWriter = new PdfWriter(outputStream);
        PdfDocument pdfDocument = new PdfDocument(pdfWriter);
        //设置纸张大小
        pdfDocument.setDefaultPageSize(pageSize);

        //添加中文字体支持
        ConverterProperties properties = new ConverterProperties();
        FontProvider fontProvider = new FontProvider();

        //添加自定义字体,例如微软雅黑
        if (CollectionUtils.isNotEmpty(fontPathList)) {
            fontPathList.forEach(e -> fontProvider.addFont(e, PdfEncodings.IDENTITY_H));
        }

        PdfFont pdfFont = fontProvider.getFontSet()
                .getFonts()
                .stream()
                .findFirst()
                .map(fontProvider::getPdfFont)
                .orElse(null);


			// 添加页眉
			pdfDocument.addEventHandler(PdfDocumentEvent.START_PAGE, new PdfHeaderMarkerEventHandler(pdfFont, headerText, logoUrl));
			// 添加水印
			if (StringUtils.isNotBlank(waterMark)) {
				pdfDocument.addEventHandler(PdfDocumentEvent.INSERT_PAGE, new PdfWaterMarkEventHandler(pdfFont, waterMark));
			}
			if (hasFooter) {
				// 添加页脚
				pdfDocument.addEventHandler(PdfDocumentEvent.END_PAGE, new PdfPageMarkerEventHandler(pdfFont));
			}
        properties.setFontProvider(fontProvider);
        // 读取Html文件流,查找出当中的&nbsp;或出现类似的符号空格字符
        inputStream = readInputStrem(inputStream);

        try {
            // 生成pdf文档
            HtmlConverter.convertToPdf(inputStream, pdfDocument, properties);
        } catch (IOException e) {
            e.printStackTrace();
            log.error("错误信息:pdf转换失败{}", e.getMessage());
        } finally {
            try {
                pdfWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            pdfDocument.close();
        }
    }

    /**
     * 读取HTML 流文件,并查询当中的&nbsp;或类似符号直接替换为空格
     *
     * @param inputStream 输入流
     * @return 去掉特殊标记的输入流
     */
    private static InputStream readInputStrem(InputStream inputStream) {
        // 定义一些特殊字符的正则表达式 如:
        String regEx_special = "\\&[a-zA-Z]{1,10};";
        try(ByteArrayOutputStream baos = new ByteArrayOutputStream();) {
            // 创建缓存大小
            byte[] buffer = new byte[1024]; // 1KB
            // 每次读取到内容的长度
            int len = -1;
            // 开始读取输入流中的内容
            while ((len = inputStream.read(buffer)) != -1) { //当等于-1说明没有数据可以读取了
                baos.write(buffer, 0, len);   //把读取到的内容写到输出流中
            }
            // 把字节数组转换为字符串 设置utf-8字符编码
            String content = baos.toString(String.valueOf(StandardCharsets.UTF_8));
            // 关闭输入流和输出流
            inputStream.close();
            // 判断HTML内容是否具有HTML的特殊字符标记
            Pattern compile = Pattern.compile(regEx_special, Pattern.CASE_INSENSITIVE);
            Matcher matcher = compile.matcher(content);
            String replaceAll = matcher.replaceAll("");
            // 将字符串转化为输入流返回
            return getStringStream(replaceAll);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("错误信息:pdf字符串格式化特殊字符失败{}", e.getMessage());
            return null;
        }
    }

    /**
     * 将一个字符串转化为输入流
     * @param sInputString 字符串
     * @return 字符串对应的输入流
     */
    public static InputStream getStringStream(String sInputString) {
        if (sInputString != null && !sInputString.trim().equals("")) {
            try {
                return new ByteArrayInputStream(sInputString.getBytes(StandardCharsets.UTF_8)); // 设置utf-8字符编码
            } catch (Exception e) {
                e.printStackTrace();
                log.error("错误信息:pdf字符串转输入流失败,{}", e.getMessage());
            }
        }
        return null;
    }


	/**
	 * 将给定List集合中的pdf文档,按照顺序依次合并,生成最终的目标PDF文档
	 *
	 * @param pdfPathLists 待合并的PDF文档路径集合,可以是本地PDF文档,也可以是网络上的PDF文档
	 * @param destPath     目标合并生成的PDF文档路径
	 */
	public static void mergeMultiplePdfs(List<String> pdfPathLists, String destPath) {
		try {
			int size = pdfPathLists.size();
			byte[] pdfData = getPdfBytes(pdfPathLists.get(0));
			for (int i = 1; i < size; i++) {
				pdfData = mergePdfBytes(pdfData, getPdfBytes(pdfPathLists.get(i)));
			}
			if (pdfData != null) {
				FileOutputStream fis = new FileOutputStream(destPath);
				fis.write(pdfData);
				fis.close();
			}
		} catch (Exception e) {
			log.error("合并PDF异常:", e);
		}
	}

	/**
	 * 基于内存中的字节数组进行PDF文档的合并
	 * @param firstPdf 第一个PDF文档
	 * @param secondPdf 第二个PDF文档
	 */
	private static byte[] mergePdfBytes(byte[] firstPdf, byte[] secondPdf) throws IOException {
		if (firstPdf != null && secondPdf != null) {
			// 创建字节数组,基于内存进行合并
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			PdfDocument destDoc = new PdfDocument(new PdfWriter(baos));
			// 合并的pdf文件对象
			PdfDocument firstDoc = new PdfDocument(new PdfReader(new ByteArrayInputStream(firstPdf)));
			PdfDocument secondDoc = new PdfDocument(new PdfReader(new ByteArrayInputStream(secondPdf)));
			// 合并对象
			PdfMerger merger = new PdfMerger(destDoc);
			merger.merge(firstDoc, 1, firstDoc.getNumberOfPages());
			merger.merge(secondDoc, 1, secondDoc.getNumberOfPages());
			// 关闭文档流
			merger.close();
			firstDoc.close();
			secondDoc.close();
			destDoc.close();
			return baos.toByteArray();
		}
		return null;
	}

	/**
	 * 将pdf文档转换成字节数组
	 * @param pdf PDF文档路径
	 * @return 返回对应PDF文档的字节数组
	 */
	private static byte[] getPdfBytes(String pdf) throws Exception {
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		InputStream is = Files.newInputStream(Paths.get(pdf));

		byte[] data = new byte[2048];
		int len;
		while ((len = is.read(data)) != -1) {
			out.write(data, 0, len);
		}
		return out.toByteArray();
	}

	/**
	 * 远程文件转化为字节
	 * @param pdf
	 * @return
	 * @throws Exception
	 */
	public static byte[] getRemotePdfBytes(String pdf) throws Exception {
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		URL url = new URL(pdf);

		InputStream is =url.openStream();

		byte[] data = new byte[2048];
		int len;
		while ((len = is.read(data)) != -1) {
			out.write(data, 0, len);
		}
		return out.toByteArray();
	}
}
/**
 * 页眉实现类
 */
public class PdfHeaderMarkerEventHandler implements IEventHandler {

    /**
     * pdf字体
     */
    private final PdfFont pdfFont;
    /**
     * 页眉显示
     */
    private final String title;

	private final String logoUrl;

    public PdfHeaderMarkerEventHandler(PdfFont pdfFont, String title, String logoUrl) {
        this.pdfFont = pdfFont;
        this.title = title;
        this.logoUrl = logoUrl;
    }

	@Override
	public void handleEvent(Event event) {

		if (StringUtils.isEmpty(title)) return;

		PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
		PdfDocument pdf = docEvent.getDocument();
		PdfPage page = docEvent.getPage();
		Rectangle pageSize = page.getPageSize();
		PdfCanvas pdfCanvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), pdf);
		Canvas canvas = new Canvas(pdfCanvas, pageSize);
		float x = pageSize.getRight() -60;
		float y = pageSize.getTop() - 32;
		Paragraph p = new Paragraph(title)
			.setFontSize(9)
			.setFont(pdfFont);
		// 显示在顶部右侧位置
		canvas.showTextAligned(p, x, y, TextAlignment.RIGHT);
		URL url = null;
		try {
			url = new URL(logoUrl);
		} catch (MalformedURLException e) {
			throw new RuntimeException(e);
		}
		Image logo = new Image(ImageDataFactory.create(url));
		logo.scaleAbsolute(100, 20);
		logo.setMarginLeft(30);
		logo.setMarginTop(15);
		canvas.add(logo);
		canvas.close();
	}

}
/**
 * 页脚(页码)实现类
 */
public class PdfPageMarkerEventHandler implements IEventHandler {

    /**
     * pdf字体
     */
    private final PdfFont pdfFont;

    public PdfPageMarkerEventHandler(PdfFont pdfFont) {
        this.pdfFont = pdfFont;
    }

    @Override
    public void handleEvent(Event event) {
        PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
        PdfDocument pdf = docEvent.getDocument();
        PdfPage page = docEvent.getPage();
        int pageNumber = pdf.getPageNumber(page);
        Rectangle pageSize = page.getPageSize();
        PdfCanvas pdfCanvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), pdf);
        Canvas canvas = new Canvas(pdfCanvas, pageSize);
        float x = (pageSize.getLeft() + pageSize.getRight()) / 2;
        float y = pageSize.getBottom() + 15;

		Paragraph p = new Paragraph();
		if (1 == pageNumber) {
			p = new Paragraph("")
				.setFontSize(10f)
				.setFont(pdfFont);
		} else {
			p = new Paragraph("第" + (pageNumber -1) + "页")
				.setFontSize(10f)
				.setFont(pdfFont);
		}
        // 绘制到底部中间位置
        canvas.showTextAligned(p, x, y, TextAlignment.CENTER);
        canvas.close();
    }
}

/**
 * pdf水印
 */
public class PdfWaterMarkEventHandler implements IEventHandler {

    /**
     * 水印内容
     */
    private final String waterMarkContent;

    /**
     * 一页中有几行水印
     */
    private final int waterMarkX;

    /**
     * 一页中每列有多少水印
     */
    private final int waterMarkY;
    /**
     * Pdf字体
     */
    private final PdfFont pdfFont;

    /**
     * 默认水印效果5行5列
     * @param pdfFont pdf字体
     * @param waterMarkContent 水印内容
     */
    public PdfWaterMarkEventHandler(PdfFont pdfFont, String waterMarkContent) {
        this(pdfFont, waterMarkContent, 5, 5);
    }

    /**
     * 水印效果
     *
     * @param pdfFont          pdf字体
     * @param waterMarkContent 水印内容
     * @param waterMarkX       一页中有多少行水印
     * @param waterMarkY       一页中有多少列水印
     */
    public PdfWaterMarkEventHandler(PdfFont pdfFont, String waterMarkContent, int waterMarkX, int waterMarkY) {
        this.waterMarkContent = waterMarkContent;
        this.waterMarkX = waterMarkX;
        this.waterMarkY = waterMarkY;
        this.pdfFont = pdfFont;
    }

    @Override
    public void handleEvent(Event event) {

        if (StringUtils.isEmpty(waterMarkContent)) return;

        // 获取pdf对象、页面属性
        PdfDocumentEvent documentEvent = (PdfDocumentEvent) event;
        PdfDocument document = documentEvent.getDocument();
        PdfPage page = documentEvent.getPage();
        Rectangle pageSize = page.getPageSize();

        // 设置画布属性、水印属性
        PdfCanvas pdfCanvas = new PdfCanvas(page.newContentStreamAfter(), page.getResources(), document);
        Paragraph waterMark = new Paragraph(waterMarkContent).setOpacity(0.5f);
        Canvas canvas = new Canvas(pdfCanvas, pageSize)
            .setFontColor(WebColors.getRGBColor("lightgray")) // 水印颜色效果
            .setFontSize(16)
            .setFont(pdfFont);

        float y = pageSize.getHeight(); // 页面高度
        float x = pageSize.getWidth(); // 页面宽度

        // 根据行列进行绘制
        for (int i = 0; i < waterMarkX; i++) {
            float width = (x / waterMarkX)  * i + 50;
            for (int j = waterMarkY; j > 0; j--) {
                float height = (y / waterMarkY)  * j - 50;
//                System.out.println("waterMarkX: "+width+" waterMarkY: "+height);
                canvas.showTextAligned(waterMark, width, height, document.getNumberOfPages(), TextAlignment.CENTER, VerticalAlignment.MIDDLE, 120);
            }
        }
        canvas.close();
    }

5、 内容html页面上特定字符及锚点id:

	<div class="marginTop20" id="companyinfo">
	    <span style="color: #FFFFFF">directory_1.企业信息</span>
	    <img style="width: 100%" src="/upload/20231031/26e3e390f15ce1eae58367405960519f.jpg"/>
	</div>

6、查询特定内容所在页码

	int companyInfoNum = searchFileNum(fileName, zongtiNum, "directory_1.企业信息");


	 * 查询标题所在页码方法
	 * @param fileUrl
	 * @param startPage
	 * @param content
	 * @return
	 * @throws IOException
	 */
	public int searchFileNum(String fileUrl, int startPage, String content) throws IOException {
		int curent = 0;
		// 创建PdfReader对象
		PdfReader pdfWriter = new PdfReader(fileUrl);
		PdfDocument pdfDocument = new PdfDocument(pdfWriter);
		int numberOfPages = pdfDocument.getNumberOfPages();
		for (int i = startPage; i <= numberOfPages; i++) {
			PdfPage page = pdfDocument.getPage(i);
			String textFromPage = PdfTextExtractor.getTextFromPage(page);
			int index = textFromPage.indexOf(content);
			if (index >= 0) {
				curent = i;
			}
		}
		pdfDocument.close();
		pdfWriter.close();

		return curent;
	}

7、目录html页面替换变量、添加锚点

	<div class="directory1 level1">
         <span class="directory-name"><a href="#companyinfo">1.企业信息</a></span>
         <span class="directory-dot"></span>
         <span class="directory-page">${companyInfoNum}</span>
     </div>

8、合并生成的多个pdf

PdfUtils.mergeMultiplePdfs(Arrays.asList(headerName, pdfDescName, pdfOverViewName, directoryAndContentName), pdfAllName);

9、删除多余的pdf文件文章来源地址https://www.toymoban.com/news/detail-829087.html

	File headerFile = new File(headerName);

	if (headerFile.exists()) {
		headerFile.delete();
	}

到了这里,关于itextpdf7 使用之 html转pdf,生成目录可跳转、添加页眉页脚的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 【工具推荐】使用D3.js制作网页版网络拓扑图,可拖转可跳转链接

    【工具推荐】使用D3.js制作网页版网络拓扑图,可拖转可跳转链接

    有一些 JavaScript 库可以帮助你创建网络拓扑图,并且支持将每个节点作为超链接。 以下是一些我推荐的库: D3.js :D3 是一个非常强大的 JavaScript 库,用于创建数据驱动的文档。你可以使用它来创建复杂的网络拓扑图,并且可以轻松地将每个节点作为超链接。 Cytoscape.js :Cy

    2024年02月07日
    浏览(15)
  • Vue使用html2canvas将DOM节点生成对应的PDF

    要通过Vue使用html2canvas将DOM节点生成对应的PDF,您需要安装html2canvas和jspdf这两个库。html2canvas用于将DOM节点转换为Canvas,而jspdf用于将Canvas转换为PDF。以下是一个简单的示例代码,展示了如何使用html2canvas和jspdf生成PDF文件: 首先,安装html2canvas和jspdf依赖: 然后,在Vue组件中

    2024年02月11日
    浏览(12)
  • (vue)Vue项目中使用jsPDF和html2canvas生成PDF

    (vue)Vue项目中使用jsPDF和html2canvas生成PDF

    效果: 1.:安装jsPDF和html2canvas 2.在需要生成PDF文档的组件中引入jsPDF和html2canvas 解决参考: 1.https://www.jianshu.com/p/31d37bef539b 2.https://www.php.cn/faq/556634.html 3.https://blog.csdn.net/m0_54967474/article/details/123820384

    2024年02月10日
    浏览(9)
  • SpringBoot+Thymeleaf 后端转html,pdf HTML生成PDF SpringBoot生成PDF Java PDF生成

    SpringBoot+Thymeleaf 后端转html,pdf HTML生成PDF SpringBoot生成PDF Java PDF生成

    本文详细介绍了如何使用SpringBoot和Thymeleaf将后端HTML转换为PDF,包括依赖介绍、模板渲染以及PDF生成等步骤。

    2024年02月09日
    浏览(22)
  • Java导出PDF(itextpdf)-通俗易懂

    Java导出PDF(itextpdf)-通俗易懂

    在java开发的过程中会遇到太多太多文档pdf导出,excle导出等业务场景,时隔三个月或半年来一次每一次遇到这样的业务场景对我都是非常痛苦的过程,本文旨在记录工具类使用方法和技术分享。 itextpdf是一个开源的Java库,用于创建和操作PDF文档。使用itextpdf,您可以创建新的

    2024年02月12日
    浏览(13)
  • 【PDF】html/dom生成pdf

    【PDF】html/dom生成pdf

    上一篇博客主要讲的是pdf文件转换成canvas,然后进行相关的画框截图操作。 【PDF】Canvas绘制PDF及截图 本篇博客主要讲html中dom如何生成pdf文件(前端生成pdf),后端生成pdf当然也可以,原理也是将html网页通过后端服务导出成pdf,然后css设置break-after:always;作为分页逻辑,但是

    2024年02月16日
    浏览(8)
  • 【无标题】使用html2canvas和jspdf生成的pdf在不同大小的屏幕下文字大小不一样

    【无标题】使用html2canvas和jspdf生成的pdf在不同大小的屏幕下文字大小不一样

    问题:使用html2canvas和jspdf生成的pdf在不同大小的屏幕下文字大小不一样,在mac下,一切正常,看起来很舒服,但是当我把页面放在扩展屏幕下(27寸),再生成一个pdf,虽然排版一样,但是文字就变得非常小 下面的是在mac下的,上面是在扩展屏幕下的,最开始我以为是文字大

    2024年02月16日
    浏览(13)
  • PDF 书签制作和 Word 文档转 PDF 生成书签保留目录超链接的方法

    PDF 书签制作和 Word 文档转 PDF 生成书签保留目录超链接的方法

    根据 PDF 文档创建性质来制作书签 由可编辑文档创建 由不可编辑的图片创建 一、Word 文档目录转 PDF 生成书签 Word自带转换为PDF同时生成目录书签(office 2010 以后均支持) 打开 Word 文档,选择【文件】–【另存为 Adobe PDF】 –【选项】 –【将Word 标题转换为书签(H)】 在已安

    2024年02月04日
    浏览(14)
  • 在浏览器中使用javascript打印HTML中指定Div带背景图片内容生成PDF电子证书查询的解决方案

    在浏览器中使用javascript打印HTML中指定Div带背景图片内容生成PDF电子证书查询的解决方案

    要调用浏览器中的打印功能,并指定需要打印的内容为特定的DIV内的内容,你可以使用JavaScript来实现。下面是一种实现方法: 首先,在需要打印的DIV标签上添加一个唯一的ID属性,例如: 接下来,在JavaScript中使用 window.print() 方法来调用浏览器的打印功能,并指定打印的内容

    2024年02月13日
    浏览(13)
  • Java HTML生成PDF(格式不变)

    Java HTML生成PDF(格式不变)

    Java是大规模和企业级应用程序最常用的编程语言之一。PDF 格式代表可移植文档格式,为人们提供了一种简单、可靠的方式来呈现和交换文档 - 无论查看文档的任何人使用何种软件、硬件或操作系统。因此,pdf 是一种广泛使用的格式,用于在软件应用程序中生成文档。 HTML 用

    2024年02月12日
    浏览(7)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包