Skip to content

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 分类,例如 authfileflowseal
  • operationName 操作名
  • 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.javae签宝统一响应包裹codemessagedata
OperationResultData.java通用操作结果successresult
DownloadUrlData.java下载链接返回downloadUrl
AbstractExtensibleRequest.java官方扩展字段透传基类extraFields

认证与授权结构:

结构作用关键字段
AuthModels.PersonAuthUrlRequest发起个人授权链接请求psnAccountredirectUrlnotifyUrl
AuthModels.OrganizationAuthUrlRequest发起机构授权链接请求orgIdredirectUrlnotifyUrl
AuthModels.PersonIdentityInfoData个人实名信息返回psnIdpsnAccountauthStatus
AuthModels.OrganizationIdentityInfoData机构实名信息返回orgIdorgNameorgIDCardNum
AuthModels.AuthUrlData授权链接结果authUrlshortUrl
AuthModels.PersonAuthorizedInfoData / OrganizationAuthorizedInfoData授权状态结果psnId/orgIdauthStatus

文件与流程结构:

结构作用关键字段
ContractFileModels.FileUploadUrlRequest申请文件上传地址fileNamecontentTypefileSizecontentMd5convertToPdf
ContractFileModels.FileUploadUrlData文件上传地址返回fileIduploadUrlfileUploadUrl
ContractFileModels.FileStatusData文件状态返回fileIdfileStatus
ContractFileModels.CreateByFileRequest按文件发起签署流程flowNamedocs
ContractFileModels.DocumentInfo流程里的单个文档描述fileId
ContractFileModels.SignUrlRequest获取签署链接请求常用扩展字段如 operatorIdredirectUrl 通过 putExtraField(...) 透传
ContractFileModels.SignFlowCreateData签署流程创建结果signFlowIdshortUrlurl
ContractFileModels.SignFlowDetailData签署流程详情signFlowIdflowStatusdocs
ContractFileModels.KeywordPositionQueryRequest / KeywordPositionData关键字定位请求与结果keywordskeywordPositionspageNumposXposY
ContractFileModels.RescissionUrlRequest解约链接请求常用扩展字段如 redirectUrl 通过 putExtraField(...) 透传

模板、成员、印章、账号、出证结构:

结构作用关键字段
FlowTemplateModels.CreateBySignTemplateRequest按模板发起流程signTemplateIdflowName
FlowTemplateModels.SignTemplateData流程模板信息signTemplateIdsignTemplateNamestatus
MemberModels.AddMemberRequest / MemberData成员新增与成员信息orgIdpsnIdmemberNamemobile
SealModels.CreatePersonTemplateSealRequest / CreateOrganizationTemplateSealRequest个人/机构印章创建请求accountIdorgIdsealName
SealModels.SealData印章信息sealIdsealName
AccountModels.CreatePersonAccountRequest / CreateOrganizationAccountRequest个人/机构账号创建请求nameidTypeidNumberthirdPartyUserId
AccountModels.AccountData账号信息accountIdpsnId/orgIdnamethirdPartyUserId
EvidenceModels.ApplyReportRequest / ApplyReportData出证申请请求与结果signFlowIdreportIdreportDownloadUrl

starter 内部编排与归档结构:

结构作用关键字段
CreateEsignFlowCommand.javastarter 内部统一发起命令tenantCodesystemCodebusinessIdflowTyperequestPayload
EsignFlowRecord.java流程主档案signFlowIdbusinessIdstatusrequestPayloadresponsePayload
EsignFlowTaskRecord.java流程任务档案taskIdtaskKeysignerIdactioncallbackPayload
EsignPersonIdentityRecord / EsignOrganizationIdentityRecord主体档案psnAccount/psnIdorgIdCardNum/orgIdauthStatus
EsignPersonAccountRecord / EsignOrganizationAccountRecord账号档案accountIdthirdPartyUserIdname
EsignPersonAuthRecord / EsignOrganizationAuthRecord授权档案authStatusauthUrlshortUrlredirectUrlnotifyUrl
EsignSealRecord印章档案sealIdsealNameownerTypeownerId
EsignTemplateRecord模板档案signTemplateIdsignTemplateNamestatus
EsignFileRecord / EsignFlowDetailRecord / EsignEvidenceReportRecord文件、流程详情、出证档案fileIdsignFlowIdreportId
EsignApiCallLogRecordAPI 调用日志档案apiCategoryoperationNamehttpStatusesignCodedurationMillis

默认持久化实体:

结构作用对应表
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
EsignApiCallLogEntityAPI 调用日志实体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/OrganizationIdentityInfoDataPersonAuthorizedInfoData/OrganizationAuthorizedInfoData,并由授权归档服务写入 EsignPersonIdentityRecord/EsignOrganizationIdentityRecordEsignPersonAuthRecord/EsignOrganizationAuthRecord
  • 文件签署链路:ContractFileModels.FileUploadUrlRequest 先申请上传地址,文件上传完成后通过 CreateByFileRequest 发起签署流程,返回 SignFlowCreateData,查询时得到 SignFlowDetailData;starter 会把流程主档写入 EsignFlowRecord,任务维度写入 EsignFlowTaskRecord,文件和流程详情分别沉淀到 EsignFileRecordEsignFlowDetailRecord
  • 模板签署链路:FlowTemplateModels.CreateBySignTemplateRequest 负责按模板发起签署,模板元数据通过 SignTemplateListData/SignTemplateSummary 表达,本地归档使用 EsignTemplateRecordEsignTemplateEntity
  • 出证链路:EvidenceModels.ApplyReportRequest 发起出证,结果由 ApplyReportData 返回 reportId 和下载地址,并归档到 EsignEvidenceReportRecordEsignEvidenceReportEntity

关键字段语义补充:

  • thirdPartyUserId:业务系统自己的用户主键,用来和 e签宝账号做稳定映射。
  • psnAccount:个人授权/认证入口常用账号标识,通常是手机号。
  • psnId / orgId / accountId:分别表示个人主体、机构主体、账号三个不同层级的 e签宝资源标识,不建议混用。
  • authStatus:实名/授权状态码,starter 原样保留 e签宝返回值,便于和官方文档对照排查。
  • signFlowId / taskId:分别表示签署流程主键与流程内任务主键;一个流程通常会对应多个任务。
  • requestPayload / responsePayload / latestPayload / callbackPayload:用于补档、审计和排障的原始报文快照,默认不会主动裁剪业务字段。
  • tenantCode / systemCode:starter 内部做多租户、多业务系统隔离的统一维度,建议调用方在全链路保持稳定。

4. 模块结构

text
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 仓储、默认持久化适配
  • model e签宝请求/响应实体
  • web Starter 默认回调入口

4.1 设计模式与架构意图

starter 不是简单把接口“平铺封装”一遍,而是围绕可扩展接入做了几层模式组合。下面这张表可以直接拿来对照源码看:

模式解决的问题主要类如何扩展
Facade(门面)不让业务系统直接面对几十个端点、签名、解包细节EsignApiFacade.java业务侧统一注入 EsignApiFacade,按 auth()/contractFile()/flowTemplate() 等领域调用
Composite Facade(组合门面)把多个 API 组合成更贴近业务视角的聚合能力EsignApiFacade.EnterpriseConsoleApiEsignApiFacade.ContractManagementApi当某个业务视角不是单一 REST 域时,优先在组合门面里聚合而不是把调用散在 Controller/Service
Builder(构建器)统一构建请求元信息,避免 method/path/body/signRequired 四散传递EsignRequest.Builder新增端点时优先继续走 EsignRequest.builder() 组装请求
Template Method / Pipeline(模板方法思路)固定“序列化 -> 签名 -> HTTP -> 解包 -> 记录日志”主流程,避免每个 API 重复写EsignRequestExecutor.java新能力域只描述端点和响应类型,不重复实现执行流程
Strategy(策略)不同业务系统创建流程时,请求组装和后置联动逻辑不一样EsignBusinessSystemSupport.javaEsignBusinessSystemSupportRegistry.java每个系统实现一个 EsignBusinessSystemSupport,按 systemCode 注册
Registry(注册表)不把 systemCode -> 适配器 的判断写成 if/elseEsignBusinessSystemSupportRegistry.java新增系统适配器后自动进入注册表,未命中时回退默认实现
Repository(仓储)领域编排层不依赖具体 ORM/表结构EsignFlowRecordRepositoryEsignPersonAuthRecordRepository 等 SPI可以继续使用默认 JPA,也可以自己实现仓储 SPI 替换
Adapter(适配器)把领域记录和具体存储实现隔开,或把业务系统命令映射为 e签宝请求JpaEsignFlowRecordRepositoryJpaEsignPersonAuthRecordRepositoryDefaultEsignBusinessSystemSupport一类适配数据库,一类适配业务系统输入;都可以单独替换
Hook / Chain of Responsibility(钩子/责任链)授权回调需要“starter 默认归档先执行,集成方逻辑后执行”EsignAuthCallbackEndpoint.javaEsignAuthCallbackHook.java可以实现 Hook,也可以继承 Endpoint;两者都会在默认逻辑之后执行
Auto Configuration + Default Fallback(自动装配 + 默认兜底)调用方只引依赖和配置就能跑,同时又能覆盖默认 BeanEsignAutoConfiguration.java通过 @ConditionalOnMissingBean@ConditionalOnProperty 覆盖默认实现

可以把整个 starter 的调用链理解成:

  1. 门面模式解决“业务系统怎么调”。
  2. 模板方法解决“请求怎么稳定执行”。
  3. 策略 + 注册表解决“不同系统怎么差异化接入”。
  4. 仓储 + 适配器解决“记录怎么落库且不绑死实现”。
  5. Hook/责任链解决“默认逻辑和集成方逻辑怎么有序共存”。

推荐按这个顺序看源码:

  1. EsignAutoConfiguration.java
  2. EsignApiFacade.java
  3. EsignRequestExecutor.java
  4. EsignFlowIntegrationService.java
  5. EsignBusinessSystemSupportRegistry.java
  6. EsignAuthCallbackEndpoint.java

5. 依赖与最小配置

5.1 Maven 依赖

xml
<dependency>
    <groupId>top.qnmdmyy</groupId>
    <artifactId>esign-helper-starter</artifactId>
    <version>0.2.0-SNAPSHOT</version>
</dependency>

5.2 最小配置

yaml
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: 4000

5.3 完整配置项说明

对应配置类见 EsignProperties.java

配置项说明是否必填默认值
esign.enabled是否启用 startertrue
esign.app-ide签宝应用 ID
esign.app-secrete签宝应用密钥
esign.hoste签宝开放平台域名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 默认回调 Controllertrue
esign.callback-path默认回调路径/esign/callback/sign-notify
esign.auth-callback-endpoint-enabled是否启用 starter 默认授权回调 Controllertrue
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-pathAPI 调用日志根路径,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

示例一:使用项目相对路径

yaml
esign:
  api-log-file-path: logs/custom/esign-api

示例二:使用绝对路径

yaml
esign:
  api-log-file-path: /data/app-logs/esign

如果你是从旧配置升级上来的,之前写成了 *.log 文件路径,starter 会自动兼容并取该文件的父目录作为根路径。

如果你完全不需要 starter 记录 API 日志,则可以关闭:

yaml
esign:
  api-log-enabled: false

6. Starter 会自动给你注入哪些核心 Bean

接入成功后,通常你最常用的是这些 Bean:

  • EsignApiFacade
  • EsignFlowIntegrationService
  • EsignIdentityArchiveService
  • EsignSealArchiveService
  • EsignResourceArchiveService
  • EsignAccountArchiveService
  • EsignAuthorizationArchiveService
  • EsignEvidenceArchiveService
  • EsignWorkspaceArchiveService
  • EsignContractArchiveService
  • EsignApiLogService

选择建议:

  • 只想调接口:用 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 编排与归档服务覆盖清单

  • EsignFlowIntegrationService
    • createFileFlow(...)
    • 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,适合连续组装请求参数

示例:

java
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_IDCARD
  • EsignIdCardType.CRED_ORG_USCC

9. EsignApiFacade 全场景代码示例

下面这部分覆盖 EsignApiFacade 的所有主要场景。
如果你只是想把 e签宝当成一个开放平台调用入口来用,这一章基本就够了。

9.1 注入方式

java
@Service
public class EsignOpenApiService {

    private final EsignApiFacade esignApiFacade;

    public EsignOpenApiService(EsignApiFacade esignApiFacade) {
        this.esignApiFacade = esignApiFacade;
    }
}

9.2 个人实名信息查询

java
public String queryPersonPsnId(String mobile) {
    return esignApiFacade.auth()
            .getPersonIdentityInfo(mobile)
            .getData()
            .getPsnId();
}

9.3 机构实名信息查询

java
public String queryOrganizationId(String uscc) {
    return esignApiFacade.auth()
            .getOrganizationIdentityInfo(uscc)
            .getData()
            .getOrgId();
}

9.4 获取个人授权链接

java
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 查询个人授权结果

java
public Integer queryPersonAuthStatus(String psnId) {
    return esignApiFacade.auth()
            .getPersonAuthorizedInfo(psnId)
            .getData()
            .getAuthStatus();
}

9.6 获取机构授权链接

java
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 查询机构授权结果

java
public Integer queryOrganizationAuthStatus(String orgId) {
    return esignApiFacade.auth()
            .getOrganizationAuthorizedInfo(orgId)
            .getData()
            .getAuthStatus();
}

9.8 获取文件上传地址

java
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 上传文件二进制

java
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 下载文件二进制

java
public byte[] downloadByUrl(String downloadUrl) {
    return esignApiFacade.file().downloadFile(downloadUrl);
}

9.11 查询文件状态

java
public Integer queryFileStatus(String fileId) {
    ContractFileModels.FileStatusData data = esignApiFacade.file()
            .queryFileStatus(fileId)
            .getData();
    return data.getFileStatus() != null ? data.getFileStatus() : data.getStatus();
}

9.12 查询关键字定位

java
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 预览签署文件

java
public String previewSignedFile(String signFlowId, String docFileId) {
    return esignApiFacade.file()
            .previewSignedFile(signFlowId, docFileId)
            .getData()
            .getDownloadUrl();
}

9.14 按文件创建签署流程

java
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 启动签署流程

java
public void startFlow(String signFlowId) {
    esignApiFacade.signFlow().start(signFlowId);
}

9.16 获取签署链接

java
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 完结签署流程

java
public void finishFlow(String signFlowId) {
    esignApiFacade.signFlow().finish(signFlowId);
}

9.18 下载已签文件地址

java
public String getFinishedFileDownloadUrl(String signFlowId) {
    return esignApiFacade.signFlow()
            .downloadFinishedFile(signFlowId)
            .getData()
            .getDownloadUrl();
}

9.19 追加签署区

java
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 查询流程详情

java
public ContractFileModels.SignFlowDetailData queryFlowDetail(String signFlowId) {
    return esignApiFacade.signFlow()
            .detail(signFlowId)
            .getData();
}

9.21 获取解约链接

java
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()

java
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() 查询关键字定位

java
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() 预览签署文件

java
public String previewFromContractFile(String signFlowId, String docFileId) {
    return esignApiFacade.contractFile()
            .previewSignedFile(signFlowId, docFileId)
            .getData()
            .getDownloadUrl();
}

9.25 contractFile() 追加签署区

java
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() 获取解约链接

java
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 按模板创建流程

java
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 查询模板列表

java
public FlowTemplateModels.SignTemplateListData listTemplates(String orgId) {
    return esignApiFacade.flowTemplate()
            .listTemplates(orgId, 1, 20, null)
            .getData();
}

9.29 template() 别名用法

java
public Integer countTemplates(String orgId) {
    return esignApiFacade.template()
            .listTemplates(orgId)
            .getData()
            .getTotal();
}

9.30 新增机构成员

java
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 查询机构成员列表

java
public java.util.List<MemberModels.MemberSummary> listMembers(String orgId) {
    return esignApiFacade.member()
            .listMembers(orgId, 1, 20)
            .getData()
            .getMembers();
}

9.32 创建个人模板印章

java
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 创建机构模板印章

java
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 查询机构自有印章列表

java
public java.util.List<SealModels.SealSummary> listOrganizationSeals(String orgId) {
    return esignApiFacade.seal()
            .listOrganizationOwnSeals(orgId, 1, 20)
            .getData()
            .getSeals();
}

9.35 创建个人账号

java
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 查询个人账号

java
public AccountModels.PersonAccountData getPersonAccount(String accountId) {
    return esignApiFacade.account()
            .getPersonAccount(accountId)
            .getData();
}

9.37 删除个人账号

java
public void deletePersonAccount(String accountId) {
    esignApiFacade.account().deletePersonAccount(accountId);
}

9.38 创建机构账号

java
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 查询机构账号

java
public AccountModels.OrganizationAccountData getOrganizationAccount(String orgId) {
    return esignApiFacade.account()
            .getOrganizationAccount(orgId)
            .getData();
}

9.40 删除机构账号

java
public void deleteOrganizationAccount(String orgId) {
    esignApiFacade.account().deleteOrganizationAccount(orgId);
}

9.41 申请出证报告

java
public String applyEvidenceReport(String signFlowId) {
    EvidenceModels.ApplyReportRequest request = new EvidenceModels.ApplyReportRequest();
    request.setSignFlowId(signFlowId);

    return esignApiFacade.evidence()
            .applyReport(request)
            .getData()
            .getReportId();
}

9.42 获取企业工作台组合视图

java
public Object loadWorkspace(String orgId) {
    return esignApiFacade.enterpriseConsole().workspace(orgId, 1, 20);
}

9.43 获取企业工作台组合视图默认重载

java
public Object loadWorkspaceDefault(String orgId) {
    return esignApiFacade.enterpriseConsole().workspace(orgId);
}

9.44 获取合同管理组合视图

java
public Object loadContractOverview(String signFlowId, String docFileId) {
    return esignApiFacade.contractManagement().overview(signFlowId, docFileId);
}

9.45 通过合同管理组合门面按模板创建流程

java
public String createByTemplateFromManagement(String signTemplateId) {
    FlowTemplateModels.CreateBySignTemplateRequest request =
            new FlowTemplateModels.CreateBySignTemplateRequest()
                    .setSignTemplateId(signTemplateId)
                    .setFlowName("合同管理视角模板流程");

    return esignApiFacade.contractManagement()
            .createBySignTemplate(request)
            .getData()
            .getSignFlowId();
}

9.46 通过合同管理组合门面申请出证

java
public String applyEvidenceFromManagement(String signFlowId) {
    EvidenceModels.ApplyReportRequest request = new EvidenceModels.ApplyReportRequest();
    request.setSignFlowId(signFlowId);

    return esignApiFacade.contractManagement()
            .applyEvidenceReport(request)
            .getData()
            .getReportId();
}

9.47 读取 starter 能力分组目录

java
public java.util.List<top.qnmdmyy.starter.core.EsignEndpointCatalog.CapabilityGroup> listCapabilityGroups() {
    return esignApiFacade.catalog().capabilityGroups();
}

9.48 读取 starter 已知端点列表

java
public java.util.List<top.qnmdmyy.starter.core.EsignEndpointCatalog.KnownEndpoint> listKnownEndpoints() {
    return esignApiFacade.catalog().knownEndpoints();
}

9.49 按领域读取端点目录

java
public java.util.List<top.qnmdmyy.starter.core.EsignEndpointCatalog.KnownEndpoint> listContractFileEndpoints() {
    return esignApiFacade.catalog().knownEndpointsByDomain("contractFile");
}

9.50 raw() 原始调用场景

适合场景:

  • 官方出了新接口,但 starter 还没来得及封装
  • 你在联调某个暂未收口为强类型的方法
  • 你想先快速验证某个路径是否通
java
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() + 强类型响应映射

java
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() + 已知端点枚举

java
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
  • 某些非标准域名或临时环境联调
java
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 注入方式

java
@Service
public class ContractFlowApplicationService {

    private final EsignFlowIntegrationService flowIntegrationService;

    public ContractFlowApplicationService(EsignFlowIntegrationService flowIntegrationService) {
        this.flowIntegrationService = flowIntegrationService;
    }
}

10.2 创建文件流程

java
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 创建模板流程

java
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 时,可以自己接收回调,然后转交给编排服务。

java
public EsignFlowIntegrationService.CallbackResult handleCallback(java.util.Map<String, Object> callbackBody) {
    return flowIntegrationService.handleCallback(callbackBody);
}

10.5 按 signFlowId 读取流程聚合

java
public EsignFlowIntegrationService.FlowAggregate loadAggregate(String signFlowId) {
    return flowIntegrationService.loadBySignFlowId(signFlowId);
}

10.6 按业务号查询流程

java
public java.util.List<top.qnmdmyy.starter.integration.model.EsignFlowRecord> loadByBusiness(String businessId) {
    return flowIntegrationService.loadByBusiness("CRM", businessId);
}

10.7 按租户 + 系统 + 业务号查询流程

java
public java.util.List<top.qnmdmyy.starter.integration.model.EsignFlowRecord> loadByBusinessWithTenant(String businessId) {
    return flowIntegrationService.loadByBusiness("TENANT_A", "CRM", businessId);
}

10.8 自定义回调 Controller 的完整写法

java
@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);
    }
}

对应配置:

yaml
esign:
  callback-endpoint-enabled: false

11. EsignIdentityArchiveService 全场景代码示例

这个服务解决的是:
以后你只想知道“这个手机号对应哪个 psnId”,“这个统一社会信用代码对应哪个 orgId”,不想每次都查远端。

11.0 注入方式

java
@Service
public class IdentityArchiveUsageService {

    private final EsignIdentityArchiveService identityArchiveService;

    public IdentityArchiveUsageService(EsignIdentityArchiveService identityArchiveService) {
        this.identityArchiveService = identityArchiveService;
    }
}

11.1 注入方式

java
@Service
public class IdentityArchiveApplicationService {

    private final EsignIdentityArchiveService identityArchiveService;

    public IdentityArchiveApplicationService(EsignIdentityArchiveService identityArchiveService) {
        this.identityArchiveService = identityArchiveService;
    }
}

11.2 本地优先获取个人主体档案

java
public top.qnmdmyy.starter.integration.model.EsignPersonIdentityRecord getOrSyncPerson(String mobile) {
    return identityArchiveService.getOrSyncPersonByMobile("TENANT_A", mobile);
}

11.3 本地优先获取机构主体档案

java
public top.qnmdmyy.starter.integration.model.EsignOrganizationIdentityRecord getOrSyncOrganization(String uscc) {
    return identityArchiveService.getOrSyncOrganizationByIdCardNum("TENANT_A", uscc);
}

11.4 只查本地个人主体

java
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignPersonIdentityRecord> findLocalPerson(String mobile) {
    return identityArchiveService.findLocalPersonByMobile("TENANT_A", mobile);
}

11.5 按 psnId 查本地个人主体

java
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignPersonIdentityRecord> findPersonByPsnId(String psnId) {
    return identityArchiveService.findLocalPersonByPsnId("TENANT_A", psnId);
}

11.6 按 accountId 查本地个人主体

java
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignPersonIdentityRecord> findPersonByAccountId(String accountId) {
    return identityArchiveService.findLocalPersonByAccountId("TENANT_A", accountId);
}

11.7 只查本地机构主体

java
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignOrganizationIdentityRecord> findLocalOrganization(String uscc) {
    return identityArchiveService.findLocalOrganizationByIdCardNum("TENANT_A", uscc);
}

11.8 按 orgId 查本地机构主体

java
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignOrganizationIdentityRecord> findOrganizationByOrgId(String orgId) {
    return identityArchiveService.findLocalOrganizationByOrgId("TENANT_A", orgId);
}

11.9 按 accountId 查本地机构主体

java
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignOrganizationIdentityRecord> findOrganizationByAccountId(String accountId) {
    return identityArchiveService.findLocalOrganizationByAccountId("TENANT_A", accountId);
}

11.10 强制从远端同步个人主体

java
public top.qnmdmyy.starter.integration.model.EsignPersonIdentityRecord syncPerson(String mobile) {
    return identityArchiveService.syncPersonByMobile("TENANT_A", mobile);
}

11.11 强制从远端同步机构主体

java
public top.qnmdmyy.starter.integration.model.EsignOrganizationIdentityRecord syncOrganization(String uscc) {
    return identityArchiveService.syncOrganizationByIdCardNum("TENANT_A", uscc);
}

11.12 直接归档个人实名查询结果

java
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 直接归档机构实名查询结果

java
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 直接归档个人授权结果

java
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 直接归档机构授权结果

java
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 直接归档个人账号结果

java
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 直接归档机构账号结果

java
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 从回调中顺手补档个人主体

java
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 注入方式

java
@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 查印章

java
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignSealRecord> findSeal(String sealId) {
    return sealArchiveService.findLocalBySealId("TENANT_A", sealId);
}

12.2 本地按归属主体查印章

java
public java.util.List<top.qnmdmyy.starter.integration.model.EsignSealRecord> findOrganizationSeals(String orgId) {
    return sealArchiveService.findLocalByOwner("TENANT_A", "ORGANIZATION", orgId);
}

12.3 创建个人模板印章并归档

java
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 创建机构模板印章并归档

java
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 同步机构自有印章列表并归档

java
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 注入方式

java
@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 同步机构成员

java
public java.util.List<top.qnmdmyy.starter.integration.model.EsignMemberRecord> syncMembers(String orgId) {
    return resourceArchiveService.syncOrganizationMembers("TENANT_A", orgId, 1, 20);
}

13.2 同步模板列表

java
public java.util.List<top.qnmdmyy.starter.integration.model.EsignTemplateRecord> syncTemplates(String orgId) {
    return resourceArchiveService.syncSignTemplates("TENANT_A", orgId, 1, 20, null);
}

13.3 同步文件状态

java
public top.qnmdmyy.starter.integration.model.EsignFileRecord syncFile(String fileId) {
    return resourceArchiveService.syncFileStatus("TENANT_A", fileId);
}

13.4 同步流程详情

java
public top.qnmdmyy.starter.integration.model.EsignFlowDetailRecord syncFlowDetail(String signFlowId) {
    return resourceArchiveService.syncFlowDetail("TENANT_A", signFlowId);
}

13.5 本地查文件档案

java
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignFileRecord> findFile(String fileId) {
    return resourceArchiveService.findLocalFile("TENANT_A", fileId);
}

13.6 本地查流程详情档案

java
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignFlowDetailRecord> findFlowDetail(String signFlowId) {
    return resourceArchiveService.findLocalFlowDetail("TENANT_A", signFlowId);
}

13.7 本地查成员档案

java
public java.util.List<top.qnmdmyy.starter.integration.model.EsignMemberRecord> findLocalMembers(String orgId) {
    return resourceArchiveService.findLocalMembers("TENANT_A", orgId);
}

13.8 本地查模板档案

java
public java.util.List<top.qnmdmyy.starter.integration.model.EsignTemplateRecord> findLocalTemplates(String orgId) {
    return resourceArchiveService.findLocalTemplates("TENANT_A", orgId);
}

13.9 在文件创建成功时顺手归档

java
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 在流程创建成功时顺手归档

java
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 全场景代码示例

这个服务解决的是:
账号是独立于主体的资源,应该单独归档,后续系统可以按 accountIdthirdPartyUserId 等维度直接本地查询。

14.0 注入方式

java
@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 创建个人账号并归档

java
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 创建机构账号并归档

java
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 强制同步个人账号

java
public top.qnmdmyy.starter.integration.model.EsignPersonAccountRecord syncPersonAccount(String accountId) {
    return accountArchiveService.syncPersonAccountByAccountId("TENANT_A", accountId);
}

14.4 按 orgId 强制同步机构账号

java
public top.qnmdmyy.starter.integration.model.EsignOrganizationAccountRecord syncOrganizationAccount(String orgId) {
    return accountArchiveService.syncOrganizationAccountByOrgId("TENANT_A", orgId);
}

14.5 直接归档个人账号查询结果

java
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 直接归档机构账号查询结果

java
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 本地查个人账号

java
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignPersonAccountRecord> findPersonAccount(String accountId) {
    return accountArchiveService.findLocalPersonByAccountId("TENANT_A", accountId);
}

14.8 按 psnId 查本地个人账号

java
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignPersonAccountRecord> findPersonAccountByPsnId(String psnId) {
    return accountArchiveService.findLocalPersonByPsnId("TENANT_A", psnId);
}

14.9 按 thirdPartyUserId 查本地个人账号

java
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignPersonAccountRecord> findPersonAccountByThirdPartyUserId(String thirdPartyUserId) {
    return accountArchiveService.findLocalPersonByThirdPartyUserId("TENANT_A", thirdPartyUserId);
}

14.10 本地查机构账号

java
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignOrganizationAccountRecord> findOrganizationAccount(String orgId) {
    return accountArchiveService.findLocalOrganizationByOrgId("TENANT_A", orgId);
}

14.11 按 accountId 查本地机构账号

java
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignOrganizationAccountRecord> findOrganizationAccountByAccountId(String accountId) {
    return accountArchiveService.findLocalOrganizationByAccountId("TENANT_A", accountId);
}

14.12 按 thirdPartyUserId 查本地机构账号

java
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignOrganizationAccountRecord> findOrganizationAccountByThirdPartyUserId(String thirdPartyUserId) {
    return accountArchiveService.findLocalOrganizationByThirdPartyUserId("TENANT_A", thirdPartyUserId);
}

15. EsignAuthorizationArchiveService 全场景代码示例

这个服务解决的是:
授权链接和授权结果本身就是很有价值的资源,不应该只在一次请求里用完即丢。

15.0 注入方式

java
@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 申请个人授权链接并归档

java
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 申请机构授权链接并归档

java
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 强制同步个人授权结果

java
public top.qnmdmyy.starter.integration.model.EsignPersonAuthRecord syncPersonAuthorizedInfo(String psnId) {
    return authorizationArchiveService.syncPersonAuthorizedInfo("TENANT_A", psnId);
}

15.4 强制同步机构授权结果

java
public top.qnmdmyy.starter.integration.model.EsignOrganizationAuthRecord syncOrganizationAuthorizedInfo(String orgId) {
    return authorizationArchiveService.syncOrganizationAuthorizedInfo("TENANT_A", orgId);
}

15.5 直接归档个人授权结果

java
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 直接归档机构授权结果

java
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 本地按手机号查个人授权档案

java
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignPersonAuthRecord> findPersonAuthByMobile(String mobile) {
    return authorizationArchiveService.findLocalPersonByPsnAccount("TENANT_A", mobile);
}

15.8 本地按 psnId 查个人授权档案

java
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignPersonAuthRecord> findPersonAuthByPsnId(String psnId) {
    return authorizationArchiveService.findLocalPersonByPsnId("TENANT_A", psnId);
}

15.9 本地按 orgId 查机构授权档案

java
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignOrganizationAuthRecord> findOrganizationAuthByOrgId(String orgId) {
    return authorizationArchiveService.findLocalOrganizationByOrgId("TENANT_A", orgId);
}

15.10 本地按统一社会信用代码查机构授权档案

java
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignOrganizationAuthRecord> findOrganizationAuthByUscc(String uscc) {
    return authorizationArchiveService.findLocalOrganizationByOrgIdCardNum("TENANT_A", uscc);
}

16. EsignEvidenceArchiveService 全场景代码示例

16.0 注入方式

java
@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 申请出证并归档

java
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 直接归档出证申请结果

java
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 查本地出证档案

java
public java.util.Optional<top.qnmdmyy.starter.integration.model.EsignEvidenceReportRecord> findEvidenceByReportId(String reportId) {
    return evidenceArchiveService.findLocalByReportId("TENANT_A", reportId);
}

16.4 按 signFlowId 查本地出证档案列表

java
public java.util.List<top.qnmdmyy.starter.integration.model.EsignEvidenceReportRecord> findEvidenceBySignFlowId(String signFlowId) {
    return evidenceArchiveService.findLocalBySignFlowId("TENANT_A", signFlowId);
}

17. EsignWorkspaceArchiveService 全场景代码示例

企业工作台不是一张独立表,而是授权、成员、印章、模板四类资源的组合归档。

17.0 注入方式

java
@Service
public class WorkspaceArchiveUsageService {

    private final EsignWorkspaceArchiveService workspaceArchiveService;

    public WorkspaceArchiveUsageService(EsignWorkspaceArchiveService workspaceArchiveService) {
        this.workspaceArchiveService = workspaceArchiveService;
    }
}

17.1 同步企业工作台相关资源

java
public EsignWorkspaceArchiveService.WorkspaceArchiveResult syncWorkspace(String orgId) {
    return workspaceArchiveService.syncOrganizationWorkspace("TENANT_A", orgId, 1, 20, null);
}

18. EsignContractArchiveService 全场景代码示例

合同管理也是组合视图,它依赖文件、流程详情、出证等多类数据。

18.0 注入方式

java
@Service
public class ContractArchiveUsageService {

    private final EsignContractArchiveService contractArchiveService;

    public ContractArchiveUsageService(EsignContractArchiveService contractArchiveService) {
        this.contractArchiveService = contractArchiveService;
    }
}

18.1 同步合同相关资源快照

java
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 读取当前回调路径

java
@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/notify
  • RedirectUrl -> /esign/auth/redirect

默认 Controller:

19.5 默认授权回调会做什么

  • 优先从回调报文里解析 psnId / orgId
  • 在拿不到主标识时,尝试按本地已归档的 psnAccount / orgIdCardNum 反查
  • 若报文自带 authStatus,直接更新本地授权档案
  • 若报文未带 authStatus,回退到远端授权结果同步
  • starter 默认逻辑完成后,再执行集成方通过继承 Controller 或实现 EsignAuthCallbackHook 追加的逻辑

19.6 自定义 NotifyUrl / RedirectUrl 的推荐方式

如果你只是想改路径,但仍然复用 starter 默认逻辑,直接改配置即可:

yaml
esign:
  auth-notify-path: /crm/esign/auth/notify
  auth-redirect-path: /crm/esign/auth/redirect

如果你还想在 starter 默认逻辑之后追加业务动作,推荐实现:

示例:

java
@Component
public class CrmEsignAuthCallbackHook implements EsignAuthCallbackHook {

    @Override
    public void afterNotifyHandled(
            EsignAuthorizationCallbackService.CallbackResult result,
            Map<String, Object> payload) {
        // starter 默认归档已完成,这里再联动你们自己的业务系统
    }
}

20. 多租户与系统隔离

starter 默认按照这些维度做隔离:

  • tenantCode
  • systemCode
  • businessId

规则:

  • 不传 tenantCode 时,回退到 esign.default-tenant-code
  • 不传 systemCode 时,回退到 esign.default-system-code
  • 流程查询默认按 tenant + system + business 维度过滤
  • 所有归档表都带 tenantCode

推荐实践:

  • SaaS 场景一定显式传 tenantCode
  • 单租户项目也建议保留 tenantCode 概念,后续扩展更平滑

21. 默认表结构

starter 当前默认提供 15 张表:

  • ESIGN_FLOW
  • ESIGN_FLOW_TASK
  • ESIGN_PSN_IDENTITY
  • ESIGN_ORG_IDENTITY
  • ESIGN_SEAL
  • ESIGN_MEMBER
  • ESIGN_TEMPLATE
  • ESIGN_FILE
  • ESIGN_FLOW_DETAIL
  • ESIGN_PSN_ACCOUNT
  • ESIGN_ORG_ACCOUNT
  • ESIGN_PSN_AUTH
  • ESIGN_ORG_AUTH
  • ESIGN_EVIDENCE_REPORT
  • ESIGN_API_CALL_LOG

其中 ESIGN_API_CALL_LOG 专门用于保存 starter 统一 API 调用日志,和日志文件形成“双写”:

  • 表记录用于后续接 Web 端查询、筛选和统计
  • 文件记录用于运维排障和原始调用追踪

详细数据字典请看:

四类数据库完整 DDL:


22. Flyway 增量升级

如果你是新系统,直接使用完整 DDL 即可。
如果你是存量系统,建议使用 Flyway。

Flyway 目录:

  • classpath:db/migration/esign/mysql
  • classpath:db/migration/esign/postgresql
  • classpath:db/migration/esign/oracle
  • classpath:db/migration/esign/sqlserver

当前版本拆分:

  • V1__init_flow_tables.sql
  • V2__add_identity_and_seal_tables.sql
  • V3__add_resource_tables.sql
  • V4__add_account_auth_and_evidence_tables.sql
  • V5__add_api_call_log_table.sql

详细升级说明请看:

22.1 Flyway 配置示例

yaml
spring:
  flyway:
    enabled: true
    locations:
      - classpath:db/migration/esign/mysql

23. 默认持久化与自定义持久化

23.1 什么情况下启用 starter 默认 JPA 仓储

当你的运行环境同时具备:

  • DataSource
  • JPA

starter 会自动启用默认 JPA 实体和默认 JPA 仓储。

23.2 没有数据库时会怎样

starter 会自动退回内存仓储。

适合:

  • 本地联调
  • 快速演示
  • 单元测试

不适合:

  • 正式环境
  • 微服务集群

23.3 想完全替换默认仓储怎么办

你可以直接实现对应的仓储 SPI,然后在 Spring 中注册为 Bean。


24. SPI 扩展示例

这一章不是“可选看”,而是给需要接复杂业务系统的同学准备的。
如果你们多个系统的流程创建参数不完全一样,或者流程创建成功后要联动自己的业务表,这一章很重要。

24.1 业务系统扩展点 EsignBusinessSystemSupport

接口见:

适合扩展这些点:

  • 文件流程请求体组装
  • 模板流程请求体组装
  • 流程创建成功后的系统联动
  • 回调处理成功后的系统联动

24.2 自定义系统适配器示例

java
@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。下面给一个最小示例。

java
@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 最小接入

适合只想先跑通一条链路的团队:

  1. 引入 esign-helper-starter
  2. 配好 esign.app-idesign.app-secret
  3. 使用 starter 默认回调
  4. EsignFlowIntegrationService 创建一条文件流程
  5. 回调联通后,再补主体/账号/授权等归档

25.2 标准生产接入

适合准备长期维护的系统:

  1. 引 starter
  2. 使用默认 14 张表
  3. 用 Flyway 管控表结构升级
  4. 显式传 tenantCodesystemCode
  5. EsignBusinessSystemSupport 固化本系统的流程组装规则
  6. 用各归档服务沉淀主体、印章、模板、账号、授权、出证资源

25.3 平台化接入

适合后续想演进成共享服务的团队:

  1. 先以 starter 嵌入多个系统
  2. 在多个系统里统一表结构和接入方式
  3. 再按需迁移到 esign-helper-server

26. 对开发者最重要的几个建议

26.1 优先用归档服务,而不是每次都查远端

例如:

  • 已知手机号时,优先走 EsignIdentityArchiveService
  • 已知 accountId 时,优先走 EsignAccountArchiveService
  • 已知 signFlowId 时,优先走 EsignFlowIntegrationServiceEsignContractArchiveService

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,推荐按这个顺序读:

  1. EsignProperties.java
  2. EsignAutoConfiguration.java
  3. EsignApiFacade.java
  4. EsignEndpointCatalog.java
  5. EsignFlowIntegrationService.java
  6. 各类 ArchiveService
  7. 默认实体和默认 JPA 仓储

29. 测试与验证

starter 当前关键测试包括:

运行命令:

bash
mvn -q -f esign-helper-starter/pom.xml test

30. 继续阅读

如果你已经读完这份 README,下一步建议按需求继续看:

如果你是第一次接入,建议从 第 9 章第 10 章第 11 章 开始读。
如果你是基础平台同学,建议优先看 第 21 章第 22 章第 24 章

最近更新

EsignHelper Portal powered by VitePress Theme Teek