# e签宝OAuth2.0手写签署流程说明 ## 概述 本文档说明如何使用e签宝官方SDK(paas-sdk-3.1.4.jar)实现OAuth2.0鉴权方式的手写签署功能。 ## OAuth2.0鉴权机制 ### 1. 认证流程 e签宝使用OAuth2.0客户端凭证模式(client_credentials)进行API鉴权: ``` ┌──────────┐ ┌──────────┐ ┌──────────┐ │ 应用 │ │ e签宝API │ │ e签宝 │ │ │ │ 网关 │ │ 认证服务 │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ │ 1. 请求access_token │ │ │ (AppId + AppSecret) │ │ ├─────────────────────────────>│ │ │ ├─────────────────────────────>│ │ │ │ │ │ 2. 返回access_token │ │ │<─────────────────────────────┤ │ 3. 返回access_token │ │ │<─────────────────────────────┤ │ │ │ │ │ 4. 使用access_token调用API │ │ ├─────────────────────────────>│ │ │ │ │ │ 5. 返回API响应 │ │ │<─────────────────────────────┤ │ │ │ │ ``` ### 2. 获取Access Token **请求方式**: GET **请求URL**: `{apiUrl}/v1/oauth2/access_token` **请求参数**: - `appId`: 应用ID - `secret`: 应用密钥 - `grantType`: 固定值 `client_credentials` **请求头**: ``` X-Tsign-Open-App-Id: {appId} X-Tsign-Open-App-Secret: {secret} Content-Type: application/json ``` **响应示例**: ```json { "code": 0, "message": "成功", "data": { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expiresIn": 7200, "refreshToken": "refresh_token_string" } } ``` **Token有效期**: 通常为7200秒(2小时),需要定期刷新 ### 3. 使用Access Token调用API 所有API请求需要在请求头中携带access_token: **请求头**: ``` X-Tsign-Open-App-Id: {appId} X-Tsign-Open-Token: {access_token} Content-Type: application/json; charset=UTF-8 ``` ## 手写签署流程 ### 完整业务流程 ``` ┌─────────────────────────────────────────────────────────────┐ │ 步骤1: OAuth2.0获取访问令牌 │ │ POST /v1/oauth2/access_token │ │ 输入: AppId + AppSecret │ │ 输出: access_token │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 步骤2: 创建或获取签署人账号 │ │ POST /v3/accounts/createByThirdPartyUserId │ │ 输入: mobile + name + idNumber │ │ 输出: accountId │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 步骤3: 上传合同文件(如需) │ │ POST /v3/files/getUploadUrl │ │ 输入: fileName + fileSize │ │ 输出: uploadUrl + fileId │ │ 然后: PUT uploadUrl (上传文件) │ │ 最后: POST /v3/files/{fileId}/uploadComplete │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 步骤4: 创建签署流程 │ │ POST /v3/signflows │ │ 输入: docs + signers (包含手写签署区域) │ │ 输出: flowId │ │ │ │ 关键配置: │ │ - signFieldStyle: 1 (手写签名) │ │ - signFieldType: 5 (签名类型) │ │ - autoExecute: false (手动签署) │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 步骤5: 启动签署流程 │ │ POST /v3/signflows/{flowId}/start │ │ 输入: flowId │ │ 输出: 成功/失败 │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 步骤6: 获取手写签署链接 │ │ GET /v3/signflows/{flowId}/signers/{accountId}/signUrl │ │ 输入: flowId + accountId │ │ 输出: signUrl (签署人通过此链接进行手写签名) │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 步骤7: 签署人进行手写签署 │ │ 打开signUrl,在签署页面进行手写签名 │ │ - 可以在手机或PC端进行手写 │ │ - 支持触摸屏手写和鼠标手写 │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 步骤8: 查询签署状态 │ │ GET /v3/signflows/{flowId} │ │ 输入: flowId │ │ 输出: flowStatus (0-草稿, 1-签署中, 2-签署完成, ...) │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 步骤9: 下载已签署文档 │ │ GET /v3/signflows/{flowId}/documents │ │ 输入: flowId │ │ 输出: 已签署的PDF文档下载地址 │ └─────────────────────────────────────────────────────────────┘ ``` ## 代码实现 ### 1. OAuth2.0工具类 (EsignSdkUtil) ```java package com.loan.system.utils; import cn.hutool.http.HttpRequest; import cn.hutool.json.JSONObject; import lombok.extern.slf4j.Slf4j; /** * e签宝OAuth2.0鉴权工具类 * 使用OAuth2.0客户端凭证模式(client_credentials)获取访问令牌 */ @Slf4j public class EsignSdkUtil { private static volatile String cachedToken; private static volatile long tokenExpireTime; /** * 获取OAuth2.0访问令牌 */ public static String getAccessToken(String appId, String appSecret, String apiUrl) { // 检查缓存的token是否仍然有效 if (cachedToken != null && System.currentTimeMillis() < (tokenExpireTime - 5 * 60 * 1000)) { return cachedToken; } String url = apiUrl + "/v1/oauth2/access_token"; String getUrl = url + "?appId=" + appId + "&secret=" + appSecret + "&grantType=client_credentials"; HttpResponse response = HttpRequest.get(getUrl) .header("X-Tsign-Open-App-Id", appId) .header("X-Tsign-Open-App-Secret", appSecret) .execute(); JSONObject result = JSONUtil.parseObj(response.body()); if (result.getInt("code") == 0) { JSONObject data = result.getJSONObject("data"); String token = data.getStr("token"); Integer expiresIn = data.getInt("expiresIn", 7200); cachedToken = token; tokenExpireTime = System.currentTimeMillis() + expiresIn * 1000L; return token; } return null; } } ``` ### 2. 创建手写签署流程 在 `EsignServiceImpl.createSignFlow()` 方法中,关键配置如下: ```java // 创建手写签署区域 private List> createSignFields(int signOrder, String signerName) { List> fields = new ArrayList<>(); Map field = new HashMap<>(); field.put("fileId", 1); // 文档序号 field.put("signerName", signerName); field.put("autoExecute", false); // 不自动执行,需要手动签署 field.put("signFieldType", 5); // 5-签名类型 field.put("signFieldStyle", 1); // 1-手写签名(重要!) field.put("signFieldStyleType", 1); // 1-手写签名样式 field.put("page", 1); // 页码 field.put("x", 100); // X坐标 field.put("y", 200); // Y坐标 field.put("width", 200); // 宽度 field.put("height", 50); // 高度 fields.add(field); return fields; } ``` ### 3. 获取手写签署链接 ```java @Override public String getCustomerSignUrl(String flowId, String accountId, String mobile, String name) { // 使用OAuth2.0鉴权获取签署链接 String url = esignProperties.getApiUrl() + "/v3/signflows/" + flowId + "/signers/" + accountId + "/signUrl"; JSONObject params = new JSONObject(); params.put("accountId", accountId); params.put("urlType", 1); // 1-移动端H5,2-PC端 // sendRequest方法内部使用OAuth2.0 token JSONObject result = sendRequest(url, params); if (result != null && result.getInt("code") == 0) { return result.getJSONObject("data").getStr("signUrl"); } return null; } ``` ## 关键配置说明 ### 手写签署关键参数 | 参数 | 值 | 说明 | |------|-----|------| | `signFieldStyle` | `1` | 手写签名样式(必须为1才能手写签署) | | `signFieldType` | `5` | 签名类型 | | `signFieldStyleType` | `1` | 手写签名样式类型 | | `autoExecute` | `false` | 不自动执行,需要签署人手动签署 | | `urlType` | `1` 或 `2` | 签署链接类型:1-移动端H5,2-PC端 | ### OAuth2.0 Token管理 1. **Token缓存**: 实现token缓存机制,避免频繁请求 2. **自动刷新**: 在token过期前5分钟自动刷新 3. **错误处理**: token失效时自动重新获取 ## API接口说明 ### 1. 创建签署流程 **接口**: `POST /v3/signflows` **请求体关键字段**: ```json { "autoExecute": false, "businessScene": "合同签署", "docs": [ { "fileId": "文件ID", "fileName": "合同.pdf" } ], "signers": [ { "signerAccount": { "signerAccountId": "签署人账号ID", "mobile": "手机号", "name": "姓名" }, "signOrder": 1, "signFields": [ { "fileId": 1, "signFieldStyle": 1, "signFieldType": 5, "signFieldStyleType": 1, "page": 1, "x": 100, "y": 200, "width": 200, "height": 50 } ] } ] } ``` ### 2. 获取签署链接 **接口**: `GET /v3/signflows/{flowId}/signers/{accountId}/signUrl` **请求参数**: - `accountId`: 签署人账号ID - `urlType`: 链接类型(1-移动端,2-PC端) **响应**: ```json { "code": 0, "data": { "signUrl": "https://..." } } ``` ### 3. 启动签署流程 **接口**: `POST /v3/signflows/{flowId}/start` 启动后,签署人才能通过签署链接进行签署。 ## 使用SDK的说明 ### 当前实现方式 当前代码使用HTTP方式直接调用e签宝API,但使用了OAuth2.0标准鉴权方式: - 使用 `EsignSdkUtil` 工具类获取OAuth2.0 token - 所有API请求都携带OAuth2.0 token - 符合OAuth2.0标准规范 ### 使用官方SDK(可选) 如果希望完全使用官方SDK(paas-sdk-3.1.4.jar),可以参考以下方式: 1. **初始化SDK客户端**: ```java // 根据SDK文档初始化ServiceClient ServiceClient client = EsignsdkServiceFactory.buildServiceClient(oauth2Config); ``` 2. **调用SDK方法**: ```java // 使用SDK的方法替代HTTP请求 client.createSignFlow(params); client.getSignUrl(flowId, accountId); ``` **注意**: SDK的具体API请参考e签宝官方文档或联系技术支持。 ## 配置说明 在 `application-dev.yaml` 中配置: ```yaml system: esign: app-id: 7439093682 # e签宝应用ID app-secret: da96bb269786338c156050cfa23eba4f # e签宝应用密钥 api-url: https://smlopenapi.esign.cn # API地址(沙箱环境) # api-url: https://openapi.esign.cn # 生产环境 enabled: true ``` ## 常见问题 ### 1. Token过期问题 **问题**: API调用返回401或token过期错误 **解决**: - Token有效期为2小时,系统会自动刷新 - 如果出现问题,可以调用 `EsignSdkUtil.clearCachedToken()` 清除缓存,重新获取 ### 2. 手写签署不显示 **问题**: 签署链接打开后没有手写签署选项 **解决**: - 确保 `signFieldStyle=1`(手写签名) - 确保 `signFieldType=5`(签名类型) - 确保流程已启动(调用start接口) ### 3. 签署位置不正确 **问题**: 手写签署区域位置不对 **解决**: - 调整 `x`、`y`、`width`、`height` 参数 - 注意坐标是从PDF左上角开始计算的像素值 - 建议先在e签宝工作台测试签署位置 ## 参考资料 - e签宝开放平台文档: https://open.esign.cn/doc - OAuth2.0规范: https://oauth.net/2/ - e签宝SDK文档: 联系e签宝技术支持获取