Starter
esign-helper-starter 是整个 EsignHelper 体系里最核心、最适合被业务系统直接集成的模块。
它不是一个只帮你拼 HTTP 请求的轻量 SDK,而是一套可产品化、可扩展、可落库、可服务化演进的 e签宝接入底座。
如果你是外部系统开发者,希望做到“引入依赖后照着文档就能用”,那么这份 README 就是按这个目标写的。
本文档会尽可能完整地覆盖:
- 模块定位与整体能力
- 对外公开 Bean 与推荐用法
- 所有公开场景的代码示例
- 多租户与默认表结构
- Flyway 升级方式
- SPI 扩展方式
- 默认回调接入方式
- 常见接入策略和推荐落地顺序
1. 模块定位
1.1 这个模块是什么
它是一个 Spring Boot Starter,供其他业务系统通过 Maven 依赖直接集成。
1.2 这个模块解决什么问题
在真实项目里,直接接 e签宝通常会遇到这些问题:
- 每个系统都要重复写请求签名和请求执行逻辑
- 各系统都要重复维护
手机号 -> psnId、统一社会信用代码 -> orgId - 各系统都要重复保存流程、文件、模板、印章、成员、账号、授权、出证等数据
- 回调处理容易散在 Controller、Service、DAO 中,后续难维护
- 老系统升级时需要增量脚本,新系统初始化时又需要完整 DDL
- 某些系统想把它作为依赖嵌入,某些系统又想最终演进成独立服务
esign-helper-starter 的目标就是统一解决这些共性问题。
1.3 它不是什么
它不是:
- 只给 demo 演示用的代码
- 只适用于某个单一业务系统的内部工具类
- 只负责 HTTP 转发的超薄封装
2. 适用对象
适合这些人直接使用:
- 业务系统开发者
- 中台/基础平台开发者
- 想统一公司 e签宝接入标准的架构师
- 需要把 e签宝能力沉淀为内部可复用能力的团队
3. 当前能力总览
3.1 API 门面能力
通过 EsignApiFacade.java 暴露统一入口,当前支持:
raw()auth()file()signFlow()contractFile()flowTemplate()template()member()seal()account()evidence()enterpriseConsole()contractManagement()catalog()
3.2 流程编排能力
通过 EsignFlowIntegrationService.java 提供:
- 文件流程创建
- 模板流程创建
- 流程主记录落库
- 流程任务记录落库
- 回调处理
- 按业务号查询流程
- 按
signFlowId读取聚合结果
3.3 资源归档能力
starter 会默认沉淀这些关键信息:
- 个人主体档案
- 机构主体档案
- 印章档案
- 成员档案
- 模板档案
- 文件档案
- 流程详情档案
- 个人账号档案
- 机构账号档案
- 个人授权档案
- 机构授权档案
- 出证报告档案
3.4 数据库能力
starter 自带:
- 默认 15 张表
- 四类关系型数据库完整建表脚本
- Flyway 版本化升级脚本
- 默认 JPA 实体与默认 JPA 仓储
3.5 API 调用日志能力
starter 会统一记录通过自身发起的 e签宝 API 调用,默认同时写入:
ESIGN_API_CALL_LOG表,供后续 Web 端查询- 本地日志文件,便于线上排查
日志会沉淀这些核心维度:
apiCategory分类,例如auth、file、flow、sealoperationName操作名requestPath/requestUrl- HTTP 状态码、e签宝业务码、耗时
- 请求报文、响应报文、错误信息
支持数据库:
- MySQL
- PostgreSQL
- Oracle
- SQL Server
3.6 核心数据结构
starter 当前涉及三层数据结构:
starter/model/**面向 e签宝开放平台请求/响应的强类型模型starter/integration/**面向 starter 内部流程编排、归档与回调处理的命令/记录模型starter/infrastructure/persistence/entity/**面向默认 JPA 落库实现的实体映射
链式 setter 说明:
- 上述三层里的请求体、响应体、命令对象、归档记录、默认实体都已支持链式 setter。
- 唯一保留传统
void setXxx(...)的是 EsignProperties.java,因为它属于@ConfigurationProperties配置绑定类,不作为业务实体使用。
常见通用结构:
| 结构 | 作用 | 关键字段 |
|---|---|---|
| EsignResponse.java | e签宝统一响应包裹 | code、message、data |
| OperationResultData.java | 通用操作结果 | success、result |
| DownloadUrlData.java | 下载链接返回 | downloadUrl |
| AbstractExtensibleRequest.java | 官方扩展字段透传基类 | extraFields |
认证与授权结构:
| 结构 | 作用 | 关键字段 |
|---|---|---|
AuthModels.PersonAuthUrlRequest | 发起个人授权链接请求 | psnAccount、redirectUrl、notifyUrl |
AuthModels.OrganizationAuthUrlRequest | 发起机构授权链接请求 | orgId、redirectUrl、notifyUrl |
AuthModels.PersonIdentityInfoData | 个人实名信息返回 | psnId、psnAccount、authStatus |
AuthModels.OrganizationIdentityInfoData | 机构实名信息返回 | orgId、orgName、orgIDCardNum |
AuthModels.AuthUrlData | 授权链接结果 | authUrl、shortUrl |
AuthModels.PersonAuthorizedInfoData / OrganizationAuthorizedInfoData | 授权状态结果 | psnId/orgId、authStatus |
文件与流程结构:
| 结构 | 作用 | 关键字段 |
|---|---|---|
ContractFileModels.FileUploadUrlRequest | 申请文件上传地址 | fileName、contentType、fileSize、contentMd5、convertToPdf |
ContractFileModels.FileUploadUrlData | 文件上传地址返回 | fileId、uploadUrl、fileUploadUrl |
ContractFileModels.FileStatusData | 文件状态返回 | fileId、fileStatus |
ContractFileModels.CreateByFileRequest | 按文件发起签署流程 | flowName、docs |
ContractFileModels.DocumentInfo | 流程里的单个文档描述 | fileId |
ContractFileModels.SignUrlRequest | 获取签署链接请求 | 常用扩展字段如 operatorId、redirectUrl 通过 putExtraField(...) 透传 |
ContractFileModels.SignFlowCreateData | 签署流程创建结果 | signFlowId、shortUrl、url |
ContractFileModels.SignFlowDetailData | 签署流程详情 | signFlowId、flowStatus、docs |
ContractFileModels.KeywordPositionQueryRequest / KeywordPositionData | 关键字定位请求与结果 | keywords、keywordPositions、pageNum、posX、posY |
ContractFileModels.RescissionUrlRequest | 解约链接请求 | 常用扩展字段如 redirectUrl 通过 putExtraField(...) 透传 |
模板、成员、印章、账号、出证结构:
| 结构 | 作用 | 关键字段 |
|---|---|---|
FlowTemplateModels.CreateBySignTemplateRequest | 按模板发起流程 | signTemplateId、flowName |
FlowTemplateModels.SignTemplateData | 流程模板信息 | signTemplateId、signTemplateName、status |
MemberModels.AddMemberRequest / MemberData | 成员新增与成员信息 | orgId、psnId、memberName、mobile |
SealModels.CreatePersonTemplateSealRequest / CreateOrganizationTemplateSealRequest | 个人/机构印章创建请求 | accountId、orgId、sealName |
SealModels.SealData | 印章信息 | sealId、sealName |
AccountModels.CreatePersonAccountRequest / CreateOrganizationAccountRequest | 个人/机构账号创建请求 | name、idType、idNumber、thirdPartyUserId |
AccountModels.AccountData | 账号信息 | accountId、psnId/orgId、name、thirdPartyUserId |
EvidenceModels.ApplyReportRequest / ApplyReportData | 出证申请请求与结果 | signFlowId、reportId、reportDownloadUrl |
starter 内部编排与归档结构:
| 结构 | 作用 | 关键字段 |
|---|---|---|
| CreateEsignFlowCommand.java | starter 内部统一发起命令 | tenantCode、systemCode、businessId、flowType、requestPayload |
| EsignFlowRecord.java | 流程主档案 | signFlowId、businessId、status、requestPayload、responsePayload |
| EsignFlowTaskRecord.java | 流程任务档案 | taskId、taskKey、signerId、action、callbackPayload |
EsignPersonIdentityRecord / EsignOrganizationIdentityRecord | 主体档案 | psnAccount/psnId、orgIdCardNum/orgId、authStatus |
EsignPersonAccountRecord / EsignOrganizationAccountRecord | 账号档案 | accountId、thirdPartyUserId、name |
EsignPersonAuthRecord / EsignOrganizationAuthRecord | 授权档案 | authStatus、authUrl、shortUrl、redirectUrl、notifyUrl |
EsignSealRecord | 印章档案 | sealId、sealName、ownerType、ownerId |
EsignTemplateRecord | 模板档案 | signTemplateId、signTemplateName、status |
EsignFileRecord / EsignFlowDetailRecord / EsignEvidenceReportRecord | 文件、流程详情、出证档案 | fileId、signFlowId、reportId 等 |
EsignApiCallLogRecord | API 调用日志档案 | apiCategory、operationName、httpStatus、esignCode、durationMillis |
默认持久化实体:
| 结构 | 作用 | 对应表 |
|---|---|---|
EsignFlowEntity | 流程主表实体 | ESIGN_FLOW |
EsignFlowTaskEntity | 流程任务表实体 | ESIGN_FLOW_TASK |
EsignPersonIdentityEntity / EsignOrganizationIdentityEntity | 主体档案实体 | ESIGN_PSN_IDENTITY / ESIGN_ORG_IDENTITY |
EsignPersonAccountEntity / EsignOrganizationAccountEntity | 账号档案实体 | ESIGN_PSN_ACCOUNT / ESIGN_ORG_ACCOUNT |
EsignPersonAuthEntity / EsignOrganizationAuthEntity | 授权档案实体 | ESIGN_PSN_AUTH / ESIGN_ORG_AUTH |
EsignSealEntity / EsignMemberEntity / EsignTemplateEntity | 印章、成员、模板实体 | ESIGN_SEAL / ESIGN_MEMBER / ESIGN_TEMPLATE |
EsignFileEntity / EsignFlowDetailEntity / EsignEvidenceReportEntity | 文件、流程详情、出证实体 | ESIGN_FILE / ESIGN_FLOW_DETAIL / ESIGN_EVIDENCE_REPORT |
EsignApiCallLogEntity | API 调用日志实体 | ESIGN_API_CALL_LOG |
如果你想进一步看字段级说明,可以直接点进对应源码:
- 请求/响应模型:
starter/model/** - 归档记录:
starter/integration/model/** - 默认实体:
starter/infrastructure/persistence/entity/**
典型结构组合关系:
- 开户链路:
AccountModels.CreatePersonAccountRequest/CreateOrganizationAccountRequest发起开户,请求成功后拿到PersonAccountData/OrganizationAccountData,再由EsignAccountArchiveService归档为EsignPersonAccountRecord/EsignOrganizationAccountRecord,如果启用默认 JPA,会进一步映射到EsignPersonAccountEntity/EsignOrganizationAccountEntity。 - 实名与授权链路:
AuthModels.PersonAuthUrlRequest/OrganizationAuthUrlRequest用于申请授权链接,返回AuthUrlData;后续实名信息、授权状态会沉淀到PersonIdentityInfoData/OrganizationIdentityInfoData、PersonAuthorizedInfoData/OrganizationAuthorizedInfoData,并由授权归档服务写入EsignPersonIdentityRecord/EsignOrganizationIdentityRecord、EsignPersonAuthRecord/EsignOrganizationAuthRecord。 - 文件签署链路:
ContractFileModels.FileUploadUrlRequest先申请上传地址,文件上传完成后通过CreateByFileRequest发起签署流程,返回SignFlowCreateData,查询时得到SignFlowDetailData;starter 会把流程主档写入EsignFlowRecord,任务维度写入EsignFlowTaskRecord,文件和流程详情分别沉淀到EsignFileRecord、EsignFlowDetailRecord。 - 模板签署链路:
FlowTemplateModels.CreateBySignTemplateRequest负责按模板发起签署,模板元数据通过SignTemplateListData/SignTemplateSummary表达,本地归档使用EsignTemplateRecord与EsignTemplateEntity。 - 出证链路:
EvidenceModels.ApplyReportRequest发起出证,结果由ApplyReportData返回reportId和下载地址,并归档到EsignEvidenceReportRecord与EsignEvidenceReportEntity。
关键字段语义补充:
thirdPartyUserId:业务系统自己的用户主键,用来和 e签宝账号做稳定映射。psnAccount:个人授权/认证入口常用账号标识,通常是手机号。psnId/orgId/accountId:分别表示个人主体、机构主体、账号三个不同层级的 e签宝资源标识,不建议混用。authStatus:实名/授权状态码,starter 原样保留 e签宝返回值,便于和官方文档对照排查。signFlowId/taskId:分别表示签署流程主键与流程内任务主键;一个流程通常会对应多个任务。requestPayload/responsePayload/latestPayload/callbackPayload:用于补档、审计和排障的原始报文快照,默认不会主动裁剪业务字段。tenantCode/systemCode:starter 内部做多租户、多业务系统隔离的统一维度,建议调用方在全链路保持稳定。
4. 模块结构
esign-helper-starter
├─ pom.xml
├─ README.md
└─ src
├─ main
│ ├─ java/top/qnmdmyy/starter
│ │ ├─ config
│ │ ├─ constant
│ │ ├─ core
│ │ ├─ integration
│ │ ├─ infrastructure
│ │ ├─ model
│ │ └─ web
│ └─ resources
│ ├─ META-INF/esign-ddl
│ └─ db/migration/esign
└─ test关键包说明:
config自动装配、Starter 配置项core请求签名、请求执行、统一 API 门面、API 日志integration流程编排、主体归档、资源归档、账号归档、授权归档、出证归档、日志记录模型infrastructure默认实体、默认 JPA 仓储、默认持久化适配modele签宝请求/响应实体webStarter 默认回调入口
4.1 设计模式与架构意图
starter 不是简单把接口“平铺封装”一遍,而是围绕可扩展接入做了几层模式组合。下面这张表可以直接拿来对照源码看:
| 模式 | 解决的问题 | 主要类 | 如何扩展 |
|---|---|---|---|
| Facade(门面) | 不让业务系统直接面对几十个端点、签名、解包细节 | EsignApiFacade.java | 业务侧统一注入 EsignApiFacade,按 auth()/contractFile()/flowTemplate() 等领域调用 |
| Composite Facade(组合门面) | 把多个 API 组合成更贴近业务视角的聚合能力 | EsignApiFacade.EnterpriseConsoleApi、EsignApiFacade.ContractManagementApi | 当某个业务视角不是单一 REST 域时,优先在组合门面里聚合而不是把调用散在 Controller/Service |
| Builder(构建器) | 统一构建请求元信息,避免 method/path/body/signRequired 四散传递 | EsignRequest.Builder | 新增端点时优先继续走 EsignRequest.builder() 组装请求 |
| Template Method / Pipeline(模板方法思路) | 固定“序列化 -> 签名 -> HTTP -> 解包 -> 记录日志”主流程,避免每个 API 重复写 | EsignRequestExecutor.java | 新能力域只描述端点和响应类型,不重复实现执行流程 |
| Strategy(策略) | 不同业务系统创建流程时,请求组装和后置联动逻辑不一样 | EsignBusinessSystemSupport.java、EsignBusinessSystemSupportRegistry.java | 每个系统实现一个 EsignBusinessSystemSupport,按 systemCode 注册 |
| Registry(注册表) | 不把 systemCode -> 适配器 的判断写成 if/else | EsignBusinessSystemSupportRegistry.java | 新增系统适配器后自动进入注册表,未命中时回退默认实现 |
| Repository(仓储) | 领域编排层不依赖具体 ORM/表结构 | EsignFlowRecordRepository、EsignPersonAuthRecordRepository 等 SPI | 可以继续使用默认 JPA,也可以自己实现仓储 SPI 替换 |
| Adapter(适配器) | 把领域记录和具体存储实现隔开,或把业务系统命令映射为 e签宝请求 | JpaEsignFlowRecordRepository、JpaEsignPersonAuthRecordRepository、DefaultEsignBusinessSystemSupport | 一类适配数据库,一类适配业务系统输入;都可以单独替换 |
| Hook / Chain of Responsibility(钩子/责任链) | 授权回调需要“starter 默认归档先执行,集成方逻辑后执行” | EsignAuthCallbackEndpoint.java、EsignAuthCallbackHook.java | 可以实现 Hook,也可以继承 Endpoint;两者都会在默认逻辑之后执行 |
| Auto Configuration + Default Fallback(自动装配 + 默认兜底) | 调用方只引依赖和配置就能跑,同时又能覆盖默认 Bean | EsignAutoConfiguration.java | 通过 @ConditionalOnMissingBean、@ConditionalOnProperty 覆盖默认实现 |
可以把整个 starter 的调用链理解成:
- 门面模式解决“业务系统怎么调”。
- 模板方法解决“请求怎么稳定执行”。
- 策略 + 注册表解决“不同系统怎么差异化接入”。
- 仓储 + 适配器解决“记录怎么落库且不绑死实现”。
- Hook/责任链解决“默认逻辑和集成方逻辑怎么有序共存”。
推荐按这个顺序看源码:
- EsignAutoConfiguration.java
- EsignApiFacade.java
- EsignRequestExecutor.java
- EsignFlowIntegrationService.java
- EsignBusinessSystemSupportRegistry.java
- EsignAuthCallbackEndpoint.java
5. 依赖与最小配置
5.1 Maven 依赖
<dependency>
<groupId>top.qnmdmyy</groupId>
<artifactId>esign-helper-starter</artifactId>
<version>0.2.0-SNAPSHOT</version>
</dependency>5.2 最小配置
esign:
enabled: true
app-id: your-app-id
app-secret: your-app-secret
host: https://openapi.esign.cn
default-tenant-code: DEFAULT_TENANT
default-system-code: DEFAULT
callback-endpoint-enabled: true
callback-path: /esign/callback/sign-notify
auth-callback-endpoint-enabled: true
auth-notify-path: /esign/auth/notify
auth-redirect-path: /esign/auth/redirect
api-log-enabled: true
api-log-file-path: logs/esign-api
api-log-payload-max-length: 40005.3 完整配置项说明
对应配置类见 EsignProperties.java。
| 配置项 | 说明 | 是否必填 | 默认值 |
|---|---|---|---|
esign.enabled | 是否启用 starter | 否 | true |
esign.app-id | e签宝应用 ID | 是 | 无 |
esign.app-secret | e签宝应用密钥 | 是 | 无 |
esign.host | e签宝开放平台域名 | 否 | https://openapi.esign.cn |
esign.connect-timeout-millis | 连接超时 | 否 | 5000 |
esign.read-timeout-millis | 读取超时 | 否 | 15000 |
esign.write-timeout-millis | 写出超时 | 否 | 15000 |
esign.default-tenant-code | 默认租户编码 | 否 | DEFAULT_TENANT |
esign.default-system-code | 默认系统编码 | 否 | DEFAULT |
esign.callback-endpoint-enabled | 是否启用 starter 默认回调 Controller | 否 | true |
esign.callback-path | 默认回调路径 | 否 | /esign/callback/sign-notify |
esign.auth-callback-endpoint-enabled | 是否启用 starter 默认授权回调 Controller | 否 | true |
esign.auth-notify-path | 默认授权通知路径,对应 NotifyUrl | 否 | /esign/auth/notify |
esign.auth-redirect-path | 默认授权跳转路径,对应 RedirectUrl | 否 | /esign/auth/redirect |
esign.api-log-enabled | 是否启用 API 调用日志 | 否 | true |
esign.api-log-file-path | API 调用日志根路径,starter 会在其下按 年/月/日/分类.log 写入 | 否 | logs/esign-api |
esign.api-log-payload-max-length | 单条日志里请求/响应报文最大保留长度 | 否 | 4000 |
5.4 自定义日志文件位置
集成方可以直接通过配置指定日志存储根路径,starter 会自动按 年/月/日/分类.log 生成文件。
例如一条 auth 分类日志在 2026 年 4 月 18 日 产生时,实际文件可能会写到:
logs/esign-api/2026/04/18/auth.log
示例一:使用项目相对路径
esign:
api-log-file-path: logs/custom/esign-api示例二:使用绝对路径
esign:
api-log-file-path: /data/app-logs/esign如果你是从旧配置升级上来的,之前写成了 *.log 文件路径,starter 会自动兼容并取该文件的父目录作为根路径。
如果你完全不需要 starter 记录 API 日志,则可以关闭:
esign:
api-log-enabled: false6. Starter 会自动给你注入哪些核心 Bean
接入成功后,通常你最常用的是这些 Bean:
EsignApiFacadeEsignFlowIntegrationServiceEsignIdentityArchiveServiceEsignSealArchiveServiceEsignResourceArchiveServiceEsignAccountArchiveServiceEsignAuthorizationArchiveServiceEsignEvidenceArchiveServiceEsignWorkspaceArchiveServiceEsignContractArchiveServiceEsignApiLogService
选择建议:
- 只想调接口:用
EsignApiFacade - 想直接接“发起流程 + 回调更新 + 流程查询”:用
EsignFlowIntegrationService - 想沉淀档案,避免后续每次查远端:用各类
ArchiveService
7. 先看一眼“所有场景覆盖清单”
为了让你明确这份 README 没有省略能力,这里先给出 starter 对外主要场景清单。后文每一项都有代码示例。
7.1 EsignApiFacade 覆盖清单
raw().invoke(...)raw().invokeAbsolute(...)auth().getPersonIdentityInfo(...)auth().getOrganizationIdentityInfo(...)auth().getPersonAuthUrl(...)auth().getPersonAuthorizedInfo(...)auth().getOrganizationAuthUrl(...)auth().getOrganizationAuthorizedInfo(...)file().getUploadUrl(...)file().uploadFile(...)file().downloadFile(...)file().queryFileStatus(...)file().queryKeywordPositions(...)file().previewSignedFile(...)signFlow().createByFile(...)signFlow().start(...)signFlow().finish(...)signFlow().getSignUrl(...)signFlow().downloadFinishedFile(...)signFlow().appendSignFields(...)signFlow().detail(...)signFlow().createRescissionUrl(...)contractFile()组合场景全部能力flowTemplate().createBySignTemplate(...)flowTemplate().listTemplates(...)template()别名用法member().addMembers(...)member().listMembers(...)seal().createPersonTemplateSeal(...)seal().createOrganizationTemplateSeal(...)seal().listOrganizationOwnSeals(...)account().createPersonAccount(...)account().getPersonAccount(...)account().deletePersonAccount(...)account().createOrganizationAccount(...)account().getOrganizationAccount(...)account().deleteOrganizationAccount(...)evidence().applyReport(...)enterpriseConsole().workspace(...)contractManagement().overview(...)contractManagement().createBySignTemplate(...)contractManagement().applyEvidenceReport(...)catalog().capabilityGroups()catalog().knownEndpoints()catalog().knownEndpointsByDomain(...)
7.2 编排与归档服务覆盖清单
EsignFlowIntegrationServicecreateFileFlow(...)createTemplateFlow(...)handleCallback(...)loadBySignFlowId(...)loadByBusiness(...)
EsignIdentityArchiveService- 个人主体本地查询、远端同步、回调补档、账号补档、授权补档
EsignSealArchiveService- 印章创建归档、本地查询、机构印章同步
EsignResourceArchiveService- 成员同步、模板同步、文件同步、流程详情同步、本地查询、创建时归档
EsignAccountArchiveService- 个人账号创建/同步/本地查询
- 机构账号创建/同步/本地查询
EsignAuthorizationArchiveService- 个人授权链接、机构授权链接、授权结果同步、本地查询
EsignEvidenceArchiveService- 出证申请、本地查询
EsignWorkspaceArchiveService- 企业工作台组合归档
EsignContractArchiveService- 合同资源组合归档
8. 公共说明:代码示例怎么阅读
8.1 示例默认前提
后面的代码示例都默认:
- 你的系统已经成功引入 starter
application.yml已配置好esign.*- Spring 已成功注入相关 Bean
- 示例里的业务值只是占位,你需要改成自己系统的真实值
8.2 关于扩展字段
很多请求实体都继承了 AbstractExtensibleRequest.java,这意味着:
- starter 先内置高频字段
- 官方还有其他字段时,你可以继续透传
- 与 e签宝交互的模型都支持链式 setter,适合连续组装请求参数
示例:
AuthModels.PersonAuthUrlRequest request = new AuthModels.PersonAuthUrlRequest()
.setPsnAccount("13800000000")
.setRedirectUrl("https://crm.example.com/esign/redirect")
.setNotifyUrl("https://crm.example.com/esign/notify")
// 额外字段透传给 e签宝
.putExtraField("clientType", "ALL")
.putExtraField("redirectType", "2");8.3 关于证件类型
常用枚举见 EsignIdCardType.java:
EsignIdCardType.CRED_PSN_CH_IDCARDEsignIdCardType.CRED_ORG_USCC
9. EsignApiFacade 全场景代码示例
下面这部分覆盖 EsignApiFacade 的所有主要场景。
如果你只是想把 e签宝当成一个开放平台调用入口来用,这一章基本就够了。
9.1 注入方式
@Service
public class EsignOpenApiService {
private final EsignApiFacade esignApiFacade;
public EsignOpenApiService(EsignApiFacade esignApiFacade) {
this.esignApiFacade = esignApiFacade;
}
}9.2 个人实名信息查询
public String queryPersonPsnId(String mobile) {
return esignApiFacade.auth()
.getPersonIdentityInfo(mobile)
.getData()
.getPsnId();
}9.3 机构实名信息查询
public String queryOrganizationId(String uscc) {
return esignApiFacade.auth()
.getOrganizationIdentityInfo(uscc)
.getData()
.getOrgId();
}9.4 获取个人授权链接
public String createPersonAuthUrl(String mobile) {
AuthModels.PersonAuthUrlRequest request = new AuthModels.PersonAuthUrlRequest()
.setPsnAccount(mobile)
.setRedirectUrl("https://crm.example.com/esign/person/redirect")
.setNotifyUrl("https://crm.example.com/esign/person/notify")
.putExtraField("clientType", "ALL");
return esignApiFacade.auth()
.getPersonAuthUrl(request)
.getData()
.getAuthUrl();
}9.5 查询个人授权结果
public Integer queryPersonAuthStatus(String psnId) {
return esignApiFacade.auth()
.getPersonAuthorizedInfo(psnId)
.getData()
.getAuthStatus();
}9.6 获取机构授权链接
public String createOrganizationAuthUrl(String orgId) {
AuthModels.OrganizationAuthUrlRequest request = new AuthModels.OrganizationAuthUrlRequest()
.setOrgId(orgId)
.setRedirectUrl("https://crm.example.com/esign/org/redirect")
.setNotifyUrl("https://crm.example.com/esign/org/notify")
.putExtraField("clientType", "ALL");
return esignApiFacade.auth()
.getOrganizationAuthUrl(request)
.getData()
.getAuthUrl();
}9.7 查询机构授权结果
public Integer queryOrganizationAuthStatus(String orgId) {
return esignApiFacade.auth()
.getOrganizationAuthorizedInfo(orgId)
.getData()
.getAuthStatus();
}9.8 获取文件上传地址
public ContractFileModels.FileUploadUrlData createUploadUrl() {
ContractFileModels.FileUploadUrlRequest request = new ContractFileModels.FileUploadUrlRequest();
request.setFileName("contract.pdf");
request.setContentType("application/pdf");
request.setFileSize(1024L);
request.setConvertToPdf(false);
request.setContentMd5("mock-md5-value");
return esignApiFacade.file()
.getUploadUrl(request)
.getData();
}9.9 上传文件二进制
public void uploadContract(byte[] pdfBytes) {
ContractFileModels.FileUploadUrlData uploadUrlData = createUploadUrl();
String uploadUrl = uploadUrlData.getUploadUrl() != null
? uploadUrlData.getUploadUrl()
: uploadUrlData.getFileUploadUrl();
esignApiFacade.file().uploadFile(uploadUrl, pdfBytes, "application/pdf");
}9.10 下载文件二进制
public byte[] downloadByUrl(String downloadUrl) {
return esignApiFacade.file().downloadFile(downloadUrl);
}9.11 查询文件状态
public Integer queryFileStatus(String fileId) {
ContractFileModels.FileStatusData data = esignApiFacade.file()
.queryFileStatus(fileId)
.getData();
return data.getFileStatus() != null ? data.getFileStatus() : data.getStatus();
}9.12 查询关键字定位
public ContractFileModels.KeywordPositionData queryKeywordPositions(String fileId) {
ContractFileModels.KeywordPositionQueryRequest request =
new ContractFileModels.KeywordPositionQueryRequest()
.putExtraField("keywords", java.util.Arrays.asList("甲方签字", "乙方盖章"));
return esignApiFacade.file()
.queryKeywordPositions(fileId, request)
.getData();
}9.13 预览签署文件
public String previewSignedFile(String signFlowId, String docFileId) {
return esignApiFacade.file()
.previewSignedFile(signFlowId, docFileId)
.getData()
.getDownloadUrl();
}9.14 按文件创建签署流程
public String createFlowByFile(String fileId) {
ContractFileModels.DocumentInfo documentInfo = new ContractFileModels.DocumentInfo()
.setFileId(fileId);
ContractFileModels.CreateByFileRequest request = new ContractFileModels.CreateByFileRequest()
.setFlowName("采购合同签署流程")
.setDocs(java.util.Collections.singletonList(documentInfo))
.putExtraField("autoStart", true);
return esignApiFacade.signFlow()
.createByFile(request)
.getData()
.getSignFlowId();
}9.15 启动签署流程
public void startFlow(String signFlowId) {
esignApiFacade.signFlow().start(signFlowId);
}9.16 获取签署链接
public String createSignUrl(String signFlowId) {
ContractFileModels.SignUrlRequest request = new ContractFileModels.SignUrlRequest()
.putExtraField("operatorId", "OPERATOR-001")
.putExtraField("redirectUrl", "https://crm.example.com/esign/sign/finish");
return esignApiFacade.signFlow()
.getSignUrl(signFlowId, request)
.getData()
.getSignUrl();
}9.17 完结签署流程
public void finishFlow(String signFlowId) {
esignApiFacade.signFlow().finish(signFlowId);
}9.18 下载已签文件地址
public String getFinishedFileDownloadUrl(String signFlowId) {
return esignApiFacade.signFlow()
.downloadFinishedFile(signFlowId)
.getData()
.getDownloadUrl();
}9.19 追加签署区
public void appendSignFields(String signFlowId) {
ContractFileModels.AppendSignFieldsRequest request =
new ContractFileModels.AppendSignFieldsRequest()
.putExtraField("signFields", java.util.Collections.singletonList(
java.util.Collections.singletonMap("signerType", "PERSON")
));
esignApiFacade.signFlow().appendSignFields(signFlowId, request);
}9.20 查询流程详情
public ContractFileModels.SignFlowDetailData queryFlowDetail(String signFlowId) {
return esignApiFacade.signFlow()
.detail(signFlowId)
.getData();
}9.21 获取解约链接
public String createRescissionUrl(String signFlowId) {
ContractFileModels.RescissionUrlRequest request =
new ContractFileModels.RescissionUrlRequest()
.putExtraField("redirectUrl", "https://crm.example.com/esign/rescission/result");
return esignApiFacade.signFlow()
.createRescissionUrl(signFlowId, request)
.getData()
.getSignUrl();
}9.22 contractFile() 组合门面完整示例
如果你的使用视角是“合同文件签署”而不是拆成 file() 与 signFlow() 两段,那么推荐直接使用 contractFile()。
public String completeContractFileFlow(byte[] pdfBytes) {
ContractFileModels.FileUploadUrlRequest uploadRequest = new ContractFileModels.FileUploadUrlRequest();
uploadRequest.setFileName("sale-contract.pdf");
uploadRequest.setContentType("application/pdf");
uploadRequest.setFileSize((long) pdfBytes.length);
uploadRequest.setConvertToPdf(false);
uploadRequest.setContentMd5("mock-md5");
ContractFileModels.FileUploadUrlData uploadData =
esignApiFacade.contractFile().getUploadUrl(uploadRequest).getData();
String uploadUrl = uploadData.getUploadUrl() != null
? uploadData.getUploadUrl()
: uploadData.getFileUploadUrl();
esignApiFacade.contractFile().uploadFile(uploadUrl, pdfBytes, "application/pdf");
String fileId = uploadData.getFileId();
esignApiFacade.contractFile().queryFileStatus(fileId);
ContractFileModels.DocumentInfo documentInfo = new ContractFileModels.DocumentInfo()
.setFileId(fileId);
ContractFileModels.CreateByFileRequest createRequest = new ContractFileModels.CreateByFileRequest()
.setFlowName("销售合同签署流程")
.setDocs(java.util.Collections.singletonList(documentInfo));
String signFlowId = esignApiFacade.contractFile()
.createByFile(createRequest)
.getData()
.getSignFlowId();
esignApiFacade.contractFile().start(signFlowId);
ContractFileModels.SignUrlRequest signUrlRequest = new ContractFileModels.SignUrlRequest()
.putExtraField("operatorId", "SALE-OPERATOR-001");
String signUrl = esignApiFacade.contractFile()
.getSignUrl(signFlowId, signUrlRequest)
.getData()
.getSignUrl();
esignApiFacade.contractFile().detail(signFlowId);
esignApiFacade.contractFile().finish(signFlowId);
esignApiFacade.contractFile().downloadFinishedFile(signFlowId);
return signUrl;
}9.23 contractFile() 查询关键字定位
public ContractFileModels.KeywordPositionData queryKeywordPositionsFromContractFile(String fileId) {
ContractFileModels.KeywordPositionQueryRequest request =
new ContractFileModels.KeywordPositionQueryRequest()
.putExtraField("keywords", java.util.Arrays.asList("甲方签字", "乙方盖章"));
return esignApiFacade.contractFile()
.queryKeywordPositions(fileId, request)
.getData();
}9.24 contractFile() 预览签署文件
public String previewFromContractFile(String signFlowId, String docFileId) {
return esignApiFacade.contractFile()
.previewSignedFile(signFlowId, docFileId)
.getData()
.getDownloadUrl();
}9.25 contractFile() 追加签署区
public void appendSignFieldsFromContractFile(String signFlowId) {
ContractFileModels.AppendSignFieldsRequest request =
new ContractFileModels.AppendSignFieldsRequest()
.putExtraField("signFields", java.util.Collections.singletonList(
java.util.Collections.singletonMap("signerType", "PERSON")
));
esignApiFacade.contractFile().appendSignFields(signFlowId, request);
}9.26 contractFile() 获取解约链接
public String createRescissionUrlFromContractFile(String signFlowId) {
ContractFileModels.RescissionUrlRequest request =
new ContractFileModels.RescissionUrlRequest()
.putExtraField("redirectUrl", "https://crm.example.com/esign/rescission/result");
return esignApiFacade.contractFile()
.createRescissionUrl(signFlowId, request)
.getData()
.getSignUrl();
}9.27 按模板创建流程
public String createFlowByTemplate(String signTemplateId) {
FlowTemplateModels.CreateBySignTemplateRequest request =
new FlowTemplateModels.CreateBySignTemplateRequest()
.setSignTemplateId(signTemplateId)
.setFlowName("模板签署流程")
.putExtraField("autoStart", true);
return esignApiFacade.flowTemplate()
.createBySignTemplate(request)
.getData()
.getSignFlowId();
}9.28 查询模板列表
public FlowTemplateModels.SignTemplateListData listTemplates(String orgId) {
return esignApiFacade.flowTemplate()
.listTemplates(orgId, 1, 20, null)
.getData();
}9.29 template() 别名用法
public Integer countTemplates(String orgId) {
return esignApiFacade.template()
.listTemplates(orgId)
.getData()
.getTotal();
}9.30 新增机构成员
public Integer addMembers(String orgId) {
MemberModels.MemberSummary member = new MemberModels.MemberSummary();
member.setPsnId("PSN-001");
member.setName("张三");
member.setMobile("13800000000");
MemberModels.AddMembersRequest request = new MemberModels.AddMembersRequest();
request.setMembers(java.util.Collections.singletonList(member));
return esignApiFacade.member()
.addMembers(orgId, request)
.getData()
.getSuccessCount();
}9.31 查询机构成员列表
public java.util.List<MemberModels.MemberSummary> listMembers(String orgId) {
return esignApiFacade.member()
.listMembers(orgId, 1, 20)
.getData()
.getMembers();
}9.32 创建个人模板印章
public String createPersonSeal(String psnId) {
SealModels.CreatePersonTemplateSealRequest request =
new SealModels.CreatePersonTemplateSealRequest();
request.setPsnId(psnId);
request.setSealName("张三个人章");
return esignApiFacade.seal()
.createPersonTemplateSeal(request)
.getData()
.getSealId();
}9.33 创建机构模板印章
public String createOrganizationSeal(String orgId) {
SealModels.CreateOrganizationTemplateSealRequest request =
new SealModels.CreateOrganizationTemplateSealRequest();
request.setOrgId(orgId);
request.setSealName("测试公司公章");
return esignApiFacade.seal()
.createOrganizationTemplateSeal(request)
.getData()
.getSealId();
}9.34 查询机构自有印章列表
public java.util.List<SealModels.SealSummary> listOrganizationSeals(String orgId) {
return esignApiFacade.seal()
.listOrganizationOwnSeals(orgId, 1, 20)
.getData()
.getSeals();
}9.35 创建个人账号
public String createPersonAccount() {
AccountModels.CreatePersonAccountRequest request =
new AccountModels.CreatePersonAccountRequest();
request.setThirdPartyUserId("USER-1001");
request.setName("张三");
request.setIdType(top.qnmdmyy.starter.model.common.EsignIdCardType.CRED_PSN_CH_IDCARD);
request.setIdNumber("110101199001010011");
return esignApiFacade.account()
.createPersonAccount(request)
.getData()
.getAccountId();
}9.36 查询个人账号
public AccountModels.PersonAccountData getPersonAccount(String accountId) {
return esignApiFacade.account()
.getPersonAccount(accountId)
.getData();
}9.37 删除个人账号
public void deletePersonAccount(String accountId) {
esignApiFacade.account().deletePersonAccount(accountId);
}9.38 创建机构账号
public String createOrganizationAccount() {
AccountModels.CreateOrganizationAccountRequest request =
new AccountModels.CreateOrganizationAccountRequest();
request.setThirdPartyUserId("ORG-1001");
request.setCreator("张三");
request.setName("测试科技有限公司");
request.setIdType(top.qnmdmyy.starter.model.common.EsignIdCardType.CRED_ORG_USCC);
request.setIdNumber("91310000MA1K000001");
return esignApiFacade.account()
.createOrganizationAccount(request)
.getData()
.getOrgId();
}9.39 查询机构账号
public AccountModels.OrganizationAccountData getOrganizationAccount(String orgId) {
return esignApiFacade.account()
.getOrganizationAccount(orgId)
.getData();
}9.40 删除机构账号
public void deleteOrganizationAccount(String orgId) {
esignApiFacade.account().deleteOrganizationAccount(orgId);
}9.41 申请出证报告
public String applyEvidenceReport(String signFlowId) {
EvidenceModels.ApplyReportRequest request = new EvidenceModels.ApplyReportRequest();
request.setSignFlowId(signFlowId);
return esignApiFacade.evidence()
.applyReport(request)
.getData()
.getReportId();
}9.42 获取企业工作台组合视图
public Object loadWorkspace(String orgId) {
return esignApiFacade.enterpriseConsole().workspace(orgId, 1, 20);
}9.43 获取企业工作台组合视图默认重载
public Object loadWorkspaceDefault(String orgId) {
return esignApiFacade.enterpriseConsole().workspace(orgId);
}9.44 获取合同管理组合视图
public Object loadContractOverview(String signFlowId, String docFileId) {
return esignApiFacade.contractManagement().overview(signFlowId, docFileId);
}9.45 通过合同管理组合门面按模板创建流程
public String createByTemplateFromManagement(String signTemplateId) {
FlowTemplateModels.CreateBySignTemplateRequest request =
new FlowTemplateModels.CreateBySignTemplateRequest()
.setSignTemplateId(signTemplateId)
.setFlowName("合同管理视角模板流程");
return esignApiFacade.contractManagement()
.createBySignTemplate(request)
.getData()
.getSignFlowId();
}9.46 通过合同管理组合门面申请出证
public String applyEvidenceFromManagement(String signFlowId) {
EvidenceModels.ApplyReportRequest request = new EvidenceModels.ApplyReportRequest();
request.setSignFlowId(signFlowId);
return esignApiFacade.contractManagement()
.applyEvidenceReport(request)
.getData()
.getReportId();
}9.47 读取 starter 能力分组目录
public java.util.List<top.qnmdmyy.starter.core.EsignEndpointCatalog.CapabilityGroup> listCapabilityGroups() {
return esignApiFacade.catalog().capabilityGroups();
}9.48 读取 starter 已知端点列表
public java.util.List<top.qnmdmyy.starter.core.EsignEndpointCatalog.KnownEndpoint> listKnownEndpoints() {
return esignApiFacade.catalog().knownEndpoints();
}9.49 按领域读取端点目录
public java.util.List<top.qnmdmyy.starter.core.EsignEndpointCatalog.KnownEndpoint> listContractFileEndpoints() {
return esignApiFacade.catalog().knownEndpointsByDomain("contractFile");
}9.50 raw() 原始调用场景
适合场景:
- 官方出了新接口,但 starter 还没来得及封装
- 你在联调某个暂未收口为强类型的方法
- 你想先快速验证某个路径是否通
public com.fasterxml.jackson.databind.JsonNode rawInvokeExample() {
java.util.Map<String, Object> requestBody = new java.util.LinkedHashMap<String, Object>();
requestBody.put("signTemplateId", "TPL-001");
requestBody.put("flowName", "原始调用模板流程");
return esignApiFacade.raw().invoke(
org.springframework.http.HttpMethod.POST,
"/v3/sign-flow/create-by-sign-template",
requestBody
);
}9.51 raw() + 强类型响应映射
public top.qnmdmyy.starter.model.common.EsignResponse<ContractFileModels.SignFlowCreateData> rawInvokeTyped() {
java.util.Map<String, Object> requestBody = new java.util.LinkedHashMap<String, Object>();
requestBody.put("signTemplateId", "TPL-001");
requestBody.put("flowName", "原始调用模板流程");
return esignApiFacade.raw().invoke(
org.springframework.http.HttpMethod.POST,
"/v3/sign-flow/create-by-sign-template",
requestBody,
ContractFileModels.SignFlowCreateData.class
);
}9.52 raw() + 已知端点枚举
public top.qnmdmyy.starter.model.common.EsignResponse<AuthModels.PersonAuthorizedInfoData> rawInvokeByEndpoint(String psnId) {
return esignApiFacade.raw().invoke(
top.qnmdmyy.starter.core.EsignEndpointCatalog.KnownEndpoint.PERSON_AUTHORIZED_INFO,
java.util.Collections.singletonMap("psnId", psnId),
null,
AuthModels.PersonAuthorizedInfoData.class
);
}9.53 raw().invokeAbsolute(...) 绝对地址场景
这个入口通常用于:
- 上传地址、下载地址等绝对 URL
- 某些非标准域名或临时环境联调
public com.fasterxml.jackson.databind.JsonNode invokeAbsoluteExample(String absoluteUrl) {
java.util.Map<String, Object> body = new java.util.LinkedHashMap<String, Object>();
body.put("mock", true);
return esignApiFacade.raw().invokeAbsolute(
org.springframework.http.HttpMethod.POST,
absoluteUrl,
body,
false
);
}10. EsignFlowIntegrationService 全场景代码示例
如果你的目标不只是“调接口”,而是“把一条流程从发起、落库、回调、查询跑完整”,那就应该优先用 EsignFlowIntegrationService。
10.1 注入方式
@Service
public class ContractFlowApplicationService {
private final EsignFlowIntegrationService flowIntegrationService;
public ContractFlowApplicationService(EsignFlowIntegrationService flowIntegrationService) {
this.flowIntegrationService = flowIntegrationService;
}
}10.2 创建文件流程
public String createFileFlow() {
CreateEsignFlowCommand command = new CreateEsignFlowCommand();
command.setTenantCode("TENANT_A");
command.setSystemCode("CRM");
command.setBusinessId("ORDER-1001");
command.setBusinessType("SALE_CONTRACT");
command.setEsignPactNo("PACT-20260418-001");
command.setContractName("销售合同");
command.setContractCode("HT-2026-001");
command.setInitiator("system");
command.setCompanyId("COMPANY-001");
command.setCompanyName("测试科技有限公司");
command.setAccountId("ACCOUNT-001");
command.setFileId("FILE-001");
command.setFlowType(top.qnmdmyy.starter.integration.model.EsignFlowType.FILE);
// 如果当前系统要透传一些特定字段,也可以放入 requestPayload
java.util.Map<String, Object> payload = new java.util.LinkedHashMap<String, Object>();
payload.put("operatorId", "OPERATOR-001");
payload.put("notifyUrl", "https://crm.example.com/esign/callback");
command.setRequestPayload(payload);
return flowIntegrationService.createFileFlow(command).getSignFlowId();
}10.3 创建模板流程
public String createTemplateFlow() {
CreateEsignFlowCommand command = new CreateEsignFlowCommand();
command.setTenantCode("TENANT_A");
command.setSystemCode("CRM");
command.setBusinessId("ORDER-2001");
command.setBusinessType("TEMPLATE_CONTRACT");
command.setContractName("模板合同");
command.setContractCode("TPL-HT-001");
command.setSignTemplateId("SIGN-TEMPLATE-001");
command.setFlowType(top.qnmdmyy.starter.integration.model.EsignFlowType.TEMPLATE);
java.util.Map<String, Object> payload = new java.util.LinkedHashMap<String, Object>();
payload.put("autoStart", true);
command.setRequestPayload(payload);
return flowIntegrationService.createTemplateFlow(command).getSignFlowId();
}10.4 手工处理回调
当你关闭 starter 默认回调 Controller 时,可以自己接收回调,然后转交给编排服务。
public EsignFlowIntegrationService.CallbackResult handleCallback(java.util.Map<String, Object> callbackBody) {
return flowIntegrationService.handleCallback(callbackBody);
}10.5 按 signFlowId 读取流程聚合
public EsignFlowIntegrationService.FlowAggregate loadAggregate(String signFlowId) {
return flowIntegrationService.loadBySignFlowId(signFlowId);
}10.6 按业务号查询流程
public java.util.List<top.qnmdmyy.starter.integration.model.EsignFlowRecord> loadByBusiness(String businessId) {
return flowIntegrationService.loadByBusiness("CRM", businessId);
}10.7 按租户 + 系统 + 业务号查询流程
public java.util.List<top.qnmdmyy.starter.integration.model.EsignFlowRecord> loadByBusinessWithTenant(String businessId) {
return flowIntegrationService.loadByBusiness("TENANT_A", "CRM", businessId);
}10.8 自定义回调 Controller 的完整写法
@RestController
@RequestMapping("/custom/esign")
public class CustomEsignCallbackController {
private final EsignFlowIntegrationService flowIntegrationService;
public CustomEsignCallbackController(EsignFlowIntegrationService flowIntegrationService) {
this.flowIntegrationService = flowIntegrationService;
}
@PostMapping("/sign-notify")
public EsignFlowIntegrationService.CallbackResult signNotify(
@RequestBody java.util.Map<String, Object> body) {
return flowIntegrationService.handleCallback(body);
}
}对应配置:
esign:
callback-endpoint-enabled: false11. EsignIdentityArchiveService 全场景代码示例
这个服务解决的是:
以后你只想知道“这个手机号对应哪个 psnId”,“这个统一社会信用代码对应哪个 orgId”,不想每次都查远端。
11.0 注入方式
@Service
public class IdentityArchiveUsageService {
private final EsignIdentityArchiveService identityArchiveService;
public IdentityArchiveUsageService(EsignIdentityArchiveService identityArchiveService) {
this.identityArchiveService = identityArchiveService;
}
}11.1 注入方式
@Service
public class IdentityArchiveApplicationService {
private final EsignIdentityArchiveService identityArchiveService;
public IdentityArchiveApplicationService(EsignIdentityArchiveService identityArchiveService) {
this.identityArchiveService = identityArchiveService;
}
}11.2 本地优先获取个人主体档案
public top.qnmdmyy.starter.integration.model.EsignPersonIdentityRecord getOrSyncPerson(String mobile) {
return identityArchiveService.getOrSyncPersonByMobile("TENANT_A", mobile);
}11.3 本地优先获取机构主体档案
public top.qnmdmyy.starter.integration.model.EsignOrganizationIdentityRecord getOrSyncOrganization(String uscc) {
return identityArchiveService.getOrSyncOrganizationByIdCardNum("TENANT_A", uscc);
}11.4 只查本地个人主体
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignPersonIdentityRecord> findLocalPerson(String mobile) {
return identityArchiveService.findLocalPersonByMobile("TENANT_A", mobile);
}11.5 按 psnId 查本地个人主体
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignPersonIdentityRecord> findPersonByPsnId(String psnId) {
return identityArchiveService.findLocalPersonByPsnId("TENANT_A", psnId);
}11.6 按 accountId 查本地个人主体
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignPersonIdentityRecord> findPersonByAccountId(String accountId) {
return identityArchiveService.findLocalPersonByAccountId("TENANT_A", accountId);
}11.7 只查本地机构主体
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignOrganizationIdentityRecord> findLocalOrganization(String uscc) {
return identityArchiveService.findLocalOrganizationByIdCardNum("TENANT_A", uscc);
}11.8 按 orgId 查本地机构主体
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignOrganizationIdentityRecord> findOrganizationByOrgId(String orgId) {
return identityArchiveService.findLocalOrganizationByOrgId("TENANT_A", orgId);
}11.9 按 accountId 查本地机构主体
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignOrganizationIdentityRecord> findOrganizationByAccountId(String accountId) {
return identityArchiveService.findLocalOrganizationByAccountId("TENANT_A", accountId);
}11.10 强制从远端同步个人主体
public top.qnmdmyy.starter.integration.model.EsignPersonIdentityRecord syncPerson(String mobile) {
return identityArchiveService.syncPersonByMobile("TENANT_A", mobile);
}11.11 强制从远端同步机构主体
public top.qnmdmyy.starter.integration.model.EsignOrganizationIdentityRecord syncOrganization(String uscc) {
return identityArchiveService.syncOrganizationByIdCardNum("TENANT_A", uscc);
}11.12 直接归档个人实名查询结果
public top.qnmdmyy.starter.integration.model.EsignPersonIdentityRecord archivePersonIdentity(String mobile) {
AuthModels.PersonIdentityInfoData data = esignApiFacade.auth()
.getPersonIdentityInfo(mobile)
.getData();
return identityArchiveService.archivePersonIdentity("TENANT_A", data);
}11.13 直接归档机构实名查询结果
public top.qnmdmyy.starter.integration.model.EsignOrganizationIdentityRecord archiveOrganizationIdentity(String uscc) {
AuthModels.OrganizationIdentityInfoData data = esignApiFacade.auth()
.getOrganizationIdentityInfo(uscc)
.getData();
return identityArchiveService.archiveOrganizationIdentity("TENANT_A", data);
}11.14 直接归档个人授权结果
public top.qnmdmyy.starter.integration.model.EsignPersonIdentityRecord archivePersonAuthorizedInfo(String psnId) {
AuthModels.PersonAuthorizedInfoData data = esignApiFacade.auth()
.getPersonAuthorizedInfo(psnId)
.getData();
return identityArchiveService.archivePersonAuthorizedInfo("TENANT_A", data);
}11.15 直接归档机构授权结果
public top.qnmdmyy.starter.integration.model.EsignOrganizationIdentityRecord archiveOrganizationAuthorizedInfo(String orgId) {
AuthModels.OrganizationAuthorizedInfoData data = esignApiFacade.auth()
.getOrganizationAuthorizedInfo(orgId)
.getData();
return identityArchiveService.archiveOrganizationAuthorizedInfo("TENANT_A", data);
}11.16 直接归档个人账号结果
public top.qnmdmyy.starter.integration.model.EsignPersonIdentityRecord archivePersonAccountIdentity(String accountId) {
AccountModels.PersonAccountData data = esignApiFacade.account()
.getPersonAccount(accountId)
.getData();
return identityArchiveService.archivePersonAccount("TENANT_A", data);
}11.17 直接归档机构账号结果
public top.qnmdmyy.starter.integration.model.EsignOrganizationIdentityRecord archiveOrganizationAccountIdentity(String orgId) {
AccountModels.OrganizationAccountData data = esignApiFacade.account()
.getOrganizationAccount(orgId)
.getData();
return identityArchiveService.archiveOrganizationAccount("TENANT_A", data);
}11.18 从回调中顺手补档个人主体
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignPersonIdentityRecord> archivePersonFromCallback() {
java.util.Map<String, Object> rawCallback = new java.util.LinkedHashMap<String, Object>();
rawCallback.put("signFlowId", "FLOW-001");
rawCallback.put("signerAccount", "13800000000");
rawCallback.put("signerId", "PSN-001");
rawCallback.put("signerName", "张三");
top.qnmdmyy.starter.integration.model.EsignCallbackPayload payload =
top.qnmdmyy.starter.integration.model.EsignCallbackPayload.from(
rawCallback,
new com.fasterxml.jackson.databind.ObjectMapper()
);
return identityArchiveService.archivePersonFromCallback("TENANT_A", payload);
}12. EsignSealArchiveService 全场景代码示例
这个服务解决的是:
个人和机构的印章信息也是业务高频资源,不应该每次都临时查。
12.0 注入方式
@Service
public class SealArchiveUsageService {
private final EsignSealArchiveService sealArchiveService;
private final EsignApiFacade esignApiFacade;
public SealArchiveUsageService(
EsignSealArchiveService sealArchiveService,
EsignApiFacade esignApiFacade) {
this.sealArchiveService = sealArchiveService;
this.esignApiFacade = esignApiFacade;
}
}12.1 本地按 sealId 查印章
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignSealRecord> findSeal(String sealId) {
return sealArchiveService.findLocalBySealId("TENANT_A", sealId);
}12.2 本地按归属主体查印章
public java.util.List<top.qnmdmyy.starter.integration.model.EsignSealRecord> findOrganizationSeals(String orgId) {
return sealArchiveService.findLocalByOwner("TENANT_A", "ORGANIZATION", orgId);
}12.3 创建个人模板印章并归档
public top.qnmdmyy.starter.integration.model.EsignSealRecord createAndArchivePersonSeal(String psnId, String accountId) {
SealModels.CreatePersonTemplateSealRequest request = new SealModels.CreatePersonTemplateSealRequest();
request.setPsnId(psnId);
request.setSealName("张三个人章");
SealModels.SealCreateData data = esignApiFacade.seal()
.createPersonTemplateSeal(request)
.getData();
return sealArchiveService.archivePersonTemplateSealCreate(
"TENANT_A",
psnId,
accountId,
request.getSealName(),
data
);
}12.4 创建机构模板印章并归档
public top.qnmdmyy.starter.integration.model.EsignSealRecord createAndArchiveOrganizationSeal(String orgId, String accountId) {
SealModels.CreateOrganizationTemplateSealRequest request = new SealModels.CreateOrganizationTemplateSealRequest();
request.setOrgId(orgId);
request.setSealName("测试科技有限公司公章");
SealModels.SealCreateData data = esignApiFacade.seal()
.createOrganizationTemplateSeal(request)
.getData();
return sealArchiveService.archiveOrganizationTemplateSealCreate(
"TENANT_A",
orgId,
accountId,
request.getSealName(),
data
);
}12.5 同步机构自有印章列表并归档
public java.util.List<top.qnmdmyy.starter.integration.model.EsignSealRecord> syncOrganizationOwnSeals(String orgId) {
return sealArchiveService.syncOrganizationOwnSeals("TENANT_A", orgId, 1, 20);
}13. EsignResourceArchiveService 全场景代码示例
这个服务主要沉淀四类资源:
- 成员
- 模板
- 文件
- 流程详情
13.0 注入方式
@Service
public class ResourceArchiveUsageService {
private final EsignResourceArchiveService resourceArchiveService;
private final EsignApiFacade esignApiFacade;
public ResourceArchiveUsageService(
EsignResourceArchiveService resourceArchiveService,
EsignApiFacade esignApiFacade) {
this.resourceArchiveService = resourceArchiveService;
this.esignApiFacade = esignApiFacade;
}
}13.1 同步机构成员
public java.util.List<top.qnmdmyy.starter.integration.model.EsignMemberRecord> syncMembers(String orgId) {
return resourceArchiveService.syncOrganizationMembers("TENANT_A", orgId, 1, 20);
}13.2 同步模板列表
public java.util.List<top.qnmdmyy.starter.integration.model.EsignTemplateRecord> syncTemplates(String orgId) {
return resourceArchiveService.syncSignTemplates("TENANT_A", orgId, 1, 20, null);
}13.3 同步文件状态
public top.qnmdmyy.starter.integration.model.EsignFileRecord syncFile(String fileId) {
return resourceArchiveService.syncFileStatus("TENANT_A", fileId);
}13.4 同步流程详情
public top.qnmdmyy.starter.integration.model.EsignFlowDetailRecord syncFlowDetail(String signFlowId) {
return resourceArchiveService.syncFlowDetail("TENANT_A", signFlowId);
}13.5 本地查文件档案
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignFileRecord> findFile(String fileId) {
return resourceArchiveService.findLocalFile("TENANT_A", fileId);
}13.6 本地查流程详情档案
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignFlowDetailRecord> findFlowDetail(String signFlowId) {
return resourceArchiveService.findLocalFlowDetail("TENANT_A", signFlowId);
}13.7 本地查成员档案
public java.util.List<top.qnmdmyy.starter.integration.model.EsignMemberRecord> findLocalMembers(String orgId) {
return resourceArchiveService.findLocalMembers("TENANT_A", orgId);
}13.8 本地查模板档案
public java.util.List<top.qnmdmyy.starter.integration.model.EsignTemplateRecord> findLocalTemplates(String orgId) {
return resourceArchiveService.findLocalTemplates("TENANT_A", orgId);
}13.9 在文件创建成功时顺手归档
public top.qnmdmyy.starter.integration.model.EsignFileRecord archiveUploadResponse() {
ContractFileModels.FileUploadUrlRequest request = new ContractFileModels.FileUploadUrlRequest();
request.setFileName("archive-demo.pdf");
request.setContentType("application/pdf");
request.setFileSize(2048L);
top.qnmdmyy.starter.model.common.EsignResponse<ContractFileModels.FileUploadUrlData> response =
esignApiFacade.file().getUploadUrl(request);
ContractFileModels.FileUploadUrlData data = response.getData();
return resourceArchiveService.archiveFileCreateResponse(
"TENANT_A",
data.getFileId(),
data.getFileUploadUrl(),
response
);
}13.10 在流程创建成功时顺手归档
public top.qnmdmyy.starter.integration.model.EsignFlowDetailRecord archiveFlowCreateResponse(String signFlowId, String shortUrl, String url) {
return resourceArchiveService.archiveFlowCreateResponse(
"TENANT_A",
signFlowId,
shortUrl,
url,
java.util.Collections.singletonMap("source", "manual-demo")
);
}14. EsignAccountArchiveService 全场景代码示例
这个服务解决的是:
账号是独立于主体的资源,应该单独归档,后续系统可以按 accountId、thirdPartyUserId 等维度直接本地查询。
14.0 注入方式
@Service
public class AccountArchiveUsageService {
private final EsignAccountArchiveService accountArchiveService;
private final EsignApiFacade esignApiFacade;
public AccountArchiveUsageService(
EsignAccountArchiveService accountArchiveService,
EsignApiFacade esignApiFacade) {
this.accountArchiveService = accountArchiveService;
this.esignApiFacade = esignApiFacade;
}
}14.1 创建个人账号并归档
public top.qnmdmyy.starter.integration.model.EsignPersonAccountRecord createPersonAccountArchive() {
AccountModels.CreatePersonAccountRequest request = new AccountModels.CreatePersonAccountRequest();
request.setThirdPartyUserId("USER-1001");
request.setName("张三");
request.setIdType(top.qnmdmyy.starter.model.common.EsignIdCardType.CRED_PSN_CH_IDCARD);
request.setIdNumber("110101199001010011");
return accountArchiveService.createPersonAccount("TENANT_A", request);
}14.2 创建机构账号并归档
public top.qnmdmyy.starter.integration.model.EsignOrganizationAccountRecord createOrganizationAccountArchive() {
AccountModels.CreateOrganizationAccountRequest request = new AccountModels.CreateOrganizationAccountRequest();
request.setThirdPartyUserId("ORG-1001");
request.setCreator("张三");
request.setName("测试科技有限公司");
request.setIdType(top.qnmdmyy.starter.model.common.EsignIdCardType.CRED_ORG_USCC);
request.setIdNumber("91310000MA1K000001");
return accountArchiveService.createOrganizationAccount("TENANT_A", request);
}14.3 按 accountId 强制同步个人账号
public top.qnmdmyy.starter.integration.model.EsignPersonAccountRecord syncPersonAccount(String accountId) {
return accountArchiveService.syncPersonAccountByAccountId("TENANT_A", accountId);
}14.4 按 orgId 强制同步机构账号
public top.qnmdmyy.starter.integration.model.EsignOrganizationAccountRecord syncOrganizationAccount(String orgId) {
return accountArchiveService.syncOrganizationAccountByOrgId("TENANT_A", orgId);
}14.5 直接归档个人账号查询结果
public top.qnmdmyy.starter.integration.model.EsignPersonAccountRecord archivePersonAccountDirectly(String accountId) {
AccountModels.PersonAccountData data = esignApiFacade.account()
.getPersonAccount(accountId)
.getData();
return accountArchiveService.archivePersonAccount("TENANT_A", data);
}14.6 直接归档机构账号查询结果
public top.qnmdmyy.starter.integration.model.EsignOrganizationAccountRecord archiveOrganizationAccountDirectly(String orgId) {
AccountModels.OrganizationAccountData data = esignApiFacade.account()
.getOrganizationAccount(orgId)
.getData();
return accountArchiveService.archiveOrganizationAccount("TENANT_A", data);
}14.7 本地查个人账号
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignPersonAccountRecord> findPersonAccount(String accountId) {
return accountArchiveService.findLocalPersonByAccountId("TENANT_A", accountId);
}14.8 按 psnId 查本地个人账号
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignPersonAccountRecord> findPersonAccountByPsnId(String psnId) {
return accountArchiveService.findLocalPersonByPsnId("TENANT_A", psnId);
}14.9 按 thirdPartyUserId 查本地个人账号
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignPersonAccountRecord> findPersonAccountByThirdPartyUserId(String thirdPartyUserId) {
return accountArchiveService.findLocalPersonByThirdPartyUserId("TENANT_A", thirdPartyUserId);
}14.10 本地查机构账号
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignOrganizationAccountRecord> findOrganizationAccount(String orgId) {
return accountArchiveService.findLocalOrganizationByOrgId("TENANT_A", orgId);
}14.11 按 accountId 查本地机构账号
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignOrganizationAccountRecord> findOrganizationAccountByAccountId(String accountId) {
return accountArchiveService.findLocalOrganizationByAccountId("TENANT_A", accountId);
}14.12 按 thirdPartyUserId 查本地机构账号
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignOrganizationAccountRecord> findOrganizationAccountByThirdPartyUserId(String thirdPartyUserId) {
return accountArchiveService.findLocalOrganizationByThirdPartyUserId("TENANT_A", thirdPartyUserId);
}15. EsignAuthorizationArchiveService 全场景代码示例
这个服务解决的是:
授权链接和授权结果本身就是很有价值的资源,不应该只在一次请求里用完即丢。
15.0 注入方式
@Service
public class AuthorizationArchiveUsageService {
private final EsignAuthorizationArchiveService authorizationArchiveService;
private final EsignApiFacade esignApiFacade;
public AuthorizationArchiveUsageService(
EsignAuthorizationArchiveService authorizationArchiveService,
EsignApiFacade esignApiFacade) {
this.authorizationArchiveService = authorizationArchiveService;
this.esignApiFacade = esignApiFacade;
}
}15.1 申请个人授权链接并归档
public top.qnmdmyy.starter.integration.model.EsignPersonAuthRecord requestPersonAuthUrl(String mobile) {
AuthModels.PersonAuthUrlRequest request = new AuthModels.PersonAuthUrlRequest();
request.setPsnAccount(mobile);
request.setRedirectUrl("https://crm.example.com/esign/person/redirect");
request.setNotifyUrl("https://crm.example.com/esign/person/notify");
return authorizationArchiveService.requestPersonAuthUrl("TENANT_A", request);
}15.2 申请机构授权链接并归档
public top.qnmdmyy.starter.integration.model.EsignOrganizationAuthRecord requestOrganizationAuthUrl(String orgId) {
AuthModels.OrganizationAuthUrlRequest request = new AuthModels.OrganizationAuthUrlRequest();
request.setOrgId(orgId);
request.setRedirectUrl("https://crm.example.com/esign/org/redirect");
request.setNotifyUrl("https://crm.example.com/esign/org/notify");
return authorizationArchiveService.requestOrganizationAuthUrl("TENANT_A", request);
}15.3 强制同步个人授权结果
public top.qnmdmyy.starter.integration.model.EsignPersonAuthRecord syncPersonAuthorizedInfo(String psnId) {
return authorizationArchiveService.syncPersonAuthorizedInfo("TENANT_A", psnId);
}15.4 强制同步机构授权结果
public top.qnmdmyy.starter.integration.model.EsignOrganizationAuthRecord syncOrganizationAuthorizedInfo(String orgId) {
return authorizationArchiveService.syncOrganizationAuthorizedInfo("TENANT_A", orgId);
}15.5 直接归档个人授权结果
public top.qnmdmyy.starter.integration.model.EsignPersonAuthRecord archivePersonAuthorizedInfoDirectly(String psnId) {
AuthModels.PersonAuthorizedInfoData data = esignApiFacade.auth()
.getPersonAuthorizedInfo(psnId)
.getData();
return authorizationArchiveService.archivePersonAuthorizedInfo("TENANT_A", data);
}15.6 直接归档机构授权结果
public top.qnmdmyy.starter.integration.model.EsignOrganizationAuthRecord archiveOrganizationAuthorizedInfoDirectly(String orgId) {
AuthModels.OrganizationAuthorizedInfoData data = esignApiFacade.auth()
.getOrganizationAuthorizedInfo(orgId)
.getData();
return authorizationArchiveService.archiveOrganizationAuthorizedInfo("TENANT_A", data);
}15.7 本地按手机号查个人授权档案
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignPersonAuthRecord> findPersonAuthByMobile(String mobile) {
return authorizationArchiveService.findLocalPersonByPsnAccount("TENANT_A", mobile);
}15.8 本地按 psnId 查个人授权档案
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignPersonAuthRecord> findPersonAuthByPsnId(String psnId) {
return authorizationArchiveService.findLocalPersonByPsnId("TENANT_A", psnId);
}15.9 本地按 orgId 查机构授权档案
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignOrganizationAuthRecord> findOrganizationAuthByOrgId(String orgId) {
return authorizationArchiveService.findLocalOrganizationByOrgId("TENANT_A", orgId);
}15.10 本地按统一社会信用代码查机构授权档案
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignOrganizationAuthRecord> findOrganizationAuthByUscc(String uscc) {
return authorizationArchiveService.findLocalOrganizationByOrgIdCardNum("TENANT_A", uscc);
}16. EsignEvidenceArchiveService 全场景代码示例
16.0 注入方式
@Service
public class EvidenceArchiveUsageService {
private final EsignEvidenceArchiveService evidenceArchiveService;
private final EsignApiFacade esignApiFacade;
public EvidenceArchiveUsageService(
EsignEvidenceArchiveService evidenceArchiveService,
EsignApiFacade esignApiFacade) {
this.evidenceArchiveService = evidenceArchiveService;
this.esignApiFacade = esignApiFacade;
}
}16.1 申请出证并归档
public top.qnmdmyy.starter.integration.model.EsignEvidenceReportRecord applyAndArchiveEvidence(String signFlowId) {
EvidenceModels.ApplyReportRequest request = new EvidenceModels.ApplyReportRequest();
request.setSignFlowId(signFlowId);
return evidenceArchiveService.applyReport("TENANT_A", request);
}16.2 直接归档出证申请结果
public top.qnmdmyy.starter.integration.model.EsignEvidenceReportRecord archiveEvidenceApplyResult(String signFlowId) {
EvidenceModels.ApplyReportRequest request = new EvidenceModels.ApplyReportRequest();
request.setSignFlowId(signFlowId);
EvidenceModels.ApplyReportData data = esignApiFacade.evidence()
.applyReport(request)
.getData();
return evidenceArchiveService.archiveApplyReport("TENANT_A", signFlowId, data);
}16.3 按 reportId 查本地出证档案
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignEvidenceReportRecord> findEvidenceByReportId(String reportId) {
return evidenceArchiveService.findLocalByReportId("TENANT_A", reportId);
}16.4 按 signFlowId 查本地出证档案列表
public java.util.List<top.qnmdmyy.starter.integration.model.EsignEvidenceReportRecord> findEvidenceBySignFlowId(String signFlowId) {
return evidenceArchiveService.findLocalBySignFlowId("TENANT_A", signFlowId);
}17. EsignWorkspaceArchiveService 全场景代码示例
企业工作台不是一张独立表,而是授权、成员、印章、模板四类资源的组合归档。
17.0 注入方式
@Service
public class WorkspaceArchiveUsageService {
private final EsignWorkspaceArchiveService workspaceArchiveService;
public WorkspaceArchiveUsageService(EsignWorkspaceArchiveService workspaceArchiveService) {
this.workspaceArchiveService = workspaceArchiveService;
}
}17.1 同步企业工作台相关资源
public EsignWorkspaceArchiveService.WorkspaceArchiveResult syncWorkspace(String orgId) {
return workspaceArchiveService.syncOrganizationWorkspace("TENANT_A", orgId, 1, 20, null);
}18. EsignContractArchiveService 全场景代码示例
合同管理也是组合视图,它依赖文件、流程详情、出证等多类数据。
18.0 注入方式
@Service
public class ContractArchiveUsageService {
private final EsignContractArchiveService contractArchiveService;
public ContractArchiveUsageService(EsignContractArchiveService contractArchiveService) {
this.contractArchiveService = contractArchiveService;
}
}18.1 同步合同相关资源快照
public EsignContractArchiveService.ContractArchiveSnapshot syncContractSnapshot(String signFlowId, String fileId) {
return contractArchiveService.syncContractResources("TENANT_A", signFlowId, fileId);
}19. Starter 默认回调能力
19.1 默认回调路径
默认路径:
/esign/callback/sign-notify
默认 Controller:
19.2 默认回调会做什么
- 解析原始回调报文
- 提取
signFlowId - 更新流程主记录
- 更新流程任务记录
- 在有手机号与
psnId的情况下顺手补档个人主体
19.3 读取当前回调路径
@Service
public class CallbackPathService {
private final top.qnmdmyy.starter.web.EsignCallbackEndpoint callbackEndpoint;
public CallbackPathService(top.qnmdmyy.starter.web.EsignCallbackEndpoint callbackEndpoint) {
this.callbackEndpoint = callbackEndpoint;
}
public String currentCallbackPath() {
return callbackEndpoint.getCallbackPath();
}
}19.4 默认授权回调路径
默认路径:
NotifyUrl -> /esign/auth/notifyRedirectUrl -> /esign/auth/redirect
默认 Controller:
19.5 默认授权回调会做什么
- 优先从回调报文里解析
psnId/orgId - 在拿不到主标识时,尝试按本地已归档的
psnAccount/orgIdCardNum反查 - 若报文自带
authStatus,直接更新本地授权档案 - 若报文未带
authStatus,回退到远端授权结果同步 - starter 默认逻辑完成后,再执行集成方通过继承 Controller 或实现
EsignAuthCallbackHook追加的逻辑
19.6 自定义 NotifyUrl / RedirectUrl 的推荐方式
如果你只是想改路径,但仍然复用 starter 默认逻辑,直接改配置即可:
esign:
auth-notify-path: /crm/esign/auth/notify
auth-redirect-path: /crm/esign/auth/redirect如果你还想在 starter 默认逻辑之后追加业务动作,推荐实现:
示例:
@Component
public class CrmEsignAuthCallbackHook implements EsignAuthCallbackHook {
@Override
public void afterNotifyHandled(
EsignAuthorizationCallbackService.CallbackResult result,
Map<String, Object> payload) {
// starter 默认归档已完成,这里再联动你们自己的业务系统
}
}20. 多租户与系统隔离
starter 默认按照这些维度做隔离:
tenantCodesystemCodebusinessId
规则:
- 不传
tenantCode时,回退到esign.default-tenant-code - 不传
systemCode时,回退到esign.default-system-code - 流程查询默认按
tenant + system + business维度过滤 - 所有归档表都带
tenantCode
推荐实践:
- SaaS 场景一定显式传
tenantCode - 单租户项目也建议保留
tenantCode概念,后续扩展更平滑
21. 默认表结构
starter 当前默认提供 15 张表:
ESIGN_FLOWESIGN_FLOW_TASKESIGN_PSN_IDENTITYESIGN_ORG_IDENTITYESIGN_SEALESIGN_MEMBERESIGN_TEMPLATEESIGN_FILEESIGN_FLOW_DETAILESIGN_PSN_ACCOUNTESIGN_ORG_ACCOUNTESIGN_PSN_AUTHESIGN_ORG_AUTHESIGN_EVIDENCE_REPORTESIGN_API_CALL_LOG
其中 ESIGN_API_CALL_LOG 专门用于保存 starter 统一 API 调用日志,和日志文件形成“双写”:
- 表记录用于后续接 Web 端查询、筛选和统计
- 文件记录用于运维排障和原始调用追踪
详细数据字典请看:
四类数据库完整 DDL:
22. Flyway 增量升级
如果你是新系统,直接使用完整 DDL 即可。
如果你是存量系统,建议使用 Flyway。
Flyway 目录:
classpath:db/migration/esign/mysqlclasspath:db/migration/esign/postgresqlclasspath:db/migration/esign/oracleclasspath:db/migration/esign/sqlserver
当前版本拆分:
V1__init_flow_tables.sqlV2__add_identity_and_seal_tables.sqlV3__add_resource_tables.sqlV4__add_account_auth_and_evidence_tables.sqlV5__add_api_call_log_table.sql
详细升级说明请看:
22.1 Flyway 配置示例
spring:
flyway:
enabled: true
locations:
- classpath:db/migration/esign/mysql23. 默认持久化与自定义持久化
23.1 什么情况下启用 starter 默认 JPA 仓储
当你的运行环境同时具备:
DataSource- JPA
starter 会自动启用默认 JPA 实体和默认 JPA 仓储。
23.2 没有数据库时会怎样
starter 会自动退回内存仓储。
适合:
- 本地联调
- 快速演示
- 单元测试
不适合:
- 正式环境
- 微服务集群
23.3 想完全替换默认仓储怎么办
你可以直接实现对应的仓储 SPI,然后在 Spring 中注册为 Bean。
24. SPI 扩展示例
这一章不是“可选看”,而是给需要接复杂业务系统的同学准备的。
如果你们多个系统的流程创建参数不完全一样,或者流程创建成功后要联动自己的业务表,这一章很重要。
24.1 业务系统扩展点 EsignBusinessSystemSupport
接口见:
适合扩展这些点:
- 文件流程请求体组装
- 模板流程请求体组装
- 流程创建成功后的系统联动
- 回调处理成功后的系统联动
24.2 自定义系统适配器示例
@Component
public class CrmEsignBusinessSystemSupport implements EsignBusinessSystemSupport {
@Override
public String getSystemCode() {
return "CRM";
}
@Override
public ContractFileModels.CreateByFileRequest buildFileFlowRequest(CreateEsignFlowCommand command) {
ContractFileModels.DocumentInfo documentInfo = new ContractFileModels.DocumentInfo()
.setFileId(command.getFileId());
ContractFileModels.CreateByFileRequest request = new ContractFileModels.CreateByFileRequest()
.setFlowName(command.getContractName())
.setDocs(java.util.Collections.singletonList(documentInfo));
// 把业务系统独有的透传字段放进去
for (java.util.Map.Entry<String, Object> entry : command.safeRequestPayload().entrySet()) {
request.addExtraField(entry.getKey(), entry.getValue());
}
return request;
}
@Override
public FlowTemplateModels.CreateBySignTemplateRequest buildTemplateFlowRequest(CreateEsignFlowCommand command) {
FlowTemplateModels.CreateBySignTemplateRequest request =
new FlowTemplateModels.CreateBySignTemplateRequest()
.setSignTemplateId(command.getSignTemplateId())
.setFlowName(command.getContractName());
for (java.util.Map.Entry<String, Object> entry : command.safeRequestPayload().entrySet()) {
request.addExtraField(entry.getKey(), entry.getValue());
}
return request;
}
@Override
public void afterFlowCreated(top.qnmdmyy.starter.integration.model.EsignFlowRecord flowRecord) {
// 这里可以联动更新你们自己的业务单据表
// 例如:把 signFlowId 回写到订单表
}
@Override
public void afterCallbackHandled(
top.qnmdmyy.starter.integration.model.EsignFlowRecord flowRecord,
top.qnmdmyy.starter.integration.model.EsignCallbackPayload payload) {
// 这里可以联动更新你们自己的业务状态
// 例如:订单状态 -> 已签署
}
}24.3 自定义流程仓储示例
如果你不想用 starter 默认表,也可以替换仓储 SPI。下面给一个最小示例。
@Repository
public class CustomFlowRecordRepository implements top.qnmdmyy.starter.integration.spi.EsignFlowRecordRepository {
@Override
public top.qnmdmyy.starter.integration.model.EsignFlowRecord save(
top.qnmdmyy.starter.integration.model.EsignFlowRecord record) {
// 这里换成你们自己的 ORM / Mapper / DAO 持久化逻辑
return record;
}
@Override
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignFlowRecord> findBySignFlowId(String signFlowId) {
return java.util.Optional.empty();
}
@Override
public java.util.List<top.qnmdmyy.starter.integration.model.EsignFlowRecord> findByTenantCodeAndSystemCodeAndBusinessId(
String tenantCode, String systemCode, String businessId) {
return java.util.Collections.emptyList();
}
}实际项目里,你还可以用同样的方式替换:
- 流程任务仓储
- 主体仓储
- 印章仓储
- 成员仓储
- 模板仓储
- 文件仓储
- 流程详情仓储
- 账号仓储
- 授权仓储
- 出证仓储
25. 推荐接入路径
25.1 最小接入
适合只想先跑通一条链路的团队:
- 引入
esign-helper-starter - 配好
esign.app-id、esign.app-secret - 使用 starter 默认回调
- 用
EsignFlowIntegrationService创建一条文件流程 - 回调联通后,再补主体/账号/授权等归档
25.2 标准生产接入
适合准备长期维护的系统:
- 引 starter
- 使用默认 14 张表
- 用 Flyway 管控表结构升级
- 显式传
tenantCode和systemCode - 用
EsignBusinessSystemSupport固化本系统的流程组装规则 - 用各归档服务沉淀主体、印章、模板、账号、授权、出证资源
25.3 平台化接入
适合后续想演进成共享服务的团队:
- 先以 starter 嵌入多个系统
- 在多个系统里统一表结构和接入方式
- 再按需迁移到
esign-helper-server
26. 对开发者最重要的几个建议
26.1 优先用归档服务,而不是每次都查远端
例如:
- 已知手机号时,优先走
EsignIdentityArchiveService - 已知
accountId时,优先走EsignAccountArchiveService - 已知
signFlowId时,优先走EsignFlowIntegrationService或EsignContractArchiveService
26.2 raw() 只做兜底,不要作为主开发方式
raw() 非常有用,但建议用于:
- 新接口试水
- starter 未封装端点的临时过渡
- 某些极个别扩展场景
常规开发仍然建议优先使用强类型门面和归档服务。
26.3 正式环境一定要有共享持久化
如果是生产环境,尤其是微服务集群:
- 不要依赖内存仓储
- 要使用数据库
- 要用 Flyway 管控升级
27. 常见问题
27.1 我只想要手机号对应的 psnId,用哪个服务
优先用:
EsignIdentityArchiveService.getOrSyncPersonByMobile(...)
27.2 我只想要统一社会信用代码对应的 orgId
优先用:
EsignIdentityArchiveService.getOrSyncOrganizationByIdCardNum(...)
27.3 我已经拿到 signFlowId,想查本地流程
优先用:
EsignFlowIntegrationService.loadBySignFlowId(...)
27.4 我想让不同业务系统有不同流程组装逻辑
实现:
EsignBusinessSystemSupport
27.5 我不想用默认表
实现:
- 对应仓储 SPI
27.6 我想让文档里的示例尽量贴近生产用法
这份 README 已按这个原则写了:
- 示例尽量使用真实服务名和真实业务语义
- 尽量通过归档服务体现“查本地优先”的推荐方式
- Jackson 回填场景继续保留
addExtraField(...),业务代码里更推荐链式使用putExtraField(...)
28. 关键源码阅读顺序
如果你要深入理解 starter,推荐按这个顺序读:
- EsignProperties.java
- EsignAutoConfiguration.java
- EsignApiFacade.java
- EsignEndpointCatalog.java
- EsignFlowIntegrationService.java
- 各类
ArchiveService - 默认实体和默认 JPA 仓储
29. 测试与验证
starter 当前关键测试包括:
- EsignFlowIntegrationServiceTest.java
- EsignResourceArchiveServiceTest.java
- EsignPlatformArchiveServiceTest.java
- EsignFlywayScriptStructureTest.java
运行命令:
mvn -q -f esign-helper-starter/pom.xml test30. 继续阅读
如果你已经读完这份 README,下一步建议按需求继续看:
- 数据字典: starter-default-schema.md
- Flyway 升级指南: starter-flyway-guide.md
- 服务化部署说明: esign-helper-server/README.md
- 示例接入说明: esign-helper-demo/README.md
如果你是第一次接入,建议从 第 9 章、第 10 章、第 11 章 开始读。
如果你是基础平台同学,建议优先看 第 21 章、第 22 章、第 24 章。