# 多平台签署整合实现方案(e签宝 + 法大大) > 基于现有企业入驻流程的改造设计,供评审后决定实施范围与优先级。 > 关联文档:[企业入驻-e签宝功能分析.md](./企业入驻-e签宝功能分析.md) --- ## 1. 背景与目标 ### 1.1 现状 当前企业入驻的**企业实名认证 + 合同签署**全流程绑定在 **e签宝** 单一平台: - 管理员审核通过后,直接调用 e签宝 `/v3/org-auth-url` 生成 `auth_url` - 企业认证、模板填单、签署流程、回调/轮询均硬编码在 `CertificationApplicationServiceImpl` + `internal/shared/esign` ### 1.2 目标 1. `certifications` 主表新增 **`sign_platform`(签署平台)** 字段 2. 管理员审核通过后,**由用户选择签署平台**(e签宝 / 法大大) 3. 根据所选平台发放对应的**企业认证链接**与**合同签署链接** 4. 尽量复用现有状态机,降低对已完成用户的影响 ### 1.3 非目标(本期可不做) - 同一用户在不同平台各签一份合同 - 签署中途切换平台(选定后锁定,除非管理员重置) - 替换掉 e签宝 的全部历史数据 --- ## 2. 改造后流程设计 ### 2.1 新增步骤:选择签署平台 在「人工审核通过」与「企业认证 iframe」之间插入一步: ``` 填写企业信息 → 人工审核(info_pending_review) → 【新增】选择签署平台(info_submitted 前/后,见 2.2) → 企业实名认证(info_submitted) → 合同预览与签署(enterprise_verified → contract_applied → completed) ``` ### 2.2 平台选择时机(推荐方案) | 方案 | 时机 | 优点 | 缺点 | |------|------|------|------| | **A(推荐)** | 审核通过后、生成认证链接前,用户主动选择 | 符合需求;认证与签署同一平台;不浪费第三方调用 | 多一个前端步骤 | | B | 提交企业信息时一并选择 | 步骤少 | 审核拒绝后需重选;用户尚不清楚平台差异 | | C | 仅合同阶段选择,认证仍固定 e签宝 | 改动小 | 法大大合同要求法大大侧企业实名,链路不完整 | **推荐采用方案 A**,状态流转如下: ```mermaid flowchart TD A[info_pending_review] --> B[管理员审核通过] B --> C{sign_platform 已选择?} C -->|否| D[前端展示平台选择页
POST /select-sign-platform] D --> E[写入 sign_platform] E --> F[按平台生成 auth_url] C -->|是| F F --> G[info_submitted + auth_url] G --> H[iframe 企业认证] H --> I[enterprise_verified + 生成合同] I --> J[apply-contract 按平台发起签署] J --> K[completed] ``` ### 2.3 平台锁定规则 | 规则 | 说明 | |------|------| | 可选时机 | `info_pending_review` 审核通过后 ~ `info_submitted` 之前(`sign_platform` 为空) | | 锁定时机 | 用户确认平台后写入 `sign_platform`,并生成该平台认证链接 | | 不可变更 | 进入 `info_submitted` 后不允许用户自行切换 | | 管理员重置 | 仅管理员可将 `sign_platform` 置空并重置到待选平台状态(见 4.3) | --- ## 3. 数据库变更 ### 3.1 `certifications` 主表 新增字段: ```sql ALTER TABLE certifications ADD COLUMN sign_platform VARCHAR(20) DEFAULT NULL COMMENT '签署平台: esign | fadada,NULL 表示尚未选择'; ``` | 字段 | 类型 | 说明 | |------|------|------| | `sign_platform` | varchar(20), nullable | `esign` / `fadada` / `NULL` | **现有 e签宝 字段复用策略(推荐不重命名):** | 现有字段 | 多平台语义 | |----------|------------| | `auth_flow_id` | 第三方企业认证流程 ID(平台无关) | | `auth_url` | 第三方企业认证页面 URL | | `contract_file_id` | 第三方合同文件 ID | | `esign_flow_id` | 第三方签署流程 ID(建议后续迭代改名为 `sign_flow_id`) | | `contract_sign_url` | 第三方签署页面 URL | > 首期可保留 `esign_flow_id` 列名不动,避免大面积迁移;在代码层用 `SignFlowID` 别名理解即可。 ### 3.2 子记录表(建议同步加平台字段) | 表 | 变更 | |----|------| | `esign_contract_generate_records` | 新增 `sign_platform varchar(20)`;后期可重命名为 `contract_generate_records` | | `esign_contract_sign_records` | 新增 `sign_platform varchar(20)`;后期可重命名为 `contract_sign_records` | | `contract_infos` | 新增 `sign_platform varchar(20)`,便于历史合同溯源 | ### 3.3 枚举定义(Go) ```go // internal/domains/certification/enums/sign_platform.go type SignPlatform string const ( SignPlatformEsign SignPlatform = "esign" SignPlatformFadada SignPlatform = "fadada" ) func IsValidSignPlatform(p SignPlatform) bool { ... } ``` ### 3.4 历史数据迁移 ```sql -- 已有认证记录默认归属 e签宝 UPDATE certifications SET sign_platform = 'esign' WHERE sign_platform IS NULL; UPDATE esign_contract_generate_records SET sign_platform = 'esign' WHERE sign_platform IS NULL; UPDATE esign_contract_sign_records SET sign_platform = 'esign' WHERE sign_platform IS NULL; UPDATE contract_infos SET sign_platform = 'esign' WHERE sign_platform IS NULL; ``` 对于**进行中**且 `sign_platform IS NULL` 的记录:迁移后视为已选 e签宝,与当前行为一致。 --- ## 4. 后端架构改造 ### 4.1 核心思路:签署平台抽象层(Strategy + Registry) 将 e签宝 特有逻辑从 `CertificationApplicationServiceImpl` 中抽离,定义统一接口: ``` internal/domains/certification/ports/sign_platform_provider.go # 接口(端口) internal/infrastructure/external/esign/esign_provider.go # e签宝 实现 internal/infrastructure/external/fadada/fadada_provider.go # 法大大 实现 internal/infrastructure/external/signplatform/registry.go # 按 sign_platform 路由 ``` **接口草案:** ```go type SignPlatformProvider interface { Platform() enums.SignPlatform // 企业实名 GenerateEnterpriseAuth(ctx context.Context, req *EnterpriseAuthRequest) (*AuthLinkResult, error) QueryOrgVerified(ctx context.Context, req *OrgIdentityQuery) (bool, error) // 合同 GenerateContractFile(ctx context.Context, req *ContractGenerateRequest) (*ContractFileResult, error) CreateSignFlow(ctx context.Context, req *SignFlowCreateRequest) (*SignFlowResult, error) QuerySignStatus(ctx context.Context, flowID string) (*SignStatusResult, error) DownloadSignedFiles(ctx context.Context, flowID string) ([]*SignedFile, error) // 回调 ParseCallback(ctx context.Context, raw []byte, headers map[string]string) (*CallbackEvent, error) VerifyCallback(ctx context.Context, raw []byte, headers map[string]string) error } ``` `CertificationApplicationServiceImpl` 中所有 `s.esignClient.XXX()` 改为: ```go provider := s.platformRegistry.Get(cert.SignPlatform) provider.GenerateEnterpriseAuth(...) ``` ### 4.2 需改造的后端触点(按优先级) | 方法 | 现状 | 改造 | |------|------|------| | `AdminApproveSubmitRecord` | 直接 `esignClient.GenerateEnterpriseAuth` | 审核通过后**不立即**生成链接;仅将状态保持在「待选平台」或新增中间状态 | | **新增** `SelectSignPlatform` | 无 | 用户选择平台 → 写 `sign_platform` → 调 provider 生成 `auth_url` → `info_submitted` | | `ConfirmAuth` / `checkAndCompleteEnterpriseVerification` | 查 e签宝 identity | 调当前 provider 的 `QueryOrgVerified` | | `generateAndAddContractFile` | e签宝 FillTemplate | 调 provider 的 `GenerateContractFile` | | `ApplyContract` / `generateContractAndSignURL` | e签宝 SignFlow | 调 provider 的 `CreateSignFlow` | | `checkAndUpdateSignStatus` | e签宝 QuerySignFlowDetail | 调 provider 的 `QuerySignStatus` | | `handleContractAfterSignComplete` | e签宝 DownloadSignedFile | 调 provider 的 `DownloadSignedFiles` | | `HandleEsignCallback` | 仅处理 e签宝 | 拆为 `/callbacks/esign` + `/callbacks/fadada`,或统一入口按 header/字段分发 | ### 4.3 状态机微调(可选两种做法) **做法 1(最小改动,推荐):不新增状态** - 管理员审核通过后,认证状态仍为 `info_pending_review` 或新增语义:`info_submitted` 但 `sign_platform=NULL` 且 `auth_url` 为空 - 前端根据 `metadata.need_select_platform=true` 展示平台选择页 - 用户选完平台后才进入 `info_submitted`(有 `auth_url`) **做法 2:新增状态 `platform_pending`** - 语义更清晰,但需改 `certification_status.go`、前端步骤映射、管理端筛选 - 适合后续平台数量继续增加时 **推荐做法 1**,通过 metadata 驱动前端,避免状态机膨胀: ```json { "status": "info_pending_review", "metadata": { "manual_review_status": "approved", "need_select_platform": true, "available_platforms": ["esign", "fadada"] } } ``` 或审核通过后直接将状态推到 `info_submitted`,但 `auth_url` 为空、`need_select_platform=true`。 ### 4.4 新增 API | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/v1/certifications/sign-platforms` | 返回可选平台列表(名称、图标、说明、是否可用) | | POST | `/api/v1/certifications/select-sign-platform` | 用户选择平台,生成认证链接 | | POST | `/api/v1/certifications/callbacks/fadada` | 法大大回调(与 esign 并列) | | POST | `/api/v1/certifications/admin/reset-sign-platform` | 管理员重置平台选择(可选) | **`SelectSignPlatform` 请求体:** ```json { "sign_platform": "fadada" } ``` **响应(与现有 details 结构对齐):** ```json { "status": "info_submitted", "sign_platform": "fadada", "metadata": { "auth_url": "https://...", "next_action": "请完成企业认证" } } ``` **`apply-contract` / `confirm-sign` / `confirm-auth`**:无需改路径,内部按 `cert.SignPlatform` 路由。 ### 4.5 管理员审核逻辑调整 当前 `AdminApproveSubmitRecord` 审核通过后会**立即**调用 e签宝。改造后: ``` 审核通过 → 仅更新 submit_record.manual_review_status = approved → 认证状态:保持 info_pending_review(或推到 info_submitted 但无 auth_url) → 不调用任何第三方 用户选择平台 → SelectSignPlatform → 调对应 provider → 写入 auth_url → info_submitted ``` 管理员后台可展示 `sign_platform` 列,便于客服排查。 --- ## 5. 法大大接入方案 ### 5.1 平台与文档 - 产品:法大大 **FASC OpenAPI v5.1**(公有云) - 开放平台:`https://cloud.fadada.com` / 开发文档 `https://dev.fadada.com` - 参考 SDK:`fasc-openapi-go-sdk`(官方 Go SDK,建议优先使用) ### 5.2 需准备的法大大侧资源 | 资源 | 用途 | |------|------| | AppID / AppSecret | API 鉴权 | | openCorpId | 接入企业(海宇数据自身)在法大大侧的企业 ID | | 应用模板 / 签署模板 | 对应合作协议 PDF 模板 | | 测试环境域名 | 沙箱联调 | | 回调地址 | `https://{domain}/api/v1/certifications/callbacks/fadada` | ### 5.3 法大大 API 与 e签宝 映射 | 业务步骤 | e签宝(现有) | 法大大 FASC(待接) | |----------|---------------|---------------------| | 获取企业认证链接 | `POST /v3/org-auth-url` | `Corp` 模块 - 获取企业认证/授权 URL | | 查询企业是否已实名 | `GET /v3/organizations/identity-info` | `Corp` 模块 - 查询企业认证状态 | | 模板生成合同文件 | `POST /v3/files/create-by-doc-template` | `AppTemplate` + `Doc` 模块 - 填写模板生成文档 | | 创建签署流程 | `POST /v3/sign-flow/create-by-file` | `fadada.CreateSignFlow` → `/sign-task/create` 或 `/sign-task/create-with-template` | | 获取签署链接 | `POST /v3/sign-flow/{id}/sign-url` | `fadada.GetSignURL` → `/sign-task/actor/get-url` | | 查询签署状态 | `GET /v3/sign-flow/{id}/detail` | `fadada.QuerySignStatus` → `/sign-task/app/get-detail`(`task_finished`/`sign_completed`=完成) | | 下载已签文件 | `GET /v3/sign-flow/{id}/file-download-url` | `fadada.DownloadSignedFiles` → `/sign-task/owner/get-download-url` | | 回调 | `AUTH_PASS` 等 | `fadada.VerifyCallback` + `ParseCallback`(`X-FASC-*` HMAC-SHA256) | > 具体接口路径以法大大 v5.1 文档为准;上表为模块级映射,实施时按官方 SDK 方法名落地。 ### 5.4 法大大合同模板控件 模板 ID:`1784013372537192273`(`config.yaml` → `fadada.template_id`) | 业务字段 | e签宝 控件 key | 法大大控件编码 (fieldId) | 配置键 | |----------|----------------|--------------------------|--------| | 协议编号 | `xybh` | 多控件同值(见 `config.yaml` `agreement_no` 列表) | `agreement_no` | | 签订日期 | (系统) | `2039326319` | `contract_date` | | 甲方企业名 | `jfqym` / `jfqym2` | `6920634255` | `party_a_name` | | 甲方统一信用代码 | `jftyshxydm` | `8702060291` | `party_a_uscc` | | 甲方联系地址 | `jflxdz` | `4557539440` | `party_a_address` | | 甲方授权代表/法人 | `jfsqdb` | `8627432823` | `party_a_rep` | | 甲方签署日期 | `qsrq1` / `qsrq3` | `6047306418` | `party_a_sign_date` | | 乙方签署日期 | `qsrq2` | `3849218908` | `party_b_sign_date` | 日期格式与现网一致:`2006年01月02日`。实现:`fadada.Client.FillTemplate` / `FillSignTaskFields`。 ### 5.5 法大大 SDK 目录规划 ``` hyapi-server/internal/shared/fadada/ # 或 infrastructure/external/fadada/ ├── client.go # 封装官方 SDK Client ├── config.go ├── corp_service.go # 企业认证 ├── signtask_service.go # 签署任务 ├── template_service.go # 模板/文档 ├── callback.go # 回调解析与验签 └── provider.go # 实现 SignPlatformProvider ``` --- ## 6. 配置变更 ### 6.1 config.yaml 新增法大大段 ```yaml # 签署平台全局开关 sign_platform: enabled: ["esign", "fadada"] # 前端可选列表 default: "" # 空=强制用户选择 esign: # 保持现有配置不变 ... fadada: app_id: "" app_secret: "" server_url: "https://api.fadada.com" # 以官方文档为准 open_corp_id: "" # 海宇数据在法大大的企业 ID template_id: "" # 应用签署模板 ID contract: name: "海宇数据-合作协议" expire_days: 7 auth: redirect_url: "https://console.haiyudata.com/certification/callback/fadada/auth" sign: redirect_url: "https://console.haiyudata.com/certification/callback/fadada/sign" callback: enabled: true ``` ### 6.2 Go Config 结构 ```go type SignPlatformConfig struct { Enabled []string `mapstructure:"enabled"` Default string `mapstructure:"default"` } type FadadaConfig struct { AppID string `mapstructure:"app_id"` AppSecret string `mapstructure:"app_secret"` ServerURL string `mapstructure:"server_url"` OpenCorpID string `mapstructure:"open_corp_id"` TemplateID string `mapstructure:"template_id"` Contract ContractConfig `mapstructure:"contract"` Auth AuthConfig `mapstructure:"auth"` Sign SignConfig `mapstructure:"sign"` Callback CallbackConfig `mapstructure:"callback"` } ``` --- ## 7. 前端改造 ### 7.1 新增步骤与组件 ``` certification/components/ ├── PlatformSelect.vue # 【新增】签署平台选择 ├── EnterpriseVerify.vue # 改造:支持不同平台 iframe / 跳转方式 ├── ContractSign.vue # 改造:按平台展示签署页 └── ... ``` **步骤条调整:** ```javascript const certificationSteps = [ { key: 'enterprise_info', title: '填写企业信息', ... }, { key: 'manual_review', title: '人工审核', ... }, { key: 'platform_select', title: '选择签署平台', ... }, // 新增 { key: 'enterprise_verify', title: '企业认证', ... }, { key: 'contract_sign', title: '签署合同', ... }, { key: 'completed', title: '完成', ... }, ] ``` ### 7.2 步骤路由逻辑(`setCurrentStepByStatus`) ```javascript // 审核已通过,但未选平台 if (status === 'info_pending_review' && metadata.manual_review_status === 'approved' && !data.sign_platform) { currentStep.value = 'platform_select' return } // 或:status === 'info_submitted' && !metadata.auth_url && metadata.need_select_platform ``` ### 7.3 PlatformSelect.vue 交互 - 调用 `GET /sign-platforms` 获取可选平台卡片(e签宝 / 法大大) - 每张卡片展示:平台名称、说明、预计耗时、合规提示 - 用户点击「确认选择」→ `POST /select-sign-platform` - 成功后跳转 `enterprise_verify`,iframe 加载返回的 `auth_url` ### 7.4 API 封装(`src/api/index.js`) ```javascript export const certificationApi = { // ...existing getSignPlatforms: () => request.get('/certifications/sign-platforms'), selectSignPlatform: (data) => request.post('/certifications/select-sign-platform', data), } ``` ### 7.5 回调路由扩展 现有: - `/certification/callback/auth` - `/certification/callback/sign` 建议扩展(便于各平台 redirect_url 分离统计): - `/certification/callback/esign/auth` - `/certification/callback/esign/sign` - `/certification/callback/fadada/auth` - `/certification/callback/fadada/sign` `IframeCallback.vue` 可增加 `platform` 路由参数,回调后 `postMessage` 携带 `{ type: 'CHECK_STATUS', scene, platform }`。 ### 7.6 管理端 `certification-reviews/index.vue` 列表增加「签署平台」列;详情展示平台与第三方 flow ID,方便客服排查。 --- ## 8. 回调与轮询策略 | 环节 | e签宝(现有) | 法大大(新增) | 统一策略 | |------|---------------|----------------|----------| | 企业认证完成 | 回调 `AUTH_PASS` + 轮询 identity | 法大大认证回调 + 轮询认证状态 | 两平台均保留「回调 + 轮询」双保险 | | 合同签署完成 | 主要依赖 `confirm-sign` 轮询 | 同上 | `GET /details` 时按平台自动同步 | | 验签 | 当前已注释 | **必须实现** | 生产环境两个平台都开启验签 | --- ## 9. 实施分期建议 ### Phase 0 — 准备(1~2 天) - [ ] 法大大开放平台开户、创建应用、获取 AppID/AppSecret/openCorpId - [ ] 在法大大创建合作协议模板(控件与海宇业务字段对齐) - [ ] 确认沙箱环境与回调公网可达 ### Phase 1 — 抽象层 + 数据字段(3~5 天) - [x] `certifications.sign_platform` 及子表字段 - [x] `SignPlatform` 枚举 + `SignPlatformProvider` 接口 - [x] 将现有 e签宝 逻辑迁入 `EsignProvider`(行为不变) - [x] `PlatformRegistry` 注入容器 - [ ] 历史数据迁移脚本(AutoMigrate 已加列;存量可按需刷 `esign`) - [ ] 回归测试:现有 e签宝 全流程无回归 ### Phase 2 — 平台选择流程(2~3 天) - [x] 后端 `SelectSignPlatform` + `GetSignPlatforms` - [x] 调整 `AdminApproveSubmitRecord`(审核后不立即调第三方) - [x] 前端 `PlatformSelect.vue` + 步骤条改造 - [ ] 仅 e签宝 模式下验证「先选平台 → 再认证 → 再签署」 ### Phase 3 — 法大大 Provider(5~8 天) - [x] 接入 HTTP/SDK 封装,实现 `FadadaProvider`(骨架 + OpenAPI 封装) - [x] 企业认证链接、实名查询(代码层) - [x] 模板填单、签署任务、状态查询、文件下载(代码层) - [x] `/callbacks/fadada` + 验签钩子 - [ ] 法大大沙箱端到端联调(需有效凭证与模板) ### Phase 4 — 收尾(2~3 天) - [x] 管理端展示 `sign_platform` / 待选平台 - [ ] 启用 e签宝 回调验签(安全债补齐) - [ ] 文档与 Swagger 更新 - [ ] 生产配置与灰度发布 **预估总工期:12~18 个工作日**(1 人全职,含联调;法大大审核/模板配置时间另计) --- ## 10. 风险与注意事项 | 风险 | 说明 | 缓解 | |------|------|------| | 两平台企业实名不互通 | 选法大大则必须在法大大完成企业认证 | 平台选择页明确提示;选定后锁定 | | 模板不一致 | 两份模板需法务分别审核 | 上线前做法务对齐检查清单 | | 签署状态码差异 | e签宝 `2`=完成;法大大状态码不同 | 封装在各自 Provider 内,对外统一 `SignStatusResult` | | 经办人信息 | 当前 e签宝 用法人作经办人 | 法大大侧同步规则;授权代表字段映射需确认 | | 合同文件过期 | e签宝 50 分钟过期重生成 | 法大大侧确认是否有过期策略,在 Provider 实现 | | 进行中的用户 | 迁移时默认 `esign` | 仅新用户或管理员重置后才出现平台选择 | --- ## 11. 待你决策的问题 在动手写代码前,建议确认以下事项: ### Q1. 平台选择时机 - [ ] **A. 审核通过后由用户选择**(推荐) - [ ] B. 提交企业信息时选择 - [ ] C. 其他:___________ ### Q2. 是否新增状态 `platform_pending` - [ ] **A. 不新增,用 metadata 驱动**(推荐,改动小) - [ ] B. 新增独立状态 ### Q3. e签宝 是否继续保留 - [ ] **A. 双平台并存,用户可选**(推荐) - [ ] B. 新用户仅法大大,e签宝 仅维护存量 - [ ] C. 全量迁移到法大大 ### Q4. 管理员审核后行为 - [ ] **A. 审核通过不生成链接,等用户选平台**(推荐) - [ ] B. 审核通过默认 e签宝 并生成链接,用户可在认证前改选法大大 ### Q5. 法大大接入方式 - [ ] **A. 官方 Go SDK**(推荐) - [ ] B. 自研 HTTP 封装(与 esign 包一致) ### Q6. 合同模板 - [ ] A. 法大大新建一份等价模板 - [ ] B. 非法大大模板,改用上传 PDF 方式发起签署 --- ## 12. 文件改动清单(实施时参考) ### 后端 | 文件 | 改动类型 | |------|----------| | `domains/certification/entities/certification.go` | 新增 `SignPlatform` 字段 | | `domains/certification/enums/sign_platform.go` | 新增 | | `domains/certification/ports/sign_platform_provider.go` | 新增接口 | | `infrastructure/external/esign/esign_provider.go` | 从 service_impl 抽取 | | `infrastructure/external/fadada/*` | 新增 | | `application/certification/certification_application_service_impl.go` | 改为调 registry | | `application/certification/dto/commands/` | 新增 `SelectSignPlatformCommand` | | `infrastructure/http/routes/certification_routes.go` | 新增路由 | | `infrastructure/http/handlers/certification_handler.go` | 新增 handler | | `config/config.go` + `config.yaml` | 新增 fadada / sign_platform 配置 | | `container/container.go` | 注册 FadadaProvider + Registry | ### 前端 | 文件 | 改动类型 | |------|----------| | `pages/certification/index.vue` | 步骤与状态映射 | | `pages/certification/components/PlatformSelect.vue` | 新增 | | `pages/certification/IframeCallback.vue` | 支持多平台 | | `src/api/index.js` | 新增 API | | `src/router/index.js` | 扩展 callback 路由 | | `pages/admin/certification-reviews/index.vue` | 展示 sign_platform | --- ## 13. 总结 本次改造的核心不是「替换 e签宝」,而是: 1. **主表增加 `sign_platform`**,记录用户选择的签署平台 2. **抽象 `SignPlatformProvider`**,将 e签宝 现有实现原样迁入 3. **审核通过后增加平台选择步骤**,再按平台发放认证/签署链接 4. **新增法大大 Provider**,与 e签宝 并行 这样对存量 e签宝 用户零影响,新用户可自由选择;后续若再接入第三方,只需新增 Provider 实现并加入 `enabled` 列表。 --- *请确认第 11 节中的决策项后,可据此进入具体开发。*