一、前言
- 需要进行电子签名的html文件内容,一般为富文本编辑器对应代码。
- 用户电子签名完成后的签名图片访问地址,例如:http://*****/hlwft/2_1659677726496.jpg。
- 当前仅实现了PageSize.A4格式的底部签名和左/中/右对齐。
- 签名图片访问支持http/https两种形式,http信任所有的证书与主机的客户端。
二、工程依赖(maven工程)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.3.12.RELEASE</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!--pdf文件生成-->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>html2pdf</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>font-asian</artifactId>
<version>7.2.4</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!--http工具-->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.3</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.16</version>
</dependency>
三、工程代码示例(java)
- 图片配置类
public final class ImgLayout {
private ImgLayout() {
}
/**
* 图片的宽度
* {@value}
*/
public static final int IMAGE_WIDTH = 40;
/**
* 图片的高度
* {@value}
*/
public static final int IMAGE_HEIGHT = 40;
/**
* 边界间距
*/
public static final int BORDER_INTERVAL = 8;
}
- 签名配置类
import com.itextpdf.layout.properties.TextAlignment;
public final class SignStyle {
private SignStyle() {
}
/**
* 是否每页签名:默认最后一页签名
* <ul>
* <li>{@code Boolean.FALSE}:最后一页签名</li>
* <li>{@code Boolean.TRUE}:每一页都生成签名</li>
* </ul>
*/
public static final boolean SIGN_EACH_PAGE = Boolean.FALSE;
/**
* 对齐方式:默认居中对齐
* <strong>
* 仅支持三种类型{@link TextAlignment.LEFT}/{@link TextAlignment.CENTER}/{@link TextAlignment.RIGHT}
* </strong>
*/
public static final TextAlignment SIGN_ALIGN = TextAlignment.CENTER;
}
- pdf文件样式配置类
public final class PdfStyle {
private PdfStyle() {
}
/**
* PDF页码字号
* {@value}
*/
public static final int PAGE_FONT_SIZE = 14;
/**
* 字体文件目录
* <h4>windows/linux系统字体文件目录一般如下</h4>
* ul>
* <li>windows字体文件目录:C:/Windows/Fonts</li>
* <li>linux字体文件目录:/usr/share/fonts</li>
* </ul>
*/
public static final String FONT_PATH = "C:/Windows/Fonts";
}
- UnsafeOkHttp工具类
import com.itextpdf.commons.utils.FileUtil;
import com.itextpdf.io.util.StreamUtil;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.apache.http.util.TextUtils;
import org.springframework.util.CollectionUtils;
import javax.net.ssl.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
public class UnsafeOkHttp {
/**
* 信任所有的证书与主机的客户端
* {@value}
*/
public static OkHttpClient UNSAFE_OKHTTP_CLIENT;
static {
try {
final TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[]{};
}
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
builder.hostnameVerifier(new HostnameVerifier() {
// 可以将不需要忽略的域名放入数组,也可为空(忽略所有证书)
String[] arr = new String[]{};
@Override
public boolean verify(String hostname, SSLSession session) {
if (TextUtils.isEmpty(hostname)) {
return false;
}
return !Arrays.asList(arr).contains(hostname);
}
});
UNSAFE_OKHTTP_CLIENT = builder.build();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 发起HTTPGET请求
*
* @param url
* @param headerMap
* @return
* @throws Exception
*/
public static Response unsafeGet(String url, Map<String, String> headerMap) throws IOException {
Request.Builder builder = new Request.Builder().url(url);
if (!CollectionUtils.isEmpty(headerMap)) {
headerMap.forEach((name, value) -> builder.addHeader(name, value));
}
Request request = builder.build();
return UNSAFE_OKHTTP_CLIENT.newCall(request).execute();
}
/**
* 发起HTTPGET请求,并将返回的内容写入到文件中
*
* @param url
* @param headerMap
* @param target
* @return
* @throws Exception
*/
public static void unsafeGetToFile(String url, Map<String, String> headerMap, File target) throws IOException {
Request.Builder builder = new Request.Builder().url(url);
if (!CollectionUtils.isEmpty(headerMap)) {
headerMap.forEach((name, value) -> builder.addHeader(name, value));
}
Response response = UNSAFE_OKHTTP_CLIENT.newCall(builder.build()).execute();
try (FileOutputStream fileOutputStream = new FileOutputStream(target)) {
StreamUtil.transferBytes(response.body().byteStream(), fileOutputStream);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 发起HTTPGET请求,并将返回临时文件
*
* @param url
* @param headerMap
* @return 返回临时文件,使用后需手动删除
* @throws Exception
*/
public static File unsafeGetToFile(String url, Map<String, String> headerMap) throws IOException {
Request.Builder builder = new Request.Builder().url(url);
if (!CollectionUtils.isEmpty(headerMap)) {
headerMap.forEach((name, value) -> builder.addHeader(name, value));
}
ResponseBody body = UNSAFE_OKHTTP_CLIENT.newCall(builder.build()).execute().body();
String fileSuffix = body.contentType().subtype();
File target = FileUtil.createTempFile(String.join(".", "target", fileSuffix));
try (FileOutputStream fileOutputStream = new FileOutputStream(target)) {
StreamUtil.transferBytes(body.byteStream(), fileOutputStream);
} catch (Exception e) {
e.printStackTrace();
}
return target;
}
}
- pdf签名工具类
import com.itextpdf.commons.utils.FileUtil;
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.io.image.ImageType;
import com.itextpdf.io.util.StreamUtil;
import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.element.Image;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.font.FontProvider;
import com.itextpdf.layout.properties.TextAlignment;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import java.io.*;
import java.net.URL;
import java.util.List;
import static com.example.demo.pdf.ImgLayout.*;
import static com.example.demo.pdf.PdfStyle.PAGE_FONT_SIZE;
import static com.example.demo.pdf.SignStyle.SIGN_EACH_PAGE;
/**
* pdf签名工具
* <h3>注意事项</h3>
* <ul>
* <li>签名仅支持底部签名</li>
* <li>签名对齐仅支持三种类型{@link TextAlignment.LEFT}/{@link TextAlignment.CENTER}/{@link TextAlignment.RIGHT}</li>
* <li>仅支持{@code PageSize.A4}格式</li>
* <li>签名图片参照配置类{@linkplain ImgLayout},pdf文件参照配置类{@linkplain PdfStyle},签名参照配置类{@linkplain SignStyle}</li>
* <li>签名图片访问支持http/https两种形式,http信任所有的证书与主机的客户端,如果需要配置证书需要重新实现{@linkplain UnsafeOkHttp}</li>
* </ul>
*
*/
public final class PdfUtil {
private PdfUtil() {
}
/**
* 将html写入到文件中
*
* @param html
* @param file
*/
public static final void html2Pdf(String html, File file) {
//1.配置html转换字体参数
ConverterProperties properties = new ConverterProperties();
FontProvider fontProvider = new FontProvider();
fontProvider.addDirectory(PdfStyle.FONT_PATH);
properties.setFontProvider(fontProvider);
//2.转换html2pdf
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
HtmlConverter.convertToPdf(html, fileOutputStream, properties);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* pdf图片签名
*
* @param source
* @param images
* @param signPdfName
* @return {@literal 签名整合后的pdf文件临时文件}
* @throws IOException
*/
public static final File sign(File source, List<Image> images, String signPdfName) throws IOException {
File signPdf = FileUtil.createTempFile(signPdfName);
try (FileOutputStream signStream = new FileOutputStream(signPdf);
FileInputStream sourceStream = new FileInputStream(source)) {
PdfDocument signDocument = new PdfDocument(new PdfWriter(signStream));
PdfReader sourceReader = new PdfReader(sourceStream);
PdfDocument sourceDocument = new PdfDocument(sourceReader);
signDocument.addEventHandler(PdfDocumentEvent.END_PAGE,
new PdfPageTailMarker());
signDocument.addEventHandler(PdfDocumentEvent.INSERT_PAGE,
new PdfSignImgMarker(images,
sourceDocument.getNumberOfPages()));
for (int i = 1, numberOfPage = sourceDocument.getNumberOfPages(); i <= numberOfPage; i++) {
PdfPage sourcePage = sourceDocument.getPage(i);
//复制每页内容添加到新的文件中
signDocument.addPage(sourcePage.copyTo(signDocument));
}
signDocument.close();
sourceDocument.close();
sourceReader.close();
} catch (Exception e) {
e.printStackTrace();
}
return signPdf;
}
/**
* 根据图片访问路径创建PDF签名图片对象
* <ul>
* <li>itext支持的标准图片文件格式参见{@linkplain ImageType}</li>
* <li>本方法单独针对.jpg格式图片按.jpeg格式处理</li>
* </ul>
*
* @param imgUrl
* @return
* @throws IOException
*/
public static Image createImage(String imgUrl) throws IOException {
if (StringUtils.containsIgnoreCase(imgUrl, "https")) {
//1.如果是https请求,则获取文件内容后返回
Response response = UnsafeOkHttp.unsafeGet(imgUrl, null);
ResponseBody body = response.body();
//1.1 如果因为协议问题,响应体格式未自动转换为jpeg,则开启如下代码单独处理
// if (StringUtils.equalsIgnoreCase(body.contentType().subtype(), "jpg")) {
// ImageData jpgImage = new JpgImageData(body.bytes());
// JpgImageHandler.processImage(jpgImage);
// return new Image(jpgImage);
// }
return new Image(ImageDataFactory.create(body.bytes()));
} else {
//2.如果是http请求直接返回
return new Image(ImageDataFactory.create(new URL(imgUrl)));
}
}
/**
* 将源数据转换为分片文件
*
* @param source
* @return
*/
public static final MultipartFile transformFile2MultipartFile(File source) {
FileItemFactory factory = new DiskFileItemFactory(16, null);
FileItem item = factory.createItem("textField", "text/plain", true, source.getName());
try (FileInputStream sourceStream = new FileInputStream(source);
OutputStream targetStream = item.getOutputStream()) {
StreamUtil.transferBytes(sourceStream, targetStream);
} catch (Exception e) {
e.printStackTrace();
}
return new CommonsMultipartFile(item);
}
/**
* pdf页脚处理。pdf配置参照{@linkplain PdfStyle}
*/
private static class PdfPageTailMarker implements IEventHandler {
@Override
public void handleEvent(Event event) {
PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
PdfDocument pdf = docEvent.getDocument();
PdfPage page = docEvent.getPage();
Rectangle pageSize = page.getPageSize();
Canvas canvas = new Canvas(
new PdfCanvas(page.getLastContentStream(), page.getResources(), pdf),
pageSize);
Paragraph p = new Paragraph(String.valueOf(pdf.getPageNumber(page)))
.setFontSize(PAGE_FONT_SIZE);
// 默认底部中间位置
canvas.showTextAligned(p,
(pageSize.getLeft() + pageSize.getRight()) / 2,
pageSize.getBottom() + BORDER_INTERVAL,
TextAlignment.CENTER);
canvas.close();
}
}
/**
* pdf签名图片设置
* <strong>
* 当pdf文档签名过多时,可能存在页码遮挡、超出边界问题。签名配置参照{@linkplain SignStyle}
* </strong>
*/
@AllArgsConstructor
private static class PdfSignImgMarker implements IEventHandler {
/**
* 签名图片
*/
private List<Image> images;
/**
* 总页数
*/
private int pages;
@Override
public void handleEvent(Event event) {
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);
if (SIGN_EACH_PAGE) {
this.addSignImg(canvas, pdf.getNumberOfPages(), pageSize.getBottom() + BORDER_INTERVAL);
} else {
if (pages == pdf.getNumberOfPages()) {
this.addSignImg(canvas, pdf.getNumberOfPages(), pageSize.getBottom() + BORDER_INTERVAL);
}
}
canvas.close();
}
/**
* 添加居中对齐的签名图片
* <strong>
* 以页码为中心,先左后右交替向两边扩散。图片配置参数参照{@linkplain ImgLayout}。仅支持{@code PageSize.A4}对齐。
* </strong>
*
* @param canvas
* @param numberOfPages
* @param bottom
*/
private void addCenterSignImg(Canvas canvas, int numberOfPages, float bottom) {
float center = PageSize.A4.getRight() / 2;
for (int i = 0,
leftStart = (int) (center - BORDER_INTERVAL),
rightStart = (int) (center + BORDER_INTERVAL),
imageSize = images.size(); i < imageSize; i++) {
Image image = images.get(i);
if (i % 2 == 0) {
//1.偶数向左扩散
image.setFixedPosition(numberOfPages, leftStart -= IMAGE_WIDTH, bottom);
} else {
//2.奇数向右扩散
image.setFixedPosition(numberOfPages, rightStart, bottom);
rightStart += IMAGE_WIDTH;
}
image.scaleAbsolute(IMAGE_WIDTH, IMAGE_HEIGHT);// 自定义大小
canvas.add(images.get(i));
}
}
/**
* 添加左对齐的签名图片
* <strong>
* 除去默认边距后,从左向右排列签名。图片配置参数参照{@linkplain ImgLayout}。仅支持{@code PageSize.A4}对齐。
* </strong>
*
* @param canvas
* @param numberOfPages
* @param bottom
*/
private void addLeftSignImg(Canvas canvas, int numberOfPages, float bottom) {
for (int i = 0, start = BORDER_INTERVAL, imageSize = images.size(); i < imageSize; i++) {
Image image = images.get(i);
image.setFixedPosition(numberOfPages, start, bottom);
image.scaleAbsolute(IMAGE_WIDTH, IMAGE_HEIGHT);// 自定义大小
canvas.add(images.get(i));
start += IMAGE_WIDTH;
}
}
/**
* 添加右对齐的签名图片
* <strong>
* 除去默认边距后,从右向左排列签名。图片配置参数参照{@linkplain ImgLayout}。仅支持{@code PageSize.A4}对齐。
* </strong>
*
* @param canvas
* @param numberOfPages
* @param bottom
*/
private void addRightSignImg(Canvas canvas, int numberOfPages, float bottom) {
float width = PageSize.A4.getRight();
for (int i = 0, start = (int) (width - BORDER_INTERVAL), imageSize = images.size(); i < imageSize; i++) {
Image image = images.get(i);
image.setFixedPosition(numberOfPages, start -= IMAGE_WIDTH, bottom);
image.scaleAbsolute(IMAGE_WIDTH, IMAGE_HEIGHT);// 自定义大小
canvas.add(images.get(i));
}
}
/**
* 添加签名图片
*
* @param canvas
* @param numberOfPages
* @param bottom
* @throws IllegalArgumentException
*/
private void addSignImg(Canvas canvas, int numberOfPages, float bottom) {
switch (SignStyle.SIGN_ALIGN) {
case LEFT:
addLeftSignImg(canvas, numberOfPages, bottom);
break;
case RIGHT:
addRightSignImg(canvas, numberOfPages, bottom);
break;
case CENTER:
addCenterSignImg(canvas, numberOfPages, bottom);
break;
default:
throw new IllegalArgumentException("仅支持 居中、向左、向右对齐参数");
}
}
}
}
- pdf签名测试类
import com.example.demo.Demo1Application;
import com.itextpdf.commons.utils.FileUtil;
import com.itextpdf.layout.element.Image;
import org.assertj.core.util.Lists;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.File;
import java.util.List;
import static com.example.demo.pdf.PdfUtil.*;
/**
* pdf sign test
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Demo1Application.class)
public class PdfSignTest {
/**
* 测试源
*/
public static final String SOURCE_HTML = "<p style=\"text-align: center;\"><span style=\"color: black; font-size: 22px; font-family: 宋体;\"><strong>XXX法院</strong></span></p><p style=\"text-align: center;\"><span style=\"color: black; font-size: 22px; font-family: 宋体;\"><strong>法庭审理笔录</strong></span></p><p style=\"text-align: right;\"><span style=\"color: black; font-size: 16px; font-family: 仿宋;\">(2022)陕01100100号</span></p><p style=\"text-align: left;\"><span style=\"color: black; font-size: 16px; font-family: 仿宋;\">案由:清算组成员责任纠纷</span></p><p style=\"text-align: left;\"><span style=\"color: black; font-size: 16px; font-family: 仿宋;\">开庭时间:2023-02-02 23:00:00</span></p><p style=\"text-align: left;\"><span style=\"color: black; font-size: 16px; font-family: 仿宋;\">开庭地点:远程调解</span></p><p style=\"text-align: left;\"><span style=\"color: black; font-size: 16px; font-family: 仿宋;\">审判员:法官2</span></p><p style=\"text-align: left;\"><span style=\"color: black; font-size: 16px; font-family: 仿宋;\">书记员:陈书记</span></p><p style=\"text-align: left;\"><span style=\"color: black; font-size: 16px; font-family: 仿宋;\">记录如下:</span></p>";
/**
* 签名图片源
*/
public static final String SIGN_IMG_URL_HTTP = "http://10.10.20.172:9000/hlwft/2_1659677726496.jpg";
public static final String SIGN_IMG_URL_HTTPS = "https://hlwft.tongtech.com:20011/m_file/互联网+诉讼_1671435392069.jpg";
@Test
public void testPdfSignHttp() {
try {
//1.转换html->pdf
File source = new File("test_source_http.pdf");
if (!source.exists()){
source = FileUtil.createTempFile("test_source_http.pdf");
}
html2Pdf(SOURCE_HTML, source);
//2.pdf签名
List<Image> images = Lists.newArrayList(createImage(SIGN_IMG_URL_HTTP));
File signPdf = sign(source,
images,
"test_target_http.pdf");
if (source.exists()) {
System.out.println(source.getAbsolutePath());
}
if (signPdf.exists()) {
System.out.println(signPdf.getAbsolutePath());
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testPdfSignHttps() {
try {
//1.转换html->pdf
File source = new File("test_source_https.pdf");
if (!source.exists()){
source = FileUtil.createTempFile("test_source_https.pdf");
}
html2Pdf(SOURCE_HTML, source);
//2.pdf签名
List<Image> images = Lists.newArrayList(createImage(SIGN_IMG_URL_HTTPS));
File signPdf = sign(source,
images,
"test_target_https.pdf");
if (source.exists()) {
System.out.println(source.getAbsolutePath());
}
if (signPdf.exists()) {
System.out.println(signPdf.getAbsolutePath());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
四、测试结果日志和签名截图
- 测试日志
D:\jdk8u281\bin\java.exe -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:D:\IntelliJ IDEA 2020.3.2\lib\idea_rt.jar=61236:D:\IntelliJ IDEA 2020.3.2\bin" -Dfile.encoding=UTF-8 -classpath "D:\IntelliJ IDEA 2020.3.2\lib\idea_rt.jar;D:\IntelliJ IDEA 2020.3.2\plugins\junit\lib\junit5-rt.jar;D:\IntelliJ IDEA 2020.3.2\plugins\junit\lib\junit-rt.jar;D:\jdk8u281\jre\lib\charsets.jar;D:\jdk8u281\jre\lib\deploy.jar;D:\jdk8u281\jre\lib\ext\access-bridge-64.jar;D:\jdk8u281\jre\lib\ext\cldrdata.jar;D:\jdk8u281\jre\lib\ext\dnsns.jar;D:\jdk8u281\jre\lib\ext\jaccess.jar;D:\jdk8u281\jre\lib\ext\jfxrt.jar;D:\jdk8u281\jre\lib\ext\localedata.jar;D:\jdk8u281\jre\lib\ext\nashorn.jar;D:\jdk8u281\jre\lib\ext\sunec.jar;D:\jdk8u281\jre\lib\ext\sunjce_provider.jar;D:\jdk8u281\jre\lib\ext\sunmscapi.jar;D:\jdk8u281\jre\lib\ext\sunpkcs11.jar;D:\jdk8u281\jre\lib\ext\zipfs.jar;D:\jdk8u281\jre\lib\javaws.jar;D:\jdk8u281\jre\lib\jce.jar;D:\jdk8u281\jre\lib\jfr.jar;D:\jdk8u281\jre\lib\jfxswt.jar;D:\jdk8u281\jre\lib\jsse.jar;D:\jdk8u281\jre\lib\management-agent.jar;D:\jdk8u281\jre\lib\plugin.jar;D:\jdk8u281\jre\lib\resources.jar;D:\jdk8u281\jre\lib\rt.jar;E:\jeecg-code\demo1\target\test-classes;E:\jeecg-code\demo1\target\classes;D:\mvn_repository\org\springframework\boot\spring-boot-starter-web\2.3.12.RELEASE\spring-boot-starter-web-2.3.12.RELEASE.jar;D:\mvn_repository\org\springframework\boot\spring-boot-starter\2.3.12.RELEASE\spring-boot-starter-2.3.12.RELEASE.jar;D:\mvn_repository\org\springframework\boot\spring-boot\2.3.12.RELEASE\spring-boot-2.3.12.RELEASE.jar;D:\mvn_repository\org\springframework\boot\spring-boot-autoconfigure\2.3.12.RELEASE\spring-boot-autoconfigure-2.3.12.RELEASE.jar;D:\mvn_repository\org\springframework\boot\spring-boot-starter-logging\2.3.12.RELEASE\spring-boot-starter-logging-2.3.12.RELEASE.jar;D:\mvn_repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;D:\mvn_repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;D:\mvn_repository\org\apache\logging\log4j\log4j-to-slf4j\2.13.3\log4j-to-slf4j-2.13.3.jar;D:\mvn_repository\org\apache\logging\log4j\log4j-api\2.13.3\log4j-api-2.13.3.jar;D:\mvn_repository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;D:\mvn_repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;D:\mvn_repository\org\yaml\snakeyaml\1.26\snakeyaml-1.26.jar;D:\mvn_repository\org\springframework\boot\spring-boot-starter-json\2.3.12.RELEASE\spring-boot-starter-json-2.3.12.RELEASE.jar;D:\mvn_repository\com\fasterxml\jackson\core\jackson-databind\2.11.4\jackson-databind-2.11.4.jar;D:\mvn_repository\com\fasterxml\jackson\core\jackson-annotations\2.11.4\jackson-annotations-2.11.4.jar;D:\mvn_repository\com\fasterxml\jackson\core\jackson-core\2.11.4\jackson-core-2.11.4.jar;D:\mvn_repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.11.4\jackson-datatype-jdk8-2.11.4.jar;D:\mvn_repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.11.4\jackson-datatype-jsr310-2.11.4.jar;D:\mvn_repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.11.4\jackson-module-parameter-names-2.11.4.jar;D:\mvn_repository\org\springframework\boot\spring-boot-starter-tomcat\2.3.12.RELEASE\spring-boot-starter-tomcat-2.3.12.RELEASE.jar;D:\mvn_repository\org\apache\tomcat\embed\tomcat-embed-core\9.0.46\tomcat-embed-core-9.0.46.jar;D:\mvn_repository\org\glassfish\jakarta.el\3.0.3\jakarta.el-3.0.3.jar;D:\mvn_repository\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.46\tomcat-embed-websocket-9.0.46.jar;D:\mvn_repository\org\springframework\spring-web\5.2.15.RELEASE\spring-web-5.2.15.RELEASE.jar;D:\mvn_repository\org\springframework\spring-beans\5.2.15.RELEASE\spring-beans-5.2.15.RELEASE.jar;D:\mvn_repository\org\springframework\spring-webmvc\5.2.15.RELEASE\spring-webmvc-5.2.15.RELEASE.jar;D:\mvn_repository\org\springframework\spring-aop\5.2.15.RELEASE\spring-aop-5.2.15.RELEASE.jar;D:\mvn_repository\org\springframework\spring-context\5.2.15.RELEASE\spring-context-5.2.15.RELEASE.jar;D:\mvn_repository\org\springframework\spring-expression\5.2.15.RELEASE\spring-expression-5.2.15.RELEASE.jar;D:\mvn_repository\org\springframework\boot\spring-boot-starter-test\2.3.12.RELEASE\spring-boot-starter-test-2.3.12.RELEASE.jar;D:\mvn_repository\org\springframework\boot\spring-boot-test\2.3.12.RELEASE\spring-boot-test-2.3.12.RELEASE.jar;D:\mvn_repository\org\springframework\boot\spring-boot-test-autoconfigure\2.3.12.RELEASE\spring-boot-test-autoconfigure-2.3.12.RELEASE.jar;D:\mvn_repository\com\jayway\jsonpath\json-path\2.4.0\json-path-2.4.0.jar;D:\mvn_repository\net\minidev\json-smart\2.3.1\json-smart-2.3.1.jar;D:\mvn_repository\net\minidev\accessors-smart\2.3.1\accessors-smart-2.3.1.jar;D:\mvn_repository\org\ow2\asm\asm\5.0.4\asm-5.0.4.jar;D:\mvn_repository\jakarta\xml\bind\jakarta.xml.bind-api\2.3.3\jakarta.xml.bind-api-2.3.3.jar;D:\mvn_repository\jakarta\activation\jakarta.activation-api\1.2.2\jakarta.activation-api-1.2.2.jar;D:\mvn_repository\org\assertj\assertj-core\3.16.1\assertj-core-3.16.1.jar;D:\mvn_repository\org\hamcrest\hamcrest\2.2\hamcrest-2.2.jar;D:\mvn_repository\org\junit\jupiter\junit-jupiter\5.6.3\junit-jupiter-5.6.3.jar;D:\mvn_repository\org\junit\jupiter\junit-jupiter-api\5.6.3\junit-jupiter-api-5.6.3.jar;D:\mvn_repository\org\opentest4j\opentest4j\1.2.0\opentest4j-1.2.0.jar;D:\mvn_repository\org\junit\platform\junit-platform-commons\1.6.3\junit-platform-commons-1.6.3.jar;D:\mvn_repository\org\junit\jupiter\junit-jupiter-params\5.6.3\junit-jupiter-params-5.6.3.jar;D:\mvn_repository\org\junit\jupiter\junit-jupiter-engine\5.6.3\junit-jupiter-engine-5.6.3.jar;D:\mvn_repository\org\junit\vintage\junit-vintage-engine\5.6.3\junit-vintage-engine-5.6.3.jar;D:\mvn_repository\org\apiguardian\apiguardian-api\1.1.0\apiguardian-api-1.1.0.jar;D:\mvn_repository\org\junit\platform\junit-platform-engine\1.6.3\junit-platform-engine-1.6.3.jar;D:\mvn_repository\junit\junit\4.13.2\junit-4.13.2.jar;D:\mvn_repository\org\mockito\mockito-core\3.3.3\mockito-core-3.3.3.jar;D:\mvn_repository\net\bytebuddy\byte-buddy\1.10.22\byte-buddy-1.10.22.jar;D:\mvn_repository\net\bytebuddy\byte-buddy-agent\1.10.22\byte-buddy-agent-1.10.22.jar;D:\mvn_repository\org\objenesis\objenesis\2.6\objenesis-2.6.jar;D:\mvn_repository\org\mockito\mockito-junit-jupiter\3.3.3\mockito-junit-jupiter-3.3.3.jar;D:\mvn_repository\org\skyscreamer\jsonassert\1.5.0\jsonassert-1.5.0.jar;D:\mvn_repository\com\vaadin\external\google\android-json\0.0.20131108.vaadin1\android-json-0.0.20131108.vaadin1.jar;D:\mvn_repository\org\springframework\spring-core\5.2.15.RELEASE\spring-core-5.2.15.RELEASE.jar;D:\mvn_repository\org\springframework\spring-jcl\5.2.15.RELEASE\spring-jcl-5.2.15.RELEASE.jar;D:\mvn_repository\org\springframework\spring-test\5.2.15.RELEASE\spring-test-5.2.15.RELEASE.jar;D:\mvn_repository\org\xmlunit\xmlunit-core\2.7.0\xmlunit-core-2.7.0.jar;D:\mvn_repository\org\projectlombok\lombok\1.18.20\lombok-1.18.20.jar;D:\mvn_repository\com\tencentcloudapi\tencentcloud-sdk-java\3.1.722\tencentcloud-sdk-java-3.1.722.jar;D:\mvn_repository\commons-logging\commons-logging\1.2\commons-logging-1.2.jar;D:\mvn_repository\com\squareup\okio\okio\3.2.0\okio-3.2.0.jar;D:\mvn_repository\com\squareup\okio\okio-jvm\3.2.0\okio-jvm-3.2.0.jar;D:\mvn_repository\org\jetbrains\kotlin\kotlin-stdlib-jdk8\1.3.72\kotlin-stdlib-jdk8-1.3.72.jar;D:\mvn_repository\org\jetbrains\kotlin\kotlin-stdlib-jdk7\1.3.72\kotlin-stdlib-jdk7-1.3.72.jar;D:\mvn_repository\com\google\code\gson\gson\2.8.7\gson-2.8.7.jar;D:\mvn_repository\javax\xml\bind\jaxb-api\2.3.1\jaxb-api-2.3.1.jar;D:\mvn_repository\javax\activation\javax.activation-api\1.2.0\javax.activation-api-1.2.0.jar;D:\mvn_repository\com\squareup\okhttp3\logging-interceptor\3.14.9\logging-interceptor-3.14.9.jar;D:\mvn_repository\org\ini4j\ini4j\0.5.4\ini4j-0.5.4.jar;D:\mvn_repository\com\itextpdf\html2pdf\4.0.4\html2pdf-4.0.4.jar;D:\mvn_repository\com\itextpdf\forms\7.2.4\forms-7.2.4.jar;D:\mvn_repository\com\itextpdf\kernel\7.2.4\kernel-7.2.4.jar;D:\mvn_repository\com\itextpdf\io\7.2.4\io-7.2.4.jar;D:\mvn_repository\com\itextpdf\commons\7.2.4\commons-7.2.4.jar;D:\mvn_repository\org\bouncycastle\bcpkix-jdk15on\1.70\bcpkix-jdk15on-1.70.jar;D:\mvn_repository\org\bouncycastle\bcutil-jdk15on\1.70\bcutil-jdk15on-1.70.jar;D:\mvn_repository\org\bouncycastle\bcprov-jdk15on\1.70\bcprov-jdk15on-1.70.jar;D:\mvn_repository\com\itextpdf\layout\7.2.4\layout-7.2.4.jar;D:\mvn_repository\com\itextpdf\svg\7.2.4\svg-7.2.4.jar;D:\mvn_repository\com\itextpdf\styled-xml-parser\7.2.4\styled-xml-parser-7.2.4.jar;D:\mvn_repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;D:\mvn_repository\com\itextpdf\font-asian\7.2.4\font-asian-7.2.4.jar;D:\mvn_repository\commons-fileupload\commons-fileupload\1.4\commons-fileupload-1.4.jar;D:\mvn_repository\commons-io\commons-io\2.2\commons-io-2.2.jar;D:\mvn_repository\org\apache\commons\commons-lang3\3.12.0\commons-lang3-3.12.0.jar;D:\mvn_repository\com\squareup\okhttp3\okhttp\4.9.3\okhttp-4.9.3.jar;D:\mvn_repository\org\jetbrains\kotlin\kotlin-stdlib\1.3.72\kotlin-stdlib-1.3.72.jar;D:\mvn_repository\org\jetbrains\kotlin\kotlin-stdlib-common\1.3.72\kotlin-stdlib-common-1.3.72.jar;D:\mvn_repository\org\jetbrains\annotations\13.0\annotations-13.0.jar;D:\mvn_repository\org\apache\httpcomponents\httpcore\4.4.16\httpcore-4.4.16.jar" com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit4 com.example.demo.pdf.PdfSignTest
17:30:21.726 [main] DEBUG org.springframework.test.context.junit4.SpringJUnit4ClassRunner - SpringJUnit4ClassRunner constructor called with [class com.example.demo.pdf.PdfSignTest]
17:30:21.735 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating CacheAwareContextLoaderDelegate from class [org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate]
17:30:21.745 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating BootstrapContext using constructor [public org.springframework.test.context.support.DefaultBootstrapContext(java.lang.Class,org.springframework.test.context.CacheAwareContextLoaderDelegate)]
17:30:21.795 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating TestContextBootstrapper for test class [com.example.demo.pdf.PdfSignTest] from class [org.springframework.boot.test.context.SpringBootTestContextBootstrapper]
17:30:21.817 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Neither @ContextConfiguration nor @ContextHierarchy found for test class [com.example.demo.pdf.PdfSignTest], using SpringBootContextLoader
17:30:21.823 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.example.demo.pdf.PdfSignTest]: class path resource [com/example/demo/pdf/PdfSignTest-context.xml] does not exist
17:30:21.824 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.example.demo.pdf.PdfSignTest]: class path resource [com/example/demo/pdf/PdfSignTestContext.groovy] does not exist
17:30:21.824 [main] INFO org.springframework.test.context.support.AbstractContextLoader - Could not detect default resource locations for test class [com.example.demo.pdf.PdfSignTest]: no resource found for suffixes {-context.xml, Context.groovy}.
17:30:21.885 [main] DEBUG org.springframework.test.context.support.ActiveProfilesUtils - Could not find an 'annotation declaring class' for annotation type [org.springframework.test.context.ActiveProfiles] and class [com.example.demo.pdf.PdfSignTest]
17:30:22.049 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - @TestExecutionListeners is not present for class [com.example.demo.pdf.PdfSignTest]: using defaults.
17:30:22.049 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener, org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener, org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
17:30:22.062 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Skipping candidate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener] due to a missing dependency. Specify custom listener classes or make the default listener classes and their required dependencies available. Offending class: [org/springframework/transaction/interceptor/TransactionAttributeSource]
17:30:22.062 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Skipping candidate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener] due to a missing dependency. Specify custom listener classes or make the default listener classes and their required dependencies available. Offending class: [org/springframework/transaction/interceptor/TransactionAttribute]
17:30:22.062 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@479d31f3, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@40ef3420, org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener@498d318c, org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener@6e171cd7, org.springframework.test.context.support.DirtiesContextTestExecutionListener@402bba4f, org.springframework.test.context.event.EventPublishingTestExecutionListener@795cd85e, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener@59fd97a8, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener@f5ac9e4, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener@123ef382, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener@dbf57b3, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener@384ad17b, org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener@61862a7f]
17:30:22.064 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.example.demo.pdf.PdfSignTest]
17:30:22.065 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.example.demo.pdf.PdfSignTest]
17:30:22.067 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.example.demo.pdf.PdfSignTest]
17:30:22.067 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.example.demo.pdf.PdfSignTest]
17:30:22.067 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.example.demo.pdf.PdfSignTest]
17:30:22.067 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.example.demo.pdf.PdfSignTest]
17:30:22.079 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.example.demo.pdf.PdfSignTest]
17:30:22.079 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.example.demo.pdf.PdfSignTest]
17:30:22.080 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.example.demo.pdf.PdfSignTest]
17:30:22.080 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.example.demo.pdf.PdfSignTest]
17:30:22.081 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.example.demo.pdf.PdfSignTest]
17:30:22.081 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.example.demo.pdf.PdfSignTest]
17:30:22.087 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - Before test class: context [DefaultTestContext@5158b42f testClass = PdfSignTest, testInstance = [null], testMethod = [null], testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@595b007d testClass = PdfSignTest, locations = '{}', classes = '{class com.example.demo.Demo1Application}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@6043cd28, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@5bfbf16f, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@1b26f7b2, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@13eb8acf, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@3d99d22e], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true]], class annotated with @DirtiesContext [false] with mode [null].
17:30:22.089 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.example.demo.pdf.PdfSignTest]
17:30:22.090 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.example.demo.pdf.PdfSignTest]
17:30:22.122 [main] DEBUG org.springframework.test.context.support.TestPropertySourceUtils - Adding inlined properties to environment: {spring.jmx.enabled=false, org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.12.RELEASE)
2023-03-29 17:30:22.526 INFO 2356 --- [ main] com.example.demo.pdf.PdfSignTest : Starting PdfSignTest on DESKTOP-2F8ODD2 with PID 2356 (started by dell in E:\jeecg-code\demo1)
2023-03-29 17:30:22.527 INFO 2356 --- [ main] com.example.demo.pdf.PdfSignTest : No active profile set, falling back to default profiles: default
2023-03-29 17:30:22.791 WARN 2356 --- [kground-preinit] o.s.h.c.j.Jackson2ObjectMapperBuilder : For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath
2023-03-29 17:30:30.568 INFO 2356 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2023-03-29 17:30:30.697 INFO 2356 --- [ main] o.s.b.a.w.s.WelcomePageHandlerMapping : Adding welcome page: class path resource [static/index.html]
2023-03-29 17:30:30.898 INFO 2356 --- [ main] com.example.demo.pdf.PdfSignTest : Started PdfSignTest in 8.763 seconds (JVM running for 9.721)
E:\jeecg-code\demo1\test_source_http.pdf
E:\jeecg-code\demo1\test_target_http.pdf
E:\jeecg-code\demo1\test_source_https.pdf
E:\jeecg-code\demo1\test_target_https.pdf
2023-03-29 17:30:32.700 INFO 2356 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
Process finished with exit code 0
- 签名截图
五、其他说明
当https访问网络签名图片格式为jpg时,如果因为协议问题,响应体格式未自动转换为jpeg,在PdfUtil.createImage(String)开启注释代码的情况下,需要引入如下jpg图片解析相关作业类。
- jpg图片数据类
import com.itextpdf.io.image.ImageData;
import com.itextpdf.io.image.ImageType;
import java.net.URL;
public class JpgImageData extends ImageData {
protected JpgImageData(URL url) {
super(url, ImageType.JPEG);
}
protected JpgImageData(byte[] bytes) {
super(bytes, ImageType.JPEG);
}
}
- jpg图片数据处理类
import com.itextpdf.commons.utils.MessageFormatUtil;
import com.itextpdf.io.colors.IccProfile;
import com.itextpdf.io.exceptions.IOException;
import com.itextpdf.io.image.ImageData;
import com.itextpdf.io.image.ImageType;
import com.itextpdf.io.logs.IoLogMessageConstant;
import com.itextpdf.io.util.StreamUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
public final class JpgImageHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(JpgImageHandler.class);
/**
* This is a type of marker.
*/
private static final int NOT_A_MARKER = -1;
/**
* This is a type of marker.
*/
private static final int VALID_MARKER = 0;
/**
* Acceptable Jpeg markers.
*/
private static final int[] VALID_MARKERS = {0xC0, 0xC1, 0xC2};
/**
* This is a type of marker.
*/
private static final int UNSUPPORTED_MARKER = 1;
/**
* Unsupported Jpeg markers.
*/
private static final int[] UNSUPPORTED_MARKERS = {0xC3, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF};
/**
* This is a type of marker.
*/
private static final int NOPARAM_MARKER = 2;
/**
* Jpeg markers without additional parameters.
*/
private static final int[] NOPARAM_MARKERS = {0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0x01};
/**
* Marker value
*/
private static final int M_APP0 = 0xE0;
/**
* Marker value
*/
private static final int M_APP2 = 0xE2;
/**
* Marker value
*/
private static final int M_APPE = 0xEE;
/**
* Marker value for Photoshop IRB
*/
private static final int M_APPD = 0xED;
/**
* sequence that is used in all Jpeg files
*/
private static final byte[] JFIF_ID = {0x4A, 0x46, 0x49, 0x46, 0x00};
/**
* sequence preceding Photoshop resolution data
*/
private static final byte[] PS_8BIM_RESO = {0x38, 0x42, 0x49, 0x4d, 0x03, (byte) 0xed};
/**
* Process the passed Image data as a JPEG image.
* Image is loaded and all image attributes are initialized and/or updated.
*
* @param image the image to process as a JPEG image
*/
public static void processImage(ImageData image) {
if (image.getOriginalType() != ImageType.JPEG) {
throw new IllegalArgumentException("JPEG image expected");
}
InputStream jpegStream = null;
try {
String errorID = "Byte array";
jpegStream = new java.io.ByteArrayInputStream(image.getData());
processParameters(jpegStream, errorID, image);
} catch (java.io.IOException e) {
throw new IOException(IOException.JpegImageException, e);
} finally {
if (jpegStream != null) {
try {
jpegStream.close();
} catch (java.io.IOException ignore) {
}
}
}
updateAttributes(image);
}
static void attemptToSetIccProfileToImage(byte[][] icc, ImageData image) {
if (icc != null) {
int total = 0;
for (final byte[] value : icc) {
if (value == null) {
return;
}
total += value.length - 14;
}
byte[] ficc = new byte[total];
total = 0;
for (final byte[] bytes : icc) {
System.arraycopy(bytes, 14, ficc, total, bytes.length - 14);
total += bytes.length - 14;
}
try {
image.setProfile(IccProfile.getInstance(ficc, image.getColorEncodingComponentsNumber()));
} catch (Exception e) {
LOGGER.error(MessageFormatUtil.format(
IoLogMessageConstant.DURING_CONSTRUCTION_OF_ICC_PROFILE_ERROR_OCCURRED,
e.getClass().getSimpleName(), e.getMessage()));
}
}
}
private static void updateAttributes(ImageData image) {
image.setFilter("DCTDecode");
if (image.getColorTransform() == 0) {
image.getDecodeParms().put("ColorTransform", 0);
}
int colorComponents = image.getColorEncodingComponentsNumber();
if (colorComponents != 1 && colorComponents != 3 && image.isInverted()) {
image.setDecode(new float[]{1, 0, 1, 0, 1, 0, 1, 0});
}
}
/**
* This method checks if the image is a valid JPEG and processes some parameters.
*
* @throws IOException
* @throws java.io.IOException
*/
private static void processParameters(InputStream jpegStream, String errorID, ImageData image) throws java.io.IOException {
byte[][] icc = null;
if (jpegStream.read() != 0xFF || jpegStream.read() != 0xD8) {
throw new IOException(IOException._1IsNotAValidJpegFile).setMessageParams(errorID);
}
boolean firstPass = true;
int len;
while (true) {
int v = jpegStream.read();
if (v < 0) {
throw new IOException(IOException.PrematureEofWhileReadingJpeg);
}
if (v == 0xFF) {
int marker = jpegStream.read();
if (firstPass && marker == M_APP0) {
firstPass = false;
len = getShort(jpegStream);
if (len < 16) {
StreamUtil.skip(jpegStream, len - 2);
continue;
}
byte[] bcomp = new byte[JFIF_ID.length];
int r = jpegStream.read(bcomp);
if (r != bcomp.length) {
throw new IOException(IOException._1CorruptedJfifMarker).setMessageParams(errorID);
}
boolean found = true;
for (int k = 0; k < bcomp.length; ++k) {
if (bcomp[k] != JFIF_ID[k]) {
found = false;
break;
}
}
if (!found) {
StreamUtil.skip(jpegStream, len - 2 - bcomp.length);
continue;
}
StreamUtil.skip(jpegStream, 2);
int units = jpegStream.read();
int dx = getShort(jpegStream);
int dy = getShort(jpegStream);
if (units == 1) {
image.setDpi(dx, dy);
} else if (units == 2) {
image.setDpi((int) (dx * 2.54f + 0.5f), (int) (dy * 2.54f + 0.5f));
}
StreamUtil.skip(jpegStream, len - 2 - bcomp.length - 7);
continue;
}
if (marker == M_APPE) {
len = getShort(jpegStream) - 2;
byte[] byteappe = new byte[len];
for (int k = 0; k < len; ++k) {
byteappe[k] = (byte) jpegStream.read();
}
if (byteappe.length >= 12) {
String appe = new String(byteappe, 0, 5, "ISO-8859-1");
if (appe.equals("Adobe")) {
image.setInverted(true);
}
}
continue;
}
if (marker == M_APP2) {
len = getShort(jpegStream) - 2;
byte[] byteapp2 = new byte[len];
for (int k = 0; k < len; ++k) {
byteapp2[k] = (byte) jpegStream.read();
}
if (byteapp2.length >= 14) {
String app2 = new String(byteapp2, 0, 11, "ISO-8859-1");
if (app2.equals("ICC_PROFILE")) {
int order = byteapp2[12] & 0xff;
int count = byteapp2[13] & 0xff;
// some jpeg producers don't know how to count to 1
if (order < 1) {
order = 1;
}
if (count < 1) {
count = 1;
}
if (icc == null) {
icc = new byte[count][];
}
icc[order - 1] = byteapp2;
}
}
continue;
}
if (marker == M_APPD) {
len = getShort(jpegStream) - 2;
byte[] byteappd = new byte[len];
for (int k = 0; k < len; k++) {
byteappd[k] = (byte) jpegStream.read();
}
// search for '8BIM Resolution' marker
int k;
for (k = 0; k < len - PS_8BIM_RESO.length; k++) {
boolean found = true;
for (int j = 0; j < PS_8BIM_RESO.length; j++) {
if (byteappd[k + j] != PS_8BIM_RESO[j]) {
found = false;
break;
}
}
if (found) {
break;
}
}
k += PS_8BIM_RESO.length;
if (k < len - PS_8BIM_RESO.length) {
// "PASCAL String" for name, i.e. string prefix with length byte
// padded to be even length; 2 null bytes if empty
byte namelength = byteappd[k];
// add length byte
namelength++;
// add padding
if (namelength % 2 == 1) {
namelength++;
}
// just skip name
k += namelength;
// size of the resolution data
int resosize = (byteappd[k] << 24) + (byteappd[k + 1] << 16) + (byteappd[k + 2] << 8) + byteappd[k + 3];
// should be 16
if (resosize != 16) {
// fail silently, for now
//System.err.println("DEBUG: unsupported resolution IRB size");
continue;
}
k += 4;
int dx = (byteappd[k] << 8) + (byteappd[k + 1] & 0xff);
k += 2;
// skip 2 unknown bytes
k += 2;
int unitsx = (byteappd[k] << 8) + (byteappd[k + 1] & 0xff);
k += 2;
// skip 2 unknown bytes
k += 2;
int dy = (byteappd[k] << 8) + (byteappd[k + 1] & 0xff);
k += 2;
// skip 2 unknown bytes
k += 2;
int unitsy = (byteappd[k] << 8) + (byteappd[k + 1] & 0xff);
if (unitsx == 1 || unitsx == 2) {
dx = (unitsx == 2 ? (int) (dx * 2.54f + 0.5f) : dx);
// make sure this is consistent with JFIF data
if (image.getDpiX() != 0 && image.getDpiX() != dx) {
LOGGER.debug(MessageFormatUtil.format("Inconsistent metadata (dpiX: {0} vs {1})", image.getDpiX(), dx));
} else {
image.setDpi(dx, image.getDpiY());
}
}
if (unitsy == 1 || unitsy == 2) {
dy = (unitsy == 2 ? (int) (dy * 2.54f + 0.5f) : dy);
// make sure this is consistent with JFIF data
if (image.getDpiY() != 0 && image.getDpiY() != dy) {
LOGGER.debug(MessageFormatUtil.format("Inconsistent metadata (dpiY: {0} vs {1})", image.getDpiY(), dy));
} else {
image.setDpi(image.getDpiX(), dx);
}
}
}
continue;
}
firstPass = false;
int markertype = marker(marker);
if (markertype == VALID_MARKER) {
StreamUtil.skip(jpegStream, 2);
if (jpegStream.read() != 0x08) {
throw new IOException(IOException._1MustHave8BitsPerComponent).setMessageParams(errorID);
}
image.setHeight(getShort(jpegStream));
image.setWidth(getShort(jpegStream));
image.setColorEncodingComponentsNumber(jpegStream.read());
image.setBpc(8);
break;
} else if (markertype == UNSUPPORTED_MARKER) {
throw new IOException(IOException._1UnsupportedJpegMarker2).setMessageParams(errorID, Integer.toString(marker));
} else if (markertype != NOPARAM_MARKER) {
StreamUtil.skip(jpegStream, getShort(jpegStream) - 2);
}
}
}
attemptToSetIccProfileToImage(icc, image);
}
/**
* Reads a short from the <CODE>InputStream</CODE>.
*
* @param jpegStream the <CODE>InputStream</CODE>
* @return an int
* @throws java.io.IOException
*/
private static int getShort(InputStream jpegStream) throws java.io.IOException {
return (jpegStream.read() << 8) + jpegStream.read();
}
/**
* Returns a type of marker.
*
* @param marker an int
* @return a type: <VAR>VALID_MARKER</VAR>, <VAR>UNSUPPORTED_MARKER</VAR> or <VAR>NOPARAM_MARKER</VAR>
*/
private static int marker(int marker) {
for (int i = 0; i < VALID_MARKERS.length; i++) {
if (marker == VALID_MARKERS[i]) {
return VALID_MARKER;
}
}
for (int i = 0; i < NOPARAM_MARKERS.length; i++) {
if (marker == NOPARAM_MARKERS[i]) {
return NOPARAM_MARKER;
}
}
for (int i = 0; i < UNSUPPORTED_MARKERS.length; i++) {
if (marker == UNSUPPORTED_MARKERS[i]) {
return UNSUPPORTED_MARKER;
}
}
return NOT_A_MARKER;
}
}