├── .gitignore
├── LICENSE
├── README.md
├── docs
└── images
│ ├── pdf-cert.png
│ ├── product.png
│ ├── seal-template.png
│ ├── seal-ym.png
│ ├── signature.png
│ └── verify.png
├── pom.xml
└── src
└── main
├── java
└── com
│ └── resrun
│ ├── OpenSign.java
│ ├── controller
│ ├── OpenSignController.java
│ ├── TestController.java
│ └── vo
│ │ ├── base
│ │ └── Result.java
│ │ ├── request
│ │ ├── ClipSealRequest.java
│ │ ├── GenerateSealRequest.java
│ │ ├── PositionRequest.java
│ │ ├── SignRequest.java
│ │ └── VerifyRequest.java
│ │ └── response
│ │ ├── SealResponse.java
│ │ ├── SignResponse.java
│ │ └── VerifyResponse.java
│ ├── enums
│ ├── SealColorEnum.java
│ ├── SignStatus.java
│ └── SignTypeEnum.java
│ ├── service
│ ├── cert
│ │ └── CertService.java
│ ├── image
│ │ ├── DateSealService.java
│ │ ├── EntSealClipService.java
│ │ ├── EntSealGenerateService.java
│ │ ├── PdfConvertImageService.java
│ │ └── PersonalSealService.java
│ ├── pdf
│ │ ├── CalculatePositionService.java
│ │ └── SignService.java
│ ├── pojo
│ │ ├── BaseCertificateInfo.java
│ │ ├── CertificateProperty.java
│ │ ├── ConvertImage.java
│ │ ├── GenerateCertificateInfo.java
│ │ ├── RealPositionProperty.java
│ │ ├── SelectKeywords.java
│ │ ├── SignPdfInfoVo.java
│ │ └── SourcePositionProperty.java
│ └── verify
│ │ ├── ExtImageRenderListener.java
│ │ ├── SignVerifyService.java
│ │ └── VerifySign.java
│ └── utils
│ └── Base64.java
└── resources
├── application-dev.yml
├── application.yml
└── 开源工具版说明.pdf
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | !.mvn/wrapper/maven-wrapper.jar
3 | !**/src/main/**/target/
4 | !**/src/test/**/target/
5 |
6 | ### IntelliJ IDEA ###
7 | .idea/**
8 | .idea/modules.xml
9 | .idea/jarRepositories.xml
10 | .idea/compiler.xml
11 | .idea/libraries/
12 | *.iws
13 | *.iml
14 | *.ipr
15 |
16 | ### Eclipse ###
17 | .apt_generated
18 | .classpath
19 | .factorypath
20 | .project
21 | .settings
22 | .springBeans
23 | .sts4-cache
24 |
25 | ### NetBeans ###
26 | /nbproject/private/
27 | /nbbuild/
28 | /dist/
29 | /nbdist/
30 | /.nb-gradle/
31 | build/
32 | !**/src/main/**/build/
33 | !**/src/test/**/build/
34 |
35 | ### VS Code ###
36 | .vscode/
37 |
38 | ### Mac OS ###
39 | .DS_Store
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 kaifangqian
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
开放签电子签章工具版源码
2 |
3 | #### 项目简介
4 | 将电子签章的核心技术代码和工具进行开源,开源版采用更加宽松的MIT开源协议,且不受商业限制。产品功能包括:电子印章制作,手写签名生成,数字证书生成,PDF文件转图片,电子签章(关键字签署、指定位置签署),文件验签等
5 |
6 | #### 项目源码
7 | | | 后端源码 | 前端源码 |
8 | |--- |--- | --- |
9 | | github | https://github.com/kaifangqian/open-sign | https://github.com/kaifangqian/open-sign-vue |
10 | | 码云 | https://gitee.com/kaifangqian/open-sign | https://gitee.com/kaifangqian/open-sign-vue |
11 |
12 | #### 官网地址
13 | [https://www.kaifangqian.com](https://www.kaifangqian.com)
14 |
15 | #### 体验地址
16 | [https://demo.kaifangqian.com](https://demo.kaifangqian.com)
17 |
18 | #### 工具库说明
19 | ##### 手写签名面板
20 | * 提供个人手写签名面板的前端页面,生成手写签名图片。
21 | 
22 |
23 |
24 | ##### 电子印章制作
25 | * 为企业生成电子印章,生成印章的方式有两种:
26 | * 系统生成:根据印章环绕文字、横排文字生成电子印章图片;
27 | 
28 | * 印模生成:
29 | * 在白纸上加盖印章;
30 | * 拍照或扫描上传,对印模图片进行裁剪;
31 | * 对裁剪后的印模图片进行自动透明化抠图,生成透明印章;
32 | 
33 |
34 | ##### 数字证书(公钥加密技术生成,非CA数字证书)生成
35 | * 生成用于测试电子签章的数字证书,该数字证书使用公钥加密技术进行生成,主要用于帮助开发者跑通电子签章流程,非CA机构签发,不具备法律效力!
36 | * 以下从功能和用途上对SSL证书和CA证书进行简单介绍:
37 | * SSL证书和CA证书都是数字证书,但它们在功能和用途上存在明显的区别。
38 | * CA证书由权威的数字证书颁发机构(CA)颁发。在电子签章过程中,CA数字证书可以验证签署者的身份,将签名数据与数字证书中的公钥进行加密,以保证签名的安全和可靠性。同时,CA证书还可以确保签署的文件没有被篡改,保证电子信息的完整性、保密性和不可抵赖性。通过使用CA数字证书,可以确认签署主体的身份,确保签名是符合法律规定的、是防篡改的,让电子合同和纸质合同具有同等法律效力。在签署电子合同之前,用户需要完成实名认证,由CA机构来签发数字证书,确认签署主体的身份。因此,CA数字证书在电子签章中起到了关键的作用,可以确保签署过程的合法性和安全性,使得电子签署文件具有更高的可信度和法律效力。
39 | * SSL证书是一种加密协议,用于保护网站和用户之间的数据传输安全。SSL证书通过使用公钥加密技术,将用户的敏感信息(如信用卡号、密码等)加密传输,以防止被恶意第三方截获和窃取。同时,SSL证书还可以验证网站的身份,确保用户与正确的网站进行通信。因此,SSL证书主要用于加密网站和用户之间的数据传输,保护用户的隐私和安全。
40 |
41 | 
42 |
43 | ##### PDF文件转图片
44 |
45 | * 提供PDF文件转图片的工具类。
46 | * 电子签章过程中存在着在网页上对签署文件进行预览、指定签署位置、文件签署等操作,由于图片在浏览器上的兼容性和友好性优于PDF文件,所以一般在网页上进行电子签章时,会先将PDF文件转换成图片,展示给用户。用户在页面上确定好签署位置,并进行签署时,后端服务会通过对电子印章/手写签名位置、大小以及PDF文件的大小进行计算,在PDF文件的准确位置上完成文件签署。
47 |
48 | ##### 电子文件签署
49 |
50 | * 电子文件签署是指对电子文件进行电子签章的过程。
51 | * 开放签-开源版提供了两种电子签章的方式,如下:
52 | * 关键字签署:指定PDF文件中需要进行签章的关键字,签署时,会在文档中查找对应关键字的位置,并加盖电子印章/手写签名,完成电子签章;
53 | * 指定位置签署:直接指定在PDF文件中需要加盖电子印章/手写签名的位置,签署时,直接在指定位置上完成电子签章。
54 |
55 | 
56 |
57 | ##### 签署文件验证
58 | * 验证电子文件是否被篡改以及文件中对应的电子印章/手写签名和数字证书信息。
59 | * 验证一般存在以下几种情况:
60 | * PDF文件中不存在数字签名,即该文件未进行过电子签章,无需验证;
61 | * PDF文件中存在数字证书,进行签名验证时发生错误,签名包含不正确的、无法识别的、已损坏的或可疑的数据,即文件的签名信息被篡改,验证不通过;
62 | * PDF文件中存在数字证书,且数字证书均可识别,但文件被篡改,验证不通过;
63 | * PDF文件中存在数字证书,文档未被篡改,验证通过。
64 |
65 | 
66 |
67 |
68 |
69 | #### Build Setup
70 | ```
71 | #进入项目目录
72 | cd open-sign
73 |
74 | #进行打包
75 | mvn clean package
76 |
77 | #运行jar包
78 | java -jar target/opensign-demo.jar
79 | ```
80 |
81 |
82 | #### 代码结构
83 | ```
84 | - open-sign 开放签工具
85 | - controller 提供演示接口
86 | - enums 公共枚举类
87 | - service 签约服务
88 | - cert 证书服务
89 | - image 印模图片处理、PDF转图片处理
90 | - pdf 签署位置计算、PDF文件签署
91 | - verify PDF文件验签
92 | - utils 工具类
93 | ```
94 |
95 | #### 反馈交流
96 | - QQ交流群:482074553
--------------------------------------------------------------------------------
/docs/images/pdf-cert.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhenghuihan/open-sign/f1fa38ee6e9ce4dc467123c67264c6f88b1d4559/docs/images/pdf-cert.png
--------------------------------------------------------------------------------
/docs/images/product.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhenghuihan/open-sign/f1fa38ee6e9ce4dc467123c67264c6f88b1d4559/docs/images/product.png
--------------------------------------------------------------------------------
/docs/images/seal-template.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhenghuihan/open-sign/f1fa38ee6e9ce4dc467123c67264c6f88b1d4559/docs/images/seal-template.png
--------------------------------------------------------------------------------
/docs/images/seal-ym.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhenghuihan/open-sign/f1fa38ee6e9ce4dc467123c67264c6f88b1d4559/docs/images/seal-ym.png
--------------------------------------------------------------------------------
/docs/images/signature.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhenghuihan/open-sign/f1fa38ee6e9ce4dc467123c67264c6f88b1d4559/docs/images/signature.png
--------------------------------------------------------------------------------
/docs/images/verify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhenghuihan/open-sign/f1fa38ee6e9ce4dc467123c67264c6f88b1d4559/docs/images/verify.png
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.resrun
8 | openSign
9 | 1.0
10 |
11 |
12 | org.springframework.boot
13 | spring-boot-starter-parent
14 | 2.7.18
15 |
16 |
17 |
18 |
19 |
20 | org.springframework.boot
21 | spring-boot-starter-web
22 |
23 |
24 | org.yaml
25 | snakeyaml
26 |
27 |
28 |
29 |
30 |
31 | org.yaml
32 | snakeyaml
33 | 2.2
34 |
35 |
36 |
37 |
38 |
39 | org.apache.pdfbox
40 | pdfbox-tools
41 | 2.0.27
42 |
43 |
44 | com.itextpdf.tool
45 | xmlworker
46 | 5.5.13
47 |
48 |
49 | com.itextpdf
50 | itextpdf
51 | 5.5.13.3
52 |
53 |
54 |
55 |
56 | org.bouncycastle
57 | bcpkix-jdk15on
58 | 1.70
59 |
60 |
61 | org.bouncycastle
62 | bcprov-ext-jdk15on
63 | 1.70
64 |
65 |
66 |
67 | org.projectlombok
68 | lombok
69 | 1.18.24
70 |
71 |
72 |
73 | io.swagger
74 | swagger-models
75 | 1.6.12
76 |
77 |
78 |
79 |
80 |
81 | 8
82 | 8
83 | UTF-8
84 |
85 |
86 |
87 |
88 | public
89 | aliyun nexus
90 | https://repo1.maven.org/maven2/
91 |
92 |
93 |
94 |
95 | opensign-demo
96 |
97 |
98 | org.springframework.boot
99 | spring-boot-maven-plugin
100 |
101 |
102 | false
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/src/main/java/com/resrun/OpenSign.java:
--------------------------------------------------------------------------------
1 | package com.resrun;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class OpenSign {
8 |
9 | public static void main(String[] args) {
10 | SpringApplication.run(OpenSign.class, args);
11 | }
12 |
13 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/controller/OpenSignController.java:
--------------------------------------------------------------------------------
1 | package com.resrun.controller;
2 |
3 | import com.resrun.enums.SignTypeEnum;
4 | import com.resrun.service.pojo.CertificateProperty;
5 | import com.resrun.service.pojo.GenerateCertificateInfo;
6 | import com.resrun.service.pojo.RealPositionProperty;
7 | import com.resrun.service.pojo.SourcePositionProperty;
8 | import com.resrun.service.cert.CertService;
9 | import com.resrun.service.image.EntSealClipService;
10 | import com.resrun.service.image.EntSealGenerateService;
11 | import com.resrun.service.pdf.CalculatePositionService;
12 | import com.resrun.service.pdf.SignService;
13 | import com.resrun.service.verify.SignVerifyService;
14 | import com.resrun.utils.Base64;
15 | import com.resrun.controller.vo.base.Result;
16 | import com.resrun.controller.vo.request.*;
17 | import com.resrun.controller.vo.response.SealResponse;
18 | import com.resrun.controller.vo.response.SignResponse;
19 | import com.resrun.controller.vo.response.VerifyResponse;
20 | import io.swagger.annotations.Api;
21 | import io.swagger.annotations.ApiOperation;
22 | import org.apache.pdfbox.io.IOUtils;
23 | import org.springframework.beans.factory.annotation.Autowired;
24 | import org.springframework.beans.factory.annotation.Value;
25 | import org.springframework.util.ResourceUtils;
26 | import org.springframework.web.bind.annotation.*;
27 |
28 | import java.io.*;
29 | import java.util.ArrayList;
30 | import java.util.List;
31 |
32 | /**
33 | * @Description: OpenSignController
34 | * @Package: com.resrun.controller
35 | * @ClassName: OpenSignController
36 | * @copyright 北京资源律动科技有限公司
37 | */
38 | @Api(tags = "开放签-演示demo")
39 | @RestController
40 | public class OpenSignController {
41 |
42 |
43 | @Autowired
44 | private SignVerifyService signVerifyService ;
45 | @Autowired
46 | private CalculatePositionService calculatePositionService ;
47 | @Autowired
48 | private SignService signService ;
49 | @Autowired
50 | private EntSealGenerateService entSealGenerateService ;
51 | @Autowired
52 | private EntSealClipService entSealClipService ;
53 | @Autowired
54 | private CertService certService ;
55 |
56 | @ApiOperation("生成企业签章-上传生成")
57 | @RequestMapping(value = "/clip/seal",method = RequestMethod.POST)
58 | public Result generateUpload(@RequestBody ClipSealRequest request){
59 |
60 |
61 | if(request.getImage() == null || request.getImage().length() == 0){
62 | return Result.error("图片数据为空",null);
63 | }
64 | byte[] decode = Base64.decode(request.getImage());
65 | if(decode == null || decode.length == 0){
66 | return Result.error("签章制作失败",null);
67 | }
68 |
69 | byte[] entSealByte = entSealClipService.clip(decode, request.getColorRange());
70 | if(entSealByte == null){
71 | return Result.error("签章制作失败",null);
72 | }
73 | String encode = Base64.encode(entSealByte);
74 | SealResponse response = new SealResponse();
75 | response.setEntSeal(encode);
76 | return Result.OK(response) ;
77 |
78 | }
79 |
80 | @ApiOperation("生成企业签章-参数生成")
81 | @RequestMapping(value = "/generate/seal",method = RequestMethod.POST)
82 | public Result seal(@RequestBody GenerateSealRequest request){
83 |
84 |
85 | if(request == null || request.getMiddleText() == null || request.getTopText() == null){
86 | return Result.error("参数缺失",null) ;
87 | }
88 | byte[] entSealByte = entSealGenerateService.generateEntSeal(request.getTopText(), request.getMiddleText());
89 | if(entSealByte == null){
90 | return Result.error("签章制作失败",null);
91 | }
92 | String encode = Base64.encode(entSealByte);
93 | SealResponse response = new SealResponse();
94 | response.setEntSeal(encode);
95 | return Result.OK(response) ;
96 |
97 | }
98 |
99 | @ApiOperation("签署")
100 | @RequestMapping(value = "/sign",method = RequestMethod.POST)
101 | public Result sign(@RequestBody SignRequest request){
102 |
103 | String fileName = "开源工具版说明.pdf" ;
104 | byte[] signFileBytes = null ;
105 | byte[] entSealBytes = null ;
106 | byte[] personalBytes = null ;
107 | CertificateProperty entCert = null ;
108 | CertificateProperty personalCert = null ;
109 | List entPositionList = null;
110 | List personalPositionList = null;
111 | int entSealWidth = 200 ;
112 | int entSealHeight = 200 ;
113 | int personalSealWidth = 150 ;
114 | int personalSealHeight = 70 ;
115 | //获取本地签署文件
116 | try {
117 | signFileBytes = getResourceFiles(fileName);
118 | } catch (Exception e) {
119 | e.printStackTrace();
120 | }
121 | if(signFileBytes == null){
122 | return Result.error("签署失败",null);
123 | }
124 | //生成企业证书和个人证书
125 | try {
126 | if(request.getEntName() != null && request.getEntName().length() > 0){
127 | String subject = "C=CN,ST=北京,L=北京,O=开放签 CA,OU=产品部,CN=开放签@" + request.getEntName();
128 | GenerateCertificateInfo generateCertificateInfo = certService.generateCertificate(null, subject, 10);
129 | if(generateCertificateInfo != null){
130 | entCert = new CertificateProperty();
131 | entCert.setCertType("PKCS12");
132 | entCert.setCertFile(generateCertificateInfo.getPfx());
133 | entCert.setPassword(generateCertificateInfo.getPassword());
134 | }
135 | if(entCert == null){
136 | return Result.error("签署失败",null);
137 | }
138 | }
139 | if(request.getPersonalName() != null && request.getPersonalName().length() > 0){
140 | String subject = "C=CN,ST=北京,L=北京,O=开放签 CA,OU=产品部,CN=开放签@" + request.getPersonalName();
141 | GenerateCertificateInfo generateCertificateInfo = certService.generateCertificate(null, subject, 10);
142 | if(generateCertificateInfo != null){
143 | personalCert = new CertificateProperty();
144 | personalCert.setCertType("PKCS12");
145 | personalCert.setCertFile(generateCertificateInfo.getPfx());
146 | personalCert.setPassword(generateCertificateInfo.getPassword());
147 | }
148 | if(personalCert == null){
149 | return Result.error("签署失败",null);
150 | }
151 | }
152 | } catch (Exception e) {
153 | e.printStackTrace();
154 | }
155 |
156 |
157 | //生成企业签章和个人签章
158 | if(request.getEntSeal() != null){
159 | entSealBytes = Base64.decode(request.getEntSeal());
160 | }
161 | if(request.getPersonalSeal() != null){
162 | personalBytes = Base64.decode(request.getPersonalSeal());
163 | }
164 |
165 | //计算企业签署位置和个人签署位置
166 | if(SignTypeEnum.POSITION.getCode().equals(request.getSignType())){
167 | if((request.getEntPositionList() == null || request.getEntPositionList().size() == 0 ) &&
168 | (request.getPersonalPositionList() == null || request.getPersonalPositionList().size() == 0)){
169 | return Result.error("签署失败",null);
170 | }
171 | //计算企业签署位置
172 | if(request.getEntPositionList() != null && request.getEntPositionList().size() > 0){
173 | List convert = convert(request.getEntPositionList());
174 | entPositionList = calculatePositionService.calculatePositions(convert, signFileBytes);
175 | }
176 | //计算个人签署位置
177 | if(request.getPersonalPositionList() != null && request.getPersonalPositionList().size() > 0){
178 | List convert = convert(request.getPersonalPositionList());
179 | personalPositionList = calculatePositionService.calculatePositions(convert, signFileBytes);
180 | }
181 | }else if(SignTypeEnum.KEYWORD.getCode().equals(request.getSignType())){
182 | if((request.getEntKeyword() == null || request.getEntKeyword().length() == 0 ) &&
183 | (request.getPersonalKeyword() == null || request.getPersonalKeyword().length() == 0)){
184 | return Result.error("签署失败",null);
185 | }
186 | //根据关键字计算所有企业签署位置
187 | if(request.getEntKeyword() != null && request.getEntKeyword().length() > 0){
188 | entPositionList = calculatePositionService.getAllPositionByKeyWords(signFileBytes, request.getEntKeyword(), entSealWidth, entSealHeight);
189 | }
190 | //根据关键字计算所有个人签署位置
191 | if(request.getPersonalKeyword() != null && request.getPersonalKeyword().length() > 0){
192 | personalPositionList = calculatePositionService.getAllPositionByKeyWords(signFileBytes,request.getPersonalKeyword(),personalSealWidth,personalSealHeight);
193 | }
194 | if((personalPositionList == null || personalPositionList.size() == 0 ) &&
195 | (personalPositionList == null || personalPositionList.size() == 0)){
196 | return Result.error("签署失败!签署关键字在文件中不存在,请准确设置关键字后再签署",null);
197 | }
198 | }
199 |
200 | //进行签署操作
201 | byte[] operationByte = signFileBytes ;
202 | try {
203 | //所有企业位置签署
204 | if(entPositionList.size() > 0){
205 | for(RealPositionProperty realPositionProperty : entPositionList){
206 | operationByte = signService.signingContract(operationByte, entSealBytes, entCert, realPositionProperty);
207 | }
208 | }
209 | //所有个人位置签署
210 | if(personalPositionList.size() > 0){
211 | for(RealPositionProperty realPositionProperty : personalPositionList){
212 | operationByte = signService.signingContract(operationByte, personalBytes, personalCert, realPositionProperty);
213 | }
214 | }
215 | }catch (Exception e){
216 |
217 | }
218 | if(operationByte == null){
219 | return Result.error("签署失败",null);
220 | }
221 |
222 | // try {
223 | // org.apache.commons.io.IOUtils.write(operationByte,new FileOutputStream(new File("/Users/gongfenglai/Desktop/test/pdf/" + System.currentTimeMillis() + ".pdf")));
224 | // } catch (Exception e) {
225 | // e.printStackTrace();
226 | // }
227 |
228 | String encode = Base64.encode(operationByte);
229 | SignResponse response = new SignResponse();
230 | response.setSignFile(encode);
231 | return Result.OK(response);
232 |
233 | }
234 |
235 | @ApiOperation("文件验签")
236 | @RequestMapping(value = "/verify",method = RequestMethod.POST)
237 | public Result verify(@RequestBody VerifyRequest request){
238 |
239 | String verifyFile = request.getVerifyFile();
240 | byte[] decode = Base64.decode(verifyFile);
241 |
242 | VerifyResponse imageFromPdf = signVerifyService.getImageFromPdf(decode, request.getFileName());
243 | return Result.OK(imageFromPdf) ;
244 | }
245 |
246 |
247 | public List convert(List positionRequestList){
248 | List list = new ArrayList<>();
249 | for(PositionRequest request : positionRequestList){
250 | SourcePositionProperty position = new SourcePositionProperty();
251 | position.setOffsetX(Float.valueOf(request.getOffsetX()));
252 | position.setOffsetY(Float.valueOf(request.getOffsetY()));
253 | position.setPage(request.getPage());
254 | position.setWidth(Float.valueOf(request.getWidth()));
255 | position.setHeight(Float.valueOf(request.getHeight()));
256 | position.setPageHeight(Float.valueOf(request.getPageHeight()));
257 | position.setPageWidth(Float.valueOf(request.getPageWidth()));
258 | list.add(position);
259 | }
260 | return list ;
261 | }
262 |
263 |
264 | public byte [] getResourceFiles(String path) {
265 | try {
266 | InputStream inputStream = ResourceUtils.class.getClassLoader()
267 | .getResourceAsStream(path);
268 | return read(inputStream);
269 | }catch (Exception e){
270 | System.err.println(path);
271 | e.printStackTrace();
272 | }
273 | return null;
274 | }
275 |
276 |
277 | public byte[] read(InputStream inputStream) throws IOException {
278 | try {
279 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
280 | byte[] buffer = new byte[1024];
281 | int num = inputStream.read(buffer);
282 | while (num != -1) {
283 | baos.write(buffer, 0, num);
284 | num = inputStream.read(buffer);
285 | }
286 | baos.flush();
287 | return baos.toByteArray();
288 | } finally {
289 | if (inputStream != null) {
290 | inputStream.close();
291 | }
292 | }
293 | }
294 |
295 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/controller/TestController.java:
--------------------------------------------------------------------------------
1 | package com.resrun.controller;
2 |
3 | import org.springframework.web.bind.annotation.GetMapping;
4 | import org.springframework.web.bind.annotation.RequestMapping;
5 | import org.springframework.web.bind.annotation.RestController;
6 |
7 | /**
8 | * @author : zhenghuihan
9 | * create at: 2023/12/7 15:09
10 | * @description:
11 | */
12 | @RestController
13 | @RequestMapping("/api")
14 | public class TestController {
15 |
16 | /**
17 | * @description 测试
18 | */
19 | @GetMapping(value = "/test")
20 | public String test() {
21 | return "ok";
22 | }
23 |
24 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/controller/vo/base/Result.java:
--------------------------------------------------------------------------------
1 | package com.resrun.controller.vo.base;
2 |
3 | import io.swagger.annotations.ApiModel;
4 | import io.swagger.annotations.ApiModelProperty;
5 | import lombok.AllArgsConstructor;
6 | import lombok.Data;
7 | import lombok.NoArgsConstructor;
8 |
9 | import java.io.Serializable;
10 |
11 | /**
12 | * @Description: Result
13 | * @Package: com.resrun.controller.vo
14 | * @ClassName: Result
15 | * @copyright 北京资源律动科技有限公司
16 | */
17 | @Data
18 | @AllArgsConstructor
19 | @NoArgsConstructor
20 | @ApiModel("统一返回对象")
21 | public class Result implements Serializable {
22 |
23 | private static final long serialVersionUID = 4186430915088458662L;
24 |
25 | @ApiModelProperty("成功标志")
26 | private boolean success = true;
27 | @ApiModelProperty("返回处理消息")
28 | private String message = "";
29 | @ApiModelProperty("返回代码")
30 | private Integer code = 200;
31 | @ApiModelProperty("返回数据对象")
32 | private T result;
33 | @ApiModelProperty("时间戳")
34 | private long timestamp = System.currentTimeMillis();
35 |
36 |
37 | public static Result OK(T data) {
38 | Result r = new Result();
39 | r.setSuccess(true);
40 | r.setCode(200);
41 | r.setResult(data);
42 | return r;
43 | }
44 |
45 |
46 |
47 | public static Result error(String msg, T data) {
48 | Result r = new Result();
49 | r.setSuccess(false);
50 | r.setCode(500);
51 | r.setMessage(msg);
52 | r.setResult(data);
53 | return r;
54 | }
55 |
56 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/controller/vo/request/ClipSealRequest.java:
--------------------------------------------------------------------------------
1 | package com.resrun.controller.vo.request;
2 |
3 | import io.swagger.annotations.ApiModel;
4 | import io.swagger.annotations.ApiModelProperty;
5 | import lombok.AllArgsConstructor;
6 | import lombok.Data;
7 | import lombok.NoArgsConstructor;
8 |
9 | import java.io.Serializable;
10 |
11 | /**
12 | * @Description: ClipSealRequest
13 | * @Package: com.resrun.controller.vo.request
14 | * @ClassName: ClipSealRequest
15 | * @copyright 北京资源律动科技有限公司
16 | */
17 | @Data
18 | @AllArgsConstructor
19 | @NoArgsConstructor
20 | @ApiModel("企业印章上传生成-请求对象")
21 | public class ClipSealRequest implements Serializable {
22 |
23 | private static final long serialVersionUID = 7532064967450524188L;
24 |
25 | @ApiModelProperty("图片base64")
26 | private String image ;
27 |
28 | @ApiModelProperty("色差范围0~255")
29 | private Integer colorRange ;
30 |
31 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/controller/vo/request/GenerateSealRequest.java:
--------------------------------------------------------------------------------
1 | package com.resrun.controller.vo.request;
2 |
3 | import io.swagger.annotations.ApiModel;
4 | import io.swagger.annotations.ApiModelProperty;
5 | import lombok.AllArgsConstructor;
6 | import lombok.Data;
7 | import lombok.NoArgsConstructor;
8 |
9 | import java.io.Serializable;
10 |
11 | /**
12 | * @Description: SealRequest
13 | * @Package: com.resrun.controller.vo.request
14 | * @ClassName: SealRequest
15 | * @copyright 北京资源律动科技有限公司
16 | */
17 | @Data
18 | @AllArgsConstructor
19 | @NoArgsConstructor
20 | @ApiModel("企业印章参数生成-请求对象")
21 | public class GenerateSealRequest implements Serializable {
22 |
23 | private static final long serialVersionUID = -7107056549410243340L;
24 |
25 | @ApiModelProperty("企业名称")
26 | private String topText;
27 | @ApiModelProperty("横排文字")
28 | private String middleText ;
29 |
30 |
31 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/controller/vo/request/PositionRequest.java:
--------------------------------------------------------------------------------
1 | package com.resrun.controller.vo.request;
2 |
3 | import io.swagger.annotations.ApiModel;
4 | import io.swagger.annotations.ApiModelProperty;
5 | import lombok.AllArgsConstructor;
6 | import lombok.Data;
7 | import lombok.NoArgsConstructor;
8 |
9 | import java.io.Serializable;
10 |
11 | /**
12 | * @Description: PositionRequest
13 | * @Package: com.resrun.controller.vo.request
14 | * @ClassName: PositionRequest
15 | * @copyright 北京资源律动科技有限公司
16 | */
17 | @Data
18 | @AllArgsConstructor
19 | @NoArgsConstructor
20 | @ApiModel("签署位置-请求对象")
21 | public class PositionRequest implements Serializable {
22 |
23 | private static final long serialVersionUID = 1407897694551031954L;
24 |
25 | @ApiModelProperty("控件X坐标(左上角)")
26 | private String offsetX ;
27 |
28 | @ApiModelProperty("控件Y坐标(左上角)")
29 | private String offsetY ;
30 |
31 | @ApiModelProperty("控件宽度")
32 | private String width ;
33 |
34 | @ApiModelProperty("控件高度")
35 | private String height ;
36 |
37 | @ApiModelProperty("控件所属页码")
38 | private Integer page ;
39 |
40 | @ApiModelProperty("当前文件页面宽度")
41 | private String pageWidth ;
42 |
43 | @ApiModelProperty("当前文件页面高度")
44 | private String pageHeight ;
45 |
46 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/controller/vo/request/SignRequest.java:
--------------------------------------------------------------------------------
1 | package com.resrun.controller.vo.request;
2 |
3 | import io.swagger.annotations.ApiModel;
4 | import io.swagger.annotations.ApiModelProperty;
5 | import lombok.AllArgsConstructor;
6 | import lombok.Data;
7 | import lombok.NoArgsConstructor;
8 |
9 | import java.io.Serializable;
10 | import java.util.List;
11 |
12 | /**
13 | * @Description: SignRequest
14 | * @Package: com.resrun.controller.vo.request
15 | * @ClassName: SignRequest
16 | * @copyright 北京资源律动科技有限公司
17 | */
18 | @Data
19 | @AllArgsConstructor
20 | @NoArgsConstructor
21 | @ApiModel("签署-请求对象")
22 | public class SignRequest implements Serializable {
23 |
24 | @ApiModelProperty("签署类型,1位置签署,2关键字签署")
25 | private Integer signType ;
26 |
27 | @ApiModelProperty("企业签章base64")
28 | private String entSeal ;
29 |
30 | @ApiModelProperty("企业名称")
31 | private String entName ;
32 |
33 | @ApiModelProperty("个人签章base64")
34 | private String personalSeal ;
35 |
36 | @ApiModelProperty("个人名称")
37 | private String personalName ;
38 |
39 | @ApiModelProperty("企业签署位置")
40 | private List entPositionList ;
41 |
42 | @ApiModelProperty("个人签章位置")
43 | private List personalPositionList ;
44 |
45 | @ApiModelProperty("企业签章关键字")
46 | private String entKeyword ;
47 |
48 | @ApiModelProperty("个人签章关键字")
49 | private String personalKeyword ;
50 |
51 |
52 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/controller/vo/request/VerifyRequest.java:
--------------------------------------------------------------------------------
1 | package com.resrun.controller.vo.request;
2 |
3 | import io.swagger.annotations.ApiModel;
4 | import io.swagger.annotations.ApiModelProperty;
5 | import lombok.AllArgsConstructor;
6 | import lombok.Data;
7 | import lombok.NoArgsConstructor;
8 |
9 | import java.io.Serializable;
10 |
11 | /**
12 | * @Description: VerifyRequest
13 | * @Package: com.resrun.controller.vo.request
14 | * @ClassName: VerifyRequest
15 | * @copyright 北京资源律动科技有限公司
16 | */
17 | @Data
18 | @AllArgsConstructor
19 | @NoArgsConstructor
20 | @ApiModel("文件验签-请求对象")
21 | public class VerifyRequest implements Serializable {
22 |
23 | private static final long serialVersionUID = -5412004997017887483L;
24 |
25 | @ApiModelProperty("验签文件base64")
26 | private String verifyFile ;
27 |
28 | @ApiModelProperty("验签文件名称")
29 | private String fileName ;
30 |
31 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/controller/vo/response/SealResponse.java:
--------------------------------------------------------------------------------
1 | package com.resrun.controller.vo.response;
2 |
3 | import io.swagger.annotations.ApiModel;
4 | import io.swagger.annotations.ApiModelProperty;
5 | import lombok.AllArgsConstructor;
6 | import lombok.Data;
7 | import lombok.NoArgsConstructor;
8 |
9 | import java.io.Serializable;
10 |
11 | /**
12 | * @Description: SealResponse
13 | * @Package: com.resrun.controller.vo.response
14 | * @ClassName: SealResponse
15 | * @copyright 北京资源律动科技有限公司
16 | */
17 | @Data
18 | @AllArgsConstructor
19 | @NoArgsConstructor
20 | @ApiModel("企业印章生成-返回对象")
21 | public class SealResponse implements Serializable {
22 |
23 | private static final long serialVersionUID = 3376686508340918420L;
24 |
25 | @ApiModelProperty("企业签章base64")
26 | private String entSeal ;
27 |
28 |
29 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/controller/vo/response/SignResponse.java:
--------------------------------------------------------------------------------
1 | package com.resrun.controller.vo.response;
2 |
3 | import io.swagger.annotations.ApiModel;
4 | import io.swagger.annotations.ApiModelProperty;
5 | import lombok.AllArgsConstructor;
6 | import lombok.Data;
7 | import lombok.NoArgsConstructor;
8 |
9 | import java.io.Serializable;
10 |
11 | /**
12 | * @Description: SignResponse
13 | * @Package: com.resrun.controller.vo.response
14 | * @ClassName: SignResponse
15 | * @copyright 北京资源律动科技有限公司
16 | */
17 | @Data
18 | @AllArgsConstructor
19 | @NoArgsConstructor
20 | @ApiModel("文件签署-返回对象")
21 | public class SignResponse implements Serializable {
22 |
23 | private static final long serialVersionUID = 7191345743000212815L;
24 |
25 | @ApiModelProperty("签署后文件")
26 | private String signFile ;
27 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/controller/vo/response/VerifyResponse.java:
--------------------------------------------------------------------------------
1 | package com.resrun.controller.vo.response;
2 |
3 | import com.resrun.service.pojo.SignPdfInfoVo;
4 | import io.swagger.annotations.ApiModel;
5 | import io.swagger.annotations.ApiModelProperty;
6 | import lombok.AllArgsConstructor;
7 | import lombok.Data;
8 | import lombok.NoArgsConstructor;
9 |
10 | import java.io.Serializable;
11 |
12 | /**
13 | * @Description: VerifyRequest
14 | * @Package: com.resrun.controller.vo.response
15 | * @ClassName: VerifyRequest
16 | * @copyright 北京资源律动科技有限公司
17 | */
18 | @Data
19 | @AllArgsConstructor
20 | @NoArgsConstructor
21 | @ApiModel("文件验签-返回对象")
22 | public class VerifyResponse implements Serializable {
23 |
24 | private static final long serialVersionUID = -1498109564641142303L;
25 |
26 | @ApiModelProperty("返回信息")
27 | private String msg ;
28 |
29 | @ApiModelProperty("详细数据")
30 | private SignPdfInfoVo vo ;
31 |
32 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/enums/SealColorEnum.java:
--------------------------------------------------------------------------------
1 | package com.resrun.enums;
2 |
3 | /**
4 | * @Description: 签章颜色枚举类
5 | * @Package: com.resrun.enums
6 | * @ClassName: SealColorEnum
7 | * @copyright 北京资源律动科技有限公司
8 | */
9 | public enum SealColorEnum {
10 |
11 | RED(1,"红色"),
12 | BLUE(2,"蓝色"),
13 | BLACK(3,"黑色"),
14 |
15 | ;
16 |
17 | private Integer code ;
18 | private String name ;
19 |
20 | SealColorEnum(Integer code, String name){
21 | this.code = code ;
22 | this.name = name ;
23 | }
24 |
25 | public String getName(){
26 | return this.name ;
27 | }
28 |
29 | public Integer getCode(){
30 | return this.code ;
31 | }
32 |
33 |
34 |
35 |
36 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/enums/SignStatus.java:
--------------------------------------------------------------------------------
1 | package com.resrun.enums;
2 |
3 |
4 | /**
5 | * @Description: 签名文件状态枚举
6 | * @Package: com.resrun.enums
7 | * @ClassName: SignStatus
8 | * @copyright 北京资源律动科技有限公司
9 | */
10 |
11 | public enum SignStatus {
12 | SIGN_STATUS_NOSIGNATURE("PDF文件未发现任何数字签名信息",1),
13 | SIGN_STATUS_ERROR("PDF签名验证时发生错误,签名包含不正确的、无法识别的、已损坏的或可疑的数据",2),
14 | SIGN_STATUS_FIDDLE("PDF中存在数字证书,但文档被篡改,进入下一步,展示验证结果",3),
15 | SIGN_STATUS_RIGHT("PDF中存在数字证书,文档未被篡改,进入下一步,展示验证结果",4),
16 |
17 | ;
18 |
19 | private String msg;
20 | private Integer code;
21 | SignStatus(String msg,Integer code){
22 | this.msg = msg;
23 | this.code = code;
24 | }
25 |
26 | public Integer getCode(){
27 | return code;
28 | }
29 | public String getMsg(){
30 | return msg;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/resrun/enums/SignTypeEnum.java:
--------------------------------------------------------------------------------
1 | package com.resrun.enums;
2 |
3 | /**
4 | * @Description: 签署方式枚举
5 | * @Package: com.resrun.enums
6 | * @ClassName: SignTypeEnum
7 | * @copyright 北京资源律动科技有限公司
8 | */
9 | public enum SignTypeEnum {
10 |
11 |
12 | POSITION(1,"位置签署"),
13 | KEYWORD(2,"关键字签署"),
14 |
15 | ;
16 |
17 | private String msg;
18 | private Integer code;
19 |
20 |
21 | SignTypeEnum(Integer code,String msg){
22 | this.msg = msg;
23 | this.code = code;
24 | }
25 |
26 | public Integer getCode(){
27 | return code;
28 | }
29 | public String getMsg(){
30 | return msg;
31 | }
32 |
33 |
34 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/service/cert/CertService.java:
--------------------------------------------------------------------------------
1 | package com.resrun.service.cert;
2 |
3 | import com.resrun.service.pojo.BaseCertificateInfo;
4 | import com.resrun.service.pojo.GenerateCertificateInfo;
5 | import org.bouncycastle.asn1.x500.X500Name;
6 | import org.bouncycastle.asn1.x509.Extension;
7 | import org.bouncycastle.cert.X509CertificateHolder;
8 | import org.bouncycastle.cert.X509v3CertificateBuilder;
9 | import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
10 | import org.bouncycastle.operator.ContentSigner;
11 | import org.bouncycastle.operator.OperatorCreationException;
12 | import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
13 | import org.springframework.stereotype.Service;
14 |
15 | import java.io.*;
16 | import java.math.BigInteger;
17 | import java.security.*;
18 | import java.security.cert.Certificate;
19 | import java.security.cert.CertificateException;
20 | import java.security.cert.CertificateFactory;
21 | import java.security.cert.X509Certificate;
22 | import java.util.*;
23 |
24 | /**
25 | * @Description: 证书业务
26 | * @Package: com.resrun.service.cert
27 | * @ClassName: CertService
28 | * @copyright 北京资源律动科技有限公司
29 | */
30 | @Service
31 | public class CertService {
32 |
33 |
34 | //SHA256WITHRSA
35 |
36 | private String algorithmSignature = "SHA256withRSA";
37 |
38 | private String password = "123456" ;
39 |
40 |
41 |
42 | /**
43 | * 设置证书签名算法 SHA1withRSA SHA256withRSA
44 | * @param algorithmSignature
45 | */
46 | public void setAlgorithmSignature(String algorithmSignature){
47 | this.algorithmSignature = algorithmSignature;
48 | }
49 |
50 |
51 | /**
52 | * 证书类型
53 | * @param lifespan 单位年
54 | *
55 | * @return
56 | */
57 | public GenerateCertificateInfo generateCertificate(BaseCertificateInfo root, String subject,int lifespan) throws Exception {
58 | X500Name issuer = new X500Name(subject);
59 | KeyStore.PrivateKeyEntry rootPK = null;
60 | PrivateKey signPrivateKey = null;
61 | List chains = new ArrayList<>(5);
62 | if(root != null){
63 | rootPK = (KeyStore.PrivateKeyEntry) root.getCert().getEntry(root.getAlias(),
64 | new KeyStore.PasswordProtection(root.getPassword().toCharArray()));
65 | X509Certificate rootCert = (X509Certificate) rootPK.getCertificate();
66 |
67 | issuer = X500Name.getInstance(rootCert.getSubjectX500Principal().getEncoded());
68 |
69 | signPrivateKey = rootPK.getPrivateKey();
70 |
71 | Certificate [] rootChains = rootPK.getCertificateChain();
72 | for (int i=0;i extensions) throws IOException, CertificateException, OperatorCreationException {
124 |
125 | X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(
126 | issuer, serial, startDate, expireDate,
127 | subject, publicKey);
128 | ContentSigner sigGen = new JcaContentSignerBuilder(algorithmSignature).build(privKey);
129 | //privKey是CA的私钥,publicKey是待签名的公钥,那么生成的证书就是被CA签名的证书。
130 | if (extensions != null){
131 | for (Extension ext : extensions) {
132 | builder.addExtension(ext.getExtnId(), ext.isCritical(),ext.getExtnValue());
133 | }
134 | }
135 | X509CertificateHolder holder = builder.build(sigGen);
136 | CertificateFactory cf = CertificateFactory.getInstance("X.509");
137 | InputStream is1 = new ByteArrayInputStream(holder.toASN1Structure()
138 | .getEncoded());
139 | X509Certificate theCert = (X509Certificate) cf.generateCertificate(is1);
140 | is1.close();
141 | return theCert;
142 | }
143 |
144 |
145 | // public static void main(String[] args) throws Exception {
146 | ////
147 | // KeyStore store = loadJKS("Ting111");
148 | // BaseCertificateInfo baseCertificateInfo = new BaseCertificateInfo();
149 | // baseCertificateInfo.setAlias("root");
150 | // baseCertificateInfo.setCert(store);
151 | // baseCertificateInfo.setPassword("123456");
152 | // String x500Name = "CN=Ting222,OU=研发部,O=资源律动,L=BeiJing,ST=BeiJing,C=CN";
153 | //
154 | // GenerateCertificateInfo certificateInfo = GenerateRootCertificate.instance(x500Name, CertificateType.RSA).generateCertificate(baseCertificateInfo,"123456",10);
155 | // FileUtils.writeByteArrayToFile(new File("C:\\Users\\Administrator\\Desktop\\tem\\cert\\Ting222.jks"), certificateInfo.getJks());
156 | //
157 | // }
158 |
159 | private static BigInteger str2BigInteger(String str){
160 | StringBuffer sb = new StringBuffer();
161 | //将字符串转换为字符数组
162 | char ch[] = str.toCharArray();
163 | for(int i = 0; i < ch.length; i++) {
164 | String hexString = Integer.toHexString(ch[i]);
165 | sb.append(hexString);
166 | }
167 | return new BigInteger(sb.toString());
168 | }
169 |
170 | private static String big2String(String str){
171 | String result = new String();
172 | char[] charArray = str.toString().toCharArray();
173 | for(int i = 0; i < charArray.length; i=i+2) {
174 | String st = ""+charArray[i]+""+charArray[i+1];
175 | char ch1 = (char)Integer.parseInt(st, 16);
176 | result = result + ch1;
177 | }
178 | return result;
179 | }
180 |
181 | public byte [] coverToPfx(byte [] jks, String password) {
182 | try {
183 |
184 | KeyStore inputKeyStore = KeyStore.getInstance("JKS");
185 | ByteArrayInputStream inputStream = new ByteArrayInputStream(jks);
186 | inputKeyStore.load(inputStream, password.toCharArray());
187 |
188 | KeyStore outputKeyStore = KeyStore.getInstance("PKCS12");
189 |
190 | outputKeyStore.load(null, password.toCharArray());
191 |
192 | Enumeration enums = inputKeyStore.aliases();
193 |
194 | while (enums.hasMoreElements()) { // we are readin just one certificate.
195 |
196 | String keyAlias = (String) enums.nextElement();
197 |
198 | if (inputKeyStore.isKeyEntry(keyAlias)) {
199 | Key key = inputKeyStore.getKey(keyAlias, password.toCharArray());
200 | Certificate[] certChain = inputKeyStore
201 | .getCertificateChain(keyAlias);
202 |
203 | outputKeyStore.setKeyEntry("", key, password
204 | .toCharArray(), certChain);
205 | }
206 | }
207 |
208 | ByteArrayOutputStream out = new ByteArrayOutputStream();
209 | outputKeyStore.store(out, password.toCharArray());
210 | out.close();
211 | return out.toByteArray();
212 | } catch (Exception e) {
213 | e.printStackTrace();
214 | }
215 | return null;
216 | }
217 |
218 |
219 | public static KeyStore loadJKS(String name) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException {
220 | KeyStore store = KeyStore.getInstance("JKS");
221 | File file = new File(""+name+".jks");
222 | store.load(new FileInputStream(file), "123456".toCharArray());
223 | return store;
224 | }
225 |
226 |
227 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/service/image/DateSealService.java:
--------------------------------------------------------------------------------
1 | package com.resrun.service.image;
2 |
3 | import org.springframework.stereotype.Service;
4 |
5 | import javax.imageio.ImageIO;
6 | import java.awt.*;
7 | import java.awt.image.BufferedImage;
8 | import java.io.ByteArrayOutputStream;
9 | import java.text.SimpleDateFormat;
10 | import java.util.Date;
11 |
12 | /**
13 | * @Description: 日期签章业务
14 | * @Package: com.resrun.service.image
15 | * @ClassName: DateSealService
16 | * @copyright 北京资源律动科技有限公司
17 | */
18 | @Service
19 | public class DateSealService {
20 |
21 |
22 | /**
23 | * @Description #生成日期签章
24 | * @Param date 日期值,可以为空,如果为空则会使用format生成当前时间的日期签章
25 | * format 日期格式,
26 | * width 签章宽,
27 | * height签章高
28 | * @return byte[]
29 | **/
30 | public byte[] crateDateSeal(String date ,String format, int width, int height){
31 | byte[] seal = null ;
32 | String value = null ;
33 | if(date != null && date.length() > 0){
34 | value = date ;
35 |
36 | }else if(format != null && format.length() > 0) {
37 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
38 | value = simpleDateFormat.format(new Date());
39 | }
40 | if(value == null){
41 | return null ;
42 | }
43 |
44 | // 设置背景宽高
45 | BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
46 | // 获取图形上下文对象
47 | Graphics2D graphics = image.createGraphics();
48 | graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
49 | // 填充
50 | graphics.fillRect(0, 0, width, height);
51 | // 设定字体大小及样式
52 | graphics.setFont(new Font("宋体", Font.BOLD,18));
53 | // 字体颜色
54 | graphics.setColor(Color.BLACK);
55 | // 描绘字符串
56 | graphics.drawString(value, 20, 28);
57 | graphics.dispose();
58 | if(image == null){
59 | return seal ;
60 | }
61 | try {
62 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
63 | ImageIO.write(image,"png",byteArrayOutputStream);
64 | seal = byteArrayOutputStream.toByteArray();
65 | } catch (Exception e) {
66 | e.printStackTrace();
67 | }
68 | return seal ;
69 | }
70 |
71 |
72 |
73 |
74 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/service/image/EntSealClipService.java:
--------------------------------------------------------------------------------
1 | package com.resrun.service.image;
2 |
3 | import org.springframework.stereotype.Service;
4 |
5 | import javax.imageio.ImageIO;
6 | import javax.swing.*;
7 | import java.awt.*;
8 | import java.awt.image.BufferedImage;
9 | import java.io.ByteArrayInputStream;
10 | import java.io.ByteArrayOutputStream;
11 | import java.io.IOException;
12 |
13 | /**
14 | * @Description: 企业签章业务
15 | * @Package: com.resrun.service.image
16 | * @ClassName: EntSealClipService
17 | * @copyright 北京资源律动科技有限公司
18 | */
19 | @Service
20 | public class EntSealClipService {
21 |
22 | public byte[] clip(byte[] originByte, Integer colorRange){
23 | ByteArrayInputStream inputStream = new ByteArrayInputStream(originByte);
24 | BufferedImage image = null;
25 | try {
26 | image = ImageIO.read(inputStream);
27 | } catch (Exception e) {
28 | e.printStackTrace();
29 | }
30 | // 高度和宽度
31 | int height = image.getHeight();
32 | int width = image.getWidth();
33 |
34 | // 生产背景透明和内容透明的图片
35 | ImageIcon imageIcon = new ImageIcon(image);
36 | BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
37 | Graphics2D g2D = (Graphics2D) bufferedImage.getGraphics(); // 获取画笔
38 | g2D.drawImage(imageIcon.getImage(), 0, 0, null); // 绘制Image的图片,使用了imageIcon.getImage(),目的就是得到image,直接使用image就可以的
39 |
40 | int alpha = 0; // 图片透明度
41 | // 外层遍历是Y轴的像素
42 | for (int y = bufferedImage.getMinY(); y < bufferedImage.getHeight(); y++) {
43 | // 内层遍历是X轴的像素
44 | for (int x = bufferedImage.getMinX(); x < bufferedImage.getWidth(); x++) {
45 | int rgb = bufferedImage.getRGB(x, y);
46 | // 对当前颜色判断是否在指定区间内
47 | if (colorInRange(rgb,colorRange)){
48 | // #AARRGGBB 最前两位为透明度
49 | rgb = (alpha << 24) | (rgb & 0x00ffffff);
50 | bufferedImage.setRGB(x, y, rgb);
51 | alpha = 0;
52 | }else{
53 | // 设置为不透明
54 | alpha = 255;
55 | // #AARRGGBB 最前两位为透明度
56 | rgb = (alpha << 24) | (0xff0000);
57 | bufferedImage.setRGB(x, y, rgb);
58 | }
59 |
60 | }
61 | }
62 | // 绘制设置了RGB的新图片,这一步感觉不用也可以只是透明地方的深浅有变化而已,就像蒙了两层的感觉
63 | g2D.drawImage(bufferedImage, 0, 0, null);
64 |
65 | // 生成图片为PNG
66 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
67 | try {
68 | ImageIO.write(bufferedImage, "png", outputStream);
69 | } catch (IOException e) {
70 | e.printStackTrace();
71 | }
72 | return outputStream.toByteArray();
73 | }
74 |
75 | // 判断是背景还是内容
76 | public boolean colorInRange(int color , Integer colorRange) {
77 | int red = (color & 0xff0000) >> 16;// 获取color(RGB)中R位
78 | int green = (color & 0x00ff00) >> 8;// 获取color(RGB)中G位
79 | int blue = (color & 0x0000ff);// 获取color(RGB)中B位
80 | // 通过RGB三分量来判断当前颜色是否在指定的颜色区间内
81 | if (red >= colorRange && green >= colorRange && blue >= colorRange){
82 | return true;
83 | }
84 | return false;
85 | }
86 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/service/image/EntSealGenerateService.java:
--------------------------------------------------------------------------------
1 | package com.resrun.service.image;
2 |
3 | import com.resrun.utils.Base64;
4 | import org.apache.pdfbox.io.IOUtils;
5 | import org.springframework.stereotype.Service;
6 |
7 | import javax.imageio.ImageIO;
8 | import java.awt.*;
9 | import java.awt.image.BufferedImage;
10 | import java.io.*;
11 |
12 | /**
13 | * @Description: 企业签章业务
14 | * @Package: com.resrun.service.image
15 | * @ClassName: EntSealGenerateService
16 | * @copyright 北京资源律动科技有限公司
17 | */
18 | @Service
19 | public class EntSealGenerateService {
20 |
21 |
22 |
23 | /**
24 | * @Description #生成企业签章
25 | * @Param [topText, middleText]
26 | * @return byte[]
27 | **/
28 | public byte[] generateEntSeal(String topText,String middleText){
29 |
30 | byte[] bytes = export2pic("png", middleText,topText);
31 | return bytes;
32 | }
33 |
34 |
35 |
36 |
37 | /**
38 | * 印章名称距中心点偏移量,按照y轴方向
39 | */
40 | private int nameOffset = 50;
41 | /**
42 | * 印章宽度
43 | */
44 | private int width = 200;
45 | /**
46 | * 印章高度
47 | */
48 | private int height = 200;
49 | /**
50 | * 印章中心标志(默认为五角星)外接圆半径
51 | */
52 | private float radius = 30;
53 | /**
54 | * 印章名称颜色
55 | */
56 | private Color nameColor = Color.RED;
57 | /**
58 | * 印章所属单位
59 | */
60 | // private String firm;
61 | /**
62 | * 印章所属单位颜色
63 | */
64 | private Color firmColor = Color.RED;
65 | private float firmScale = 0.7F;
66 | /**
67 | * 边框线宽
68 | */
69 | private float borderWidth = 5F;
70 | /**
71 | * 边框颜色
72 | */
73 | private Color borderColor = Color.RED;
74 | /**
75 | * 印章标记(默认为五角星)线宽
76 | */
77 | private float signBorderWidth = 3F;
78 | /**
79 | * 印章标记颜色
80 | */
81 | private Color signBorderColor = Color.RED;
82 | /**
83 | * 印章标记填充颜色
84 | */
85 | private Color signFillColor = Color.RED;
86 |
87 |
88 | public void draw(Graphics2D g2d,String middleText,String topText) {
89 | // 把绘制起点挪到圆中心点
90 | g2d.translate(width / 2, height / 2);
91 |
92 | Stroke stroke = g2d.getStroke();// 旧的线性
93 | // 填充五角星
94 | Polygon polygon = getPentaclePoints(radius);
95 | if (signFillColor != null) {
96 | g2d.setColor(signFillColor);
97 | g2d.fill(polygon);
98 | }
99 | // 绘制五角星边框
100 | g2d.setStroke(new BasicStroke(signBorderWidth));
101 | g2d.setColor(signBorderColor);
102 | g2d.draw(polygon);
103 |
104 | // 绘制印章边框
105 | g2d.setFont(nameFont);
106 | g2d.setColor(borderColor);
107 | g2d.setStroke(new BasicStroke(borderWidth));
108 | g2d.drawOval(-width / 2, -height / 2, width, height);
109 | g2d.setStroke(stroke);
110 |
111 | // 绘制印章名称
112 | g2d.setFont(nameFont);
113 | g2d.setColor(nameColor); //g2d.setStroke(new BasicStroke(10F));
114 | FontMetrics fm = g2d.getFontMetrics();
115 | int w = fm.stringWidth(middleText);// 名称宽度
116 | int h = fm.getHeight();// 名称高度
117 | int y = fm.getAscent() - h / 2;// 求得中心线经过字体的高度的一半时的字体的起绘点
118 | g2d.drawString(middleText, -w / 2, y + nameOffset);
119 |
120 | // 绘制印章单位
121 | g2d.setFont(getFirmFont(topText));
122 | g2d.setColor(firmColor);
123 | fm = g2d.getFontMetrics();
124 | h = fm.getHeight();// 字高度
125 |
126 | int count = topText.length();// 字数
127 | int r = width / 2;// 半径,就假设此印章是个矩形,方便计算
128 | float angle;
129 | float start;
130 |
131 | if(count>1 && count <=10){
132 | angle = 20f;// 字间角度
133 | start = 90+(360 - angle*(count-1))/2;// 以x轴正向为0,顺时针旋转
134 | }else if(count > 10){
135 | angle = (360 - firmAngle) / (count-1);// 字间角度
136 | start = 90+(360 - angle*(count-1))/2;// 以x轴正向为0,顺时针旋转
137 | }else{
138 | angle = 0f;// 字间角度
139 | start = 90+(360 - angle*(2-1))/2;// 以x轴正向为0,顺时针旋转
140 | }
141 | double vr = Math.toRadians(90);// 垂直旋转弧度
142 | char[] chars = topText.toCharArray();
143 | for (int i = 0; i < count; i++) {
144 | char c = chars[i];// 需要绘制的字符
145 | int cw = fm.charWidth(c);// 此字符宽度
146 | float a = start + angle * i;// 现在角度
147 | double radians = Math.toRadians(a);
148 | g2d.rotate(radians);// 旋转坐标系,让要绘制的字符处于x正轴
149 | float x = r - h;// 绘制字符的x坐标为半径减去字高度
150 | g2d.translate(x, 0);// 移动到此位置,此时字和x轴垂直
151 | g2d.rotate(vr);// 旋转90度,让字平行于x轴
152 | g2d.scale(firmScale, 1);// 缩放字体宽度
153 | g2d.drawString(String.valueOf(c), -cw / 2, 0);// 此点为字的中心点
154 | // 将所有设置还原,等待绘制下一个
155 | g2d.scale(1 / firmScale, 1);
156 | g2d.rotate(-vr);
157 | g2d.translate(-x, 0);
158 | g2d.rotate(-radians);
159 | }
160 | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
161 | }
162 |
163 | /**
164 | * 获取具有指定半径外接圆的五角星顶点
165 | *
166 | * @param radius
167 | * 圆半径
168 | */
169 | private Polygon getPentaclePoints(float radius) {
170 | if (radius <= 0)
171 | return null;
172 | float lradius = radius * 0.381966f;// 根据radius求内圆半径
173 | double halfpi = Math.PI / 180f;
174 | Point[] points = new Point[10];
175 | for (int i = 0; i < points.length; i++) {
176 | if (i % 2 == 1)
177 | points[i] = new Point(
178 | (int) (Math.sin(halfpi * 36 * i) * radius),
179 | (int) (Math.cos(halfpi * 36 * i) * radius));
180 | else
181 | points[i] = new Point(
182 | (int) (Math.sin(halfpi * 36 * i) * lradius),
183 | (int) (Math.cos(halfpi * 36 * i) * lradius));
184 | }
185 | Polygon polygon = new Polygon();
186 | for (Point p : points) {
187 | polygon.addPoint(p.x, p.y);
188 | }
189 | return polygon;
190 | }
191 |
192 | public int getWidth() {
193 | return width;
194 | }
195 |
196 | public int getHeight() {
197 | return height;
198 | }
199 |
200 |
201 | private Font nameFont = new Font("宋体", Font.PLAIN, 19);
202 |
203 | private Font getFirmFont(String topText){
204 | Font font = null;
205 | int len = topText.length();
206 | System.out.println(len);
207 | if(len==1){
208 | setFirmAngle(355);
209 | font = new Font("宋体", Font.PLAIN, 25);
210 | }else if(len>1 && len<=3){
211 | setFirmAngle(310);
212 | font = new Font("宋体", Font.PLAIN, 25);
213 | }else if(len>3 && len<=6){
214 | setFirmAngle(250);
215 | font = new Font("宋体", Font.PLAIN, 25);
216 | }else if(len>6 && len <=10){
217 | setFirmAngle(200);
218 | font = new Font("宋体", Font.PLAIN, 25);
219 | }else if(len>10 && len<=13){
220 | setFirmAngle(180);
221 | font = new Font("宋体", Font.PLAIN, 25);
222 | }
223 | else if(len>13 && len<=20){
224 | font = new Font("宋体", Font.PLAIN, 25);
225 | setFirmAngle(120);
226 | }else if(len>20 && len <= 25){
227 | font = new Font("宋体", Font.PLAIN, 23);
228 |
229 | setFirmAngle(80);
230 | }else if(len>25 && len < 30){
231 | setFirmAngle(80);
232 | font = new Font("宋体", Font.PLAIN, 19);
233 | }else if(len>=30 && len <= 40){
234 | setFirmAngle(80);
235 | font = new Font("宋体", Font.PLAIN, 19);
236 | }else{
237 | setFirmAngle(10);
238 | font = new Font("宋体", Font.PLAIN, 17);
239 | }
240 | return font;
241 | }
242 |
243 |
244 |
245 | private int firmAngle;
246 | public void setFirmAngle(int firmAngle){
247 | this.firmAngle = firmAngle;
248 | }
249 |
250 |
251 |
252 |
253 |
254 | /**
255 | * 导出此印章为透明背景的图片字节数组.
256 | *
257 | * @param format
258 | * 图片类型,如果为null,则默认为png
259 | * @return 数组
260 | * @throws FileNotFoundException
261 | * @throws IOException
262 | * 写出图像数据出现问题
263 | */
264 | public byte[] export2pic(String format,String middleText,String topText) {
265 | int fix = 5;// 宽高修正,如果宽高就为图片宽高,可能边框线被切割
266 | BufferedImage bi = new BufferedImage(getWidth() + fix * 2, getHeight()
267 | + fix * 2, 3);
268 |
269 |
270 | Graphics2D g2d = bi.createGraphics();
271 | //防锯齿状毛刺算法
272 | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
273 | g2d.translate(fix, fix);
274 | draw(g2d,middleText,topText);
275 |
276 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
277 | try {
278 | ImageIO.write(bi, format == null ? "png" : format, baos);
279 | return baos.toByteArray();
280 | } catch (IOException e) {
281 | throw new RuntimeException(topText+":生成企业签章失败",e);
282 | }finally {
283 | try {
284 | if(baos!=null)
285 | baos.close();
286 | } catch (IOException e) {
287 | }
288 | }
289 |
290 | }
291 |
292 |
293 |
294 |
295 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/service/image/PdfConvertImageService.java:
--------------------------------------------------------------------------------
1 | package com.resrun.service.image;
2 |
3 | import com.resrun.service.pojo.ConvertImage;
4 | import org.apache.pdfbox.pdmodel.PDDocument;
5 | import org.apache.pdfbox.rendering.PDFRenderer;
6 | import org.springframework.stereotype.Service;
7 |
8 | import javax.imageio.ImageIO;
9 | import java.awt.image.BufferedImage;
10 | import java.io.ByteArrayOutputStream;
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | /**
15 | * @Description: pdf文件转图片业务
16 | * @Package: com.resrun.service.image
17 | * @ClassName: Pdf2ImageService
18 | * @copyright 北京资源律动科技有限公司
19 | */
20 | @Service
21 | public class PdfConvertImageService {
22 |
23 | private Float resolution = 90f ;
24 | /**图片格式为jpg类型**/
25 | public static final String IMG_JPE = "jpg";
26 | /**图片格式为png类型**/
27 | public static final String IMG_PNG = "png";
28 |
29 | public List convertImage(byte[] signFileByte){
30 | if(signFileByte == null){
31 | return null ;
32 | }
33 | List responseList = new ArrayList<>();
34 | PDDocument doc = null;
35 | try {
36 | //加载pdf文件
37 | doc = PDDocument.load(signFileByte);
38 | PDFRenderer renderer = new PDFRenderer(doc);
39 | int pageCount = doc.getNumberOfPages();
40 | if(pageCount == 0){
41 | return responseList;
42 | }
43 |
44 | for (int i = 0; i < pageCount; ++i) {
45 | int page = i ;
46 | BufferedImage image = renderer.renderImageWithDPI(page, resolution);
47 | ByteArrayOutputStream stream = new ByteArrayOutputStream();
48 | ImageIO.write(image, IMG_PNG, stream);
49 | byte[] bytes = stream.toByteArray();
50 | if(bytes != null){
51 | ConvertImage response = new ConvertImage();
52 | response.setImageByte(bytes);
53 | response.setPage(page);
54 | responseList.add(response);
55 |
56 | }
57 | }
58 | }catch (Exception e) {
59 | e.printStackTrace();
60 | }
61 |
62 | return responseList ;
63 | }
64 |
65 |
66 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/service/image/PersonalSealService.java:
--------------------------------------------------------------------------------
1 | package com.resrun.service.image;
2 |
3 | import com.resrun.enums.SealColorEnum;
4 | import org.springframework.stereotype.Service;
5 |
6 | import java.awt.*;
7 | import java.awt.image.BufferedImage;
8 |
9 | /**
10 | * @Description: 个人签章业务
11 | * @Package: com.resrun.service.image
12 | * @ClassName: PersonalSealService
13 | * @copyright 北京资源律动科技有限公司
14 | */
15 | @Service
16 | public class PersonalSealService {
17 |
18 |
19 | private static final int WIDTH = 200;// 图片宽度
20 | private static final int HEIGHT = 200;// 图片高度
21 |
22 |
23 |
24 |
25 |
26 |
27 | /**
28 | * @Description #生成正方形带方框的签章图片
29 | * @Param [name]
30 | * @return java.awt.image.BufferedImage
31 | **/
32 | public static BufferedImage drawSquareFrame(String name, Integer color) throws Exception {
33 |
34 | int imageWidth = 166 ;
35 | int imageHeight = 166 ;
36 | Color allColor = Color.RED ;
37 | if(SealColorEnum.RED.getCode().equals(color)){
38 | allColor = Color.RED ;
39 | }else if(SealColorEnum.BLUE.getCode().equals(color)){
40 | allColor = Color.BLUE;
41 | }else if(SealColorEnum.BLACK.getCode().equals(color)){
42 | allColor = Color.BLACK;
43 | }
44 |
45 | // 生成正方形图片,宽高都是166,imageType为3是生成透明图片
46 | BufferedImage bi = new BufferedImage(imageWidth, imageHeight, 3);
47 | // 设置属性
48 | Graphics2D g2d = bi.createGraphics();
49 | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
50 | // g2d.drawImage(bi, 0, 0, imageWidth, imageHeight, Color.WHITE,null);不能是白色,不然图片就不透明了
51 | g2d.drawImage(bi, 0, 0, imageWidth, imageHeight,null);
52 |
53 | // 线宽
54 | g2d.setStroke(new BasicStroke(8F));
55 | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
56 | g2d.setColor(allColor);
57 | g2d.drawRect(1, 1, imageWidth - 3 , imageHeight - 3);
58 |
59 |
60 | char[] charArray = name.toCharArray();
61 | g2d.setFont(new Font("楷体", Font.LAYOUT_LEFT_TO_RIGHT, 70));// 写入签名
62 | g2d.setColor(allColor);
63 | if (charArray.length == 3) {
64 | // 设置字体
65 | g2d.drawString(new String(new char[] { charArray[0] }), 80, 68); // 写文字
66 | g2d.drawString(new String(new char[] { charArray[1] }), 80, 148); // 写文字
67 | g2d.drawString(new String(new char[] { charArray[2] }), 5, 108); // 写文字
68 |
69 | } else {
70 | // 设置字体
71 | g2d.drawString(new String(new char[] { charArray[0] }), 80, 68); // 写文字
72 | g2d.drawString(new String(new char[] { charArray[1] }), 80, 148); // 写文字
73 | g2d.drawString(new String(new char[] { charArray[2] }), 5, 68); // 写文字
74 | g2d.drawString(new String(new char[] { charArray[3] }), 5, 148); // 写文字
75 | }
76 |
77 |
78 | return bi;
79 |
80 |
81 | }
82 |
83 | /**
84 | * @Description #生成正方形不带方框的签章图片
85 | * @Param [name]
86 | * @return java.awt.image.BufferedImage
87 | **/
88 | public static BufferedImage drawSquareNoFrame(String name,Integer color) throws Exception {
89 |
90 | int imageWidth = 166 ;
91 | int imageHeight = 166 ;
92 | Color allColor = Color.RED ;
93 | if(SealColorEnum.RED.getCode().equals(color)){
94 | allColor = Color.RED ;
95 | }else if(SealColorEnum.BLUE.getCode().equals(color)){
96 | allColor = Color.BLUE;
97 | }else if(SealColorEnum.BLACK.getCode().equals(color)){
98 | allColor = Color.BLACK;
99 | }
100 |
101 | // 生成正方形图片,宽高都是166,imageType为3是生成透明图片
102 | BufferedImage bi = new BufferedImage(imageWidth, imageHeight, 3);
103 | // 设置属性
104 | Graphics2D g2d = bi.createGraphics();
105 | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
106 | // g2d.drawImage(bi, 0, 0, imageWidth, imageHeight, Color.WHITE,null);不能是白色,不然图片就不透明了
107 | g2d.drawImage(bi, 0, 0, imageWidth, imageHeight,null);
108 |
109 | char[] charArray = name.toCharArray();
110 | g2d.setFont(new Font("楷体", Font.LAYOUT_LEFT_TO_RIGHT, 70));// 写入签名
111 | g2d.setColor(allColor);
112 | if (charArray.length == 3) {
113 | // 设置字体
114 | g2d.drawString(new String(new char[] { charArray[0] }), 80, 68); // 写文字
115 | g2d.drawString(new String(new char[] { charArray[1] }), 80, 148); // 写文字
116 | // 设置字体
117 | g2d.drawString(new String(new char[] { charArray[2] }), 5, 108); // 写文字
118 | } else {
119 | // 设置字体
120 | g2d.drawString(new String(new char[] { charArray[0] }), 80, 68); // 写文字
121 | g2d.drawString(new String(new char[] { charArray[1] }), 80, 148); // 写文字
122 | g2d.drawString(new String(new char[] { charArray[2] }), 5, 68); // 写文字
123 | g2d.drawString(new String(new char[] { charArray[3] }), 5, 148); // 写文字
124 | }
125 |
126 |
127 | return bi;
128 |
129 |
130 | }
131 |
132 | /**
133 | * @Description #生成长方形带方框的签章图片
134 | * @Param [name]
135 | * @return java.awt.image.BufferedImage
136 | **/
137 | public static BufferedImage drawRectangleFrame(String name,Integer color) throws Exception {
138 |
139 | Color allColor = Color.RED ;
140 | if(SealColorEnum.RED.getCode().equals(color)){
141 | allColor = Color.RED ;
142 | }else if(SealColorEnum.BLUE.getCode().equals(color)){
143 | allColor = Color.BLUE;
144 | }else if(SealColorEnum.BLACK.getCode().equals(color)){
145 | allColor = Color.BLACK;
146 | }
147 |
148 |
149 | if (name == null) {
150 | return null;
151 | }
152 | char[] charArray = name.toCharArray();
153 | if (charArray.length <= 0) {
154 | return null;
155 | }
156 | int length = charArray.length;
157 | int step = 260;
158 | int width = step * length;
159 | // 图片长度
160 | BufferedImage bi = new BufferedImage(width, step, 3);
161 | Graphics2D g2d = bi.createGraphics();
162 | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
163 | // g2d.drawImage(bi, 0, 0, width, step, Color.WHITE,null);不能是白色,不然图片就不透明了
164 | g2d.drawImage(bi, 0, 0, width, step, null);
165 |
166 | // 线宽
167 | g2d.setStroke(new BasicStroke(8F));
168 | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
169 | g2d.setColor(allColor);
170 | g2d.drawRect(1, 1, width - 5, step - 5);
171 | // 设置字体
172 | g2d.setFont(new Font("楷体", Font.LAYOUT_LEFT_TO_RIGHT, 230));// 写入签名
173 | // g2d.scale(0.9F, 1);// 缩放字体宽度
174 | int x = 0;
175 | for (char c : charArray) {
176 |
177 | g2d.drawString(new String(new char[] { c }), x, 210); // 写文字
178 | x = x + step;
179 | }
180 | return bi;
181 | }
182 |
183 | /**
184 | * @Description #生成长方形不带方框的签章图片
185 | * @Param [name]
186 | * @return java.awt.image.BufferedImage
187 | **/
188 | public static BufferedImage drawRectangleNoFrame(String name,Integer color) throws Exception {
189 |
190 | if (name == null) {
191 | return null;
192 | }
193 | char[] charArray = name.toCharArray();
194 | if (charArray.length <= 0) {
195 | return null;
196 | }
197 | Color allColor = Color.RED ;
198 | if(SealColorEnum.RED.getCode().equals(color)){
199 | allColor = Color.RED ;
200 | }else if(SealColorEnum.BLUE.getCode().equals(color)){
201 | allColor = Color.BLUE;
202 | }else if(SealColorEnum.BLACK.getCode().equals(color)){
203 | allColor = Color.BLACK;
204 | }
205 | int length = charArray.length;
206 | int step = 260;
207 | int width = step * length;
208 | // 图片长度
209 | BufferedImage bi = new BufferedImage(width, step, 3);
210 | Graphics2D g2d = bi.createGraphics();
211 | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
212 | //绘制图片
213 | // g2d.drawImage(bi, 0, 0, width, step, Color.WHITE,null); 不能是白色,不然图片就不透明了
214 | g2d.drawImage(bi, 0, 0, width, step, null);
215 |
216 |
217 | // 设置字体
218 | g2d.setFont(new Font("楷体", Font.LAYOUT_LEFT_TO_RIGHT, 230));
219 | g2d.setColor(allColor);
220 | int x = 0;
221 | for (char c : charArray) {
222 |
223 | g2d.drawString(new String(new char[] { c }), x, 210); // 写文字
224 | x = x + step;
225 | }
226 | return bi;
227 | }
228 |
229 |
230 |
231 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/service/pdf/CalculatePositionService.java:
--------------------------------------------------------------------------------
1 | package com.resrun.service.pdf;
2 |
3 | import com.itextpdf.text.Document;
4 | import com.itextpdf.text.pdf.PdfReader;
5 | import com.resrun.service.pojo.RealPositionProperty;
6 | import com.resrun.service.pojo.SelectKeywords;
7 | import com.resrun.service.pojo.SourcePositionProperty;
8 | import org.springframework.stereotype.Service;
9 |
10 | import java.io.IOException;
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | /**
15 | * @Description: 计算签署位置业务
16 | * @Package: com.resrun.service.pdf
17 | * @ClassName: CalculatePositionService
18 | * @copyright 北京资源律动科技有限公司
19 | */
20 | @Service
21 | public class CalculatePositionService {
22 |
23 | /**
24 | * @Description #批量计算真实签署位置
25 | * @Param [sourcePositionProperties]
26 | * @return java.util.List
27 | **/
28 | public List calculatePositions(List sourcePositionProperties, byte[] pdfFileByte){
29 | List realPositionProperties = new ArrayList<>();
30 |
31 |
32 | PdfReader reader = null ;
33 | try {
34 | //将pdf文件读入PdfReader工具类
35 | reader = new PdfReader(pdfFileByte);
36 | for(SourcePositionProperty sourcePositionProperty : sourcePositionProperties){
37 | RealPositionProperty realPositionProperty = calculatePosition(sourcePositionProperty,pdfFileByte);
38 | Document document = new Document(reader.getPageSize(sourcePositionProperty.getPage()));
39 | //获取真实pdf文件指定页的真实文档宽高
40 | float realPdfHeight = document.getPageSize().getHeight();
41 | float realPdfWidth = document.getPageSize().getWidth();
42 | //获取页面上文档的宽高
43 | float sourcePageWidth = sourcePositionProperty.getPageWidth();
44 | float sourcePageHeight = sourcePositionProperty.getPageHeight();
45 | //计算真实文档的宽高和页面文档的宽高的比率
46 | float rateHeight = realPdfHeight / sourcePageHeight;
47 | float rateWidth = realPdfWidth / sourcePageWidth;
48 | //计算页面上的横纵坐标,由于页面上给出的是左上角的坐标,所以需要再转换计算一下
49 | //左下角
50 | float pageStartX = sourcePositionProperty.getOffsetX();
51 | float pageStartY = sourcePositionProperty.getOffsetY() + sourcePositionProperty.getHeight() ;
52 | //右上角
53 | float pageEndX = sourcePositionProperty.getOffsetX() + sourcePositionProperty.getWidth();
54 | float pageEndY = sourcePositionProperty.getOffsetY();
55 | //根据比率去计算真实文档上的坐标位置
56 | float startX = pageStartX * rateWidth ;
57 | float startY = pageStartY * rateHeight;
58 | float endX = pageEndX * rateWidth ;
59 | float endY = pageEndY * rateHeight ;
60 | //由于页面的纵坐标和pdf的纵坐标是相反的,所以真实的pdf的纵坐标在计算的时候需要再反转一下
61 | startY = realPdfHeight - startY ;
62 | endY = realPdfHeight - endY ;
63 | //封装返回数据
64 | realPositionProperty.setStartx(startX);
65 | realPositionProperty.setStarty(startY);
66 | realPositionProperty.setEndx(endX);
67 | realPositionProperty.setEndy(endY);
68 | realPositionProperty.setPageNum(sourcePositionProperty.getPage());
69 | document.close();
70 | realPositionProperties.add(realPositionProperty);
71 | }
72 |
73 | reader.close();
74 | } catch (Exception e) {
75 | e.printStackTrace();
76 | }
77 | return realPositionProperties ;
78 | }
79 |
80 |
81 |
82 | /**
83 | * @Description #单独计算真实签署位置
84 | * @Param [sourcePositionProperty]
85 | * @return com.resrun.modules.sign.service.tool.pojo.RealPositionProperty
86 | **/
87 | public RealPositionProperty calculatePosition(SourcePositionProperty sourcePositionProperty, byte[] pdfFileByte){
88 | RealPositionProperty realPositionProperty = new RealPositionProperty();
89 | PdfReader reader = null ;
90 | Document document = null ;
91 | try {
92 | //将pdf文件读入PdfReader工具类
93 | reader = new PdfReader(pdfFileByte);
94 | document = new Document(reader.getPageSize(sourcePositionProperty.getPage()));
95 | //获取真实pdf文件指定页的真实文档宽高
96 | float realPdfHeight = document.getPageSize().getHeight();
97 | float realPdfWidth = document.getPageSize().getWidth();
98 | //获取页面上文档的宽高
99 | float sourcePageWidth = sourcePositionProperty.getPageWidth();
100 | float sourcePageHeight = sourcePositionProperty.getPageHeight();
101 | //计算真实文档的宽高和页面文档的宽高的比率
102 | float rateHeight = realPdfHeight / sourcePageHeight;
103 | float rateWidth = realPdfWidth / sourcePageWidth;
104 | //计算页面上的横纵坐标,由于页面上给出的是左上角的坐标,所以需要再转换计算一下
105 | //左下角
106 | float pageStartX = sourcePositionProperty.getOffsetX();
107 | float pageStartY = sourcePositionProperty.getOffsetY() + sourcePositionProperty.getHeight() ;
108 | //右上角
109 | float pageEndX = sourcePositionProperty.getOffsetX() + sourcePositionProperty.getWidth();
110 | float pageEndY = sourcePositionProperty.getOffsetY();
111 | //根据比率去计算真实文档上的坐标位置
112 | float startX = pageStartX * rateWidth ;
113 | float startY = pageStartY * rateHeight;
114 | float endX = pageEndX * rateWidth ;
115 | float endY = pageEndY * rateHeight ;
116 | //由于页面的纵坐标和pdf的纵坐标是相反的,所以真实的pdf的纵坐标在计算的时候需要再反转一下
117 | startY = realPdfHeight - startY ;
118 | endY = realPdfHeight - endY ;
119 | //封装返回数据
120 | realPositionProperty.setStartx(startX);
121 | realPositionProperty.setStarty(startY);
122 | realPositionProperty.setEndx(endX);
123 | realPositionProperty.setEndy(endY);
124 | realPositionProperty.setPageNum(sourcePositionProperty.getPage());
125 |
126 | document.close();
127 | reader.close();
128 | } catch (Exception e) {
129 | e.printStackTrace();
130 | }
131 | return realPositionProperty ;
132 | }
133 |
134 |
135 | public RealPositionProperty calculatePosition(SourcePositionProperty sourcePositionProperty){
136 | RealPositionProperty realPositionProperty = new RealPositionProperty();
137 | //获取真实pdf文件指定页的真实文档宽高
138 | float realPdfHeight = sourcePositionProperty.getRealHeight();
139 | float realPdfWidth = sourcePositionProperty.getRealWidth();
140 | //获取页面上文档的宽高
141 | float sourcePageWidth = sourcePositionProperty.getPageWidth();
142 | float sourcePageHeight = sourcePositionProperty.getPageHeight();
143 | //计算真实文档的宽高和页面文档的宽高的比率
144 | float rateHeight = realPdfHeight / sourcePageHeight;
145 | float rateWidth = realPdfWidth / sourcePageWidth;
146 | //计算页面上的横纵坐标,由于页面上给出的是左上角的坐标,所以需要再转换计算一下
147 | //左下角
148 | float pageStartX = sourcePositionProperty.getOffsetX();
149 | float pageStartY = sourcePositionProperty.getOffsetY() + sourcePositionProperty.getHeight() ;
150 | //右上角
151 | float pageEndX = sourcePositionProperty.getOffsetX() + sourcePositionProperty.getWidth();
152 | float pageEndY = sourcePositionProperty.getOffsetY();
153 | //根据比率去计算真实文档上的坐标位置
154 | float startX = pageStartX * rateWidth ;
155 | float startY = pageStartY * rateHeight;
156 | float endX = pageEndX * rateWidth ;
157 | float endY = pageEndY * rateHeight ;
158 | //由于页面的纵坐标和pdf的纵坐标是相反的,所以真实的pdf的纵坐标在计算的时候需要再反转一下
159 | startY = realPdfHeight - startY ;
160 | endY = realPdfHeight - endY ;
161 | //封装返回数据
162 | realPositionProperty.setStartx(startX);
163 | realPositionProperty.setStarty(startY);
164 | realPositionProperty.setEndx(endX);
165 | realPositionProperty.setEndy(endY);
166 | realPositionProperty.setPageNum(sourcePositionProperty.getPage());
167 | return realPositionProperty ;
168 | }
169 |
170 |
171 |
172 |
173 | /**
174 | * 通过查询关键字来获得签名位置信息
175 | * @param pdfFile 签署源文件
176 | * @param keyWords 关键字
177 | * @param width 签章宽度
178 | * @param height 签章高度
179 | * @return 签署位置信息
180 | * @throws IOException
181 | */
182 | public RealPositionProperty getPositionByKeyWords(byte[] pdfFile, String keyWords, int width, int height) {
183 | RealPositionProperty positionProperty = new RealPositionProperty();
184 | //调用通过关键字查询位置的方法
185 | float[] result = new float[0];
186 | try {
187 | result = new SelectKeywords().selectKeyword(pdfFile,keyWords);
188 | } catch (Exception e) {
189 | e.printStackTrace();
190 | }
191 | if(result !=null){
192 |
193 | positionProperty.setStartx(result[0]);
194 | positionProperty.setStarty(result[1]+height/4);
195 | positionProperty.setPageNum((int)result[2]);
196 | positionProperty.setEndx(result[0]+width/2);
197 | positionProperty.setEndy(result[1]-height/4);
198 |
199 | }
200 | return positionProperty;
201 | }
202 |
203 | /**
204 | * 通过查询关键字来获得签名位置信息
205 | *
206 | * 同一个关键字出现在多处会一次性全部找出
207 | *
208 | * @param pdfFile 签署源文件
209 | * @param keyWords 关键字
210 | * @param width 签章宽度
211 | * @param height 签章高度
212 | * @return 签署位置信息
213 | * @throws IOException
214 | */
215 | public List getAllPositionByKeyWords(byte[] pdfFile,String keyWords,int width,int height) {
216 | List positions = new ArrayList();
217 | //调用通过关键字查询位置的方法
218 | List results = null;
219 | try {
220 | results = new SelectKeywords().selectAllKeyword(pdfFile, keyWords);
221 | } catch (Exception e) {
222 | e.printStackTrace();
223 | }
224 | if(results !=null && results.size()>0){
225 | for (float[] result : results) {
226 | RealPositionProperty positionProperty = new RealPositionProperty();
227 |
228 | positionProperty.setStartx(result[0]);
229 | positionProperty.setStarty(result[1]+height/4);
230 | positionProperty.setPageNum((int)result[2]);
231 | positionProperty.setEndx(result[0]+width/2);
232 | positionProperty.setEndy(result[1]-height/4);
233 |
234 |
235 | positions.add(positionProperty);
236 | }
237 | }
238 | return positions;
239 | }
240 |
241 |
242 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/service/pdf/SignService.java:
--------------------------------------------------------------------------------
1 | package com.resrun.service.pdf;
2 |
3 | import com.itextpdf.text.DocumentException;
4 | import com.itextpdf.text.Image;
5 | import com.itextpdf.text.Rectangle;
6 | import com.itextpdf.text.pdf.PdfReader;
7 | import com.itextpdf.text.pdf.PdfSignatureAppearance;
8 | import com.itextpdf.text.pdf.PdfStamper;
9 | import com.itextpdf.text.pdf.security.*;
10 | import com.resrun.service.pojo.CertificateProperty;
11 | import com.resrun.service.pojo.RealPositionProperty;
12 | import org.bouncycastle.jce.provider.BouncyCastleProvider;
13 | import org.springframework.stereotype.Service;
14 |
15 | import java.io.ByteArrayInputStream;
16 | import java.io.ByteArrayOutputStream;
17 | import java.io.IOException;
18 | import java.security.GeneralSecurityException;
19 | import java.security.KeyStore;
20 | import java.security.PrivateKey;
21 | import java.security.Security;
22 | import java.security.cert.Certificate;
23 |
24 | /**
25 | * @Description: 签署业务
26 | * @Package: com.resrun.service.pdf
27 | * @ClassName: SignService
28 | * @copyright 北京资源律动科技有限公司
29 | */
30 | @Service
31 | public class SignService {
32 |
33 |
34 | public byte[] signingContract(byte[] pdfFile, byte[] signBadge, CertificateProperty cert,
35 | RealPositionProperty position) throws GeneralSecurityException, IOException, DocumentException {
36 | System.setProperty("javax.xml.parsers.DocumentBuilderFactory","com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl");
37 | Security.addProvider(new BouncyCastleProvider());
38 | //1、解析证书
39 | // Java 安全属性文件中指定的默认 keystore 类型;如果不存在此类属性,则返回字符串 "jks"。 PKCS12
40 | KeyStore ks = KeyStore.getInstance(cert.getCertType());
41 | try {
42 | char[] chars = cert.getPassword().toCharArray();
43 | ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cert.getCertFile());
44 | ks.load(byteArrayInputStream, chars);
45 | } catch (Exception e) {
46 | e.printStackTrace();
47 | }
48 | // 获取keystore中的所有别名
49 | String alias = (String) ks.aliases().nextElement();
50 | // 返回:请求的密钥, 入力参数:别名,用于恢复密钥的密码
51 | PrivateKey pk = (PrivateKey) ks.getKey(alias, cert.getPassword().toCharArray());
52 | // 证书链(按用户证书在前,根证书授权在后的顺序)
53 | Certificate[] chain = ks.getCertificateChain(alias);
54 |
55 | byte[] signedFileByte = null ;
56 | PdfReader reader = null ;
57 | ByteArrayOutputStream signedFile = null ;
58 | PdfStamper stamper = null ;
59 | try {
60 | //2、读取PDF文件
61 | reader = new PdfReader(pdfFile);
62 | signedFile = new ByteArrayOutputStream();
63 | stamper = PdfStamper.createSignature(reader, signedFile, '\0', null, true);
64 | //3、给签署属性服务
65 | PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
66 | if (signBadge == null || position == null) {
67 | appearance.setCertificationLevel(certificationLevel);
68 | } else {
69 | int pageNum = 0;
70 | if (inspect) {
71 | //如果检查就会抛出检查异常
72 | pageNum = position.getPageNum();
73 | if (pageNum == 0)
74 | throw new IllegalArgumentException("Pdf page number must be greater than one....!!!");
75 | } else {
76 | pageNum = position.getPageNum() <= 0 ? 1 : position.getPageNum();
77 | }
78 | appearance.setVisibleSignature(new Rectangle(position.getStartx(), position.getStarty(), position.getEndx(), position.getEndy()), pageNum, null);
79 | // 添加签章图片
80 | Image img = Image.getInstance(signBadge);
81 | appearance.setSignatureGraphic(img);
82 | appearance.setImageScale(-1);
83 | appearance.setCertificationLevel(certificationLevel);
84 | appearance.setRenderingMode(renderingMode);
85 | }
86 | appearance.setReason(reason);
87 | appearance.setLocation(location);
88 | //4、调用签署 Creating the signature
89 | ExternalSignature pks = new PrivateKeySignature(pk, hashAlgorithm, BouncyCastleProvider.PROVIDER_NAME);
90 | ExternalDigest digest = new BouncyCastleDigest();
91 | MakeSignature.signDetached(appearance, digest, pks, chain, null, ocspClient, tsaClient, 0, cryptoStandard);
92 | signedFileByte = signedFile.toByteArray();
93 | } catch (Exception e){
94 | e.printStackTrace();
95 | }finally {
96 | // 关闭流
97 | if (stamper != null) stamper.close();
98 | if (signedFile != null) signedFile.close();
99 | if (reader != null) reader.close();
100 | }
101 | return signedFileByte ;
102 | }
103 |
104 |
105 |
106 | //是否判断校验不校验PDF页码
107 | private boolean inspect = true;
108 |
109 | private int certificationLevel = PdfSignatureAppearance.NOT_CERTIFIED;
110 |
111 | private PdfSignatureAppearance.RenderingMode renderingMode = PdfSignatureAppearance.RenderingMode.GRAPHIC;
112 |
113 | private String hashAlgorithm = DigestAlgorithms.SHA256;
114 |
115 | private MakeSignature.CryptoStandard cryptoStandard = MakeSignature.CryptoStandard.CMS;
116 |
117 | private String reason = "防伪造防篡改数字校验"; //原因
118 |
119 | private String location; //位置
120 |
121 | private TSAClient tsaClient; //时间戳服务
122 |
123 | private OcspClient ocspClient;
124 |
125 |
126 |
127 |
128 | public boolean isInspect() {
129 | return inspect;
130 | }
131 |
132 | public void setInspect(boolean inspect) {
133 | this.inspect = inspect;
134 | }
135 |
136 | public int getCertificationLevel() {
137 | return certificationLevel;
138 | }
139 |
140 | public void setCertificationLevel(int certificationLevel) {
141 | this.certificationLevel = certificationLevel;
142 | }
143 |
144 | public PdfSignatureAppearance.RenderingMode getRenderingMode() {
145 | return renderingMode;
146 | }
147 |
148 | public void setRenderingMode(PdfSignatureAppearance.RenderingMode renderingMode) {
149 | this.renderingMode = renderingMode;
150 | }
151 |
152 | public String getHashAlgorithm() {
153 | return hashAlgorithm;
154 | }
155 |
156 | public void setHashAlgorithm(String hashAlgorithm) {
157 | this.hashAlgorithm = hashAlgorithm;
158 | }
159 |
160 | public MakeSignature.CryptoStandard getCryptoStandard() {
161 | return cryptoStandard;
162 | }
163 |
164 | public void setCryptoStandard(MakeSignature.CryptoStandard cryptoStandard) {
165 | this.cryptoStandard = cryptoStandard;
166 | }
167 |
168 | public String getReason() {
169 | return reason;
170 | }
171 |
172 | public void setReason(String reason) {
173 | this.reason = reason;
174 | }
175 |
176 | public String getLocation() {
177 | return location;
178 | }
179 |
180 | public void setLocation(String location) {
181 | this.location = location;
182 | }
183 |
184 | public TSAClient getTsaClient() {
185 | return tsaClient;
186 | }
187 |
188 | public void setTsaClient(TSAClient tsaClient) {
189 | this.tsaClient = tsaClient;
190 | }
191 |
192 | public OcspClient getOcspClient() {
193 | return ocspClient;
194 | }
195 |
196 | public void setOcspClient(OcspClient ocspClient) {
197 | this.ocspClient = ocspClient;
198 | }
199 |
200 |
201 |
202 |
203 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/service/pojo/BaseCertificateInfo.java:
--------------------------------------------------------------------------------
1 | package com.resrun.service.pojo;
2 |
3 | import java.security.KeyStore;
4 |
5 | /**
6 | * @Description: 证书文件属性类
7 | * @Package: com.resrun.service.pojo
8 | * @ClassName: CertificateProperty
9 | * @copyright 北京资源律动科技有限公司
10 | */
11 | public class BaseCertificateInfo {
12 |
13 | /**
14 | * 根证书
15 | */
16 | private KeyStore cert;
17 |
18 | /**
19 | * 证书别名
20 | */
21 | private String alias;
22 |
23 | /**
24 | * 证书密码
25 | */
26 | private String password;
27 |
28 | public KeyStore getCert() {
29 | return cert;
30 | }
31 |
32 | public void setCert(KeyStore cert) {
33 | this.cert = cert;
34 | }
35 |
36 | public String getAlias() {
37 | return alias;
38 | }
39 |
40 | public void setAlias(String alias) {
41 | this.alias = alias;
42 | }
43 |
44 | public String getPassword() {
45 | return password;
46 | }
47 |
48 | public void setPassword(String password) {
49 | this.password = password;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/com/resrun/service/pojo/CertificateProperty.java:
--------------------------------------------------------------------------------
1 | package com.resrun.service.pojo;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Data;
5 | import lombok.NoArgsConstructor;
6 |
7 | import java.io.Serializable;
8 |
9 | /**
10 | * @Description: 证书文件属性类
11 | * @Package: com.resrun.service.pojo
12 | * @ClassName: CertificateProperty
13 | * @copyright 北京资源律动科技有限公司
14 | */
15 | @AllArgsConstructor
16 | @NoArgsConstructor
17 | @Data
18 | public class CertificateProperty implements Serializable {
19 |
20 | private static final long serialVersionUID = -2073805779543816269L;
21 |
22 |
23 | private byte[] certFile;
24 | /** 证书的类型 比如:PKCS12和jks*/
25 | private String certType;
26 | /** 证书密码 */
27 | private String password;
28 |
29 |
30 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/service/pojo/ConvertImage.java:
--------------------------------------------------------------------------------
1 | package com.resrun.service.pojo;
2 |
3 | import io.swagger.annotations.ApiModelProperty;
4 | import lombok.AllArgsConstructor;
5 | import lombok.Data;
6 | import lombok.NoArgsConstructor;
7 |
8 | import java.io.Serializable;
9 |
10 | /**
11 | * @Description: 图片转换对象
12 | * @Package: com.resrun.service.pojo
13 | * @ClassName: ConvertImage
14 | * @copyright 北京资源律动科技有限公司
15 | */
16 | @Data
17 | @AllArgsConstructor
18 | @NoArgsConstructor
19 | public class ConvertImage implements Serializable {
20 |
21 |
22 | private static final long serialVersionUID = -7285616018724242137L;
23 |
24 | @ApiModelProperty("页码")
25 | private Integer page ;
26 |
27 | @ApiModelProperty("图片数据")
28 | private byte[] imageByte ;
29 |
30 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/service/pojo/GenerateCertificateInfo.java:
--------------------------------------------------------------------------------
1 | package com.resrun.service.pojo;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Data;
5 | import lombok.NoArgsConstructor;
6 |
7 | import java.util.Date;
8 |
9 |
10 | /**
11 | * @Description: 证书生成对象
12 | * @Package: com.resrun.service.pojo
13 | * @ClassName: GenerateCertificateInfo
14 | * @copyright 北京资源律动科技有限公司
15 | */
16 | @Data
17 | @AllArgsConstructor
18 | @NoArgsConstructor
19 | public class GenerateCertificateInfo {
20 |
21 | /**
22 | * 证书密码
23 | */
24 | private String password;
25 |
26 | /**
27 | * 证书类型 JKS PFX
28 | */
29 | private String certFileType;
30 |
31 | /**
32 | * 证书库
33 | */
34 | private byte [] jks;
35 |
36 |
37 | /**
38 | * 签名证书
39 | */
40 | private byte [] pfx;
41 |
42 | /**
43 | * 证书序列号
44 | */
45 | private String serial;
46 | /**
47 | * 证书签名算法 SHA1withRSA SHA256withRSA
48 | */
49 | private String algorithmSignature;
50 |
51 | /**
52 | * 证书算法类型: RSA、SM2
53 | */
54 | private String algorithm;
55 |
56 | /**
57 | * 证书有效期起始时间
58 | */
59 | private Date termOfValidityStartTime;
60 |
61 | /**
62 | * 证书有效期结束时间
63 | */
64 | private Date termOfValidityEndTime;
65 |
66 |
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/com/resrun/service/pojo/RealPositionProperty.java:
--------------------------------------------------------------------------------
1 | package com.resrun.service.pojo;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Data;
5 | import lombok.NoArgsConstructor;
6 |
7 | import java.io.Serializable;
8 |
9 | /**
10 | * @Description: 经过计算后的文件签署位置属性类
11 | * @Package: com.resrun.service.pojo
12 | * @ClassName: PositionProperty
13 | * @copyright 北京资源律动科技有限公司
14 | */
15 | @AllArgsConstructor
16 | @NoArgsConstructor
17 | @Data
18 | public class RealPositionProperty implements Serializable {
19 |
20 |
21 | private static final long serialVersionUID = 8586984409612483553L;
22 |
23 | /** 签章左下角x坐标 */
24 | private float startx;
25 |
26 | /** 签章左下角y坐标*/
27 | private float starty;
28 |
29 | /** 签章右上角x坐标*/
30 | private float endx;
31 |
32 | /** 签章右上角x坐标*/
33 | private float endy;
34 |
35 | private int pageNum;
36 |
37 | // 填写值,填写专用
38 | private String value ;
39 | //对齐方式
40 | private String align ;
41 | //字体
42 | private String fontFamily ;
43 | //文字大小
44 | private Integer fontSize ;
45 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/service/pojo/SelectKeywords.java:
--------------------------------------------------------------------------------
1 | package com.resrun.service.pojo;
2 |
3 | import org.apache.commons.logging.Log;
4 | import org.apache.commons.logging.LogFactory;
5 | import org.apache.pdfbox.pdmodel.PDDocument;
6 | import org.apache.pdfbox.text.PDFTextStripper;
7 | import org.apache.pdfbox.text.TextPosition;
8 |
9 | import java.io.ByteArrayInputStream;
10 | import java.io.IOException;
11 | import java.text.DecimalFormat;
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | /**
16 | * @Description: 关键字计算位置
17 | * @Package: com.resrun.service.pojo
18 | * @ClassName: SelectKeywords
19 | * @copyright 北京资源律动科技有限公司
20 | */
21 | public class SelectKeywords extends PDFTextStripper {
22 |
23 | private static ThreadLocal keyWorkPair = new ThreadLocal();
24 |
25 | private Log logger = LogFactory.getLog(SelectKeywords.class);
26 |
27 | public SelectKeywords() throws IOException {
28 | super.setSortByPosition(true);
29 | }
30 |
31 | // public static void main(String[] args) throws Exception {
32 | // //selectKeyword
33 | // File file = new File("e:/test/948ad938bab14f4e8a2d843f6dd81d57.pdf");
34 | // float [] resus = new SelectKeywords().selectKeyword(IOUtils.toByteArray(file), "948ad938bab14f4e8a2d843f6dd81d57");//66 571
35 | // System.out.println(resus[0]+"--"+resus[1]+"---"+resus[2]);
36 | // }
37 | /**
38 | * 查出PDF里所有的关键字
39 | * @param pdfFile
40 | * @param KEY_WORD
41 | * @return
42 | */
43 | public List selectAllKeyword(byte [] pdfFile, String KEY_WORD) {
44 | keyWorkPair.set(new KeyWorkPair(KEY_WORD.split(",")));
45 | ByteArrayInputStream in = null;
46 | PDDocument document = null;
47 | try {
48 | in = new ByteArrayInputStream(pdfFile);
49 | document = PDDocument.load(in);//加载pdf文件
50 | this.getText(document);
51 | List allResu = getAllResult();
52 | return allResu;
53 | } catch (Exception e) {
54 | e.printStackTrace();
55 | }finally{
56 | try {
57 | if(in!=null) in.close();
58 | if(document!=null) document.close();
59 | } catch (IOException e) {
60 | }
61 | }
62 | return null;
63 | }
64 | private List getAllResult(){
65 | KeyWorkPair pair = keyWorkPair.get();
66 | if(pair!=null && pair.getResu()!=null){
67 | keyWorkPair.set(null);
68 | return pair.getAllResu();
69 | }else{
70 | keyWorkPair.set(null);
71 | return null;
72 | }
73 | }
74 | /**
75 | * 查出PDF里最后一个关键字
76 | * @param pdfFile
77 | * @param KEY_WORD
78 | * @return
79 | */
80 | public float [] selectKeyword(byte [] pdfFile,String KEY_WORD) {
81 | keyWorkPair.set(new KeyWorkPair(KEY_WORD.split(",")));
82 | ByteArrayInputStream in = null;
83 | PDDocument document = null;
84 | try {
85 | in = new ByteArrayInputStream(pdfFile);
86 | document = PDDocument.load(in);//加载pdf文件
87 | this.getText(document);
88 | float[] resu = getResult();
89 | return resu;
90 | } catch (Exception e) {
91 | e.printStackTrace();
92 | }finally{
93 | try {
94 | if(in!=null) in.close();
95 | if(document!=null) document.close();
96 | } catch (IOException e) {
97 | }
98 | }
99 | return null;
100 | }
101 |
102 | private float[] getResult(){
103 | KeyWorkPair pair = keyWorkPair.get();
104 | if(pair!=null && pair.getResu()!=null){
105 | keyWorkPair.set(null);
106 | return pair.getResu();
107 | }else{
108 | keyWorkPair.set(null);
109 | return null;
110 | }
111 | }
112 |
113 | @Override
114 | protected void writeString(String string, List textPositions) throws IOException {
115 | for (TextPosition text : textPositions) {
116 | String tChar = text.toString();
117 | char c = tChar.charAt(0);
118 | String REGEX = "[,.\\[\\](:;!?)/]";
119 | lineMatch = matchCharLine(text);
120 | if ((!tChar.matches(REGEX)) && (!Character.isWhitespace(c))) {
121 | if ((!is1stChar) && (lineMatch == true)) {
122 | appendChar(tChar);
123 | } else if (is1stChar == true) {
124 | setWordCoord(text, tChar);
125 | }
126 | } else {
127 | endWord();
128 | }
129 | }
130 | endWord();
131 | }
132 | protected void appendChar(String tChar) {
133 | tWord.append(tChar);
134 | is1stChar = false;
135 | }
136 |
137 | /**
138 | *
139 | * %拼接字符串%。
140 | */
141 | protected void setWordCoord(TextPosition text, String tChar) {
142 | itext = text;
143 | tWord.append("(").append(pageNo).append(")[").append(roundVal(Float.valueOf(text.getXDirAdj()))).append(" : ")
144 | .append(roundVal(Float.valueOf(text.getYDirAdj()))).append("] ").append(tChar);
145 | is1stChar = false;
146 | }
147 |
148 | protected boolean matchCharLine(TextPosition text) {
149 |
150 | Double yVal = roundVal(Float.valueOf(text.getYDirAdj()));
151 | if (yVal.doubleValue() == lastYVal) {
152 | return true;
153 | }
154 | lastYVal = yVal.doubleValue();
155 | endWord();
156 | return false;
157 | }
158 |
159 | protected Double roundVal(Float yVal) {
160 | DecimalFormat rounded = new DecimalFormat("0.0'0'");
161 | Double yValDub = new Double(rounded.format(yVal));
162 | return yValDub;
163 | }
164 |
165 | protected void endWord() {
166 | // String newWord = tWord.toString().replaceAll("[^\\x00-\\x7F]",
167 | // "");//为了检索速度 使用正则去掉中文
168 | String newWord = tWord.toString();// 去掉正则 可以检索中文
169 | KeyWorkPair pair = keyWorkPair.get();
170 |
171 | try {
172 | String[] seekA = pair.getSeekA();
173 | float[] resu = new float[3];
174 | String sWord = newWord.substring(newWord.lastIndexOf(' ') + 1);
175 | if (!"".equals(sWord)) {
176 | if (sWord.contains(seekA[0])) {
177 | resu[2] = getCurrentPageNo();// (595,842)
178 | resu[0] = (float) (roundVal(Float.valueOf(itext.getXDirAdj())) + 0.0F);
179 | resu[1] = 842.0F - (float) (roundVal(Float.valueOf(itext.getYDirAdj())) + 0.0F);
180 | logger.info("PDF关键字信息:[页数:" + resu[2] + "][X:" + resu[0] + "][Y:" + resu[1] + "]");
181 | pair.setResu(resu);
182 | pair.addResuList(resu);//把每一次找出的关键字放在一个集合里
183 | keyWorkPair.set(pair);
184 | }
185 | }
186 | } catch (Exception e) {
187 | e.printStackTrace();
188 | keyWorkPair.set(null);
189 | throw new RuntimeException();
190 | }
191 | tWord.delete(0, tWord.length());
192 | is1stChar = true;
193 | }
194 |
195 | private StringBuilder tWord = new StringBuilder();
196 | private boolean is1stChar = true;
197 | private boolean lineMatch;
198 | private int pageNo = 0;
199 | private double lastYVal;
200 |
201 | private TextPosition itext;
202 |
203 | /**
204 | * 关键字和返回的位置信息类
205 | */
206 | class KeyWorkPair {
207 |
208 | public KeyWorkPair(String[] seekA) {
209 | super();
210 | this.seekA = seekA;
211 | }
212 | public KeyWorkPair(String[] seekA, float[] resu) {
213 | super();
214 | this.seekA = seekA;
215 | this.resu = resu;
216 | }
217 | public KeyWorkPair() {
218 | super();
219 | }
220 | public String[] getSeekA() {
221 | return seekA;
222 | }
223 | public void setSeekA(String[] seekA) {
224 | this.seekA = seekA;
225 | }
226 | public float[] getResu() {
227 | return resu;
228 | }
229 | public void setResu(float[] resu) {
230 | this.resu = resu;
231 | }
232 |
233 | public void addResuList(float[] resu) {
234 | resuAll.add(resu);
235 | }
236 | public List getAllResu() {
237 | return resuAll;
238 | }
239 |
240 | private String[] seekA;
241 | private float[] resu;
242 | //所有的位置
243 | private List resuAll = new ArrayList<>();
244 | }
245 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/service/pojo/SignPdfInfoVo.java:
--------------------------------------------------------------------------------
1 | package com.resrun.service.pojo;
2 |
3 | import io.swagger.annotations.ApiModel;
4 | import io.swagger.annotations.ApiModelProperty;
5 | import lombok.AllArgsConstructor;
6 | import lombok.Data;
7 | import lombok.NoArgsConstructor;
8 |
9 | import java.util.ArrayList;
10 | import java.util.Date;
11 | import java.util.List;
12 |
13 | /**
14 | * @Description: 数字签名信息
15 | * @Package: com.resrun.service.pojo
16 | * @ClassName: SignPdfInfoVo
17 | * @copyright 北京资源律动科技有限公司
18 | */
19 | @Data
20 | @NoArgsConstructor
21 | @AllArgsConstructor
22 | @ApiModel("数字签名信息返回对象")
23 | public class SignPdfInfoVo {
24 | /**
25 | * PDF名称
26 | */
27 | @ApiModelProperty(value = "PDF名称")
28 | private String pdfName;
29 | /**
30 | * PDF大小
31 | */
32 | @ApiModelProperty(value = "PDF大小")
33 | private String pdfSize;
34 |
35 | /**
36 | * 文件签名结果
37 | */
38 | @ApiModelProperty(value = "文件签名结果")
39 | private Integer pdfSingResult;
40 |
41 | @ApiModelProperty(value = "数字签名详细集合")
42 | private List signatureDetails = new ArrayList<>();
43 |
44 | public static class SignatureDetail {
45 | /**
46 | * 数字签名名称
47 | */
48 | @ApiModelProperty(value = "数字签名名称")
49 | public String signName;
50 | /**
51 | * 签名日期时间
52 | */
53 | @ApiModelProperty(value = "签名日期时间")
54 | public Date signTime;
55 | /**
56 | * 有效期开始时间
57 | */
58 | @ApiModelProperty(value = "有效期开始时间")
59 | public Date validStartTime;
60 | /**
61 | * 有效期结束时间
62 | */
63 | @ApiModelProperty(value = "有效期结束时间")
64 | public Date validEndTime;
65 | /**
66 | * 证书名称
67 | */
68 | @ApiModelProperty(value = "证书名称")
69 | public String certName;
70 | /**
71 | * 证书序列号
72 | */
73 | @ApiModelProperty(value = "证书序列号")
74 | public String serialNumber;
75 | /**
76 | * 证书公钥
77 | */
78 | @ApiModelProperty(value = "证书公钥")
79 | public String publicKey;
80 | /**
81 | * 证书格式
82 | */
83 | @ApiModelProperty(value = "证书格式")
84 | public String pubKeyFormat;
85 | /**
86 | * 证书签名算法
87 | */
88 | @ApiModelProperty(value = "证书签名算法")
89 | public String sigAlgName;
90 | /**
91 | * 证书颁发者
92 | */
93 | @ApiModelProperty(value = "证书颁发者")
94 | public String userDnName;
95 | /**
96 | * 是否被篡改
97 | */
98 | @ApiModelProperty(value = "是否被篡改")
99 | public boolean validate;
100 |
101 | @ApiModelProperty(value = "图片内容")
102 | public String imageContent;
103 | /**
104 | * 图片路径
105 | */
106 | @ApiModelProperty(value = "图片路径")
107 | public String imagePath;
108 |
109 | /**
110 | * 签名位置
111 | */
112 | @ApiModelProperty(value = "签名位置")
113 | public String reason;
114 | /**
115 | * 签名类型
116 | */
117 | @ApiModelProperty(value = "签名类型")
118 | public String location;
119 |
120 | /**
121 | * 签名页面
122 | */
123 | @ApiModelProperty(value = "签名页面")
124 | public int pageNum;
125 |
126 | /**
127 | * 图片的base64编码
128 | */
129 | @ApiModelProperty(value = "图片的base64编码")
130 | public String sealBase64;
131 |
132 | /**
133 | * 文件摘要
134 | */
135 | @ApiModelProperty(value = "文件摘要")
136 | public String fileDigest;
137 |
138 | public float llx, lly, urx, ury;
139 | /**
140 | * 文件路径
141 | */
142 | // public String filePath;
143 | }
144 |
145 | }
146 |
--------------------------------------------------------------------------------
/src/main/java/com/resrun/service/pojo/SourcePositionProperty.java:
--------------------------------------------------------------------------------
1 | package com.resrun.service.pojo;
2 |
3 | import io.swagger.annotations.ApiModelProperty;
4 | import lombok.AllArgsConstructor;
5 | import lombok.Data;
6 | import lombok.NoArgsConstructor;
7 |
8 | import java.io.Serializable;
9 |
10 | /**
11 | * @Description: 原始文件签署位置属性
12 | * @Package: com.resrun.service.pojo
13 | * @ClassName: SourcePositionProperty
14 | * @copyright 北京资源律动科技有限公司
15 | */
16 | @Data
17 | @AllArgsConstructor
18 | @NoArgsConstructor
19 | public class SourcePositionProperty implements Serializable {
20 |
21 | private static final long serialVersionUID = 725976764583634367L;
22 |
23 | @ApiModelProperty("控件X坐标(左上角)")
24 | private Float offsetX ;
25 | @ApiModelProperty("控件Y坐标(左上角)")
26 | private Float offsetY ;
27 | @ApiModelProperty("控件宽度")
28 | private Float width ;
29 | @ApiModelProperty("控件高度")
30 | private Float height ;
31 | @ApiModelProperty("当前文件页面宽度")
32 | private Float pageWidth ;
33 | @ApiModelProperty("当前文件页面高度")
34 | private Float pageHeight ;
35 | @ApiModelProperty("控件所属页码")
36 | private Integer page ;
37 |
38 | @ApiModelProperty("当前文件页面宽度")
39 | private Float realWidth ;
40 | @ApiModelProperty("当前文件页面高度")
41 | private Float realHeight ;
42 |
43 |
44 | }
--------------------------------------------------------------------------------
/src/main/java/com/resrun/service/verify/ExtImageRenderListener.java:
--------------------------------------------------------------------------------
1 | package com.resrun.service.verify;
2 |
3 | import com.itextpdf.text.pdf.PdfDictionary;
4 | import com.itextpdf.text.pdf.parser.ImageRenderInfo;
5 | import com.itextpdf.text.pdf.parser.PdfImageObject;
6 | import com.itextpdf.text.pdf.parser.RenderListener;
7 | import com.itextpdf.text.pdf.parser.TextRenderInfo;
8 | import lombok.Data;
9 | import lombok.SneakyThrows;
10 | import lombok.extern.slf4j.Slf4j;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | /**
16 | * @Description: 图像监听类
17 | * @Package: com.resrun.service.verify
18 | * @ClassName: ExtImageRenderListener
19 | * @copyright 北京资源律动科技有限公司
20 | */
21 | @Slf4j
22 | @Data
23 | public class ExtImageRenderListener implements RenderListener {
24 | private int i;
25 | private String basePath;
26 |
27 | private byte [] seal;
28 | @Override
29 | public void beginTextBlock() {
30 |
31 | }
32 |
33 | public void setBasePath(String basePath) {
34 | if (basePath.endsWith("/")) {
35 | this.basePath = basePath;
36 | } else {
37 | this.basePath = basePath + "/";
38 | }
39 | }
40 |
41 | @Override
42 | public void renderText(TextRenderInfo renderInfo) {
43 | }
44 |
45 | @Override
46 | public void endTextBlock() {
47 | }
48 | PdfDictionary resources;
49 |
50 | List> numbers = new ArrayList<>();
51 |
52 |
53 | @SneakyThrows
54 | @Override
55 | public void renderImage(ImageRenderInfo renderInfo) {
56 | PdfImageObject image = renderInfo.getImage();
57 | if (image == null) {
58 | log.debug("Image {} could not be read", renderInfo.getRef().getNumber());
59 | return;
60 | }
61 | seal = image.getImageAsBytes();
62 | }
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/src/main/java/com/resrun/service/verify/SignVerifyService.java:
--------------------------------------------------------------------------------
1 | package com.resrun.service.verify;
2 |
3 |
4 | import com.resrun.service.pojo.SignPdfInfoVo;
5 | import com.resrun.controller.vo.response.VerifyResponse;
6 | import lombok.extern.slf4j.Slf4j;
7 | import org.springframework.stereotype.Service;
8 |
9 |
10 | /**
11 | * @Description: 在线签名验签服务实现类
12 | * @Package: com.resrun.service.verify
13 | * @ClassName: SignVerifyServiceImpl
14 | * @copyright 北京资源律动科技有限公司
15 | */
16 | @Slf4j
17 | @Service
18 | public class SignVerifyService{
19 |
20 | /**
21 | * 获取pdf签名图片信息
22 | * @return 提取结果
23 | */
24 | public VerifyResponse getImageFromPdf(byte[] bytes, String fileName) {
25 | VerifyResponse response = new VerifyResponse();
26 | SignPdfInfoVo signPdfInfo = new SignPdfInfoVo();
27 |
28 | try {
29 |
30 | signPdfInfo = VerifySign.getSignFromPdf(bytes);
31 | signPdfInfo.setPdfName(fileName);
32 | signPdfInfo.setPdfSize(String.format("%.2f",bytes.length/1024.0));
33 | switch (signPdfInfo.getPdfSingResult()) {
34 | case 1:
35 | return new VerifyResponse(com.resrun.enums.SignStatus.SIGN_STATUS_NOSIGNATURE.getMsg(),signPdfInfo);
36 | case 2:
37 | return new VerifyResponse(com.resrun.enums.SignStatus.SIGN_STATUS_ERROR.getMsg(), signPdfInfo);
38 | case 3:
39 | return new VerifyResponse(com.resrun.enums.SignStatus.SIGN_STATUS_FIDDLE.getMsg(), signPdfInfo);
40 | default:
41 | return new VerifyResponse(com.resrun.enums.SignStatus.SIGN_STATUS_RIGHT.getMsg(), signPdfInfo);
42 | }
43 |
44 |
45 | }catch (Exception e){
46 | signPdfInfo.setPdfName(fileName);
47 | signPdfInfo.setPdfSize(String.valueOf(bytes.length/1024));
48 |
49 | signPdfInfo.setPdfSingResult(com.resrun.enums.SignStatus.SIGN_STATUS_NOSIGNATURE.getCode());
50 | return new VerifyResponse(com.resrun.enums.SignStatus.SIGN_STATUS_NOSIGNATURE.getMsg(),signPdfInfo);
51 | }
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/resrun/service/verify/VerifySign.java:
--------------------------------------------------------------------------------
1 | package com.resrun.service.verify;
2 |
3 |
4 | import com.itextpdf.text.Rectangle;
5 | import com.itextpdf.text.pdf.*;
6 | import com.itextpdf.text.pdf.parser.ContentByteUtils;
7 | import com.itextpdf.text.pdf.parser.PdfContentStreamProcessor;
8 | import com.itextpdf.text.pdf.security.CertificateInfo;
9 | import com.itextpdf.text.pdf.security.PdfPKCS7;
10 | import com.resrun.enums.SignStatus;
11 | import com.resrun.service.pojo.SignPdfInfoVo;
12 | import lombok.extern.slf4j.Slf4j;
13 | import org.bouncycastle.asn1.ASN1InputStream;
14 | import org.bouncycastle.asn1.ASN1ObjectIdentifier;
15 | import org.bouncycastle.asn1.ASN1Sequence;
16 | import org.bouncycastle.asn1.x509.TBSCertificate;
17 | import org.bouncycastle.jce.provider.BouncyCastleProvider;
18 |
19 | import javax.imageio.ImageIO;
20 | import javax.swing.*;
21 | import java.awt.*;
22 | import java.awt.image.BufferedImage;
23 | import java.io.*;
24 | import java.lang.reflect.Field;
25 | import java.security.GeneralSecurityException;
26 | import java.security.Principal;
27 | import java.security.Security;
28 | import java.security.cert.X509Certificate;
29 | import java.util.ArrayList;
30 | import java.util.Base64;
31 | import java.util.List;
32 | import java.util.Set;
33 |
34 | /**
35 | * @Description: 在线签名验签工具类
36 | * @Package: com.resrun.service.verify
37 | * @ClassName: VerifySign
38 | * @copyright 北京资源律动科技有限公司
39 | */
40 | @Slf4j
41 | public class VerifySign {
42 |
43 | static {
44 | Security.addProvider(new BouncyCastleProvider());
45 | }
46 |
47 | /**
48 | * 验证PDF证书信息是否有效
49 | * @throws IOException
50 | * @throws GeneralSecurityException
51 | */
52 | public static Integer checkSignIsValid(String path) {
53 | Integer signStatus = SignStatus.SIGN_STATUS_RIGHT.getCode();
54 | try{
55 |
56 | FileInputStream fis = new FileInputStream(path);
57 | PdfReader reader = new PdfReader(fis);
58 | int numberOfPages = reader.getNumberOfPages();
59 |
60 | PdfReader pdfReader = new PdfReader(path);
61 | AcroFields acroFields = pdfReader.getAcroFields();
62 | List namesList = acroFields.getSignatureNames();
63 |
64 | if(namesList!=null && namesList.size()<=0){
65 | signStatus=SignStatus.SIGN_STATUS_NOSIGNATURE.getCode();
66 | }
67 |
68 | for (String name : namesList) {
69 | PdfDictionary signatureDict = acroFields.getSignatureDictionary(name);
70 | //时间戳
71 | String timestrap = signatureDict.getAsString(PdfName.M).toString().replace("D:","").substring(0,12);
72 |
73 | PdfPKCS7 pdfPKCS7 = acroFields.verifySignature(name);
74 |
75 | X509Certificate x509Certificate = pdfPKCS7.getSigningCertificate();
76 | Principal principal = x509Certificate.getIssuerDN();
77 | //证书颁发机构
78 | String s = principal.toString().split("CN")[2].replace("=","");
79 | //时间戳有效性
80 | boolean flag = pdfPKCS7.verifyTimestampImprint();
81 | //签署人姓名
82 | String signerName = CertificateInfo.getSubjectFields(pdfPKCS7.getSigningCertificate()).getField("CN"); //证书信息
83 | //文档是否被修改
84 | boolean isChange = pdfPKCS7.verify();
85 | if (isChange) {
86 | // log.info("有效签名");
87 | signStatus=SignStatus.SIGN_STATUS_RIGHT.getCode();
88 | } else {
89 | // log.info("无效签名");
90 | signStatus=SignStatus.SIGN_STATUS_FIDDLE.getCode();
91 | }
92 | // log.info(signerName + "\t时间戳是否有效:" +flag + "\t" + timestrap + "\t颁发机构:" +s+ "\t是否被篡改:"+isChange);
93 | }
94 | return signStatus;
95 |
96 | }catch (Exception e){
97 | signStatus=SignStatus.SIGN_STATUS_ERROR.getCode();
98 | }
99 | return signStatus;
100 | }
101 |
102 |
103 |
104 | /**
105 | * 获取PDF中数字签名信息
106 | * @param file pdf文件
107 | * @return 提取结果
108 | */
109 | public static SignPdfInfoVo getSignFromPdf(byte [] file) throws IOException {
110 | SignPdfInfoVo pdfSignInfo = new SignPdfInfoVo();
111 | // String fileBasePath = System.getProperty("user.dir") + "/resrun-paas-system/src/main/resources/upload/";
112 | // File oldFile = new File(path);
113 | try {
114 | ByteArrayInputStream fis = new ByteArrayInputStream(file);
115 | PdfReader reader = new PdfReader(fis);
116 |
117 | // String fileName = path.substring(path.lastIndexOf("/") + 1, path.lastIndexOf("."));
118 | // if (!fileBasePath.endsWith("/")) {
119 | // fileBasePath = fileBasePath + "/";
120 | // }
121 |
122 |
123 | // pdfSignInfo.setPdfName(path.substring(path.lastIndexOf("/") + 1));
124 | // pdfSignInfo.setPdfSize(String.valueOf(oldFile.length()));
125 | Field rsaDataField = PdfPKCS7.class.getDeclaredField("RSAdata");
126 | rsaDataField.setAccessible(true);
127 |
128 | int numberOfPages = reader.getNumberOfPages();
129 |
130 | int xrefSize = reader.getXrefSize();
131 |
132 |
133 | // listener.setBasePath(fileBasePath);
134 |
135 | // 获取acro字段
136 | AcroFields fields = reader.getAcroFields();
137 | Rectangle pageSize = reader.getPageSize(numberOfPages );
138 | // pdf 作用域尺寸(全屏)
139 | // System.out.println("pdf rectangle: " + pageSize.getLeft() + "," + pageSize.getBottom() + "," + pageSize.getRight() + "," + pageSize.getTop());
140 | // 获取签名名称
141 | ArrayList signatureNames = fields.getSignatureNames();
142 | if(signatureNames!=null && signatureNames.size()<=0){
143 | pdfSignInfo.setPdfSingResult(SignStatus.SIGN_STATUS_NOSIGNATURE.getCode());
144 | }
145 | for (String name : signatureNames) {
146 | ExtImageRenderListener listener = new ExtImageRenderListener();
147 | // 指定提供者 获取签名的pkcs7数据
148 | PdfPKCS7 pkcs7 = fields.verifySignature(name);
149 | X509Certificate certificate = pkcs7.getSigningCertificate();
150 | SignPdfInfoVo.SignatureDetail info = new SignPdfInfoVo.SignatureDetail();
151 | boolean isChange = pkcs7.verify();
152 | if (isChange) {
153 | pdfSignInfo.setPdfSingResult(SignStatus.SIGN_STATUS_RIGHT.getCode());
154 | } else {
155 | pdfSignInfo.setPdfSingResult(SignStatus.SIGN_STATUS_FIDDLE.getCode());
156 | }
157 |
158 | String[] appearanceStates = fields.getAppearanceStates(name);
159 | // 表单域的位置
160 | List fieldPositions = fields.getFieldPositions(name);
161 | for (AcroFields.FieldPosition fieldPosition : fieldPositions) {
162 | Rectangle position = fieldPosition.position;
163 |
164 | info.pageNum = fieldPosition.page;
165 | info.llx = position.getLeft();
166 | info.lly = position.getBottom();
167 | info.urx = position.getRight();
168 | info.ury = position.getTop();
169 | }
170 |
171 | AcroFields.Item fieldItem = fields.getFieldItem(name);
172 | int size = fieldItem.size();
173 | for (int i = 0; i < size; i++) {
174 | PdfDictionary value = fieldItem.getValue(i);
175 | Set keys = value.getKeys();
176 | for (PdfName key : keys) {
177 | String keyName = PdfName.decodeName(new String(key.getBytes()));
178 | if ("Rect".equalsIgnoreCase(keyName)) {
179 | log.debug("签名域的坐标为:" + value.get(key));
180 | }
181 | }
182 | PdfObject pdfObject = value.get(PdfName.AP);
183 | if (pdfObject != null) {
184 | PdfDictionary dictionary = (PdfDictionary) pdfObject;
185 | PdfIndirectReference ref = (PdfIndirectReference) dictionary.get(PdfName.N);
186 | int number = ref.getNumber();
187 |
188 | // 根据ap 获取签章图片
189 | PdfObject pdfObjectRelease = reader.getPdfObject(number);
190 | if (pdfObjectRelease instanceof PdfStream) {
191 | PdfStream s = (PdfStream) pdfObjectRelease;
192 | PdfDictionary resources = s.getAsDict(PdfName.RESOURCES);
193 | listener.setI(number);
194 | try {
195 |
196 | PdfContentStreamProcessor processor = new PdfContentStreamProcessor(listener);
197 | processor.processContent(ContentByteUtils.getContentBytesFromContentObject(s), resources);
198 | if(listener.getSeal() != null && listener.getSeal().length>0){
199 | byte [] seal = changeImgColor(listener.getSeal());
200 | info.sealBase64 = Base64.getEncoder().encodeToString(seal);
201 | }
202 | } catch (Exception ignore) {
203 | ignore.printStackTrace();
204 | pdfSignInfo.setPdfSingResult(SignStatus.SIGN_STATUS_ERROR.getCode());
205 | }
206 | }
207 | }
208 | }
209 | // certificate.getSignature()
210 | // TBSCertificate tbsCertificate = new TBSCertificate();
211 | // pkcs7.getSigningCertificate();
212 | ByteArrayInputStream bIn = new ByteArrayInputStream(certificate.getTBSCertificate());
213 | ASN1InputStream aIn = new ASN1InputStream(bIn);
214 | ASN1Sequence sequence = (ASN1Sequence) aIn.readObject();
215 | TBSCertificate tbs = TBSCertificate.getInstance(sequence);
216 | ASN1ObjectIdentifier asn1ObjectIdentifierCN = new ASN1ObjectIdentifier("2.5.4.3");
217 | ASN1ObjectIdentifier asn1ObjectIdentifierO = new ASN1ObjectIdentifier("2.5.4.10");
218 | if(pkcs7.getTimeStampToken() == null){
219 | info.location = "本地时间";
220 | }else{
221 | info.location = "可信时间认证机构";
222 | }
223 | // 签名信息
224 | info.signName = name;
225 | info.signTime = pkcs7.getSignDate().getTime();
226 | info.validStartTime = certificate.getNotBefore();
227 | info.validEndTime = certificate.getNotAfter();
228 | // info.certName = certificate.getSubjectDN().getName();
229 | info.certName = tbs.getSubject().getRDNs(asn1ObjectIdentifierCN)[0].getFirst().getValue().toString();
230 | // info.serialNumber = String.valueOf(certificate.getSerialNumber());
231 | info.serialNumber = tbs.getSerialNumber().getValue().toString(16);
232 | Base64.Encoder encoder = Base64.getEncoder();
233 | info.publicKey = encoder.encodeToString(certificate.getPublicKey().getEncoded());
234 | info.pubKeyFormat = certificate.getPublicKey().getFormat();
235 | info.sigAlgName = certificate.getSigAlgName();
236 | // info.userDnName = certificate.getIssuerDN().getName();
237 | info.userDnName = tbs.getIssuer().getRDNs(asn1ObjectIdentifierCN)[0].getFirst().getValue().toString();
238 | info.reason = pkcs7.getReason();
239 | // info.location = pkcs7.getLocation();
240 | info.validate = pkcs7.verify();
241 | Field digestAttrField = PdfPKCS7.class.getDeclaredField("digestAttr");
242 | digestAttrField.setAccessible(true);
243 | byte[] digestAttr = (byte[]) digestAttrField.get(pkcs7);
244 | info.fileDigest = encoder.encodeToString(digestAttr);
245 |
246 |
247 | pdfSignInfo.getSignatureDetails().add(info);
248 |
249 | }
250 | } catch (Exception e) {
251 | pdfSignInfo.setPdfSingResult(SignStatus.SIGN_STATUS_ERROR.getCode());
252 | log.error("取PDF中数字签名信息失败", e.getMessage());
253 | }
254 | return pdfSignInfo;
255 | }
256 |
257 |
258 |
259 | /**
260 | * 将背景替换为透明
261 | * @return
262 | * @throws IOException the io exception
263 | */
264 | public static void changeImgColor(String path) throws IOException {
265 |
266 | File file = new File(path);
267 | String fileName = file.getName();
268 | BufferedImage bi = ImageIO.read(file);
269 |
270 | BufferedImage image = bi;
271 | //将原图片的二进制转化为ImageIcon
272 | ImageIcon imageIcon = new ImageIcon(image);
273 | int width = imageIcon.getIconWidth();
274 | int height = imageIcon.getIconHeight();
275 |
276 | //图片缓冲流
277 | BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
278 | Graphics2D graphics2D = (Graphics2D) bufferedImage.getGraphics();
279 | graphics2D.drawImage(imageIcon.getImage(), 0, 0, imageIcon.getImageObserver());
280 |
281 | int alpha = 255;
282 |
283 | //这个背景底色的选择,我这里选择的是比较偏的位置,可以修改位置。背景色选择不知道有没有别的更优的方式(比如先过滤一遍获取颜色次数最多的,但是因为感觉做起来会比较复杂没去实现),如果有可以评论。
284 | int RGB=bufferedImage.getRGB(width-1, height-1);
285 |
286 | for(int i = bufferedImage.getMinX(); i < width; i++) {
287 | for(int j = bufferedImage.getMinY(); j < height; j++) {
288 |
289 | int rgb = bufferedImage.getRGB(i, j);
290 |
291 | int r = (rgb & 0xff0000) >>16;
292 | int g = (rgb & 0xff00) >> 8;
293 | int b = (rgb & 0xff);
294 | int R = (RGB & 0xff0000) >>16;
295 | int G = (RGB & 0xff00) >> 8;
296 | int B = (RGB & 0xff);
297 | //a为色差范围值,渐变色边缘处理,数值需要具体测试,50左右的效果比较可以
298 | int a = 45;
299 | if(Math.abs(R-r) < a && Math.abs(G-g) < a && Math.abs(B-b) < a ) {
300 | alpha = 0;
301 | } else {
302 | alpha = 255;
303 | }
304 | rgb = (alpha << 24)|(rgb & 0x00ffffff);
305 | bufferedImage.setRGB(i,j,rgb);
306 | }
307 | }
308 |
309 | String[] split = fileName.split("\\.");
310 | fileName = split[0]+"(已转换)."+split[1];
311 | ImageIO.write(bufferedImage, "png", new File(path));
312 | }
313 |
314 | /**
315 | * 将背景替换为透明
316 | * @return
317 | * @throws IOException the io exception
318 | */
319 | public static byte[] changeImgColor(byte [] seal) throws IOException {
320 |
321 | ByteArrayInputStream bis = new ByteArrayInputStream(seal);
322 | // String fileName = file.getName();
323 | BufferedImage bi = ImageIO.read(bis);
324 |
325 | BufferedImage image = bi;
326 | //将原图片的二进制转化为ImageIcon
327 | ImageIcon imageIcon = new ImageIcon(image);
328 | int width = imageIcon.getIconWidth();
329 | int height = imageIcon.getIconHeight();
330 |
331 | //图片缓冲流
332 | BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
333 | Graphics2D graphics2D = (Graphics2D) bufferedImage.getGraphics();
334 | graphics2D.drawImage(imageIcon.getImage(), 0, 0, imageIcon.getImageObserver());
335 |
336 | int alpha = 255;
337 |
338 | //这个背景底色的选择,我这里选择的是比较偏的位置,可以修改位置。背景色选择不知道有没有别的更优的方式(比如先过滤一遍获取颜色次数最多的,但是因为感觉做起来会比较复杂没去实现),如果有可以评论。
339 | int RGB=bufferedImage.getRGB(width-1, height-1);
340 |
341 | for(int i = bufferedImage.getMinX(); i < width; i++) {
342 | for(int j = bufferedImage.getMinY(); j < height; j++) {
343 |
344 | int rgb = bufferedImage.getRGB(i, j);
345 |
346 | int r = (rgb & 0xff0000) >>16;
347 | int g = (rgb & 0xff00) >> 8;
348 | int b = (rgb & 0xff);
349 | int R = (RGB & 0xff0000) >>16;
350 | int G = (RGB & 0xff00) >> 8;
351 | int B = (RGB & 0xff);
352 | //a为色差范围值,渐变色边缘处理,数值需要具体测试,50左右的效果比较可以
353 | int a = 5;
354 | if(Math.abs(R-r) < a && Math.abs(G-g) < a && Math.abs(B-b) < a ) {
355 | alpha = 0;
356 | } else {
357 | alpha = 255;
358 | }
359 | rgb = (alpha << 24)|(rgb & 0x00ffffff);
360 | bufferedImage.setRGB(i,j,rgb);
361 | }
362 | }
363 |
364 | // String[] split = fileName.split("\\.");
365 | // fileName = split[0]+"(已转换)."+split[1];
366 | ByteArrayOutputStream bos = new ByteArrayOutputStream();
367 | ImageIO.write(bufferedImage, "png", bos);
368 | return bos.toByteArray();
369 | }
370 |
371 |
372 | public static void main(String[] args) throws IOException {
373 | // checkSignIsValid("/Users/huaiyong/Desktop/B.pdf");
374 | // File file = new File("F://download//电信电子发票202308142220 (1).pdf");
375 | // File file = new File("F://download//版权登记申请代理委托合同.pdf");
376 | File file = new File("F://download//签署文件-1678695004917124386-北京小轻新商贸中心.pdf");
377 |
378 | FileInputStream fis = new FileInputStream(file);
379 | ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
380 | byte[] b = new byte[1000];
381 | int n;
382 | while ((n = fis.read(b)) != -1) {
383 | bos.write(b, 0, n);
384 | }
385 | byte[] data = bos.toByteArray();
386 | getSignFromPdf(data);
387 | fis.close();
388 | // getSignFromPdf("B.pdf");
389 | }
390 |
391 | }
392 |
--------------------------------------------------------------------------------
/src/main/java/com/resrun/utils/Base64.java:
--------------------------------------------------------------------------------
1 | package com.resrun.utils;
2 |
3 |
4 | import java.io.ByteArrayOutputStream;
5 |
6 | /**
7 | * @Description: base64转换
8 | * @Package: com.resrun.utils
9 | * @ClassName: Base64
10 | * @copyright 北京资源律动科技有限公司
11 | */
12 | public class Base64 {
13 |
14 | private static char[] base64EncodeChars = new char[] { 'A', 'B', 'C', 'D',
15 | 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
16 | 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
17 | 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
18 | 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3',
19 | '4', '5', '6', '7', '8', '9', '+', '/' };
20 |
21 | private static byte[] base64DecodeChars = new byte[] { -1, -1, -1, -1, -1,
22 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
23 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
24 | -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59,
25 | 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
26 | 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1,
27 | -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
28 | 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1,
29 | -1, -1 };
30 |
31 | private Base64(){
32 | }
33 |
34 | public static String encode(byte[] data){
35 | StringBuffer sb = new StringBuffer();
36 | int len = data.length;
37 | int i = 0;
38 | int b1, b2, b3;
39 |
40 | while (i < len) {
41 | b1 = data[i++] & 0xff;
42 | if (i == len) {
43 | sb.append(base64EncodeChars[b1 >>> 2]);
44 | sb.append(base64EncodeChars[(b1 & 0x3) << 4]);
45 | sb.append("==");
46 | break;
47 | }
48 | b2 = data[i++] & 0xff;
49 | if (i == len) {
50 | sb.append(base64EncodeChars[b1 >>> 2]);
51 | sb.append(base64EncodeChars[((b1 & 0x03) << 4)
52 | | ((b2 & 0xf0) >>> 4)]);
53 | sb.append(base64EncodeChars[(b2 & 0x0f) << 2]);
54 | sb.append("=");
55 | break;
56 | }
57 | b3 = data[i++] & 0xff;
58 | sb.append(base64EncodeChars[b1 >>> 2]);
59 | sb.append(base64EncodeChars[((b1 & 0x03) << 4)
60 | | ((b2 & 0xf0) >>> 4)]);
61 | sb.append(base64EncodeChars[((b2 & 0x0f) << 2)
62 | | ((b3 & 0xc0) >>> 6)]);
63 | sb.append(base64EncodeChars[b3 & 0x3f]);
64 | }
65 | return sb.toString();
66 | }
67 |
68 | public static byte[] decode(String str){
69 | byte[] data = str.getBytes();
70 | int len = data.length;
71 | ByteArrayOutputStream buf = new ByteArrayOutputStream(len);
72 | int i = 0;
73 | int b1, b2, b3, b4;
74 |
75 | while (i < len) {
76 |
77 | /* b1 */
78 | do {
79 | b1 = base64DecodeChars[data[i++]];
80 | } while (i < len && b1 == -1);
81 | if (b1 == -1) {
82 | break;
83 | }
84 |
85 | /* b2 */
86 | do {
87 | b2 = base64DecodeChars[data[i++]];
88 | } while (i < len && b2 == -1);
89 | if (b2 == -1) {
90 | break;
91 | }
92 | buf.write(((b1 << 2) | ((b2 & 0x30) >>> 4)));
93 |
94 | /* b3 */
95 | do {
96 | b3 = data[i++];
97 | if (b3 == 61) {
98 | return buf.toByteArray();
99 | }
100 | b3 = base64DecodeChars[b3];
101 | } while (i < len && b3 == -1);
102 | if (b3 == -1) {
103 | break;
104 | }
105 | buf.write((((b2 & 0x0f) << 4) | ((b3 & 0x3c) >>> 2)));
106 |
107 | /* b4 */
108 | do {
109 | b4 = data[i++];
110 | if (b4 == 61) {
111 | return buf.toByteArray();
112 | }
113 | b4 = base64DecodeChars[b4];
114 | } while (i < len && b4 == -1);
115 | if (b4 == -1) {
116 | break;
117 | }
118 | buf.write((((b3 & 0x03) << 6) | b4));
119 | }
120 | return buf.toByteArray();
121 | }
122 | }
--------------------------------------------------------------------------------
/src/main/resources/application-dev.yml:
--------------------------------------------------------------------------------
1 | server:
2 | # 项目启动端口号
3 | port: 8666
4 | tomcat:
5 | # 上传文件限制 -1 无限制
6 | max-swallow-size: -1
7 | servlet:
8 | #路径前缀
9 | context-path: /openSign
10 |
--------------------------------------------------------------------------------
/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | profiles:
3 | active: dev
--------------------------------------------------------------------------------
/src/main/resources/开源工具版说明.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhenghuihan/open-sign/f1fa38ee6e9ce4dc467123c67264c6f88b1d4559/src/main/resources/开源工具版说明.pdf
--------------------------------------------------------------------------------