# e签宝合同模板填充使用说明 ## 功能概述 实现了两种合同模板填充方式: 1. **本地Word模板填充**:使用poi-tl填充本地Word模板,转换为PDF后上传到e签宝 2. **e签宝工作台模板填充**:直接使用e签宝工作台中已有的模板,通过API填充字段并生成文件(推荐) ## 主要功能 ### 方式一:本地Word模板(适用于开发和测试) 1. **Word模板填充**:使用poi-tl库填充Word模板中的变量 2. **PDF转换**:将填充后的Word文档转换为PDF格式(使用LibreOffice) 3. **文件上传**:将PDF文件上传到e签宝平台,获得fileId 4. **签署流程创建**:基于上传的文件创建e签宝签署流程 ### 方式二:e签宝工作台模板(推荐用于生产环境) 1. **获取模板列表**:从e签宝工作台获取已创建的模板列表 2. **获取模板详情**:查看模板的详细信息和控件配置 3. **模板字段填充**:使用e签宝API直接填充模板中的控件字段 4. **生成文件**:自动生成填充后的PDF文件,获得fileId 5. **签署流程创建**:基于生成的文件创建e签宝签署流程 ## 核心组件 ### 1. EsignContractUtil(工具类) 提供以下静态方法: - `fillTemplateAndUpload()`: 填充模板、转换PDF、上传到e签宝(一步完成) - `fillWordTemplate()`: 仅填充Word模板,返回字节数组 - `fillTemplateAndConvertToPdf()`: 填充模板并转换为PDF - `fillTemplateAndSavePdf()`: 填充模板并保存为PDF文件 ### 2. EsignService(服务接口) - `uploadDocument()`: 上传文件到e签宝,返回fileId - `createSignFlow()`: 创建签署流程 ### 3. API接口 #### 3.1 完整流程接口(推荐) **接口地址**: `POST /api/wechat/esign/flow/createFromTemplate` **功能**: 填充模板、上传文件、创建签署流程,一步完成 **请求体示例**: ```json { "contractId": 123, "caseId": 456, "templatePath": "template1.docx", "contractName": "浙江宝路同典当抵押合同", "contract": { "contractNo": "HT20250101001", "name": "张三", "idNumber": "330123199001011234", "mobile": "13800138000", "contractAmount": 100000.0, "contractPeriod": 30, "amountRate": 1.5, "serviceCost": 2.0, "loanRate": 3.0, "bankName": "中国工商银行", "bankAccount": "6222021234567890123", "address": "杭州市西湖区文三路123号", "collateralNumber": "101", "collateralArea": "100平方米", "contractType": "抵押合同", "contractAttr": "不动产抵押", "leastDay": 30, "contactAddress1": "杭州市西湖区", "contactAddress2": "杭州市西湖区", "userMobile": "13900139000", "signLocation1": "杭州", "signLocation2": "杭州" }, "customerName": "张三", "customerMobile": "13800138000", "customerIdNumber": "330123199001011234", "businessName": "浙江宝路同典当有限公司", "businessMobile": "13900139000", "businessIdNumber": "", "signDeadline": 1735632000000, "remark": "测试合同" } ``` **响应示例**: ```json { "code": 200, "message": "模板填充并创建签署流程成功", "data": "flowId123456789" } ``` #### 3.2 仅上传接口 **接口地址**: `POST /api/wechat/esign/template/upload` **功能**: 仅填充模板并上传到e签宝,不创建签署流程 **请求体**: 与完整流程接口相同 **响应示例**: ```json { "code": 200, "message": "模板填充并上传成功", "data": "fileId123456789" } ``` #### 3.3 创建签署流程接口 **接口地址**: `POST /api/wechat/esign/flow/create` **功能**: 使用已上传的文件ID创建签署流程 **请求体示例**: ```json { "contractId": 123, "caseId": 456, "fileId": "fileId123456789", "documentName": "浙江宝路同典当抵押合同.pdf", "customerName": "张三", "customerMobile": "13800138000", "customerIdNumber": "330123199001011234", "businessName": "浙江宝路同典当有限公司", "businessMobile": "13900139000", "signDeadline": 1735632000000 } ``` ## Word模板制作 ### 支持的变量类型 1. **文本变量**: 使用 `{{variableName}}` 格式 2. **图片变量**: 使用 `{{@pictureVariable}}` 格式(用于签名图片) ### 模板变量列表 根据 `ContractInformation` 类,支持的变量包括: | 变量名 | 说明 | 示例值 | |--------|------|--------| | contractNo | 合同编号 | HT20250101001 | | customerName | 客户姓名 | 张三 | | customerIdNumber | 客户身份证号 | 330123199001011234 | | customerMobile | 客户手机号 | 13800138000 | | contractType | 业务类型 | 抵押合同 | | contractAttr | 业务属性 | 不动产抵押 | | contractAmount | 合同金额 | 100000.0 | | contractPeriod | 合同期限(天) | 30 | | interestRate | 当金利率 | 1.5% | | serviceCost | 综合服务费 | 2.0% | | loanRate | 借款利率 | 3.0% | | bankName | 客户银行 | 中国工商银行 | | bankAccount | 客户银行账号 | 6222021234567890123 | | collateralAddress | 押品地址 | 杭州市西湖区文三路123号 | | numberAndArea | 押品编号和面积 | 101 100平方米 | | contactAddress1 | 客户联系地址 | 杭州市西湖区 | | contactAddress2 | 业务方联系地址 | 杭州市西湖区 | | userMobile | 业务方手机号 | 13900139000 | | signLocation1 | 客户签署地点 | 杭州 | | signLocation2 | 业务方签署地点 | 杭州 | | signature1 | 客户签名图片 | (图片) | | signature2 | 业务方签名图片 | (图片) | | startDateTime | 借款日期 | 2025-01-01 | | endDateTime | 还款日期 | 2025-01-31 | ### 模板示例 在Word模板中使用以下格式: ``` 合同编号:{{contractNo}} 客户姓名:{{customerName}} 身份证号:{{customerIdNumber}} 合同金额:{{contractAmount}}元 合同期限:{{contractPeriod}}天 当金利率:{{interestRate}} 综合服务费:{{serviceCost}} 借款利率:{{loanRate}} 押品地址:{{collateralAddress}} 押品信息:{{numberAndArea}} 客户签名:{{@signature1}} 业务方签名:{{@signature2}} ``` ## 配置说明 ### application-dev.yaml / application-prod.yaml ```yaml system: esign: app-id: your-app-id app-secret: your-app-secret api-url: https://smlopenapi.esign.cn project-id: your-project-id enabled: true upload: templatePath: /app/upload/template/ # 模板文件路径 libreoffice: remote-url: http://libreoffice-server:8080 # 或本地模式 office-home: "" # 留空使用远程模式 ``` ## 使用示例 ### Java代码示例 ```java @Autowired private EsignService esignService; // 方式1: 使用工具类(推荐) ContractInformation contract = new ContractInformation(); contract.setContractNo("HT20250101001"); contract.setName("张三"); // ... 设置其他字段 String templatePath = "/app/upload/template/template1.docx"; String fileId = EsignContractUtil.fillTemplateAndUpload( templatePath, contract, "浙江宝路同典当抵押合同", esignService ); // 方式2: 分步执行 // 1. 填充模板 byte[] docxBytes = EsignContractUtil.fillWordTemplate(templatePath, contract); // 2. 转换为PDF byte[] pdfBytes = LibreOfficePdfUtil.convertDocxToPdf(docxBytes); // 3. 上传到e签宝 String fileId = esignService.uploadDocument(pdfBytes, "合同.pdf", "application/pdf"); // 4. 创建签署流程 EsignCreateFlowDTO flowDTO = new EsignCreateFlowDTO(); flowDTO.setFileId(fileId); flowDTO.setDocumentName("合同.pdf"); // ... 设置其他字段 String flowId = esignService.createSignFlow(flowDTO); ``` ## 注意事项 1. **模板路径**: - 如果使用相对路径(如 `template1.docx`),会自动拼接配置的 `upload.templatePath` - 如果使用绝对路径,直接使用该路径 2. **PDF转换**: - 需要确保LibreOffice服务正常运行 - 开发环境可以使用本地LibreOffice - 生产环境建议使用远程LibreOffice服务 3. **文件上传**: - e签宝上传分为三步:获取上传URL -> 上传到OSS -> 通知上传完成 - 上传成功后返回fileId,后续需要使用fileId创建签署流程 4. **错误处理**: - 所有步骤都有异常处理和日志记录 - 如果某个步骤失败,会返回详细的错误信息 5. **签名图片**: - 签名图片路径应该是文件的绝对路径或相对路径 - 如果图片加载失败,会记录警告日志但不会中断流程 ## 常见问题 ### Q1: 模板填充后格式错乱? A: 检查Word模板中的变量格式是否正确,确保使用 `{{variableName}}` 格式。 ### Q2: PDF转换失败? A: 检查LibreOffice服务是否正常运行,查看日志中的错误信息。 ### Q3: 上传到e签宝失败? A: 检查e签宝配置是否正确(app-id、app-secret),确保网络连接正常。 ### Q4: 如何自定义模板变量? A: 修改 `EsignContractUtil.fillWordTemplate()` 方法中的变量映射部分。 --- ## 方式二:使用e签宝工作台模板(推荐) ### 准备工作 1. **在e签宝工作台创建模板**: - 登录e签宝工作台(https://smlopen.esign.cn) - 进入"合同模板"管理页面 - 点击"新建模板",上传您的合同文件(支持Word或PDF格式) - 在模板编辑器中,添加需要填充的控件: - 文本控件:用于填充文本字段(如合同编号、客户姓名等) - 为每个控件设置唯一的**控件ID(key)**,这个ID将用于API填充 - 保存模板,记录模板ID(docTemplateId) 2. **了解模板控件ID**: - 在模板详情中可以查看所有控件的ID - 也可以通过API获取模板详情查看控件配置 ### API接口 #### 1. 获取模板列表 **接口地址**: `GET /api/wechat/esign/template/list` **请求参数**: - `pageNum` (可选): 页码,默认1 - `pageSize` (可选): 每页大小,默认20 **响应示例**: ```json { "code": 200, "message": "获取模板列表成功", "data": { "list": [ { "docTemplateId": "template123456", "docTemplateName": "浙江宝路同典当抵押合同", "docTemplateType": "DOCX", "createTime": "2025-01-01 10:00:00" } ], "total": 10, "pageNum": 1, "pageSize": 20 } } ``` #### 2. 获取模板详情 **接口地址**: `GET /api/wechat/esign/template/{templateId}` **功能**: 获取模板的详细信息,包括所有控件的配置 **响应示例**: ```json { "code": 200, "message": "获取模板详情成功", "data": { "docTemplateId": "template123456", "docTemplateName": "浙江宝路同典当抵押合同", "structComponents": [ { "key": "contractNo", "componentType": "TEXT", "componentName": "合同编号" }, { "key": "customerName", "componentType": "TEXT", "componentName": "客户姓名" } ] } } ``` #### 3. 完整流程:填充模板并创建签署流程(推荐) **接口地址**: `POST /api/wechat/esign/flow/createFromEsignTemplate` **功能**: 一步完成模板填充、文件生成、签署流程创建 **请求体示例**: ```json { "contractId": 123, "caseId": 456, "templateId": "template123456", "contractName": "浙江宝路同典当抵押合同", "formFields": { "contractNo": "HT20250101001", "customerName": "张三", "customerIdNumber": "330123199001011234", "customerMobile": "13800138000", "contractAmount": "100000", "contractPeriod": "30", "interestRate": "1.5", "bankName": "中国工商银行", "bankAccount": "6222021234567890123" }, "customerName": "张三", "customerMobile": "13800138000", "customerIdNumber": "330123199001011234", "businessName": "浙江宝路同典当有限公司", "businessMobile": "13900139000", "signDeadline": 1735632000000 } ``` **请求体说明**: - `templateId`: e签宝工作台中的模板ID(必填) - `formFields`: 字段映射(key为模板控件ID,value为填充值) - 如果`formFields`为空,可以传入`contract`对象,系统会自动映射常见字段 - `customerName`, `customerMobile`等: 用于创建签署流程的签署人信息 **响应示例**: ```json { "code": 200, "message": "使用e签宝模板创建签署流程成功", "data": "flowId123456789" } ``` #### 4. 仅填充模板生成文件 **接口地址**: `POST /api/wechat/esign/template/fill` **功能**: 仅填充模板并生成文件,不创建签署流程 **请求体**: 与完整流程接口相同(但不创建流程) **响应示例**: ```json { "code": 200, "message": "模板填充并生成文件成功", "data": "fileId123456789" } ``` ### 自动字段映射 如果您不想手动构建`formFields`,可以传入`contract`对象,系统会自动将常见字段映射到e签宝模板控件。 **支持的自动映射字段**: | ContractInformation字段 | 映射到的模板控件ID | |------------------------|-------------------| | contractNo | contractNo, 合同编号 | | name | customerName, 客户姓名, name | | idNumber | customerIdNumber, 客户身份证号, idNumber | | mobile | customerMobile, 客户手机号, mobile | | contractAmount | contractAmount, 合同金额, 金额 | | contractPeriod | contractPeriod, 合同期限, 期限 | | amountRate | interestRate, 当金利率 | | serviceCost | serviceCost, 综合服务费 | | loanRate | loanRate, 借款利率 | | bankName | bankName, 银行名称 | | bankAccount | bankAccount, 银行账号 | | address | collateralAddress, 押品地址, address | | contractType | contractType, 业务类型 | | contractAttr | contractAttr, 业务属性 | | startDateTime | startDateTime, 借款日期 | | endDateTime | endDateTime, 还款日期 | **使用示例**: ```json { "templateId": "template123456", "contractName": "浙江宝路同典当抵押合同", "contract": { "contractNo": "HT20250101001", "name": "张三", "idNumber": "330123199001011234", "mobile": "13800138000", "contractAmount": 100000.0, "contractPeriod": 30 }, "customerName": "张三", "customerMobile": "13800138000", "businessName": "浙江宝路同典当有限公司", "businessMobile": "13900139000" } ``` ### Java代码示例 ```java @Autowired private EsignService esignService; // 方式1: 使用手动字段映射 Map formFields = new HashMap<>(); formFields.put("contractNo", "HT20250101001"); formFields.put("customerName", "张三"); formFields.put("contractAmount", "100000"); String fileId = esignService.fillTemplateAndGenerateFile( "template123456", "合同.pdf", formFields ); // 方式2: 使用自动字段映射 ContractInformation contract = new ContractInformation(); contract.setContractNo("HT20250101001"); contract.setName("张三"); // ... 设置其他字段 Map formFields = EsignContractUtil.convertContractToTemplateFields(contract); String fileId = esignService.fillTemplateAndGenerateFile( "template123456", "合同.pdf", formFields ); ``` ### e签宝模板控件命名建议 为了使用自动字段映射功能,建议在e签宝工作台中创建模板时,使用以下控件ID: **合同基本信息**: - `contractNo` 或 `合同编号` - `contractAmount` 或 `合同金额` - `contractPeriod` 或 `合同期限` **客户信息**: - `customerName` 或 `客户姓名` 或 `name` - `customerIdNumber` 或 `客户身份证号` 或 `idNumber` - `customerMobile` 或 `客户手机号` 或 `mobile` **利率信息**: - `interestRate` 或 `当金利率` - `serviceCost` 或 `综合服务费` - `loanRate` 或 `借款利率` **银行信息**: - `bankName` 或 `银行名称` - `bankAccount` 或 `银行账号` **押品信息**: - `collateralAddress` 或 `押品地址` 或 `address` - `numberAndArea` 或 `押品信息` **日期信息**: - `startDateTime` 或 `借款日期` - `endDateTime` 或 `还款日期` ### 两种方式对比 | 特性 | 本地Word模板 | e签宝工作台模板 | |------|------------|---------------| | 模板管理 | 需要自行管理模板文件 | e签宝工作台统一管理 | | 模板编辑 | 需要使用Word编辑器 | e签宝可视化编辑器 | | 字段填充 | 使用变量名 `{{variable}}` | 使用控件ID(key) | | PDF转换 | 需要LibreOffice服务 | e签宝自动转换 | | 适用场景 | 开发和测试 | 生产环境(推荐) | | 模板更新 | 需要重新上传文件 | 在e签宝工作台直接更新 | ### 使用e签宝模板的注意事项 1. **模板ID获取**: - 模板ID可以从e签宝工作台查看,也可以通过API获取模板列表 - 模板ID是固定的,不会因为模板更新而改变 2. **控件ID(key)**: - 控件ID是在e签宝工作台创建模板时设置的 - 控件ID必须唯一,且一旦设置不建议修改 - 如果修改了控件ID,需要同步更新API调用中的字段映射 3. **字段映射**: - `formFields`中的key必须与模板中的控件ID完全一致 - 如果控件ID使用中文,key也必须使用中文 - 建议使用英文控件ID,便于维护 4. **自动映射**: - 自动映射功能基于常见的字段名规则 - 如果模板控件ID不符合命名规则,需要手动构建`formFields` - 可以通过获取模板详情API查看实际的控件ID 5. **模板更新**: - 在e签宝工作台更新模板后,不需要修改代码 - 只要控件ID不变,API调用就可以正常工作 ## 完整业务流程说明 ### 方式一:使用e签宝工作台模板(推荐) #### 准备工作(一次性) 1. **在e签宝工作台创建模板**: - 登录e签宝工作台,上传合同文件 - 在模板编辑器中添加文本控件 - 为每个控件设置控件ID(如 `contractNo`、`customerName`) - **记录模板ID(docTemplateId)**,例如:`template123456` #### 业务流程(每次签署合同) **步骤1:使用模板ID填充字段并创建签署流程** ```http POST /api/wechat/esign/flow/createFromEsignTemplate ``` 这个接口会**一步完成**以下操作: - ✅ 根据 `templateId` 填充模板字段(使用 `formFields` 或 `contract`) - ✅ 生成填充后的PDF文件 - ✅ 创建签署流程 - ✅ 返回 `flowId` **请求示例**: ```json { "contractId": 123, "caseId": 456, "templateId": "template123456", "contractName": "浙江宝路同典当抵押合同", "formFields": { "contractNo": "HT20250101001", "customerName": "张三", "customerMobile": "13800138000" }, "customerName": "张三", "customerMobile": "13800138000", "businessName": "浙江宝路同典当有限公司", "businessMobile": "13900139000" } ``` **响应示例**: ```json { "code": 200, "message": "使用e签宝模板创建签署流程成功", "data": "flowId123456789" } ``` **步骤2:获取签署链接** 获取客户签署链接: ```http GET /api/wechat/esign/flow/{flowId}/customer/sign-url?mobile=13800138000&name=张三 ``` 获取业务方签署链接: ```http GET /api/wechat/esign/flow/{flowId}/business/sign-url?mobile=13900139000&name=公司名称 ``` **步骤3:启动签署流程** ```http POST /api/wechat/esign/flow/{flowId}/start ``` 启动后,签署人才能通过签署链接进行签署。 **步骤4:签署和查询** - 客户和业务方通过签署链接完成签署 - 查询签署状态:`GET /api/wechat/esign/flow/{flowId}/status` - 下载已签署文档:`GET /api/wechat/esign/flow/{flowId}/download` --- ### 方式二:分步执行(如果需要更精细的控制) 如果不想一步完成,可以分步执行: **步骤1:填充模板生成文件** ```http POST /api/wechat/esign/template/fill ``` 返回 `fileId` **步骤2:使用fileId创建签署流程** ```http POST /api/wechat/esign/flow/create ``` 传入 `fileId`,返回 `flowId` **步骤3-5**:同方式一的步骤2-4 --- ### 完整流程图 ``` ┌─────────────────────────────────────────────────────────────┐ │ 准备工作(一次性) │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ 1. 在e签宝工作台创建模板 │ │ │ │ 2. 设置控件ID(如:contractNo, customerName) │ │ │ │ 3. 记录模板ID(templateId) │ │ │ └───────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 业务流程(每次签署) │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ 步骤1: 填充模板并创建签署流程 │ │ │ │ POST /api/wechat/esign/flow/createFromEsignTemplate │ │ │ │ 输入: templateId + formFields/contract │ │ │ │ 输出: flowId │ │ │ └───────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ 步骤2: 获取签署链接 │ │ │ │ GET /api/wechat/esign/flow/{flowId}/customer/sign-url│ │ │ │ GET /api/wechat/esign/flow/{flowId}/business/sign-url│ │ │ └───────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ 步骤3: 启动签署流程 │ │ │ │ POST /api/wechat/esign/flow/{flowId}/start │ │ │ └───────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ 步骤4: 客户和业务方签署 │ │ │ │ 通过签署链接完成签署 │ │ │ └───────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ 步骤5: 查询状态和下载 │ │ │ │ GET /api/wechat/esign/flow/{flowId}/status │ │ │ │ GET /api/wechat/esign/flow/{flowId}/download │ │ │ └───────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ### 关键概念说明 1. **templateId(模板ID)**: - 这是e签宝工作台中模板的唯一标识 - 在创建模板时获得,**不会改变** - 不需要从合同列表获取,**模板ID是固定的** 2. **flowId(流程ID)**: - 每次创建签署流程时生成的唯一标识 - 用于后续的签署链接获取、状态查询等操作 - 一个模板ID可以创建多个flowId(对应多个合同) 3. **fileId(文件ID)**: - 填充模板后生成的PDF文件在e签宝中的标识 - 如果使用 `createFromEsignTemplate` 接口,fileId会自动处理,无需关心 ### 常见误解 ❌ **错误理解**: 1. 先填充模板字段 2. 从合同列表中获取填充完的templateId 3. 开始签署合同 ✅ **正确理解**: 1. **templateId是固定的**(在e签宝工作台创建模板时获得) 2. 调用 `createFromEsignTemplate` 接口时,传入templateId和要填充的数据 3. 接口会:填充模板 → 生成文件 → 创建签署流程,返回flowId 4. 使用flowId获取签署链接、启动流程、完成签署 --- ## 后续流程 模板填充和签署流程创建成功后,可以: 1. 获取签署链接:调用 `/api/wechat/esign/flow/{flowId}/customer/sign-url` 2. 启动签署流程:调用 `/api/wechat/esign/flow/{flowId}/start` 3. 查询流程状态:调用 `/api/wechat/esign/flow/{flowId}/status` 4. 下载已签署文档:调用 `/api/wechat/esign/flow/{flowId}/download` 详细接口说明请参考 `EsignController.java`。