当前位置: 首页 > news >正文

最新 生成pdf文字和表格

生成pdf文字和表格

先看效果

在这里插入图片描述

介绍

java项目,使用apache的pdfbox工具,可分页,自定义列

依赖

<dependency><groupId>org.apache.pdfbox</groupId><artifactId>pdfbox</artifactId><version>2.0.22</version>
</dependency>
<dependency><groupId>org.apache.pdfbox</groupId><artifactId>fontbox</artifactId><version>2.0.22</version>
</dependency>
<dependency><groupId>org.apache.pdfbox</groupId><artifactId>xmpbox</artifactId><version>2.0.22</version>
</dependency>
<dependency><groupId>org.apache.pdfbox</groupId><artifactId>preflight</artifactId><version>2.0.22</version>
</dependency>
<dependency><groupId>org.apache.pdfbox</groupId><artifactId>pdfbox-tools</artifactId><version>2.0.22</version>
</dependency>

基本使用的api

创建一个页面

        String filePath = "D:\\imgTest\\test.pdf";PDDocument document = new PDDocument();PDPage page = new PDPage();document.addPage(page);document.save(new File(filePath));

执行后就可以在 D:\imgTest 目录下看到test.pdf 打开后有一个空白页

写入文字

 		PDDocument document = new PDDocument();PDFont font = PDType0Font.load(document,new File("D://imgtest/font/simfang.ttf"));PDPage page = new PDPage();PDPageContentStream contentStream = new PDPageContentStream(document, page);contentStream.beginText();contentStream.setFont(font,20.0f);contentStream.showText("写入文字");contentStream.endText();contentStream.close();document.addPage(page);document.save(new File(filePath));

在这里插入图片描述

但是可以看到文字在最后显示

文字从顶部写入

计算出page高度 文字高度 然后就得到了书写的位置

contentStream.newLineAtOffset(0,pageHeight - fontHeight);

        PDDocument document = new PDDocument();PDFont font = PDType0Font.load(document,new File("D://imgtest/font/simfang.ttf"));PDPage page = new PDPage();float pageWidth = page.getMediaBox().getWidth();float pageHeight = page.getMediaBox().getHeight();float fontSize = 20.0f;float fontHeight = font.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * fontSize;float fontWidth = font.getAverageFontWidth() / 1000 * fontSize;PDPageContentStream contentStream = new PDPageContentStream(document, page);contentStream.beginText();contentStream.setFont(font,fontSize);contentStream.setLeading(10.0f);contentStream.newLineAtOffset(0,pageHeight - fontHeight);contentStream.showText("写入文字");contentStream.endText();contentStream.close();document.addPage(page);document.save(new File(filePath));

在这里插入图片描述

写入多行文字并自动换行

如果写入一段文字的时候,要自动换行,

现在我们知道一个文字的宽度,可以算出一行总共可以写多少文字,这样就知道多少行了然后每行写入就可以了

代码

		PDDocument document = new PDDocument();PDFont font = PDType0Font.load(document,new File("D://imgtest/font/simfang.ttf"));PDPage page = new PDPage();float pageWidth = page.getMediaBox().getWidth();float pageHeight = page.getMediaBox().getHeight();float fontSize = 20.0f;float fontHeight = font.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * fontSize;float fontWidth = font.getAverageFontWidth() / 1000 * fontSize;int lineNum = (int)Math.floor(pageWidth/fontWidth);String text = "初升的太阳照耀大地,北部战区空军某雷达站营区披上了一层金色的外衣。祖国东极,一年中最冷的季节,这样的光,给人以希望和温暖。" +"  6时55分,上等兵孙野推开雷达方舱的大门,揉了揉酸痛的双眼,沐浴着第一缕阳光,脑海中浮现一句熟悉的歌词:“我把太阳迎进祖国。”迎接阳光,这是雷达兵最自豪的时刻。\n" +"  这一刻,一级军士长王传星带着“徒弟”走进方舱值班。在这位技术骨干心中,雷达兵的眼睛,就是祖国的眼睛,使命重如泰山,须臾不可懈怠。雷达屏幕上,闪烁的光标汇在一起,那是他和战友们心头永远的璀璨。\n" +"  这一刻,驾驶员刘磊再次出发踏上征程。一次次驾车行驶在熟悉的山路上,他陪伴着战友,也了解了很多不为人知的故事。守护着他们的坚强与坚韧,见证着他们的笑与泪,这位老兵决心成为战友身边和煦的光。\n" +"  这一刻,年轻骨干李航宇和李玉溪全副武装来到了曙光初照的训练场,备战即将来临的上级比武。为了心中的目标,他们把班长当作标杆;为了成为像班长那样的“光”,他们一往无前,奋力冲锋。\n" +"  “我把太阳迎进祖国,太阳把光热洒给万里山河,我持枪向太阳致以军礼,请它也带上我的光、我的热……”\n" +"  新年来临,在这个边防雷达站,许多官兵都在守望中找到了属于自己的光。为梦想而追光,他们有怎样的收获和成长,又有怎样的期待和展望?请看来自祖国东极某雷达站的报道。\n" +"  下连之初,班长就曾对孙野说,雷达站驻地太阳升起的时间,比他的家乡福建早了一个多小时。作为东极雷达兵,每当阳光照耀大地,他们感受更多的是肩头的责任。\n" +"  “阳光在哪里,坚守就在哪里。”在雷达站官兵心中,头上的阳光很暖,肩上的责任很重,脚下的路很长";PDPageContentStream contentStream = new PDPageContentStream(document, page);contentStream.beginText();contentStream.setFont(font,fontSize);contentStream.setLeading(fontHeight);List<String> textList = handleText(text,lineNum);contentStream.newLineAtOffset(0,pageHeight - fontHeight);for(int i=0;i<textList.size();i++){log.info(textList.get(i));contentStream.showText(textList.get(i));contentStream.newLine();}contentStream.endText();contentStream.close();document.addPage(page);document.save(new File(filePath));

在这里插入图片描述

注意换行写入使用的方法

contentStream.beginText();
contentStream.setFont(font,fontSize);
contentStream.setLeading(fontHeight);for ...{contentStream.showText(内容);contentStream.newLine();}

文字内容基本就是这样了,可以根据自己的方式添加padding 和 margin 也就是在pageWidth pageHeight 上进行调整

自己可以试试

表格

画一条横线和竖线
PDDocument document = new PDDocument();PDPage page = new PDPage();float pageWidth = page.getMediaBox().getWidth();float pageHeight = page.getMediaBox().getHeight();PDPageContentStream contentStream = new PDPageContentStream(document, page);contentStream.moveTo(0, pageHeight/2);//横线contentStream.lineTo(pageWidth, pageHeight/2);//横线contentStream.stroke();//横线contentStream.moveTo(pageWidth/2, pageHeight);//竖线contentStream.lineTo(pageWidth/2, 0);//竖线contentStream.stroke();//竖线contentStream.close();document.addPage(page);document.save(new File(filePath));

在这里插入图片描述

画一个简单的表格(10 X 5 的表格)

11 条横线 6条数线

这个只是为了画格子,长度和宽度都是自定的,如果写入文字的话就得考虑文字超出的问题,格子的长度和高度就不能是自定义的了

  PDDocument document = new PDDocument();PDPage page = new PDPage();float pageWidth = page.getMediaBox().getWidth();float pageHeight = page.getMediaBox().getHeight();PDPageContentStream contentStream = new PDPageContentStream(document, page);float rowNum = (pageHeight - 20) / 10;float colNum = (pageWidth - 20) / 6;for (int i = 0; i <= 11; i++) {float row = pageHeight - 10 - i * rowNum;contentStream.moveTo(10, row);contentStream.lineTo(pageWidth - 10, row);contentStream.stroke();}for (int i = 0; i <= 6; i++) {float col = pageWidth - 10 - i * colNum;contentStream.moveTo(col, 10);contentStream.lineTo(col, pageHeight - 10);contentStream.stroke();}contentStream.close();document.addPage(page);document.save(new File(filePath));

在这里插入图片描述

表格中写入文字

考虑一个问题,先画表格还是先写文字

其实都可以

主要的问题是要考虑 一行中,每个列 文字的长度,如果换行的话 文字的高度和最高的那一列,找到这个最高值就知道这一行最下面边的位置了

完整代码

因为内容比较多,单独封装了一个类

@Slf4j
class Table {private float fontWidth;private float fontHeight;private float pageHeight;private float pageWidth;private PDPageContentStream contentStream;private PDFont font;private float padding = 2f;private float pageMargin = 10f;private float fontSize = 20.0f;private String[] header;private int[] cellSpan;private PDPage page;private float courY;private float leading = 20f;private List<String[]> contexts;private float[] cellWidth;private float[] courXPosion;public Table(PDPage page, PDPageContentStream contentStream, PDFont font, int[] cellSpan, String[] header, List<String[]> contexts) {this.contentStream = contentStream;this.page = page;this.font = font;this.pageWidth = this.page.getMediaBox().getWidth();this.pageHeight = this.page.getMediaBox().getHeight();this.fontHeight = font.getFontDescriptor().getFontBoundingBox().getHeight() / 1000f * fontSize;this.fontWidth = font.getAverageFontWidth() / 1000f * fontSize;this.courY = pageHeight - pageMargin;this.header = header;this.cellSpan = cellSpan;this.contexts = contexts;courXPosion = new float[header.length + 1];cellWidth = new float[header.length];courXPosion[0] = pageMargin;for (int i = 0; i < cellSpan.length; i++) {float width = (pageWidth - pageMargin * 2) * (cellSpan[i] / 100f);courXPosion[i + 1] = courXPosion[i] + width;cellWidth[i] = width;}contexts.add(0, header);}public void createTable() throws IOException {//画第一条横线contentStream.moveTo(pageMargin, courY);contentStream.lineTo(pageWidth - pageMargin, courY);contentStream.stroke();//填充内容for (int i = 0; i < contexts.size(); i++) {courY = courY - padding - fontHeight;String[] rows = contexts.get(i);handleData(rows);}//统一画竖线  最后画就可以for (int i = 0; i < courXPosion.length; i++) {contentStream.moveTo(courXPosion[i], pageHeight - pageMargin);contentStream.lineTo(courXPosion[i], courY);contentStream.stroke();}}public void handleData(String[] data) throws IOException {int maxLineNum = 1;float courX = pageMargin + padding;for (int i = 0; i < data.length; i++) {int lineNum = writeText(data[i], cellWidth[i] - padding * 2, courX);maxLineNum = lineNum > maxLineNum ? lineNum : maxLineNum;courX = courX + cellWidth[i];}courY = courY - padding - fontHeight * (maxLineNum - 1 ) - leading * (20f / 72f);contentStream.moveTo(pageMargin, courY);contentStream.lineTo(pageWidth - pageMargin, courY);contentStream.stroke();}public int writeText(String text, float cellWidth, float x) throws IOException {int lineNum = (int) Math.floor(cellWidth / fontWidth);List<String> textList = handleText(text, lineNum);contentStream.beginText();contentStream.setFont(font, fontSize);contentStream.setLeading(leading);contentStream.newLineAtOffset(x, courY);for (int i = 0; i < textList.size(); i++) {//log.info(textList.get(i));contentStream.showText(textList.get(i));contentStream.newLine();}contentStream.endText();return textList.size();}public List<String> handleText(String text, int lineNum) {text = text.replace("\n", "");text = StringUtils.deleteWhitespace(text);List<String> list = new ArrayList<>();if (text.length() <= lineNum) {list.add(text);return list;}while (text.length() > lineNum) {list.add(text.substring(0, lineNum));text = text.substring(lineNum);}if (text.length() > 0) {list.add(text);}return list;}
}
测试调用
   @Testpublic void test6() throws IOException {PDDocument document = new PDDocument();PDPage page = new PDPage();PDFont font = PDType0Font.load(document, new File("D://imgtest/font/simfang.ttf"));PDPageContentStream contentStream = new PDPageContentStream(document, page);//组装内容String[] header = new String[]{"序号", "名称", "作者", "时间", "内容"};List<String[]> listText =IntStream.range(0, 2).mapToObj(row ->new String[]{String.valueOf(row + 1),"《沁园春·雪》","毛泽东","1936","北国风光,千里冰封,万里雪飘。望长城内外,惟余莽莽;大河上下,顿失滔滔。山舞银蛇,原驰蜡象,欲与天公试比高。须晴日,看红装素裹,分外妖娆。江山如此多娇,引无数英雄竞折腰。惜秦皇汉武,略输文采;唐宗宋祖,稍逊风骚。一代天骄,成吉思汗,只识弯弓射大雕。俱往矣,数风流人物,还看今朝。"}).collect(Collectors.toList());//提前设置好列比例,可以自己调整,只要保证最后是100就行int[] cellSpan = {10, 25, 15, 15, 35};Table table = new Table(page, contentStream, font, cellSpan, header, listText);table.createTable();contentStream.close();document.addPage(page);document.save(new File(filePath));}
效果展示

在这里插入图片描述

最后

好了以上就是完整的代码和测试用例,当然还有比较多的问题

比如

  1. 如果这一页满了该怎么办?
  2. 如果要水平和垂直居中
  3. 比如要插入图片

有需要的小伙伴可以私信讨论,或者回复索要代码

相关文章:

最新 生成pdf文字和表格

生成pdf文字和表格 先看效果 介绍 java项目&#xff0c;使用apache的pdfbox工具&#xff0c;可分页&#xff0c;自定义列 依赖 <dependency><groupId>org.apache.pdfbox</groupId><artifactId>pdfbox</artifactId><version>2.0.22<…...

安全基础~攻防特性3

文章目录 SSTI(模板注入)1. 简介2. 成因3. 常见框架存在注入4. 判断存在SSTI SSTI(模板注入) 1. 简介 (Server-Side Template Injection) 服务端模板注入 1、使用框架&#xff08;MVC的模式&#xff09;&#xff0c;如python的flask&#xff0c;php的tp&#xff0c;java的sp…...

Windows7关闭谷歌浏览器提示“若要接收后续 Google Chrome 更新,您需使用 Windows 10 或更高版本”的方法

背景 电脑比较老&#xff0c;系统一直没有更新&#xff0c;硬件和软件版本如下&#xff1a; 操作系统版本&#xff1a;Windows7 企业版 谷歌浏览器版本&#xff1a;109.0.5414.120&#xff08;正式版本&#xff09; &#xff08;64 位&#xff09; 该版本的谷歌浏览器是支持…...

[一]ffmpeg音视频解码

[一]ffmpeg音视频解码 一.编译ffmpeg1.安装vmware虚拟机2.vmware虚拟机安装linux操作系统3.安装ftp和fshell软件4.在Ubuntu&#xff08;Linux&#xff09;中编译Android平台的FFmpeg&#xff08; arm和x86 &#xff09;5.解压FFmpeg6.Android编译脚本&#xff08;1&#xff09;…...

k8s-认证授权 14

Kubernetes的认证授权分为认证&#xff08;鉴定用户身份&#xff09;、授权&#xff08;操作权限许可鉴别&#xff09;、准入控制&#xff08;资源对象操作时实现更精细的许可检查&#xff09;三个阶段。 Authentication&#xff08;认证&#xff09; 认证方式现共有8种&…...

在全志H616核桃派上实现USB摄像头的OpenCV颜色检测

在给核桃派开发板用OpenCV读取图像并显示到pyqt5的窗口上并加入颜色检测功能&#xff0c;尝试将图像中所有蓝色的东西都用一个框标记出来。 颜色检测核心api 按照惯例&#xff0c;先要介绍一下opencv中常用的hsv像素格式。颜色还是那个颜色&#xff0c;只是描述颜色用的参数变…...

mac安装部署gitbook教程

mac安装部署gitbook教程 前言一、安装准备二、GitBook安装三、项目初始化 前言 一些自己实际操作的记录。 一、安装准备 Node.js gitbook基于Node.js&#xff0c;所以需要提前安装。 下载地址&#xff1a;https://nodejs.org/en/&#xff0c;可以下载比较新的版本。(但我的建议…...

有关软件测试的,任何时间都可以,软件测试主要服务项目:测试用例 报告 计划

有关软件测试的&#xff0c;任何时间都可以&#xff0c;软件测试主要服务项目&#xff1a; 1. 测试用例 2. 测试报告 3. 测试计划 4. 白盒测试 5. 黑盒测试 6. 接口测试 7.自动…...

快乐过寒假,安全不放假

寒假将至&#xff0c;春节即来&#xff0c;为了使孩子们过上一个平安、快乐、文明、祥和、健康、有益的寒假和春节&#xff0c;在共青团永宁县委员会、永宁县望洪镇人民政府的大力支持下&#xff0c;在永宁新华中心村校外少工委的积极配合下&#xff0c;1月20日下午宁夏妇女儿童…...

qt学习:模仿qq界面+添加资源+无边框界面+修改样式

目录 一,创建登录ui界面类 LoginWidget 二,添加图片资源 三,通过样式的方法将图片设置成圆圈的背景 四,新建登录后的ui界面 MWindow 简陋的就可以,因为只为了学习,可以自己补充 五,新建三个嵌套ui界面类,ChatWidget聊天界面 FriendWiidget好友界面 CollectW…...

【Linux】基本指令收尾

文章目录 日期查找打包压缩系统信息Linux和Windows互传文件 日期 这篇是基本指令的收尾了&#xff0c;还有几个基本指令我们需要说一下 首先是Date&#xff0c;它是用来显示时间和日期 直接输入date的话显示是有点不好看的&#xff0c;所以我们可以根据自己的喜欢加上分隔符&…...

精准核酸检测 - 华为OD统一考试

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 100分 题解&#xff1a; Java / Python / C 题目描述 为了达到新冠疫情精准防控的需要&#xff0c;为了避免全员核酸检测带来的浪费&#xff0c;需要精准圈定可能被感染的人群。 现在根据传染病流调以及大数据分析&a…...

LINUX文件fd(file descriptor)文件描述符

目录 1.文件接口 1.1open 1.2C语言为什么要对open进行封装 2.fd demo代码 第一个问题 第二个问题 打开文件流程 引言&#xff1a;在学习C语言的时候&#xff0c;我们见过很多的文件的接口&#xff0c;例如fopen&#xff0c;fwrite&#xff0c;fclose等等&#xff0c;但…...

SpringMVC 的理解

MVC MVC&#xff08;Model-View-Controller&#xff09;是一种软件设计模式&#xff0c;用于实现用户界面。它将应用程序划分为三个互相交互的部分&#xff0c;以分离内部逻辑表示和表现层。这种分离有助于管理复杂的应用程序&#xff0c;因为它允许开发者单独修改模型、视图或…...

SpringBoot 3.1.7 集成Sentinel

一、背景 我的项目需要引入限流&#xff0c;降级&#xff0c;熔断框架&#xff0c;由于 Spring Cloud 2022.0.4 已经不再支持 Hystrix&#xff0c;Spring Cloud 提供了替代方案&#xff0c;如 Resilience4j&#xff0c;可以使用它来替换 Hystrix。但是网上搜了一下国内Resilie…...

Elastic Stack 8.12:通过对 ES|QL 等的改进增强了向量搜索

作者&#xff1a;来自 Elastic Tyler Perkins, Shani Sagiv, Gilad Gal, Ninoslav Miskovic Elastic Stack 8.12 构建于 Apache Lucene 9.9&#xff08;有史以来最快的 Lucene 版本&#xff09;之上&#xff0c;基于我们对标量量化和搜索并发性的贡献&#xff0c;为文本、向量和…...

结构体的内存对齐(计算题常考点)

许久不见我考完试回来啦&#xff0c;让我们接着将结构体进行到底&#xff01; 目录 结构体对齐的意义&#xff1a; 结构体对齐的实现&#xff1a; 对齐规则&#xff1a; 训练&#xff1a; 好到这里误区来了&#xff1a; 总结&#xff1a; 往期回顾&#xff1a; 下期预告&…...

设置Json对象输出字段顺序

场景 通过情况下对前端输出json格式不需要关注字段顺序&#xff0c;但某些特殊场景需要设置字段输出顺序(例nginx需要对特殊字段顺序进行加密处理)&#xff1b;框架有默认的顺序&#xff0c;如 jackson 默认使用字段声明的顺序&#xff0c; fastjson 默认是使用字典序。 jackso…...

当 OpenTelemetry 遇上阿里云 Prometheus

作者&#xff1a;逸陵 背景 在云原生可观测蓬勃发展的当下&#xff0c;想必大家对 OpenTelemetry & Prometheus 并不是太陌生。OpenTelemetry 是 CNCF&#xff08;Cloud Native Computing Foundation&#xff09;旗下的开源项目&#xff0c;它的目标是在云原生时代成为应…...

【Flink-1.17-教程】-【四】Flink DataStream API(1)源算子(Source)

【Flink-1.17-教程】-【四】Flink DataStream API&#xff08;1&#xff09;源算子&#xff08;Source&#xff09; 1&#xff09;执行环境&#xff08;Execution Environment&#xff09;1.1.创建执行环境1.2.执行模式&#xff08;Execution Mode&#xff09;1.3.触发程序执行…...

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...

【力扣数据库知识手册笔记】索引

索引 索引的优缺点 优点1. 通过创建唯一性索引&#xff0c;可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度&#xff08;创建索引的主要原因&#xff09;。3. 可以加速表和表之间的连接&#xff0c;实现数据的参考完整性。4. 可以在查询过程中&#xff0c;…...

遍历 Map 类型集合的方法汇总

1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

如何在看板中有效管理突发紧急任务

在看板中有效管理突发紧急任务需要&#xff1a;设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP&#xff08;Work-in-Progress&#xff09;弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中&#xff0c;设立专门的紧急任务通道尤为重要&#xff0c;这能…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

今日科技热点速览

&#x1f525; 今日科技热点速览 &#x1f3ae; 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售&#xff0c;主打更强图形性能与沉浸式体验&#xff0c;支持多模态交互&#xff0c;受到全球玩家热捧 。 &#x1f916; 人工智能持续突破 DeepSeek-R1&…...

【Oracle】分区表

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

Springboot社区养老保险系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;社区养老保险系统小程序被用户普遍使用&#xff0c;为方…...

算法笔记2

1.字符串拼接最好用StringBuilder&#xff0c;不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...

IP如何挑?2025年海外专线IP如何购买?

你花了时间和预算买了IP&#xff0c;结果IP质量不佳&#xff0c;项目效率低下不说&#xff0c;还可能带来莫名的网络问题&#xff0c;是不是太闹心了&#xff1f;尤其是在面对海外专线IP时&#xff0c;到底怎么才能买到适合自己的呢&#xff1f;所以&#xff0c;挑IP绝对是个技…...