完整代码:java-boot-highpin-background: 背调服务 (gitee.com) 【暂不开源】 前端代码:zhaopin: 招聘背调 (gitee.com) 【暂不开源】 全部代码在课程资料里面:Java实现集成E签宝签署_哔哩哔哩_bilibili 配置信息 1.在application.yml中配置appid、密钥信息,包含沙箱环境 ```java esign: host: https://smlopenapi.esign.cn appId: your appId appSecret: your secret ``` 步骤说明 2.实现电子签的主要流程在BaseAuthInfoServiceImpl里面 1.根据模板生成word文件(word文件模板在resources里面) 2.生成好的文件进行上传,上传分两步:具体实现看uploadMFile方法 3.查询文件上传状态 4.获取文件坐标 5.创建签署流程,返回签署流程id 6.最后返回页面签署路径url,返回给前端用于给用户访问签署的页面
3.后端代码实现:
/**
* 签署完成后回调通知
* @return
*/
@PostMapping("/returnNotify")
public Map<String, String> returnNotify(@RequestBody ReturnNotifyDto returnNotifyDto){
Map<String, String> result = baseAuthInfoService.returnNotify(returnNotifyDto);
return result;
}
/**
* 签署回调通知
*
* @param returnNotifyDto
*/
@Override
public Map<String, String> returnNotify(ReturnNotifyDto returnNotifyDto) {
boolean update = false;
String action = returnNotifyDto.getAction();
if ("SIGN_MISSON_COMPLETE".equalsIgnoreCase(action)) {
// 签署流程id
String signFlowId = returnNotifyDto.getSignFlowId();
// 签署描述
// todo 省略代码,完整代码见课程资料中的完整代码
// signResult 签署结果 2 - 签署完成,4 - 拒签
// todo 省略代码,完整代码见课程资料中的完整代码
// baseAuthInfoEntity.setAuthFlowId(signFlowId);
baseAuthInfoEntity.setAuthStatus(signResult);
baseAuthInfoEntity.setAuthExplain(resultDescription);
LambdaUpdateWrapper<BaseAuthInfoEntity> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(BaseAuthInfoEntity::getAuthFlowId, signFlowId);
update = this.update(baseAuthInfoEntity, updateWrapper);
}
Map<String, String> result = new HashMap<>();
if (update) {
result.put("code", "200");
result.put("msg", "success");
} else {
result.put("code", "201");
result.put("msg", "success");
}
return result;
}
package io.renren.modules.zhaopin.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import io.renren.common.esign.EsignFileBean;
import io.renren.common.esign.EsignHttpHelper;
import io.renren.common.esign.EsignHttpResponse;
import io.renren.common.esign.enums.EsignHeaderConstant;
import io.renren.common.esign.enums.EsignRequestType;
import io.renren.common.esign.exception.EsignDemoException;
import io.renren.common.utils.*;
import io.renren.modules.zhaopin.dao.BaseAuthInfoDao;
import io.renren.modules.zhaopin.entity.BackgroundCheckOrdersEntity;
import io.renren.modules.zhaopin.entity.BaseAuthInfoEntity;
import io.renren.modules.zhaopin.service.BackgroundCheckOrdersService;
import io.renren.modules.zhaopin.service.BaseAuthInfoService;
import io.renren.modules.zhaopin.util.HTTPHelper;
import io.renren.modules.zhaopin.util.IdWorker;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
@Slf4j
@Service("baseAuthInfoService")
public class BaseAuthInfoServiceImpl extends ServiceImpl<BaseAuthInfoDao, BaseAuthInfoEntity> implements BaseAuthInfoService {
@Value("${esign.host}")
private String host;
@Value("${esign.appId}")
private String appId;
@Value("${esign.appSecret}")
private String appSecret;
@Value("${esign.noticeUrl}")
private String noticeUrl;
@Autowired
private BackgroundCheckOrdersService backgroundCheckOrdersService;
@Autowired
private BaseAuthInfoService baseAuthInfoService;
@Autowired
private IdWorker idWorker;
@Override
public PageUtils queryPage(Map<String, Object> params) {
IPage<BaseAuthInfoEntity> page = this.page(
new Query<BaseAuthInfoEntity>().getPage(params),
new QueryWrapper<BaseAuthInfoEntity>()
);
return new PageUtils(page);
}
/**
* 生成word文件
*
* @param reportId 报告id
*/
private void generateWordFile(String reportId) {
LambdaQueryWrapper<BackgroundCheckOrdersEntity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(BackgroundCheckOrdersEntity::getReportId, reportId);
BackgroundCheckOrdersEntity checkOrders = backgroundCheckOrdersService.getOne(queryWrapper);
try {
// todo 省略代码,完整代码见课程资料中的完整代码
System.out.println("生成文档路径:" + wordPath);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
/**
* 上传本地文件
*
* @return
*/
private Map<String, Object> uploadMFile() {
String contentType = "application/pdf";
String postUrl = "/v3/files/file-upload-url";
String postAllUrl = host + postUrl;
try {
File file = ResourceUtils.getFile("classpath:最终版授权声明.docx");
String path = file.getPath();
String contentMD5 = ESignUtils.getFileContentMD5(path);
// 计算签名拼接的url
// 构建请求Body体
// todo 省略代码,完整代码见课程资料中的完整代码
System.out.println("文件上传成功,状态码:" + code);
return resultMap;
} catch (Exception e) {
e.printStackTrace();
String msg = MessageFormat.format("请求签名鉴权方式调用接口出现异常: {0}", e.getMessage());
System.out.println(msg);
throw new RuntimeException(e);
}
}
private Map<String, Object> getStringObjectMapPost(JSONObject reqBodyObj, String postUrl, String postAllUrl) throws Exception {
// 请求Body体数据
String reqBodyData = reqBodyObj.toString();
// 对请求Body体内的数据计算ContentMD5
String contentMD5 = ESignUtils.getBodyContentMD5(reqBodyData);
System.out.println("请求body数据:" + reqBodyData);
System.out.println("body的md5值:" + contentMD5);
// todo 省略代码,完整代码见课程资料中的完整代码
String result = HTTPHelper.sendPOST(postAllUrl, reqBodyData, header, "UTF-8");
JSONObject resultObj = JSONObject.parseObject(result);
// json转map
Map<String, Object> resultMap = (Map<String, Object>) JSONObject.toJavaObject(resultObj, Map.class);
System.out.println("请求返回信息: " + resultObj.toString());
System.out.println("请求返回信息: " + resultMap.toString());
return resultMap;
}
/**
* 创建签署流程发送请求API
*
* @param reqBodyObj 请求体
* @param postUrl url
* @param postAllUrl 全url
* @return 返回数据
* @throws Exception 异常处理
*/
private Map<String, Object> getCreatePostSign(JSONObject reqBodyObj, String postUrl, String postAllUrl) throws Exception {
// 请求Body体数据
String reqBodyData = reqBodyObj.toString();
// 对请求Body体内的数据计算ContentMD5
String contentMD5 = ESignUtils.getBodyContentMD5(reqBodyData);
System.out.println("请求body数据:" + reqBodyData);
System.out.println("body的md5值:" + contentMD5);
// 构建待签名字符串
String method = "POST";
String accept = "*/*";
String contentType = "application/json;charset=UTF-8";
String date = "";
String headers = "";
StringBuffer sb = new StringBuffer();
sb.append(method).append("\n").append(accept).append("\n").append(contentMD5).append("\n")
.append(contentType).append("\n").append(date).append("\n");
if ("".equals(headers)) {
sb.append(headers).append(postUrl);
} else {
sb.append(headers).append("\n").append(postUrl);
}
// todo 省略代码,完整代码见课程资料中的完整代码
JSONObject resultObj = JSONObject.parseObject(result);
// json转map
Map<String, Object> resultMap = (Map<String, Object>) JSONObject.toJavaObject(resultObj, Map.class);
int code = (int) resultMap.get("code");
if (code == 0) {
Map<String, Object> dataMap = (Map<String, Object>) resultMap.get("data");
// 签署流程id
String signFlowId = (String) dataMap.get("signFlowId");
resultMap.put("signFlowId", signFlowId);
}
System.out.println("请求返回信息: " + resultObj.toString());
return resultMap;
}
/**
* 签署流程id 获取签署页面链接
*
* @param signFlowId 签署流程id
*/
private String getSignUrl(String signFlowId) {
String postAllUrl = host + "/v3/sign-flow/" + signFlowId + "/sign-url";
String postUrl = "/v3/sign-flow/" + signFlowId + "/sign-url";
// todo 省略代码,完整代码见课程资料中的完整代码
return "";
}
/**
* 查询文件上传状态
*
* @param fileId 报告id
* @return
*/
private Integer getFileStatus(String fileId) throws Exception {
String getAllUrl = host + "/v3/files/" + fileId;
String postUrl = "/v3/files/" + fileId;
JSONObject reqBodyObj = new JSONObject();
reqBodyObj.put("fileId", fileId);
// GET请求时ContentMD5为""
String contentMD5 = "";
// 构建待签名字符串
String method = "GET";
String accept = "*/*";
String contentType = "application/json; charset=UTF-8";
String date = "";
String headers = "";
StringBuffer sb = new StringBuffer();
sb.append(method).append("\n").append(accept).append("\n").append(contentMD5).append("\n")
.append(contentType).append("\n").append(date).append("\n");
if ("".equals(headers)) {
sb.append(headers).append(postUrl);
} else {
sb.append(headers).append("\n").append(postUrl);
}
// todo 省略代码,完整代码见课程资料中的完整代码
System.out.println(resultMap);
return (Integer) resultMap.get("code");
}
/**
* 获取文件坐标
*/
private Map<String, Object> getPosition(String fileId) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
String postUrl = "/v3/files/" + fileId + "/keyword-positions";
String postAllUrl = host + postUrl;
JSONObject reqBodyObj = new JSONObject();
reqBodyObj.put("fileId", fileId);
List<String> keywords = new ArrayList<>();
keywords.add("本人签名");
reqBodyObj.put("keywords", keywords);
try {
Map<String, Object> map = getStringObjectMapPost(reqBodyObj, postUrl, postAllUrl);
System.out.println(map);
Map<String, Object> dataMap = (Map<String, Object>) map.get("data");
if (dataMap == null) {
throw new RuntimeException("解析失败");
}
// todo 省略代码,完整代码见课程资料中的完整代码
return position;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 签署
*
* @param reportId
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public R sign(String reportId) {
try {
String id = idWorker.nextId() + "";
// todo 省略代码,完整代码见课程资料中的完整代码;
System.out.println(resultMap);
/*
1. 查看所上传文件的当前状态(转换pdf/html文件状态)文件名称和下载链接。
2. 当返回的文件状态status值为 2 或 5 时,此文件才可以被应用到签署流程中。
*/
Integer fileStatus = getFileStatus(fileId);
if (fileStatus == 0) {
Map<String, Object> position = getPosition(fileId);
BigDecimal positionY = (BigDecimal) position.get("positionY");
BigDecimal positionX = (BigDecimal) position.get("positionX");
Integer pageNums = (Integer) position.get("pageNums");
Map<String, Object> sign = createSign(fileId, reportId, positionX, positionY, pageNums);
String signUrl = (String) sign.get("signUrl");
String signFlowId = (String) sign.get("signFlowId");
// 保存到数据库 fileUploadId
BaseAuthInfoEntity baseAuthInfoEntity = new BaseAuthInfoEntity();
baseAuthInfoEntity.setId(id);
baseAuthInfoEntity.setOrderId(reportId);
baseAuthInfoEntity.setAuthFlowId(signFlowId);
baseAuthInfoEntity.setAuthUrl(signUrl);
baseAuthInfoEntity.setCreateTime(LocalDateTime.now());
baseAuthInfoEntity.setUpdateTime(LocalDateTime.now());
baseAuthInfoEntity.setFileId(fileId);
baseAuthInfoEntity.setFileUploadUrl(fileUploadUrl);
baseAuthInfoService.save(baseAuthInfoEntity);
return R.ok(sign);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return R.ok("签署流程失败");
}
/**
* 创建签署流程
*/
public Map<String, Object> createSign(String fileId, String reportId, BigDecimal positionX, BigDecimal positionY, Integer pageNums) {
LambdaQueryWrapper<BackgroundCheckOrdersEntity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(BackgroundCheckOrdersEntity::getReportId, reportId);
BackgroundCheckOrdersEntity checkOrders = backgroundCheckOrdersService.getOne(queryWrapper);
Map<String, Object> paramsMap = new HashMap<>();
Map<String, Object> docsMap = new HashMap<>();
// 待签署文件ID
docsMap.put("fileName", "最终版授权声明.docx");
docsMap.put("fileId", fileId);
Map<String, Object> signFlowConfigMap = new HashMap<>();
// todo 省略代码,完整代码见课程资料中的完整代码
System.out.println("reqBodyObj:" + reqBodyObj);
System.out.println("paramsMap:" + paramsMap);
try {
Map<String, Object> map = getCreatePostSign(reqBodyObj, postUrl, postAllUrl);
String signFlowId = (String) map.get("signFlowId");
// 签署流程id 获取签署页面链接
String signUrl = getSignUrl(signFlowId);
map.put("signUrl", signUrl);
map.put("signFlowId", signFlowId);
return map;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
工具类代码比较多,就不全部写出来了
4.前端界面
点击去授权后就会调用E签宝,然后到签署流程页面,签署完成后回调通知接口
前端代码环境:Vue3
我封装了一些组件