在线签名前后端业务实现(pc端及移动端)
在线签名前后端业务实现
前言
最近做了一个需求,是一个用户在线完成个人承诺书生成并手写签字的功能,最开始的时候是需要用户自行下载附件,并打印签名后上传,但是这种方式的不好之处在于,你压根就不知道用户上传的到底是不是你要的附件。后来服务升级,客户让参照国家app的形式,在线生成承诺书,并签名。最近新需求刚上线,我来整理下实现过程。
效果图
pdf模板
pc端
表单数据填写
未签名承诺书预览
签名
签名后预览
微信端
填写表单数据
生成承诺书
签名确认
签名后预览
需求实现过程
最开始拿到这个需求的时候,我就想到了通过js
实现签名,所以在查了一些资料以后,前端确认用jSignature
,后端根据用户信息,通过pdf表单的方式生成承诺书,然后返回未签名的承诺书图片,最后用户签名确认后,后端将签名和承诺书进行合成,然后返回给前端,如果用户确认办理业务,则后端保存签名后的个人承诺书。
总结以下整个流程:
因为项目是前后端分离的,所以图片是通过base64传输的。签名部分我是通过图片合成的,当然你也可以做成电子签章的方式,这样也更合理。
这里再补充说明下jSignature
,这个js
库可以直接生成base64的签名,当然它支持的格式很多,具体的可以看文档:
下面直接放代码了:
java接口
根据pdf模板生成pdf
/**
* 根据pdf模板,生成pdf
*
* @param pdfReader PdfReader
* @param saveFullFileName 保存文件的完整文件名(包含文件路径)
* @param parameters 参数
* @throws DocumentException
*/
public static void exportPdf(PdfReader pdfReader, File saveFullFileName, Map<String, String> parameters) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = null;
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(saveFullFileName);
byteArrayOutputStream = new ByteArrayOutputStream();
PdfStamper pdfStamper = new PdfStamper(pdfReader, byteArrayOutputStream);
//获取模板所有域参数
AcroFields acroFields = pdfStamper.getAcroFields();
//解决中文字体不显示的问题
BaseFont baseFont = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
ArrayList<BaseFont> fontArrayList = new ArrayList<BaseFont>();
fontArrayList.add(baseFont);
acroFields.setSubstitutionFonts(fontArrayList);
for (String key : parameters.keySet()) {
acroFields.setField(key, parameters.get(key));
}
pdfStamper.setFormFlattening(true);//如果为false那么生成的PDF文件还能编辑,一定要设为true
pdfStamper.flush();
pdfStamper.close();
//设置纸张,可以在Excel制作是设定好纸张大小
Document doc = new Document(PageSize.A4);
PdfCopy copy = new PdfCopy(doc, fileOutputStream);
doc.open();
PdfImportedPage importPage = copy.getImportedPage(new PdfReader(byteArrayOutputStream.toByteArray()), 1);
copy.addPage(importPage);
doc.close();
} catch (Exception e) {
throw new Exception("生成pdf文件失败", e);
} finally {
if (byteArrayOutputStream != null) {
try {
byteArrayOutputStream.close();
} catch (IOException e) {
throw new Exception("生成pdf文件失败,关闭资源失败");
}
}
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
throw new Exception("生成pdf文件失败,关闭资源失败");
}
}
if (pdfReader != null) {
pdfReader.close();
}
}
}
pdf转图片
/**
* pdf转图片
*
* @param pdfFullName pdf完整文件名
* @param imgSavePath 图片保存路径
* @throws Exception
*/
public static void pdf2Pic(String pdfFullName, String imgSavePath) throws Exception {
org.icepdf.core.pobjects.Document document = new org.icepdf.core.pobjects.Document();
document.setFile(pdfFullName);
//缩放比例
float scale = 2.5f;
//旋转角度
float rotation = 0f;
// 文件名
String pdfFileName = new File(pdfFullName).getName();
try {
for (int i = 0; i < document.getNumberOfPages(); i++) {
BufferedImage image = (BufferedImage)
document.getPageImage(i, GraphicsRenderingHints.SCREEN, org.icepdf.core.pobjects.Page.BOUNDARY_CROPBOX, rotation, scale);
String imgName = pdfFileName + i + ".png";
System.out.println(imgName);
File file = new File(imgSavePath + imgName);
ImageIO.write(image, "png", file);
image.flush();
}
} catch (Exception e) {
throw new Exception("pdf文件转图片失败", e);
}
document.dispose();
}
合成签名
/**
*
* @Title: 构造图片
* @Description: 生成水印并返回java.awt.image.BufferedImage
* @param buffImg 源文件(BufferedImage)
* @param waterImg 水印文件(BufferedImage)
* @param x 距离右下角的X偏移量
* @param y 距离右下角的Y偏移量
* @param alpha 透明度, 选择值从0.0~1.0: 完全透明~完全不透明
* @param scale 缩放比例,整数,如50表示缩放为0.5
* @return BufferedImage
* @throws IOException
*/
public static BufferedImage overlyingImage(BufferedImage buffImg, BufferedImage waterImg,
int x, int y, float alpha, int scale) {
// 创建Graphics2D对象,用在底图对象上绘图
Graphics2D g2d = buffImg.createGraphics();
int waterImgWidth = waterImg.getWidth()*scale/100;// 获取层图的宽度
int waterImgHeight = waterImg.getHeight()*scale/100;// 获取层图的高度
logger.debug("图片宽度" + waterImgWidth);
logger.debug("图片高度" + waterImgHeight);
// 在图形和图像中实现混合和透明效果
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
// 绘制
g2d.drawImage(waterImg, x, y, waterImgWidth, waterImgHeight, null);
g2d.dispose();// 释放图形上下文使用的系统资源
return buffImg;
}
js核心代码
function signInit() {
$("#signature").html('');
// This is the part where jSignature is initialized.
var $sigdiv = $("#signature").jSignature({'height': '300px', 'width': '100%'})
$('#sign-make-sure').click(function () {
notice();
var data = $sigdiv.jSignature('getData', 'image');
var datas = sessionStorage.getItem("apply-record-datas");
var signSourceImg = sessionStorage.getItem("sign-source-img");
var parseJSON = JSON.parse(datas);
parseJSON['FILENAME'] = signSourceImg;
parseJSON['sign'] = data[1];
var requestParameter = {"datas": JSON.stringify(parseJSON)};
var url = "/signTest/signPersonalCommitment";
aj(url, requestParameter, function (result) {
if (result.success) {
swal.close();
var modal_body_html = "<img id='sign-source-img' style='width: 100%' src='data:image/png;base64, " + result.obj.datas + "' >"
$("#ydjyrecodSignModal .modal-body").html(modal_body_html);
var modal_footer_html = "<button class=\"btn btn-primary\" type=\"button\" onclick=\"toSign()\">\n" +
" <i class=\"fa fa-fw fa-lg fa-check-circle\"></i>重新签名\n</button>\n" +
" <button id=\"save-commit\" class=\"btn btn-danger\" type=\"button\" onclick='saveCommit()'>\n" +
" <i class=\"fa fa-fw fa-lg fa-trash\"></i>保存并提交\n</button>\n";
$("#ydjyrecodSignModal .modal-footer").html(modal_footer_html);
$("#ydjyrecodSignModal").modal({
backdrop: "static",//点击空白处不关闭对话框
show: true
});
} else {
swal("生成个人承诺书失败", result.msg, "error");
}
}, function (err) {
console.log(err);
});
});
$('#rest').click(function () {
$sigdiv.jSignature('reset')
});
}
签名区html
<div id = "sign-tips" style="margin-bottom: 5px; text-align:center">
<p>请用鼠标在虚线框内绘制签名</p>
</div>
<div id=\"content\">
<div id=\"signatureparent\">
<div id=\"signature\"></div>
</div>
</div>
完整的示例,请看文末链接。
结语
今天的分享,只是提供一种解决方案的思路,如果能帮到你或者能够给你一些启发,那一切都值得。这里是本文的项目地址,感兴趣的小伙伴可以去了解下: