Skill: alex098929/shuzhi-open (version 1.0.3)

--- name: shuzhi-open description: 数秦开放平台统一接口封装，支持区块链API服务、自动化取证、保管单组件、电子签章 keywords: - 数秦 - 区块链 - 上链 - 自动化取证 - 保管单 - 电子签章 - 电子印章 - 签章 - 合同 - 合同签署 --- # shuzhi-open 数秦开放平台统一接口封装，支持区块链API服务、自动化取证、保管单组件、电子签章。 > ⚠️ **重要原则：所有不确定的信息必须询问用户，禁止自动填充！** > > 本 skill 涉及法律效力文件（保管单、电子合同等），所有字段必须： > - ✅ 由用户明确提供 > - ✅ 来源可追溯 > - ✅ 格式已确认 > > **禁止行为：** > - ❌ 自动填充任何未确认的字段 > - ❌ 假设字段的计算方式（如 evidenceHash） > - ❌ 猜测模板的字段定义 ## 配置 编辑 `config.json`，填入你的凭证和产品标识： ```json { "baseUrl": "接口地址", "appKey": "你的 appKey", "appSecret": "你的 appSecret", "products": { "chain": { "endpoints": { "upload": { "productId": "上链产品标识" }, "queryResult": { "productId": "查询结果产品标识" } } }, "evidence": { "productId": "自动化取证产品标识" }, "certificate": { "productId": "保管单产品标识" }, "sign": { "productId": "电子签章产品标识" } } } ``` > ⚠️ 注意： > - `appKey` 和 `appSecret` 由数秦开放平台提供 > - 区块链API服务的每个接口都有独立的产品标识，需要分别配置 ## 用法 ### 区块链API服务 支持数据上链存证，支持 `tritium` 和 `conflux` 两条链。 #### 数据上链 ```bash node scripts/chain/upload.js --data "业务数据" ``` #### 带可选参数上链 ```bash node scripts/chain/upload.js --data '{"ano":"BQ123","sha256":"xxx"}' --business EVIDENCE_PRESERVATION --source BQ_INTERNATIONAL ``` #### 批量查询上链结果 ```bash node scripts/chain/query.js --index "交易索引" ``` #### 查询多个交易 ```bash node scripts/chain/query.js --index "索引1,索引2,索引3" ``` --- ### 自动化取证 支持拼多多、淘宝、抖音、公众号取证。 **⚠️ 重要：所有不确定的信息必须询问用户！** **取证类型：** - `1` - 拼多多电商取证 - `3` - 淘宝电商取证 - `4` - 抖音电商取证 - `12` - 公众号取证 **🌟 推荐：交互式取证（会询问所有必要信息）** ```bash node scripts/evidence/create-task-interactive.js ``` #### 创建取证任务（命令行） ```bash node scripts/evidence/create-task.js --batch-id "批次ID" --url "https://..." --type 1 --att-id "保全号" ``` --- ### 📋 自动化取证流程（交互式） **核心原则：所有不确定的信息必须询问用户！** #### Step 1: 询问取证类型 ``` 📋 请选择取证类型： | 序号 | 类型 | 说明 | |------|------|------| | 1 | 拼多多电商取证 | 商品详情页、店铺信息等 | | 3 | 淘宝电商取证 | 商品详情页、店铺信息等 | | 4 | 抖音电商取证 | 商品详情页、店铺信息等 | | 12 | 公众号取证 | 公众号文章内容 | 请输入序号或类型代码： ``` #### Step 2: 询问取证信息 ``` ❓ 请提供以下信息： 取证类型: [已选择] 取证信息: 取证URL: [填写网页地址] 批次ID: [填写] 保全号: [填写] 取证名称: [可选，直接回车跳过] 可选参数: 是否取证营业执照: [yes/no] ``` **示例回复：** ``` 取证类型: 拼多多电商取证 取证信息: 取证URL: https://mobile.yangkeduo.com/goods.html?goods_id=xxx 批次ID: batch-001 保全号: att-001 取证名称: 测试取证 可选参数: 是否取证营业执照: no ``` #### Step 3: 确认信息 ``` 📝 确认信息 取证类型: 拼多多电商取证 取证信息: 取证URL: https://mobile.yangkeduo.com/goods.html?goods_id=xxx 批次ID: batch-001 保全号: att-001 取证名称: 测试取证 可选参数: 是否取证营业执照: 否 确认创建取证任务？(yes/no) ``` #### Step 4: 执行并返回结果 ``` 确认后执行： 1. 创建取证任务 2. 返回任务状态 3. 提供查询和下载方式 ``` --- ### ❓ 必须询问的问题 | 信息 | 必须询问的问题 | |------|----------------| | 取证类型 | 选择哪种取证类型？ | | URL | 要取证的网页地址是什么？ | | 批次ID | 如何标识这批取证任务？ | | 保全号 | 如何标识单个取证任务？ | | 是否需要营业执照 | 是否需要取证营业执照？（可选） | --- ### CAP-保管单组件 生成和管理保管单 PDF 文件。 **🌟 推荐：交互式生成（会询问所有不确定的信息）** ```bash node scripts/certificate/generate-interactive.js ``` #### 生成保管单（命令行） ```bash node scripts/certificate/create.js --file params.json ``` #### 下载保管单 ```bash node scripts/certificate/download.js --cert-no "存证编号" ``` #### 获取模板列表 ```bash node scripts/certificate/templates.js ``` --- ### 电子签章 完整的电子签章流程管理。 **⚠️ 重要：所有不确定的信息必须询问用户！** **🌟 推荐：AI 助手通过对话收集信息后调用** ```bash node scripts/sign/workflow.js --contract "合同文件路径" --signers '[{"signerNo":"甲方","type":"enterprise",...}]' ``` **交互式模式（用户直接在终端输入）** ```bash node scripts/sign/workflow.js --interactive ``` **使用配置文件** ```bash node scripts/sign/workflow.js --file signers.json ``` #### 企业主体管理 ```bash # 创建企业 node scripts/sign/enterprise.js create --name "公司名称" --credit-code "统一社会信用代码" # 查询企业列表 node scripts/sign/enterprise.js list --name "公司名称" # 查询企业详情 node scripts/sign/enterprise.js detail --user-id "用户ID" # 删除企业 node scripts/sign/enterprise.js delete --user-id "用户ID" ``` #### 个人主体管理 ```bash # 创建个人 node scripts/sign/person.js create --name "姓名" --id-card "身份证号" # 查询个人列表 node scripts/sign/person.js list --name "姓名" # 查询个人详情 node scripts/sign/person.js detail --user-id "用户ID" # 删除个人 node scripts/sign/person.js delete --user-id "用户ID" ``` #### 签署流程管理 ```bash # 创建签署流程 node scripts/sign/sign-flow.js create --file params.json # 查询流程详情 node scripts/sign/sign-flow.js detail --flow-id "流程ID" # 获取签署链接 node scripts/sign/sign-flow.js preview --flow-id "流程ID" # 获取签署文件 node scripts/sign/sign-flow.js files --flow-id "流程ID" # 获取签署方列表 node scripts/sign/sign-flow.js signers --flow-id "流程ID" # 下载签署后的文件 node scripts/sign/sign-flow.js download --flow-id "流程ID" [--output "输出路径"] ``` --- ## 📋 电子签章流程（交互式） **核心原则：所有不确定的信息必须询问用户！** ### Step 1: 询问合同文件和签署方数量 ``` ❓ 请提供以下信息： 合同文件: 文件路径: [填写PDF/DOCX/DOC路径，最大10MB] 签署方: 共几个签署方: [填写总数，最多6个] 其中企业方: [填写几方企业] 其中个人方: [填写几方个人] ``` ### Step 2: 提供信息填写模板 根据签署方数量，提供填写模板： ``` 【企业方 1】 企业名称: [填写] 统一社会信用代码: [填写] 联系人姓名: [填写] 联系人身份证: [填写] 联系人手机: [填写] 【个人方 1】 姓名: [填写] 身份证: [填写] 手机: [填写] （如有多个企业方/个人方，按序号继续：企业方 2、个人方 2...） ``` ### Step 3: 确认信息 ``` 📝 确认信息 合同文件: [文件路径] 签署方: [X]方企业 + [Y]方个人 【企业方】 签署方标识: xxx 企业名称: xxx ... 【个人方】 签署方标识: xxx 姓名: xxx ... 确认创建签署流程？(yes/no) ``` ### Step 4: 执行流程 用户确认后，AI 助手执行脚本： ```bash node scripts/sign/workflow.js --contract "合同文件路径" --signers '[ { "signerNo": "甲方", "type": "enterprise", "enterpriseName": "企业名称", "creditCode": "统一社会信用代码", "contactName": "联系人姓名", "contactIdCard": "联系人身份证", "contactMobile": "联系人手机" }, { "signerNo": "乙方", "type": "person", "name": "姓名", "idCard": "身份证号", "mobile": "手机号" } ]' ``` **注意**： - `--signers` 参数需要用单引号包裹 JSON 字符串 - 企业签署方 `type` 为 `"enterprise"` - 个人签署方 `type` 为 `"person"` 执行结果： 1. 创建主体（企业/个人） 2. 创建印章 3. 上传合同文件 4. 创建合同模板 5. 返回预览链接（用户配置签署位置） --- ### ❓ 必须询问的问题 | 信息 | 必须询问的问题 | |------|----------------| | 合同文件 | 合同文件路径是什么？文件格式是否符合要求？ | | 签署方数量 | 共有几个签署方？ | | 签署主体类型 | 是企业签署还是个人签署？ | | 企业信息 | 企业名称、统一社会信用代码是什么？ | | 个人信息 | 姓名、身份证号、手机号是什么？ | | 印章样式 | 使用什么印章样式？（默认：模板印章） | --- ### ⚠️ 禁止行为 **❌ 禁止自动填充以下信息：** - 签署方的姓名、身份证、手机号 - 企业名称、统一社会信用代码 - 合同文件路径 - 印章样式（除非用户确认使用默认） **❌ 禁止假设：** - 假设用户只有一个签署方 - 假设签署方的角色（甲方/乙方） - 假设印章样式或字体 --- ### 信息收集模板 请用户提供以下信息： **1. 合同文件（必填）** - 合同文件路径（支持格式：PDF、DOCX、DOC，最大10MB） **2. 签署方信息（最多6个）** | 字段 | 说明 | 示例 | |------|------|------| | 签署方标识 | 如：甲方、乙方、丙方等 | 甲方 | | 签署主体类型 | 企业 或 个人 | 企业 | | 企业名称 | 公司全称（企业必填） | XX科技有限公司 | | 统一社会信用代码 | 18位信用代码（企业必填） | 91XXXXXXXXXXXXXXX | | 姓名 | 签署人姓名（个人必填）/ 联系人姓名（企业必填） | 张三 | | 身份证 | 18位身份证号 | 36XXXXXXXXXXXXXXX | | 手机号码 | 11位手机号 | 138XXXXXXXX | **注意**：用户必须先提供合同文件路径，才能创建合同模板。 **示例格式**： ``` 【合同文件】 - 文件路径：D:\合同文件\contract.pdf 【甲方】 - 签署主体类型：企业 - 企业名称：XX科技有限公司 - 统一社会信用代码：91XXXXXXXXXXXXXXX - 姓名：张三 - 身份证：36XXXXXXXXXXXXXXX - 手机号码：138XXXXXXXX 【乙方】 - 签署主体类型：个人 - 姓名：李四 - 身份证：33XXXXXXXXXXXXXXX - 手机号码：139XXXXXXXX ``` **说明**： - 签署主体类型填写"企业"或"个人" - 企业签署方需填写企业名称和统一社会信用代码 - 个人签署方无需填写企业相关字段 ### Step 0: 信息校验 **检查签署方是否存在，并验证信息一致性：** 1. **企业校验** - 根据「统一社会信用代码」查询企业是否存在 - 如果存在：检查「企业名称」是否与系统记录一致 - 如果不一致：返回错误提示，告知用户系统中该信用代码对应的企业名称 2. **个人校验** - 根据「身份证号」查询个人是否存在 - 如果存在：检查「姓名」是否与系统记录一致 - 如果不一致：返回错误提示，告知用户系统中该身份证对应的姓名 **校验失败示例：** ``` ❌ 信息校验失败 【甲方】企业信息不一致： - 提供的企业名称：XXX公司 - 系统中该信用代码对应的企业名称：YYY公司 - 统一社会信用代码：91340124MA8N303B80 请确认企业名称是否正确，或联系管理员处理。 ``` ### Step 1: 创建主体 - **企业签署**：创建企业主体 → 获取企业的 userId - **个人签署**：创建个人主体 → 获取个人的 userId ### Step 2: 创建印章 - **企业签署**：为企业创建印章（默认：enterpriseSealType: 1, sealMode: 2） - **个人签署**：为个人创建签名（默认：sealMode: 2） ### Step 3: 上传合同文件 - 上传待签署的合同文件 - **支持格式**：PDF、DOCX、DOC - **文件大小**：最大10MB ### Step 4: 创建合同模板 - 创建模板，配置签署方 ### Step 5: 获取模板预览链接 - 返回预览链接供用户配置签署位置 - **链接格式**：使用超链接格式，用户可直接点击打开配置 ``` 📋 模板预览链接：[点击打开配置]({preview_url}) ``` ### Step 6: 创建签署流程 根据签署主体类型使用对应的 userId： **企业签署：** ```json { "signerNo": "甲方", "userId": "{enterprise_user_id}", // 企业的 userId "name": "{contact_name}", // 联系人姓名 "idCard": "{contact_idcard}", // 联系人身份证 "mobile": "{contact_mobile}" // 联系人手机 } ``` **个人签署：** ```json { "signerNo": "乙方", "userId": "{person_user_id}", // 个人的 userId "name": "{person_name}", // 个人姓名 "idCard": "{person_idcard}", // 个人身份证 "mobile": "{person_mobile}" // 个人手机 } ``` ### Step 7: 获取签署链接 - 返回 PC 预览链接和 H5 签署链接 - **链接格式**： - 模板预览链接：超链接格式，点击可直接配置 - 签署链接：同时提供超链接和纯文本链接（方便手机签署时复制） ``` 📋 模板预览链接：[点击打开配置]({preview_url}) 🖥️ PC预览链接：[点击打开]({preview_url}) 📱 H5签署链接：[点击签署]({sign_url}) 或复制链接：{sign_url} ``` ### 输出结果 流程完成后，系统将返回： 1. **主体信息** - userId、sealId（企业或个人） 2. **合同模板信息** - templateId 3. **模板预览链接** - 超链接格式，点击可直接配置签署位置 4. **签署流程信息** - signFlowId、signFlowNo 5. **签署链接** - PC预览链接、H5签署链接（超链接格式，可直接点击） --- ## 代码调用 也可在代码中直接调用模块： ```javascript const path = require('path'); const { init } = require('./lib/client'); const chain = require('./lib/modules/chain'); const evidence = require('./lib/modules/evidence'); const certificate = require('./lib/modules/certificate'); const sign = require('./lib/modules/sign'); // 初始化配置 const config = require(path.join(__dirname, 'config.json')); init(config); // 区块链API服务 async function demoChain() { // 上链 const result = await chain.upload('{"ano":"BQ123","sha256":"xxx"}'); console.log('交易索引:', result.index); console.log('请求ID:', result.requestId); // 查询结果 const status = await chain.queryResult([result.index]); console.log('上链状态:', status); } // 自动化取证 async function demoEvidence() { // 创建任务 const task = await evidence.createTask('batch-001', [{ att_id: 'att-001', url: 'https://example.com', evidence_name: '测试取证', type: 1 }]); console.log('任务创建:', task); // 等待完成 const result = await evidence.waitForComplete('att-001'); console.log('取证结果:', result); } ``` --- ## 回调验签 电子签章组件的回调需要验证签名： ```javascript const { verifyCallback } = require('./lib/callback'); // 验证回调 app.post('/callback', (req, res) => { const params = req.body; if (!verifyCallback(params, config.appSecret)) { return res.status(400).json({ error: '签名验证失败' }); } // 处理回调 console.log('回调数据:', params); res.json({ code: 200 }); }); ``` --- ## 🧪 开发规则 在开发调试接口时，遵循以下规则： ### 1. 不确定信息必须询问 对于 API 文档中**未明确说明**的字段或参数，**必须询问用户**，禁止自行假设或填充。 **示例 - 需要询问的情况**: - 字段的值应该从哪个接口获取？ - 字段的计算规则是什么？（如哈希算法） - 字段的格式要求是什么？ - 字段之间的关联关系是什么？ **错误做法**: ```javascript // ❌ 错误：自行假设 evidenceHash 的计算方式 const evidenceHash = crypto.createHash('sha256').update(data).digest('hex'); ``` **正确做法**: ``` ✅ 正确：询问用户 "evidenceHash 字段应该如何获取？是从上链接口返回，还是需要自行计算？如果需要计算，使用什么算法？" ``` ### 2. 数据来源追溯 开发时需要明确每个字段的数据来源： | 字段类型 | 来源说明 | |----------|----------| | 用户输入 | 由用户提供（如姓名、身份证号） | | 接口返回 | 从其他接口响应中获取 | | 系统生成 | 由系统自动生成（如时间戳、UUID） | | 计算得出 | 需明确计算规则，并确认规则来源 | ### 3. 开发前确认 在编写代码前，需要确认： 1. 所有必填字段的数据来源 2. 关联字段之间的依赖关系 3. 字段的格式和取值范围 --- ## 📋 保管单生成流程（交互式） **⚠️ 核心原则：所有不确定的信息必须询问用户！** 当用户请求生成保管单时，采用**多次交互确认**的方式： ### Step 1: 查询并选择模板 ``` 📋 可用的保管单模板： | 序号 | 模板ID | 模板名称 | |------|--------|----------| | 1 | 2036284038445596672 | hash存证 | | 2 | 2036020528155262976 | 自动化取证模版111 | 请选择要使用的模板（输入序号或模板ID）： ``` ### Step 2: 询问字段信息 **⚠️ 重要：模板字段因客户而异，必须询问用户！** ``` ❓ 请提供以下信息： 模板ID: [填写模板ID或序号] 默认字段: 申请人: [填写] 账号: [填写] [其他字段...] 自定义字段: [字段名]: [值] [如无则写"无"] ``` **示例回复：** ``` 模板ID: 2036020528155262976 默认字段: 申请人: 张三 账号: zhangsan 自定义字段: testname: 中欧城 ``` ### Step 3: 确认信息 **展示完整信息让用户确认：** ``` 📝 确认信息 模板ID: 2036020528155262976 (自动化取证模版111) 默认字段: 申请人: 张三 账号: zhangsan 自定义字段: testname: 中欧城 上链数据: applicantName: 张三 applicantAccount: zhangsan testname: 中欧城 timestamp: （将在上链时自动生成） 确认生成保管单？(yes/no) ``` ### Step 4: 执行并返回结果 ``` 用户确认后执行： 1. 数据上链 → 获取交易索引 2. 查询上链结果 → 获取区块链哈希、区块高度 3. 生成保管单 → 获取存证编号 4. 获取下载链接 → 返回 PDF 下载地址 ``` --- ### 📌 API 参数格式 **自定义字段格式：** ```json { "templateId": "模板ID", "requestId": "请求ID", "defaultFields": { "applicantName": "张三", "applicantAccount": "zhangsan", "evidenceHash": "...", "blockchainHash": "...", "blockchainHeight": "...", "attestTime": "..." }, "customFieldList": [ { "name": "testname", "value": "中欧城" } ] } ``` --- ### 📌 字段取值逻辑 | 字段 | 来源 | 计算方式 | |------|------|----------| | `evidenceHash` | 计算得出 | SHA256(JSON.stringify(上链数据)) | | `blockchainHash` | 接口返回 | chain_results[].hash（交易哈希） | | `blockchainHeight` | 接口返回 | chain_results[].height | | `attestTime` | 系统生成 | 上链成功后的北京时间 | **⚠️ 注意：** - `blockchainHash` 使用 `block_hash`（区块哈希），不是 `hash`（交易哈希） - `attestTime` 在上链成功后获取北京时间 --- ### 📌 字段来源确认表 **必须向用户确认每个字段的来源：** | 字段 | 必须确认的问题 | |------|----------------| | evidenceHash | 从哪个接口获取？还是自行计算？用什么算法？ | | blockchainHash | 从上链结果的哪个字段获取？ | | blockchainHeight | 从上链结果的哪个字段获取？ | | attestTime | 使用上链时间还是当前时间？格式要求？ | | 自定义字段 | 模板定义了哪些字段？必填还是可选？ | --- ### ❓ 询问提示模板 **当字段来源不明时，使用以下提示：** ``` ❓ 需要确认：[字段名] 该字段应该： A. 从上链接口返回获取 B. 自行计算（请指定算法） C. 手动填写 请选择并提供具体值： ``` --- ### ⚠️ 禁止行为 **❌ 错误示例（禁止这样做）：** ```javascript // 禁止：自行假设 evidenceHash 计算方式 const evidenceHash = crypto.createHash('sha256').update(data).digest('hex'); // 禁止：自动填充时间 const attestTime = new Date().toISOString(); // 禁止：假设模板字段 if (!params.testname) { params.testname = 'default'; // ❌ 禁止！ } ``` **✅ 正确做法：** ```javascript // 1. 先检查字段是否存在 if (!params.evidenceHash) { // 2. 询问用户如何获取 console.log('❓ evidenceHash 字段未提供。'); console.log(' 该字段应该：'); console.log(' A. 从上链接口返回获取'); console.log(' B. 自行计算（请指定算法）'); console.log(' C. 手动填写'); // 3. 等待用户确认后再继续 } ``` --- ## API 文档 详见 `references/` 目录： - `chain-api.md` - 区块链API服务文档 - `evidence-api.md` - 自动化取证 API 文档 - `certificate-api.md` - 保管单组件 API 文档 - `sign-api.md` - 电子签章 API 文档 --- ## 环境说明 线上环境请使用 `shuqin-open` skill。 --- ## 错误处理 所有接口调用失败会抛出错误，建议使用 try-catch： ```javascript try { const result = await chain.upload('data'); console.log(result); } catch (error) { console.error('请求失败:', error.message); } ``` --- ## 状态枚举 ### 上链状态 | 值 | 说明 | |----|------| | 0 | 上链失败 | | 1 | 上链成功 | | 2 | 等待上链 | ### 批次状态 | 值 | 说明 | |----|------| | NEW | 新建 | | ASSIGNED | 已分配 | | LOGIN | 账号登录中 | | CHECKING | 账号自检中 | | CHECKED | 账号自检完成 | | UNDERWAY | 取证中 | | SUCCESSED | 取证成功 | | FAILED | 取证失败 | ### 任务状态 | 值 | 说明 | |----|------| | NEW | 新建 | | RUNNING | 执行中 | | SUCCESSED | 成功 | | FAILED | 失败 |

path: config.json
content: {
  "baseUrl": "https://test-apisix-gateway.shuzhi.shuqinkeji.cn",
  "appKey": "",
  "appSecret": "",
  
  "products": {
    "chain": {
      "name": "区块链API服务",
      "chains": ["tritium", "conflux"],
      "status": {
        "0": "上链失败",
        "1": "上链成功",
        "2": "等待上链"
      },
      "endpoints": {
        "upload": {
          "path": "/2036009920811413506",
          "productId": "",
          "description": "数据上链"
        },
        "queryResult": {
          "path": "/2036010888269574146",
          "productId": "",
          "description": "批量查询上链结果"
        }
      }
    },
    
    "evidence": {
      "name": "自动化取证",
      "productId": "",
      "types": {
        "1": "拼多多电商取证",
        "3": "淘宝电商取证",
        "4": "抖音电商取证",
        "12": "公众号取证"
      },
      "batchStatus": {
        "NEW": "新建",
        "ASSIGNED": "已分配",
        "LOGIN": "账号登录中",
        "CHECKING": "账号自检中",
        "CHECKED": "账号自检完成",
        "UNDERWAY": "取证中",
        "SUCCESSED": "取证成功",
        "FAILED": "取证失败"
      },
      "taskStatus": {
        "NEW": "新建",
        "RUNNING": "执行中",
        "SUCCESSED": "成功",
        "FAILED": "失败"
      },
      "endpoints": {
        "createTask": "/1864501867832590338",
        "queryBatch": "/1869632051095191554",
        "queryTask": "/1869632633247809538",
        "downloadZip": "/1871136405706948610",
        "createWechatTask": "/1934901092301066241",
        "checkDevice": "/1869193676039045121",
        "assignDevice": "/1869194087395409922"
      }
    },
    
    "certificate": {
      "name": "CAP-保管单组件",
      "productId": "",
      "endpoints": {
        "create": "/2035964880718311426",
        "download": "/2035966403556847618",
        "listTemplates": "/2035964431437049858"
      }
    },
    
    "sign": {
      "name": "电子签章",
      "productId": "",
      "enums": {
        "enterpriseSealType": {
          "0": "模板印章",
          "1": "自定义印章"
        }
      },
      "endpoints": {
        "fileUpload": "/1939939844717101058",
        "wordToPdf": "/1942487146770022401",
        "fileDownload": "/1939940842202284033",
        "sealFileUpload": "/1939941330071142402",
        "fileDetail": "/1939941885120167937",
        "enterpriseCreate": "/1939942440815116290",
        "enterpriseList": "/1947556996631351298",
        "enterpriseDetail": "/1939942904034050049",
        "enterpriseDelete": "/1939943360546291713",
        "personCreate": "/1939944063343869953",
        "personList": "/1947552623800995841",
        "personDetail": "/1939944413270458370",
        "personDelete": "/1939944954960625665",
        "enterpriseSealCreate": "/1939945266308005890",
        "enterpriseSealDetail": "/1939945814935552002",
        "enterpriseSealList": "/1939946352716627969",
        "enterpriseSealDelete": "/1939947036048441346",
        "personSealCreate": "/1939947647133368321",
        "personSealDetail": "/1939948122104741889",
        "personSealList": "/1939948731725856770",
        "personSealDelete": "/1939949215664652289",
        "templateCreate": "/1939949740388859906",
        "templateDetail": "/1952183563027537922",
        "templatePreview": "/1939950972654403586",
        "templateDelete": "/1939951572360183809",
        "signFlowCreate": "/1939952221487448066",
        "signFlowDetail": "/1939953420018204673",
        "signFlowPreview": "/1939953748759363586",
        "signFlowFiles": "/1939954027504418817",
        "signFlowSigners": "/1942050249623027714"
      }
    }
  }
}

path: package.json
content: {
  "name": "shuzhi-open",
  "version": "1.0.0",
  "description": "数秦开放平台统一接口",
  "main": "lib/client.js",
  "scripts": {
    "chain:upload": "node scripts/chain/upload.js",
    "chain:query": "node scripts/chain/query.js",
    "evidence:create": "node scripts/evidence/create-task.js",
    "evidence:query": "node scripts/evidence/query.js",
    "evidence:download": "node scripts/evidence/download.js",
    "evidence:device": "node scripts/evidence/device.js",
    "cert:create": "node scripts/certificate/create.js",
    "cert:download": "node scripts/certificate/download.js",
    "sign:enterprise": "node scripts/sign/enterprise.js",
    "sign:person": "node scripts/sign/person.js",
    "sign:flow": "node scripts/sign/sign-flow.js"
  },
  "keywords": [
    "shuzhi",
    "blockchain",
    "evidence",
    "sign",
    "test"
  ],
  "license": "MIT",
  "engines": {
    "node": ">=18.0.0"
  }
}

path: scripts/sign/enterprise.js
content: #!/usr/bin/env node
/**
 * 电子签章 - 企业主体管理
 */

const path = require('path');

const libPath = path.resolve(__dirname, '../../lib');
const configPath = path.resolve(__dirname, '../../config.json');

const { init } = require(path.join(libPath, 'client'));
const sign = require(path.join(libPath, 'modules/sign'));

const config = require(configPath);
init(config);

function parseArgs() {
  const args = process.argv.slice(2);
  const params = { action: args[0] };
  
  for (let i = 1; i < args.length; i++) {
    const arg = args[i];
    if (arg.startsWith('--')) {
      const key = arg.slice(2);
      const value = args[i + 1];
      if (value && !value.startsWith('--')) {
        params[key] = value;
        i++;
      }
    }
  }
  
  return params;
}

async function main() {
  const params = parseArgs();
  const action = params.action;
  
  try {
    switch (action) {
      case 'create':
        if (!params.name || !params['credit-code']) {
          console.error('用法: node enterprise.js create --name "公司名称" --credit-code "统一社会信用代码"');
          process.exit(1);
        }
        const createResult = await sign.enterprise.create(params.name, params['credit-code']);
        console.log(JSON.stringify({ success: true, data: createResult }, null, 2));
        break;
        
      case 'list':
        const listResult = await sign.enterprise.list(params.name, params['credit-code']);
        console.log(JSON.stringify({ success: true, data: listResult }, null, 2));
        break;
        
      case 'detail':
        if (!params['user-id']) {
          console.error('用法: node enterprise.js detail --user-id "用户ID"');
          process.exit(1);
        }
        const detailResult = await sign.enterprise.detail(params['user-id']);
        console.log(JSON.stringify({ success: true, data: detailResult }, null, 2));
        break;
        
      case 'delete':
        if (!params['user-id']) {
          console.error('用法: node enterprise.js delete --user-id "用户ID"');
          process.exit(1);
        }
        const deleteResult = await sign.enterprise.delete(params['user-id']);
        console.log(JSON.stringify({ success: true, data: deleteResult }, null, 2));
        break;
        
      default:
        console.error('用法:');
        console.error('  node enterprise.js create --name "公司名称" --credit-code "统一社会信用代码"');
        console.error('  node enterprise.js list [--name "公司名称"] [--credit-code "信用代码"]');
        console.error('  node enterprise.js detail --user-id "用户ID"');
        console.error('  node enterprise.js delete --user-id "用户ID"');
        process.exit(1);
    }
  } catch (error) {
    console.error(JSON.stringify({ success: false, error: error.message }, null, 2));
    process.exit(1);
  }
}

main();

path: scripts/sign/person.js
content: #!/usr/bin/env node
/**
 * 电子签章 - 个人主体管理
 */

const path = require('path');

const libPath = path.resolve(__dirname, '../../lib');
const configPath = path.resolve(__dirname, '../../config.json');

const { init } = require(path.join(libPath, 'client'));
const sign = require(path.join(libPath, 'modules/sign'));

const config = require(configPath);
init(config);

function parseArgs() {
  const args = process.argv.slice(2);
  const params = { action: args[0] };
  
  for (let i = 1; i < args.length; i++) {
    const arg = args[i];
    if (arg.startsWith('--')) {
      const key = arg.slice(2);
      const value = args[i + 1];
      if (value && !value.startsWith('--')) {
        params[key] = value;
        i++;
      }
    }
  }
  
  return params;
}

async function main() {
  const params = parseArgs();
  const action = params.action;
  
  try {
    switch (action) {
      case 'create':
        if (!params.name || !params['id-card']) {
          console.error('用法: node person.js create --name "姓名" --id-card "身份证号"');
          process.exit(1);
        }
        const createResult = await sign.person.create(params.name, params['id-card']);
        console.log(JSON.stringify({ success: true, data: createResult }, null, 2));
        break;
        
      case 'list':
        const listResult = await sign.person.list(params.name, params['id-card']);
        console.log(JSON.stringify({ success: true, data: listResult }, null, 2));
        break;
        
      case 'detail':
        if (!params['user-id']) {
          console.error('用法: node person.js detail --user-id "用户ID"');
          process.exit(1);
        }
        const detailResult = await sign.person.detail(params['user-id']);
        console.log(JSON.stringify({ success: true, data: detailResult }, null, 2));
        break;
        
      case 'delete':
        if (!params['user-id']) {
          console.error('用法: node person.js delete --user-id "用户ID"');
          process.exit(1);
        }
        const deleteResult = await sign.person.delete(params['user-id']);
        console.log(JSON.stringify({ success: true, data: deleteResult }, null, 2));
        break;
        
      default:
        console.error('用法:');
        console.error('  node person.js create --name "姓名" --id-card "身份证号"');
        console.error('  node person.js list [--name "姓名"] [--id-card "身份证号"]');
        console.error('  node person.js detail --user-id "用户ID"');
        console.error('  node person.js delete --user-id "用户ID"');
        process.exit(1);
    }
  } catch (error) {
    console.error(JSON.stringify({ success: false, error: error.message }, null, 2));
    process.exit(1);
  }
}

main();

path: scripts/sign/sign-flow.js
content: #!/usr/bin/env node
/**
 * 电子签章 - 签署流程管理
 */

const path = require('path');
const fs = require('fs');

const libPath = path.resolve(__dirname, '../../lib');
const configPath = path.resolve(__dirname, '../../config.json');

const { init, getConfig, getCredentials } = require(path.join(libPath, 'client'));
const { buildHeaders } = require(path.join(libPath, 'auth'));
const { getEndpointConfig } = require(path.join(libPath, 'validate'));
const sign = require(path.join(libPath, 'modules/sign'));

const config = require(configPath);
init(config);

function parseArgs() {
  const args = process.argv.slice(2);
  const params = { action: args[0] };
  
  for (let i = 1; i < args.length; i++) {
    const arg = args[i];
    if (arg.startsWith('--')) {
      const key = arg.slice(2);
      const value = args[i + 1];
      if (value && !value.startsWith('--')) {
        params[key] = value;
        i++;
      }
    }
  }
  
  return params;
}

/**
 * 下载签署后的文件
 */
async function downloadSignedFile(flowId, outputPath) {
  // 获取签署文件列表
  const filesResult = await sign.signFlow.files(flowId);
  const files = filesResult.data || filesResult;
  
  if (!files || files.length === 0) {
    throw new Error('未找到签署文件');
  }
  
  const file = files[0];
  const fileName = file.signResultPath || file.filePath;
  
  if (!fileName) {
    throw new Error('未找到签署结果文件路径');
  }
  
  // 下载文件
  const { path: apiPath, productId } = getEndpointConfig(getConfig(), 'sign', 'fileDownload');
  const credentials = getCredentials();
  
  const url = `${getConfig().baseUrl}${apiPath}`;
  const body = { fileName };
  
  const headers = buildHeaders('POST', apiPath, body, {
    appKey: credentials.appKey,
    appSecret: credentials.appSecret,
    productId
  });
  
  const response = await fetch(url, {
    method: 'POST',
    headers,
    body: JSON.stringify(body)
  });
  
  if (!response.ok) {
    const text = await response.text();
    throw new Error(`下载失败: ${text}`);
  }
  
  const arrayBuffer = await response.arrayBuffer();
  const buffer = Buffer.from(arrayBuffer);
  
  // 确定输出路径
  if (!outputPath) {
    outputPath = path.join(process.cwd(), file.signResultPath || `signed_${flowId}.pdf`);
  }
  
  // 确保目录存在
  const dir = path.dirname(outputPath);
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir, { recursive: true });
  }
  
  fs.writeFileSync(outputPath, buffer);
  
  return {
    fileName: file.signResultPath,
    outputPath,
    size: buffer.length
  };
}

async function main() {
  const params = parseArgs();
  const action = params.action;
  
  try {
    switch (action) {
      case 'create':
        if (params.file) {
          const filePath = path.resolve(params.file);
          const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
          const createResult = await sign.signFlow.create(data);
          console.log(JSON.stringify({ success: true, data: createResult }, null, 2));
        } else {
          console.error('用法: node sign-flow.js create --file params.json');
          process.exit(1);
        }
        break;
        
      case 'detail':
        if (!params['flow-id']) {
          console.error('用法: node sign-flow.js detail --flow-id "流程ID"');
          process.exit(1);
        }
        const detailResult = await sign.signFlow.detail(params['flow-id']);
        console.log(JSON.stringify({ success: true, data: detailResult }, null, 2));
        break;
        
      case 'preview':
        if (!params['flow-id']) {
          console.error('用法: node sign-flow.js preview --flow-id "流程ID"');
          process.exit(1);
        }
        const previewResult = await sign.signFlow.preview(params['flow-id']);
        console.log(JSON.stringify({ 
          success: true, 
          data: {
            previewUrl: previewResult.previewUrl,
            signUrl: previewResult.signUrl
          }
        }, null, 2));
        break;
        
      case 'files':
        if (!params['flow-id']) {
          console.error('用法: node sign-flow.js files --flow-id "流程ID"');
          process.exit(1);
        }
        const filesResult = await sign.signFlow.files(params['flow-id']);
        console.log(JSON.stringify({ success: true, data: filesResult }, null, 2));
        break;
        
      case 'signers':
        if (!params['flow-id']) {
          console.error('用法: node sign-flow.js signers --flow-id "流程ID"');
          process.exit(1);
        }
        const signersResult = await sign.signFlow.signers(params['flow-id']);
        console.log(JSON.stringify({ success: true, data: signersResult }, null, 2));
        break;
        
      case 'download':
        if (!params['flow-id']) {
          console.error('用法: node sign-flow.js download --flow-id "流程ID" [--output "输出路径"]');
          process.exit(1);
        }
        const downloadResult = await downloadSignedFile(params['flow-id'], params.output);
        console.log(JSON.stringify({ 
          success: true, 
          data: {
            fileName: downloadResult.fileName,
            outputPath: downloadResult.outputPath,
            sizeKB: (downloadResult.size / 1024).toFixed(2)
          }
        }, null, 2));
        break;
        
      default:
        console.error('用法:');
        console.error('  node sign-flow.js create --file params.json');
        console.error('  node sign-flow.js detail --flow-id "流程ID"');
        console.error('  node sign-flow.js preview --flow-id "流程ID"');
        console.error('  node sign-flow.js files --flow-id "流程ID"');
        console.error('  node sign-flow.js signers --flow-id "流程ID"');
        console.error('  node sign-flow.js download --flow-id "流程ID" [--output "输出路径"]');
        process.exit(1);
    }
  } catch (error) {
    console.error(JSON.stringify({ success: false, error: error.message }, null, 2));
    process.exit(1);
  }
}

main();

path: scripts/sign/workflow.js
content: #!/usr/bin/env node
/**
 * 电子签章 - 完整流程脚本
 * 
 * 用法1: 使用配置文件
 * node scripts/sign/workflow.js --file signers.json
 * 
 * 用法2: 使用命令行参数（推荐）
 * node scripts/sign/workflow.js --contract "合同文件路径" --signers 'JSON字符串'
 * 
 * 用法3: 使用交互式输入
 * node scripts/sign/workflow.js --interactive
 */

const path = require('path');
const fs = require('fs');
const readline = require('readline');

const libPath = path.resolve(__dirname, '../../lib');
const configPath = path.resolve(__dirname, '../../config.json');

const { init } = require(path.join(libPath, 'client'));
const sign = require(path.join(libPath, 'modules/sign'));

const config = require(configPath);
init(config);

// 创建 readline 接口
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

/**
 * 信息校验
 */
async function validateSigners(signers) {
  const errors = [];
  const existingEntities = [];

  for (const s of signers) {
    if (s.type === 'enterprise') {
      const listResult = await sign.enterprise.list(s.enterpriseName, s.creditCode);
      const enterpriseList = listResult && listResult.data ? listResult.data : (listResult || []);
      
      if (enterpriseList.length > 0) {
        const existing = enterpriseList[0];
        if (existing.principalName !== s.enterpriseName) {
          errors.push({
            signerNo: s.signerNo,
            type: 'enterprise',
            field: 'enterpriseName',
            provided: s.enterpriseName,
            systemValue: existing.principalName,
            creditCode: s.creditCode
          });
        } else {
          existingEntities.push({
            signerNo: s.signerNo,
            type: 'enterprise',
            userId: existing.userId,
            exists: true
          });
        }
      } else {
        existingEntities.push({
          signerNo: s.signerNo,
          type: 'enterprise',
          exists: false
        });
      }
    } else if (s.type === 'person') {
      const listResult = await sign.person.list(s.name, s.idCard);
      const personList = listResult && listResult.data ? listResult.data : (listResult || []);
      
      if (personList.length > 0) {
        const existing = personList[0];
        if (existing.name !== s.name) {
          errors.push({
            signerNo: s.signerNo,
            type: 'person',
            field: 'name',
            provided: s.name,
            systemValue: existing.name,
            idCard: s.idCard
          });
        } else {
          existingEntities.push({
            signerNo: s.signerNo,
            type: 'person',
            userId: existing.userId,
            exists: true
          });
        }
      } else {
        existingEntities.push({
          signerNo: s.signerNo,
          type: 'person',
          exists: false
        });
      }
    }
  }

  return { errors, existingEntities };
}

/**
 * 创建主体
 */
async function createEntity(signer, existingEntity) {
  if (existingEntity.exists) {
    return { userId: existingEntity.userId, existed: true };
  }

  if (signer.type === 'enterprise') {
    try {
      const result = await sign.enterprise.create(signer.enterpriseName, signer.creditCode);
      return { userId: result.userId, existed: false };
    } catch (error) {
      if (error.message && error.message.includes('重复')) {
        console.log('    ⚠️ 企业已存在，使用现有记录...');
        const listResult = await sign.enterprise.list(signer.enterpriseName, signer.creditCode);
        const enterpriseList = listResult && listResult.data ? listResult.data : (listResult || []);
        if (enterpriseList.length > 0) {
          return { userId: enterpriseList[0].userId, existed: true };
        }
      }
      throw error;
    }
  } else {
    try {
      const result = await sign.person.create(signer.name, signer.idCard);
      return { userId: result.userId, existed: false };
    } catch (error) {
      if (error.message && error.message.includes('重复')) {
        console.log('    ⚠️ 个人已存在，使用现有记录...');
        const listResult = await sign.person.list(signer.name, signer.idCard);
        const personList = listResult && listResult.data ? listResult.data : (listResult || []);
        if (personList.length > 0) {
          return { userId: personList[0].userId, existed: true };
        }
      }
      throw error;
    }
  }
}

/**
 * 创建印章
 */
async function createSeal(signer, userId) {
  if (signer.type === 'enterprise') {
    const sealList = await sign.enterpriseSeal.list({ principalName: signer.enterpriseName });
    const seals = sealList && sealList.data ? sealList.data : (sealList || []);
    if (seals.length > 0) {
      return { sealId: seals[0].sealId, existed: true };
    }

    const sealResult = await sign.enterpriseSeal.create({
      userId: userId,
      sealName: signer.enterpriseName + '印章',
      enterpriseSealType: 1,
      sealMode: 2
    });
    
    return { sealId: sealResult.sealId, existed: false };
  } else {
    const sealList = await sign.personSeal.list(signer.name, signer.idCard);
    const seals = sealList && sealList.data ? sealList.data : (sealList || []);
    if (seals.length > 0) {
      return { sealId: seals[0].sealId, existed: true };
    }

    const sealResult = await sign.personSeal.create({
      userId: userId,
      sealName: signer.name + '签名',
      sealMode: 2
    });
    
    return { sealId: sealResult.sealId, existed: false };
  }
}

/**
 * 完整流程
 */
async function workflow(signersData) {
  const { signers, contractFile } = signersData;

  if (signers.length > 6) {
    throw new Error('签署方数量超过限制，最多支持6个签署方');
  }

  console.log('============================================================');
  console.log('电子签章流程');
  console.log('============================================================');
  console.log(`\n签署方数量: ${signers.length}`);

  // Step 0: 信息校验
  console.log('\n【Step 0】信息校验...');
  const { errors, existingEntities } = await validateSigners(signers);
  
  if (errors.length > 0) {
    let message = '❌ 信息校验失败\n\n';
    for (const err of errors) {
      if (err.type === 'enterprise') {
        message += `【${err.signerNo}】企业信息不一致：\n`;
        message += `- 提供的企业名称：${err.provided}\n`;
        message += `- 系统中该信用代码对应的企业名称：${err.systemValue}\n`;
        message += `- 统一社会信用代码：${err.creditCode}\n\n`;
      } else {
        message += `【${err.signerNo}】个人信息不一致：\n`;
        message += `- 提供的姓名：${err.provided}\n`;
        message += `- 系统中该身份证对应的姓名：${err.systemValue}\n`;
        message += `- 身份证号：${err.idCard}\n\n`;
      }
    }
    message += '请确认信息是否正确，或联系管理员处理。';
    console.log(message);
    return { success: false, errors };
  }
  console.log('✅ 信息校验通过');

  const preparedSigners = [];

  // Step 1: 创建主体
  console.log('\n【Step 1】创建主体...');
  for (let i = 0; i < signers.length; i++) {
    const s = signers[i];
    const existing = existingEntities[i];
    
    console.log(`  ${s.signerNo}: ${s.type === 'enterprise' ? s.enterpriseName : s.name}`);
    
    const entityResult = await createEntity(s, existing);
    console.log(`    userId: ${entityResult.userId} ${entityResult.existed ? '(已存在)' : '(新建)'}`);
    
    preparedSigners.push({
      ...s,
      userId: entityResult.userId
    });
  }

  // Step 2: 创建印章
  console.log('\n【Step 2】创建印章...');
  for (const s of preparedSigners) {
    console.log(`  ${s.signerNo}: ${s.type === 'enterprise' ? s.enterpriseName : s.name}`);
    
    const sealResult = await createSeal(s, s.userId);
    s.sealId = sealResult.sealId;
    console.log(`    sealId: ${s.sealId} ${sealResult.existed ? '(已存在)' : '(新建)'}`);
  }

  // Step 3: 上传合同文件
  console.log('\n【Step 3】上传合同文件...');
  const fileBuffer = fs.readFileSync(contractFile);
  const base64 = fileBuffer.toString('base64');
  console.log(`  文件: ${contractFile}`);
  console.log(`  大小: ${(fileBuffer.length / 1024).toFixed(2)} KB`);

  const uploadResult = await sign.file.upload(base64, path.basename(contractFile), '.pdf');
  const fileId = uploadResult.fileId || (uploadResult.data && uploadResult.data.fileId);
  console.log(`  ✅ fileId: ${fileId}`);

  // Step 4: 创建合同模板
  console.log('\n【Step 4】创建合同模板...');
  const templateName = `合同模板_${Date.now()}`;
  const signerNoList = preparedSigners.map(s => s.signerNo);
  
  const templateResult = await sign.template.create({
    templateName: templateName,
    fileList: [{
      fileId: fileId,
      fileName: path.basename(contractFile),
      signerList: signerNoList,
      controlList: [
        { controlName: '签署日期', controlKey: 'sign_date', controlKeyType: 'text', allowEdit: 1, isRequired: 1 }
      ]
    }]
  });

  const templateId = templateResult.templateId || (templateResult.data && templateResult.data.templateId);
  const templatePreviewUrl = templateResult.previewUrl || (templateResult.data && templateResult.data.previewUrl);
  console.log(`  ✅ templateId: ${templateId}`);
  console.log(`  模板预览链接: ${templatePreviewUrl}`);

  return {
    success: true,
    signers: preparedSigners.map(s => ({
      signerNo: s.signerNo,
      type: s.type,
      userId: s.userId,
      sealId: s.sealId,
      name: s.type === 'enterprise' ? s.enterpriseName : s.name
    })),
    template: {
      templateId: templateId,
      templateName: templateName,
      previewUrl: templatePreviewUrl
    },
    fileId: fileId,
    nextStep: '请打开模板预览链接配置签署位置，配置完成后调用创建签署流程接口'
  };
}

/**
 * 解析命令行参数
 */
function parseArgs() {
  const args = process.argv.slice(2);
  const params = {};

  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg.startsWith('--')) {
      const key = arg.slice(2);
      const value = args[i + 1];
      if (value && !value.startsWith('--')) {
        params[key] = value;
        i++;
      }
    }
  }

  return params;
}

/**
 * 显示用法
 */
function showUsage() {
  console.log(`
用法:
  1. 使用配置文件:
     node workflow.js --file signers.json

  2. 使用命令行参数:
     node workflow.js --contract "合同文件路径" --signers '[...]'

  3. 交互式输入:
     node workflow.js --interactive

signers JSON 格式:
[
  {
    "signerNo": "甲方",
    "type": "enterprise",
    "enterpriseName": "公司名称",
    "creditCode": "统一社会信用代码",
    "contactName": "联系人姓名",
    "contactIdCard": "联系人身份证",
    "contactMobile": "联系人手机"
  },
  {
    "signerNo": "乙方",
    "type": "person",
    "name": "姓名",
    "idCard": "身份证号",
    "mobile": "手机号"
  }
]
`);
}

/**
 * 交互式输入
 */
async function interactive() {
  console.log('\n============================================================');
  console.log('电子签章 - 交互式工作流');
  console.log('============================================================\n');

  const ask = (question) => new Promise(resolve => rl.question(question, resolve));

  // 收集合同文件路径
  const contractFile = await ask('合同文件路径: ');
  if (!contractFile || !fs.existsSync(contractFile)) {
    console.log('\n❌ 文件不存在或路径无效');
    rl.close();
    return;
  }

  // 收集签署方数量
  const enterpriseCount = parseInt(await ask('企业方数量: ')) || 0;
  const personCount = parseInt(await ask('个人方数量: ')) || 0;

  if (enterpriseCount + personCount === 0) {
    console.log('\n❌ 至少需要一个签署方');
    rl.close();
    return;
  }

  const signers = [];
  let signerIndex = 1;

  // 收集企业方信息
  for (let i = 0; i < enterpriseCount; i++) {
    console.log(`\n【企业方${signerIndex}】`);
    const signerNo = await ask('  签署方标识（如甲方/乙方）: ');
    const enterpriseName = await ask('  企业名称: ');
    const creditCode = await ask('  统一社会信用代码: ');
    const contactName = await ask('  联系人姓名: ');
    const contactIdCard = await ask('  联系人身份证: ');
    const contactMobile = await ask('  联系人手机: ');

    signers.push({
      signerNo,
      type: 'enterprise',
      enterpriseName,
      creditCode,
      contactName,
      contactIdCard,
      contactMobile
    });
    signerIndex++;
  }

  // 收集个人方信息
  for (let i = 0; i < personCount; i++) {
    console.log(`\n【个人方${signerIndex}】`);
    const signerNo = await ask('  签署方标识（如甲方/乙方）: ');
    const name = await ask('  姓名: ');
    const idCard = await ask('  身份证号: ');
    const mobile = await ask('  手机号: ');

    signers.push({
      signerNo,
      type: 'person',
      name,
      idCard,
      mobile
    });
    signerIndex++;
  }

  // 确认信息
  console.log('\n============================================================');
  console.log('确认信息');
  console.log('============================================================');
  console.log(`合同文件: ${contractFile}`);
  console.log(`签署方: ${enterpriseCount}方企业 + ${personCount}方个人\n`);

  for (const s of signers) {
    console.log(`【${s.signerNo}】${s.type === 'enterprise' ? '企业' : '个人'}`);
    if (s.type === 'enterprise') {
      console.log(`  企业名称: ${s.enterpriseName}`);
      console.log(`  统一社会信用代码: ${s.creditCode}`);
      console.log(`  联系人: ${s.contactName}`);
      console.log(`  手机: ${s.contactMobile}`);
    } else {
      console.log(`  姓名: ${s.name}`);
      console.log(`  身份证: ${s.idCard}`);
      console.log(`  手机: ${s.mobile}`);
    }
    console.log('');
  }

  const confirmed = await ask('确认创建签署流程？(yes/no): ');
  if (confirmed.toLowerCase() !== 'yes' && confirmed.toLowerCase() !== 'y') {
    console.log('\n❌ 已取消');
    rl.close();
    return;
  }

  rl.close();

  // 执行流程
  try {
    const result = await workflow({ signers, contractFile });
    console.log('\n============================================================');
    console.log('【结果】');
    console.log('============================================================');
    console.log(JSON.stringify(result, null, 2));
  } catch (error) {
    console.error('\n❌ 执行失败:', error.message);
    process.exit(1);
  }
}

/**
 * 主函数
 */
async function main() {
  const params = parseArgs();

  // 显示用法
  if (params.help || params.h) {
    showUsage();
    process.exit(0);
  }

  // 交互式模式
  if (params.interactive || params.i) {
    await interactive();
    return;
  }

  // 使用命令行参数
  if (params.contract && params.signers) {
    try {
      const signers = JSON.parse(params.signers);
      const result = await workflow({ signers, contractFile: params.contract });
      console.log('\n============================================================');
      console.log('【结果】');
      console.log('============================================================');
      console.log(JSON.stringify(result, null, 2));
    } catch (error) {
      console.error('\n❌ 执行失败:', error.message);
      process.exit(1);
    }
    return;
  }

  // 使用配置文件
  if (params.file) {
    if (!fs.existsSync(params.file)) {
      console.error(`❌ 文件不存在: ${params.file}`);
      process.exit(1);
    }
    const signersData = JSON.parse(fs.readFileSync(params.file, 'utf-8'));
    try {
      const result = await workflow(signersData);
      console.log('\n============================================================');
      console.log('【结果】');
      console.log('============================================================');
      console.log(JSON.stringify(result, null, 2));
    } catch (error) {
      console.error('\n❌ 执行失败:', error.message);
      process.exit(1);
    }
    return;
  }

  // 显示用法
  showUsage();
  process.exit(1);
}

main();

path: scripts/evidence/create-task-interactive.js
content: #!/usr/bin/env node
/**
 * 自动化取证 - 交互式脚本
 * 所有不确定的信息都会询问用户
 */

const path = require('path');
const readline = require('readline');

const libPath = path.resolve(__dirname, '../../lib');
const configPath = path.resolve(__dirname, '../../config.json');

const { init } = require(path.join(libPath, 'client'));
const evidence = require(path.join(libPath, 'modules/evidence'));

const config = require(configPath);
init(config);

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

function ask(question) {
  return new Promise(resolve => rl.question(question, resolve));
}

function askConfirm(question) {
  return ask(question + ' (yes/no): ').then(answer => 
    answer.toLowerCase() === 'yes' || answer.toLowerCase() === 'y'
  );
}

const EVIDENCE_TYPES = {
  '1': { name: '拼多多电商取证', desc: '商品详情页、店铺信息等' },
  '3': { name: '淘宝电商取证', desc: '商品详情页、店铺信息等' },
  '4': { name: '抖音电商取证', desc: '商品详情页、店铺信息等' },
  '12': { name: '公众号取证', desc: '公众号文章内容' }
};

async function main() {
  console.log('\n📋 自动化取证向导');
  console.log('='.repeat(50));
  console.log('说明：所有不确定的信息都会询问您确认');
  console.log('='.repeat(50) + '\n');

  try {
    // Step 1: 选择取证类型
    console.log('Step 1: 选择取证类型\n');
    console.log('📋 请选择取证类型：\n');
    console.log('| 序号 | 类型 | 说明 |');
    console.log('|------|------|------|');
    Object.entries(EVIDENCE_TYPES).forEach(([code, info]) => {
      console.log(`| ${code} | ${info.name} | ${info.desc} |`);
    });
    console.log('');
    
    const typeInput = await ask('请输入序号或类型代码：');
    const typeInfo = EVIDENCE_TYPES[typeInput];
    
    if (!typeInfo) {
      console.log('\n❌ 无效的取证类型');
      rl.close();
      return;
    }
    
    console.log(`\n✅ 已选择：${typeInfo.name}\n`);
    
    // Step 2: 输入取证信息
    console.log('Step 2: 请提供以下信息\n');
    console.log('取证类型: [已选择]\n');
    console.log('取证信息:');
    const url = await ask('  取证URL: ');
    const batchId = await ask('  批次ID: ');
    const attId = await ask('  保全号: ');
    const evidenceName = await ask('  取证名称 (可选，直接回车跳过): ');
    
    console.log('\n可选参数:');
    const needLicense = await askConfirm('  是否取证营业执照');
    
    if (!url || !batchId || !attId) {
      console.log('\n❌ 取证URL、批次ID、保全号不能为空');
      rl.close();
      return;
    }
    
    // Step 3: 确认信息
    console.log('\n' + '='.repeat(50));
    console.log('📝 确认信息\n');
    console.log('取证类型:', typeInfo.name);
    console.log('');
    console.log('取证信息:');
    console.log('  取证URL:', url);
    console.log('  批次ID:', batchId);
    console.log('  保全号:', attId);
    if (evidenceName) console.log('  取证名称:', evidenceName);
    console.log('');
    console.log('可选参数:');
    console.log('  是否取证营业执照:', needLicense ? '是' : '否');
    console.log('');
    
    const confirmed = await askConfirm('确认创建取证任务？');
    if (!confirmed) {
      console.log('\n❌ 已取消');
      rl.close();
      return;
    }
    
    // Step 4: 执行
    console.log('\nStep 3: 创建取证任务...\n');
    
    const taskParams = {
      batch_id: batchId,
      tasks: [{
        att_id: attId,
        url: url.trim(),
        evidence_name: evidenceName || `取证-${attId}`,
        type: parseInt(typeInput),
        business_license_status: needLicense
      }]
    };
    
    const result = await evidence.createTask(taskParams);
    
    console.log('✅ 取证任务创建成功！\n');
    console.log('='.repeat(50));
    console.log('🎉 任务信息');
    console.log('='.repeat(50));
    console.log('\n取证信息:');
    console.log('  取证类型:', typeInfo.name);
    console.log('  批次ID:', batchId);
    console.log('  保全号:', attId);
    console.log('\n💡 后续操作:');
    console.log('   查询状态：node scripts/evidence/query.js --batch-id ' + batchId);
    console.log('   下载证据：node scripts/evidence/download.js --att-id ' + attId);
    console.log('='.repeat(50) + '\n');
    
  } catch (error) {
    console.log('\n❌ 错误:', error.message);
  }
  
  rl.close();
}

main();

path: scripts/evidence/create-task.js
content: #!/usr/bin/env node
/**
 * 自动化取证 - 创建取证任务
 * 
 * 用法:
 *   node create-task.js --batch-id "批次ID" --att-id "保全号" --url "https://..." --type 1
 *   node create-task.js --file tasks.json
 */

const path = require('path');
const fs = require('fs');

const libPath = path.resolve(__dirname, '../../lib');
const configPath = path.resolve(__dirname, '../../config.json');

const { init } = require(path.join(libPath, 'client'));
const evidence = require(path.join(libPath, 'modules/evidence'));

const config = require(configPath);
init(config);

function parseArgs() {
  const args = process.argv.slice(2);
  const params = {};
  
  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg.startsWith('--')) {
      const key = arg.slice(2);
      const value = args[i + 1];
      if (value && !value.startsWith('--')) {
        params[key] = value;
        i++;
      }
    }
  }
  
  return params;
}

async function main() {
  const params = parseArgs();
  
  // 从文件读取任务
  if (params.file) {
    const filePath = path.resolve(params.file);
    const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
    
    try {
      const result = await evidence.createTask(data.batchId, data.tasks);
      console.log(JSON.stringify({ success: true, data: result }, null, 2));
    } catch (error) {
      console.error(JSON.stringify({ success: false, error: error.message }, null, 2));
      process.exit(1);
    }
    return;
  }
  
  // 单个任务
  const batchId = params['batch-id'] || params.batchId;
  const attId = params['att-id'] || params.attId;
  const url = params.url;
  const type = parseInt(params.type || '1');
  const evidenceName = params['evidence-name'] || params.name || '取证任务';
  
  if (!batchId || !attId || !url) {
    console.error('用法:');
    console.error('  node create-task.js --batch-id "批次ID" --att-id "保全号" --url "https://..." --type 1');
    console.error('  node create-task.js --file tasks.json');
    console.error('');
    console.error('取证类型:');
    console.error('  1 = 拼多多电商取证');
    console.error('  3 = 淘宝电商取证');
    console.error('  4 = 抖音电商取证');
    process.exit(1);
  }
  
  const tasks = [{
    att_id: attId,
    url,
    evidence_name: evidenceName,
    type
  }];
  
  try {
    const result = await evidence.createTask(batchId, tasks);
    console.log(JSON.stringify({ 
      success: true, 
      data: {
        batchId: result.batch_id,
        wsUrl: result.ws_url,
        pageUrl: result.url,
        bindTime: result.bind_time
      }
    }, null, 2));
  } catch (error) {
    console.error(JSON.stringify({ success: false, error: error.message }, null, 2));
    process.exit(1);
  }
}

main();

path: scripts/evidence/device.js
content: #!/usr/bin/env node
/**
 * 自动化取证 - 设备管理
 */

const path = require('path');

const libPath = path.resolve(__dirname, '../../lib');
const configPath = path.resolve(__dirname, '../../config.json');

const { init } = require(path.join(libPath, 'client'));
const evidence = require(path.join(libPath, 'modules/evidence'));

const config = require(configPath);
init(config);

function parseArgs() {
  const args = process.argv.slice(2);
  const params = {};
  
  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg.startsWith('--')) {
      const key = arg.slice(2);
      const value = args[i + 1];
      if (value && !value.startsWith('--')) {
        params[key] = value;
        i++;
      } else {
        params[key] = true;
      }
    }
  }
  
  return params;
}

async function main() {
  const params = parseArgs();
  
  if (params.check) {
    // 检查设备
    try {
      const result = await evidence.checkDevice();
      console.log(JSON.stringify({ 
        success: true, 
        data: { hasDevice: result.has_device }
      }, null, 2));
    } catch (error) {
      console.error(JSON.stringify({ success: false, error: error.message }, null, 2));
      process.exit(1);
    }
  } else if (params.assign) {
    // 分配设备
    const batchId = params['batch-id'] || params.batchId;
    const callbackUrl = params['callback-url'];
    
    if (!batchId) {
      console.error('用法: node device.js --assign --batch-id "批次ID" [--callback-url "回调地址"]');
      process.exit(1);
    }
    
    try {
      const result = await evidence.assignDevice(batchId, callbackUrl);
      console.log(JSON.stringify({ 
        success: true, 
        data: {
          wsUrl: result.ws_url,
          assignedTime: result.assigned_time
        }
      }, null, 2));
    } catch (error) {
      console.error(JSON.stringify({ success: false, error: error.message }, null, 2));
      process.exit(1);
    }
  } else {
    console.error('用法:');
    console.error('  node device.js --check');
    console.error('  node device.js --assign --batch-id "批次ID"');
    process.exit(1);
  }
}

main();

path: scripts/evidence/download.js
content: #!/usr/bin/env node
/**
 * 自动化取证 - 下载证据包
 */

const path = require('path');

const libPath = path.resolve(__dirname, '../../lib');
const configPath = path.resolve(__dirname, '../../config.json');

const { init } = require(path.join(libPath, 'client'));
const evidence = require(path.join(libPath, 'modules/evidence'));

const config = require(configPath);
init(config);

function parseArgs() {
  const args = process.argv.slice(2);
  const params = {};
  
  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg.startsWith('--')) {
      const key = arg.slice(2);
      const value = args[i + 1];
      if (value && !value.startsWith('--')) {
        params[key] = value;
        i++;
      }
    }
  }
  
  return params;
}

async function main() {
  const params = parseArgs();
  const attId = params['att-id'] || params.attId;
  
  if (!attId) {
    console.error('用法: node download.js --att-id "任务ID"');
    process.exit(1);
  }
  
  try {
    const result = await evidence.downloadZip(attId);
    console.log(JSON.stringify({ 
      success: true, 
      data: {
        url: result.url,
        expireTime: result.expire_time
      }
    }, null, 2));
  } catch (error) {
    console.error(JSON.stringify({ success: false, error: error.message }, null, 2));
    process.exit(1);
  }
}

main();

path: scripts/evidence/query.js
content: #!/usr/bin/env node
/**
 * 自动化取证 - 查询任务状态
 */

const path = require('path');

const libPath = path.resolve(__dirname, '../../lib');
const configPath = path.resolve(__dirname, '../../config.json');

const { init } = require(path.join(libPath, 'client'));
const evidence = require(path.join(libPath, 'modules/evidence'));

const config = require(configPath);
init(config);

function parseArgs() {
  const args = process.argv.slice(2);
  const params = {};
  
  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg.startsWith('--')) {
      const key = arg.slice(2);
      const value = args[i + 1];
      if (value && !value.startsWith('--')) {
        params[key] = value;
        i++;
      }
    }
  }
  
  return params;
}

async function main() {
  const params = parseArgs();
  
  const batchId = params['batch-id'] || params.batchId;
  const attId = params['att-id'] || params.attId;
  
  if (!batchId && !attId) {
    console.error('用法:');
    console.error('  node query.js --batch-id "批次ID"');
    console.error('  node query.js --att-id "任务ID"');
    process.exit(1);
  }
  
  try {
    if (batchId) {
      const result = await evidence.queryBatch(batchId);
      result.statusText = evidence.getBatchStatusText(result.status);
      console.log(JSON.stringify({ success: true, data: result }, null, 2));
    } else {
      const result = await evidence.queryTask(attId);
      result.statusText = evidence.getTaskStatusText(result.status);
      console.log(JSON.stringify({ success: true, data: result }, null, 2));
    }
  } catch (error) {
    console.error(JSON.stringify({ success: false, error: error.message }, null, 2));
    process.exit(1);
  }
}

main();

path: scripts/chain/query.js
content: #!/usr/bin/env node
/**
 * 区块链API服务 - 批量查询上链结果
 * 
 * 用法:
 *   node query.js --index "交易索引"
 *   node query.js --index "索引1,索引2,索引3"
 */

const path = require('path');

const libPath = path.resolve(__dirname, '../../lib');
const configPath = path.resolve(__dirname, '../../config.json');

const { init } = require(path.join(libPath, 'client'));
const chain = require(path.join(libPath, 'modules/chain'));

const config = require(configPath);
init(config);

function parseArgs() {
  const args = process.argv.slice(2);
  const params = {};
  
  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg.startsWith('--')) {
      const key = arg.slice(2);
      const value = args[i + 1];
      if (value && !value.startsWith('--')) {
        params[key] = value;
        i++;
      }
    }
  }
  
  return params;
}

async function main() {
  const params = parseArgs();
  
  const index = params.index || params.i;
  
  if (!index) {
    console.error('用法: node query.js --index "交易索引"');
    console.error('      node query.js --index "索引1,索引2,索引3"');
    process.exit(1);
  }
  
  // 支持逗号分隔的多个索引
  const indexList = index.split(',').map(s => s.trim());
  
  try {
    const result = await chain.queryResult(indexList);
    
    // 添加状态说明
    if (result && result.length > 0) {
      result.forEach(item => {
        if (item.chain_results) {
          item.chain_results.forEach(r => {
            r.statusText = chain.getStatusText(r.status);
          });
        }
      });
    }
    
    console.log(JSON.stringify({ success: true, data: result }, null, 2));
  } catch (error) {
    console.error(JSON.stringify({ success: false, error: error.message }, null, 2));
    process.exit(1);
  }
}

main();

path: scripts/chain/upload.js
content: #!/usr/bin/env node
/**
 * 区块链API服务 - 数据上链
 * 
 * 用法:
 *   node upload.js --data "业务数据"
 *   node upload.js --data '{"ano":"BQ123","sha256":"xxx"}' --business EVIDENCE_PRESERVATION --source BQ_INTERNATIONAL
 */

const path = require('path');

// 加载模块
const libPath = path.resolve(__dirname, '../../lib');
const configPath = path.resolve(__dirname, '../../config.json');

const { init } = require(path.join(libPath, 'client'));
const chain = require(path.join(libPath, 'modules/chain'));

// 初始化
const config = require(configPath);
init(config);

// 解析参数
function parseArgs() {
  const args = process.argv.slice(2);
  const params = {};
  
  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg.startsWith('--')) {
      const key = arg.slice(2);
      const value = args[i + 1];
      if (value && !value.startsWith('--')) {
        params[key] = value;
        i++;
      } else {
        params[key] = true;
      }
    }
  }
  
  return params;
}

async function main() {
  const params = parseArgs();
  
  const data = params.data || params.d;
  
  if (!data) {
    console.error('用法: node upload.js --data "业务数据"');
    console.error('      node upload.js --data \'{"ano":"BQ123"}\' --business EVIDENCE_PRESERVATION --source BQ_INTERNATIONAL');
    console.error('');
    console.error('可选参数:');
    console.error('  --business   业务名（多个用逗号分隔）');
    console.error('  --source     来源（多个用逗号分隔）');
    console.error('  --requestId  请求ID（不传则自动生成）');
    process.exit(1);
  }
  
  // 构建选项
  const options = {};
  
  if (params.business) {
    options.business = params.business.split(',').map(s => s.trim());
  }
  if (params.source) {
    options.source = params.source.split(',').map(s => s.trim());
  }
  if (params.requestId) {
    options.requestId = params.requestId;
  }
  
  try {
    const result = await chain.upload(data, options);
    console.log(JSON.stringify({ 
      success: true, 
      data: {
        index: result.index,
        requestId: result.requestId
      }
    }, null, 2));
  } catch (error) {
    console.error(JSON.stringify({ success: false, error: error.message }, null, 2));
    process.exit(1);
  }
}

main();

path: scripts/certificate/create.js
content: #!/usr/bin/env node
/**
 * 保管单组件 - 生成保管单
 */

const path = require('path');
const fs = require('fs');

const libPath = path.resolve(__dirname, '../../lib');
const configPath = path.resolve(__dirname, '../../config.json');

const { init } = require(path.join(libPath, 'client'));
const certificate = require(path.join(libPath, 'modules/certificate'));

const config = require(configPath);
init(config);

function parseArgs() {
  const args = process.argv.slice(2);
  const params = {};
  
  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg.startsWith('--')) {
      const key = arg.slice(2);
      const value = args[i + 1];
      if (value && !value.startsWith('--')) {
        params[key] = value;
        i++;
      }
    }
  }
  
  return params;
}

async function main() {
  const params = parseArgs();
  
  if (params.file) {
    const filePath = path.resolve(params.file);
    const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
    
    try {
      const result = await certificate.create(data);
      console.log(JSON.stringify({ success: true, data: result }, null, 2));
    } catch (error) {
      console.error(JSON.stringify({ success: false, error: error.message }, null, 2));
      process.exit(1);
    }
    return;
  }
  
  console.error('用法: node create.js --file params.json');
  console.error('');
  console.error('params.json 格式:');
  console.error(JSON.stringify({
    templateId: "模板ID",
    certNo: "证书编号（可选）",
    defaultFields: {
      applicantName: "申请人名称",
      applicantIdCard: "身份证号"
    },
    customFields: []
  }, null, 2));
  process.exit(1);
}

main();

path: scripts/certificate/download.js
content: #!/usr/bin/env node
/**
 * 保管单组件 - 下载保管单
 */

const path = require('path');

const libPath = path.resolve(__dirname, '../../lib');
const configPath = path.resolve(__dirname, '../../config.json');

const { init } = require(path.join(libPath, 'client'));
const certificate = require(path.join(libPath, 'modules/certificate'));

const config = require(configPath);
init(config);

function parseArgs() {
  const args = process.argv.slice(2);
  const params = {};
  
  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg.startsWith('--')) {
      const key = arg.slice(2);
      const value = args[i + 1];
      if (value && !value.startsWith('--')) {
        params[key] = value;
        i++;
      }
    }
  }
  
  return params;
}

async function main() {
  const params = parseArgs();
  const certNo = params['cert-no'] || params.certNo;
  
  if (!certNo) {
    console.error('用法: node download.js --cert-no "存证编号"');
    process.exit(1);
  }
  
  try {
    const result = await certificate.download(certNo);
    console.log(JSON.stringify({ 
      success: true, 
      data: { url: result.url }
    }, null, 2));
  } catch (error) {
    console.error(JSON.stringify({ success: false, error: error.message }, null, 2));
    process.exit(1);
  }
}

main();

path: scripts/certificate/generate-interactive.js
content: #!/usr/bin/env node
/**
 * 保管单生成 - 交互式脚本
 * 所有不确定的信息都会询问用户
 */

const path = require('path');
const crypto = require('crypto');
const readline = require('readline');

const libPath = path.resolve(__dirname, '../../lib');
const configPath = path.resolve(__dirname, '../../config.json');

const { init } = require(path.join(libPath, 'client'));
const chain = require(path.join(libPath, 'modules/chain'));
const certificate = require(path.join(libPath, 'modules/certificate'));

const config = require(configPath);
init(config);

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

function ask(question) {
  return new Promise(resolve => rl.question(question, resolve));
}

function askConfirm(question) {
  return ask(question + ' (yes/no): ').then(answer => 
    answer.toLowerCase() === 'yes' || answer.toLowerCase() === 'y'
  );
}

async function main() {
  console.log('\n📋 保管单生成向导');
  console.log('='.repeat(50));
  console.log('说明：所有不确定的信息都会询问您确认');
  console.log('='.repeat(50) + '\n');

  try {
    // Step 1: 查询模板
    console.log('Step 1: 查询可用模板...\n');
    const templates = await certificate.listTemplates();
    
    console.log('📋 可用的保管单模板：\n');
    console.log('| 序号 | 模板ID | 模板名称 |');
    console.log('|------|--------|----------|');
    templates.forEach((t, i) => {
      console.log(`| ${i + 1} | ${t.id} | ${t.tempName} |`);
    });
    console.log('');
    
    const templateInput = await ask('请选择模板（输入序号或模板ID）：');
    
    let selectedTemplate;
    const index = parseInt(templateInput) - 1;
    if (index >= 0 && index < templates.length) {
      selectedTemplate = templates[index];
    } else {
      selectedTemplate = templates.find(t => t.id === templateInput);
    }
    
    if (!selectedTemplate) {
      console.log('\n❌ 无效的选择');
      rl.close();
      return;
    }
    
    console.log(`\n✅ 已选择模板：${selectedTemplate.tempName} (${selectedTemplate.id})\n`);
    
    // Step 2: 询问字段信息
    console.log('Step 2: 请提供以下信息\n');
    console.log('⚠️ 模板字段因客户而异，需要您确认！\n');
    console.log('请登录数秦开放平台查看该模板的字段配置。\n');
    console.log('请按以下格式提供信息：\n');
    console.log('模板ID: [已自动填充]');
    console.log('');
    console.log('默认字段:');
    console.log('  申请人: [填写]');
    console.log('  账号: [填写]');
    console.log('  [其他字段...]');
    console.log('');
    console.log('自定义字段:');
    console.log('  [字段名]: [值]');
    console.log('  [如无则写"无"]');
    console.log('');
    
    const applicantName = await ask('默认字段 - 申请人: ');
    const applicantAccount = await ask('默认字段 - 账号: ');
    
    // 询问自定义字段
    console.log('\n自定义字段（格式：字段名:值，多个用逗号分隔）');
    const customFieldsInput = await ask('自定义字段: ');
    
    const customFields = {};
    if (customFieldsInput.trim() && customFieldsInput.toLowerCase() !== '无') {
      customFieldsInput.split(',').forEach(pair => {
        const [key, value] = pair.split(':').map(s => s.trim());
        if (key && value) customFields[key] = value;
      });
    }
    
    // 组装上链数据（用于确认信息展示）
    const chainData = {
      applicantName,
      applicantAccount,
      ...customFields,
      timestamp: new Date().toISOString()
    };
    
    // Step 3: 确认信息
    console.log('\n' + '='.repeat(50));
    console.log('📝 确认信息\n');
    console.log(`模板ID: ${selectedTemplate.id} (${selectedTemplate.tempName})`);
    console.log('');
    console.log('默认字段:');
    console.log(`  申请人: ${applicantName}`);
    console.log(`  账号: ${applicantAccount}`);
    console.log('');
    if (Object.keys(customFields).length > 0) {
      console.log('自定义字段:');
      Object.entries(customFields).forEach(([k, v]) => console.log(`  ${k}: ${v}`));
      console.log('');
    }
    console.log('上链数据:');
    console.log(`  applicantName: ${applicantName}`);
    console.log(`  applicantAccount: ${applicantAccount}`);
    Object.entries(customFields).forEach(([k, v]) => console.log(`  ${k}: ${v}`));
    console.log('  timestamp: （将在上链时自动生成）');
    console.log('');
    
    const confirmed = await askConfirm('\n确认生成保管单？');
    if (!confirmed) {
      console.log('\n❌ 已取消');
      rl.close();
      return;
    }
    
    // Step 4: 执行
    console.log('\nStep 3: 执行上链和生成保管单...\n');
    
    // 更新上链数据时间戳
    chainData.timestamp = new Date().toISOString();
    
    console.log('上链数据:', JSON.stringify(chainData));
    
    // 上链
    console.log('\n⏳ 正在上链...');
    const uploadResult = await chain.upload({
      business: ['EVIDENCE_PRESERVATION'],
      data: JSON.stringify(chainData),
      requestId: crypto.randomBytes(16).toString('hex'),
      source: ['BQ_INTERNATIONAL']
    });
    console.log('✅ 交易索引:', uploadResult.index);
    
    // 等待上链完成
    console.log('\n⏳ 等待上链确认...');
    const uploadComplete = await chain.waitUntilUploaded(uploadResult.index, {
      interval: 3000,
      timeout: 60000
    });
    const firstChain = uploadComplete.chain_results?.[0] || {};
    console.log('✅ 上链状态: 成功');
    console.log('   链类型:', firstChain.chain || 'N/A');
    console.log('   交易哈希:', firstChain.hash || 'N/A');
    console.log('   区块高度:', firstChain.height || 'N/A');
    
    // 生成保管单
    console.log('\n⏳ 正在生成保管单...');
    
    // 转换为本地时间字符串 (GMT+8)
    const localTime = new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString().substring(0, 19).replace('T', ' ');
    
    const custodyParams = {
      templateId: selectedTemplate.id,
      requestId: crypto.randomBytes(16).toString('hex'),
      defaultFields: {
        applicantName,
        applicantAccount,
        evidenceHash: crypto.createHash('sha256').update(JSON.stringify(chainData)).digest('hex'),
        blockchainHash: firstChain.hash || '',
        blockchainHeight: String(firstChain.height || 0),
        attestTime: localTime
      },
      customFieldList: Object.entries(customFields).map(([name, value]) => ({ name, value }))
    };
    
    const certNo = await certificate.create(custodyParams);
    console.log('✅ 存证编号:', certNo);
    
    // 获取下载链接
    console.log('\n⏳ 正在获取下载链接...');
    let downloadUrl = null;
    for (let i = 0; i < 12; i++) {
      try {
        const dl = await certificate.download(certNo);
        if (dl.url) {
          downloadUrl = dl.url;
          break;
        }
      } catch (e) {}
      await new Promise(r => setTimeout(r, 5000));
    }
    
    // 保存下载链接到文件
    if (downloadUrl) {
      const linkFile = path.join(process.cwd(), 'last-download-link.txt');
      require('fs').writeFileSync(linkFile, downloadUrl);
    }
    
    // 输出结果
    console.log('\n' + '='.repeat(50));
    console.log('🎉 保管单生成完成！');
    console.log('='.repeat(50));
    console.log('\n📄 保管单信息:');
    console.log('   存证编号:', certNo);
    console.log('   模板:', selectedTemplate.tempName);
    console.log('\n👤 申请人信息:');
    console.log('   姓名:', applicantName);
    console.log('   账号:', applicantAccount);
    console.log('\n🔗 区块链信息:');
    console.log('   链类型:', firstChain.chain || 'N/A');
    console.log('   交易索引:', uploadResult.index);
    console.log('   交易哈希:', firstChain.hash || 'N/A');
    console.log('   区块高度:', firstChain.height || 'N/A');
    console.log('\n📥 下载链接:');
    if (downloadUrl) {
      // Markdown 格式链接（适用于更多终端）
      console.log('\n   [点击下载保管单](' + downloadUrl + ')\n');
      console.log('   或复制以下链接：');
      console.log(downloadUrl);
    } else {
      console.log('   生成中，请稍后重试');
    }
    console.log('\n💡 后续操作:');
    console.log('   下载保管单：node scripts/certificate/download.js --cert-no ' + certNo);
    console.log('='.repeat(50) + '\n');
    
  } catch (error) {
    console.log('\n❌ 错误:', error.message);
  }
  
  rl.close();
}

main();

path: scripts/certificate/generate.js
content: #!/usr/bin/env node
/**
 * 保管单生成 - 快速执行脚本
 * 使用预定义参数快速生成保管单
 */

const path = require('path');
const crypto = require('crypto');

const libPath = path.resolve(__dirname, '../../lib');
const configPath = path.resolve(__dirname, '../../config.json');

const { init } = require(path.join(libPath, 'client'));
const chain = require(path.join(libPath, 'modules/chain'));
const certificate = require(path.join(libPath, 'modules/certificate'));

const config = require(configPath);
init(config);

// 默认参数（可修改）
const TEMPLATE_ID = '2036284038445596672';
const applicantName = '张三';
const applicantAccount = 'zhangsan';
const customFieldList = [];

async function main() {
  console.log('📋 保管单生成流程\n');
  console.log('='.repeat(50));
  console.log('模板 ID:', TEMPLATE_ID);
  console.log('申请人:', applicantName);
  console.log('账号:', applicantAccount);
  if (customFieldList.length > 0) {
    console.log('自定义字段:', JSON.stringify(customFieldList));
  }
  console.log('='.repeat(50));
  
  try {
    // 组装上链数据
    const chainData = {
      applicantName,
      applicantAccount,
      ...Object.fromEntries(customFieldList.map(f => [f.name, f.value])),
      timestamp: new Date().toISOString()
    };
    console.log('\n上链数据:', JSON.stringify(chainData));
    
    // 上链
    console.log('\n正在上链...');
    const uploadResult = await chain.upload({
      business: ['EVIDENCE_PRESERVATION'],
      data: JSON.stringify(chainData),
      requestId: crypto.randomBytes(16).toString('hex'),
      source: ['BQ_INTERNATIONAL']
    });
    console.log('✅ 交易索引:', uploadResult.index);
    
    // 查询结果
    console.log('\n查询上链结果...');
    const queryResult = await chain.queryResult([uploadResult.index]);
    const firstChain = queryResult[0]?.chain_results?.[0] || {};
    console.log('✅ 区块链哈希:', firstChain.hash || 'N/A');
    console.log('   区块高度:', firstChain.height || 'N/A');
    
    // 生成保管单
    console.log('\n生成保管单...');
    const custodyParams = {
      templateId: TEMPLATE_ID,
      requestId: crypto.randomBytes(16).toString('hex'),
      defaultFields: {
        applicantName,
        applicantAccount,
        evidenceHash: crypto.createHash('sha256').update(JSON.stringify(chainData)).digest('hex'),
        blockchainHash: firstChain.hash || '',
        blockchainHeight: String(firstChain.height || 0),
        attestTime: new Date().toISOString().substring(0, 19).replace('T', ' ')
      },
      customFieldList
    };
    
    const certNo = await certificate.create(custodyParams);
    console.log('✅ 存证编号:', certNo);
    
    // 获取下载链接
    console.log('\n获取下载链接...');
    let downloadUrl = null;
    for (let i = 0; i < 12; i++) {
      try {
        const dl = await certificate.download(certNo);
        if (dl.url) { downloadUrl = dl.url; break; }
      } catch (e) {}
      await new Promise(r => setTimeout(r, 5000));
    }
    
    console.log('\n' + '='.repeat(50));
    console.log('🎉 保管单生成完成！');
    console.log('='.repeat(50));
    console.log('\n存证编号:', certNo);
    console.log('下载链接:', downloadUrl);
    
  } catch (error) {
    console.log('\n❌ 生成失败:', error.message);
  }
}

main();

path: scripts/certificate/templates.js
content: #!/usr/bin/env node
/**
 * CAP-保管单组件 - 获取模板列表
 */

const path = require('path');

const libPath = path.resolve(__dirname, '../../lib');
const configPath = path.resolve(__dirname, '../../config.json');

const { init } = require(path.join(libPath, 'client'));
const certificate = require(path.join(libPath, 'modules/certificate'));

const config = require(configPath);
init(config);

async function main() {
  try {
    const result = await certificate.listTemplates();
    console.log(JSON.stringify({ success: true, data: result }, null, 2));
  } catch (error) {
    console.error(JSON.stringify({ success: false, error: error.message }, null, 2));
    process.exit(1);
  }
}

main();

path: references/certificate-api.md
content: # 保管单组件 API 文档

## 接口地址及配置

| 环境 | 接口请求地址 |
|------|-------------|
| 线上环境 | `https://api.dataqin.com` |

## 认证方式

同链开放能力，使用 HMAC-SHA256 签名认证。

## 接口列表

### 1. 生成保管单

- **接口地址**: `POST /1824399010247397378`
- **描述**: 渲染 HTML + 生成 PDF + 签章

**请求参数**:

主要包含模板信息和保管单数据，具体字段根据模板配置而定。

**请求示例**:
```json
{
  "templateId": "模板ID",
  "certNo": "证书编号（可选，不传则自动生成）",
  "defaultFields": {
    "applicantName": "申请人名称",
    "applicantIdCard": "身份证号",
    "applyTime": "申请时间"
  },
  "customFields": []
}
```

**响应参数**:

| 名称 | 说明 |
|------|------|
| certNo | 存证编号 |

---

### 2. 获取保管单下载地址

- **接口地址**: `POST /1824398465122095105`
- **描述**: 返回 PDF 文件下载地址

**请求参数**:

| 名称 | 类型 | 必填 | 说明 |
|------|------|------|------|
| certNo | string | 是 | 存证编号 |

**响应参数**:

| 名称 | 说明 |
|------|------|
| url | PDF 下载地址 |

---

## 服务响应状态码

| 状态码 | 说明 |
|--------|------|
| 200 | 成功 |
| 500 | 程序异常 |
| 500043 | 文件类型获取失败 |
| 500044 | 不支持的文件类型转换 |
| 500129 | 模板名称已存在 |
| 500130 | 模板名称不能为空 |
| 500131 | 模板描述最大长度为200 |
| 500132 | 模板名称最大长度为30 |
| 500133 | templateId 记录非启用状态 |
| 500134 | 文件不存在 |
| 500135 | 证书编号已存在 |
| 500136 | certNo 字段不能为空 |
| 500137 | certNo 字段长度不能超过32位 |
| 500138 | 未查询到存证信息 |
| 500139 | 未查询到存证文件 |
| 500140 | 字段内容不能为空 |

path: references/chain-api.md
content: # 区块链API服务文档

## 接口地址及配置

| 环境 | 接口请求地址 |
|------|-------------|
| 测试环境 | `https://test-apisix-gateway.shuzhi.shuqinkeji.cn` |

## 认证方式

所有接口使用 HMAC-SHA256 签名认证，请求头需包含：

| 参数名称 | 说明 | 必须 |
|----------|------|------|
| X-HMAC-ACCESS-KEY | 应用 appKey | 是 |
| X-APP-PRODUCT-ID | 应用产品标识 | 是 |
| X-HMAC-ALGORITHM | 固定值：hmac-sha256 | 是 |
| X-HMAC-SIGNATURE | 全局签名 | 是 |
| X-HMAC-DIGEST | 请求体 body 签名 | 是 |
| Date | GMT格式时间 | 是 |

## 签名算法

### 全局签名（X-HMAC-SIGNATURE）

```
signingString = HTTP Method + \n + URI + \n + "" + \n + appKey + \n + Date + \n
X-HMAC-SIGNATURE = Base64(HMAC-SHA256(appSecret, signingString))
```

### Body签名（X-HMAC-DIGEST）

```
X-HMAC-DIGEST = Base64(HMAC-SHA256(appSecret, body))
```

---

## 接口列表

### 1. 上链接口

- **接口地址**: `POST /2036009920811413506`
- **产品标识**: 需配置
- **描述**: 对数据进行上链操作

**请求参数**:

| 参数名 | 类型 | 说明 | 必填 |
|--------|------|------|------|
| data | string | 业务数据（JSON字符串） | 是 |
| requestId | string | 请求ID | 是 |
| business | array | 业务名，如 `["EVIDENCE_PRESERVATION"]` | 否 |
| source | array | 来源，如 `["BQ_INTERNATIONAL"]` | 否 |
| extension | array | 扩展内容 | 否 |

**请求示例**:
```json
{
  "business": ["EVIDENCE_PRESERVATION"],
  "data": "{\"ano\":\"BQ912612831273123\",\"sha256\":\"868ac2385cf0b98803ae89cf3dc204e7b97b80dbf0559de0fd0740a3402c8f6a\"}",
  "requestId": "d7c8c267a9fe4ef1b92b9ed17b0a420d",
  "source": ["BQ_INTERNATIONAL"]
}
```

**响应参数**:

| 参数名 | 类型 | 说明 |
|--------|------|------|
| code | int | 状态码 |
| msg | string | 错误提示信息 |
| data.index | string | 交易索引号 |

**响应示例**:
```json
{
  "code": 200,
  "data": {
    "code": 0,
    "data": {
      "index": "d670b1db-926c-4e71-bf65-92df6c3b8aeb"
    },
    "msg": "success"
  },
  "requestId": "f38e9880b5b4c5bd5dfae32973c2c850",
  "message": "成功"
}
```

---

### 2. 批量查询上链结果

- **接口地址**: `POST /2036010888269574146`
- **产品标识**: 需配置
- **描述**: 批量查询上链结果

**请求参数**:

| 参数名 | 类型 | 说明 | 必填 |
|--------|------|------|------|
| index_list | array[string] | 交易索引号列表 | 是 |

**请求示例**:
```json
{
  "index_list": [
    "d670b1db-926c-4e71-bf65-92df6c3b8aeb"
  ]
}
```

**响应参数**:

| 参数名 | 类型 | 说明 |
|--------|------|------|
| code | int | 状态码 |
| msg | string | 错误提示消息 |
| data | array | 业务数据 |
| data[].anchor_data | object | 请求参数 |
| data[].anchor_data.business | array | 业务名 |
| data[].anchor_data.source | array | 来源 |
| data[].anchor_data.data | string | 业务数据（存证数据） |
| data[].anchor_data.requestId | string | 请求ID |
| data[].chain_results | array | 上链结果 |
| data[].chain_results[].index | string | 索引号 |
| data[].chain_results[].chain | string | 链名 |
| data[].chain_results[].hash | string | 交易哈希 |
| data[].chain_results[].block_hash | string | 区块哈希 |
| data[].chain_results[].height | int | 区块高度 |
| data[].chain_results[].status | int | 上链状态 |

**状态码说明**:

| 值 | 说明 |
|----|------|
| 0 | 上链失败 |
| 1 | 上链成功 |
| 2 | 等待上链 |

**响应示例**:
```json
{
  "code": 200,
  "data": {
    "code": 0,
    "data": [
      {
        "anchor_data": {
          "business": ["EVIDENCE_PRESERVATION"],
          "source": ["BQ_INTERNATIONAL"],
          "data": "{\"ano\":\"BQ912612831273123\",\"sha256\":\"868ac2385cf0b98803ae89cf3dc204e7b97b80dbf0559de0fd0740a3402c8f6a\"}",
          "extension": null,
          "requestId": "d7c8c267a9fe4ef1b92b9ed17b0a420d"
        },
        "chain_results": [
          {
            "index": "d670b1db-926c-4e71-bf65-92df6c3b8aeb",
            "chain": "tritium",
            "hash": "0xfd91f74bd2406bae4c42403cf6df23a64f48f38bd0da4870f9bee16bba83e0df",
            "block_hash": "0x97a50c249e9ffc525ff7c52cc909d9908fd883d51fe79309d5db5521110f01d4",
            "height": 17036614,
            "status": 1
          },
          {
            "index": "d670b1db-926c-4e71-bf65-92df6c3b8aeb",
            "chain": "conflux",
            "hash": "0xccae9154575489094b3e7bf1376d644c0a402526692f4601178db232133cf6ef",
            "block_hash": "0xba9564bbc9f47d24823d53eb311bc0c83a82ed27e353eb9dd094833f37a04faa",
            "height": 247138655,
            "status": 1
          }
        ]
      }
    ],
    "msg": "success"
  },
  "requestId": "f38e9880b5b4c5bd5dfae32973c2c850",
  "message": "成功"
}
```

---

## 链类型

| 值 | 说明 |
|----|------|
| tritium | Tritium 链 |
| conflux | Conflux 链 |

---

## 服务响应状态码

| 状态码 | 说明 |
|--------|------|
| 0 | 成功 |
| 500 | 程序异常 |

---

## 全局响应格式

```json
{
  "code": 200,
  "message": "成功",
  "requestId": "xxx",
  "data": {}
}
```

- `code`: 200 表示请求成功
- `data.code`: 0 表示服务成功

path: references/evidence-api.md
content: # 自动化取证 API 文档

## 接口地址及配置

| 环境 | 接口请求地址 |
|------|-------------|
| 线上环境 | `https://api.dataqin.com` |

## 认证方式

同链开放能力，使用 HMAC-SHA256 签名认证。

## 接口列表

### 1. 创建批量任务

- **接口地址**: `POST /1890212993579274242`
- **描述**: 创建批量取证任务

**请求参数**:

| 名称 | 类型 | 必填 | 说明 |
|------|------|------|------|
| batch_id | string | 是 | 批次ID |
| tasks | array | 是 | 任务列表 |

**tasks 数组元素**:

| 名称 | 类型 | 必填 | 说明 |
|------|------|------|------|
| att_id | string | 是 | 保全号（≤100字符） |
| url | string | 是 | 取证地址（≤1000字符） |
| evidence_name | string | 是 | 取证名称（≤128字符） |
| evidence_label | string | 否 | 取证标签 |
| type | integer | 是 | 取证类型：1=拼多多，3=淘宝，4=抖音 |
| business_license_status | boolean | 否 | 是否取证营业执照 |
| video_publisher | string | 否 | 视频发布者 |
| callback_url | string | 否 | 回调地址（≤256字符） |

**请求示例**:
```json
{
  "batch_id": "123456789",
  "tasks": [
    {
      "att_id": "123456789",
      "url": "https://mobile.yangkeduo.com/goods.html?goods_id=xxx",
      "evidence_name": "商品取证",
      "type": 1,
      "business_license_status": true
    }
  ]
}
```

**响应参数**:

| 名称 | 说明 |
|------|------|
| batch_id | 批次号 |
| ws_url | 手机 websocket 连接 |
| url | 取证页面链接 |
| bind_time | 绑定时间 |

---

### 2. 查询单个批次结果

- **接口地址**: `POST /1890212364383342594`

**请求参数**:

| 名称 | 类型 | 必填 | 说明 |
|------|------|------|------|
| batch_id | string | 是 | 批次ID |

---

### 3. 查询单个任务结果

- **接口地址**: `POST /1890211150539182082`

**请求参数**:

| 名称 | 类型 | 必填 | 说明 |
|------|------|------|------|
| att_id | string | 是 | 任务ID |

**响应参数**:

| 名称 | 说明 |
|------|------|
| status | 任务状态 |
| zip_url | 证据包下载地址 |
| video_url | 视频下载地址 |
| error_msg | 错误信息 |

---

### 4. 获取证据包下载地址

- **接口地址**: `POST /1890210161333555201`

**请求参数**:

| 名称 | 类型 | 必填 | 说明 |
|------|------|------|------|
| att_id | string | 是 | 任务ID |

**响应参数**:

| 名称 | 说明 |
|------|------|
| url | 证据包下载地址 |
| expire_time | 下载地址有效期 |

---

### 5. 检查是否有空闲机器

- **接口地址**: `POST /1890214175638999041`
- **请求参数**: 无

**响应参数**:

| 名称 | 说明 |
|------|------|
| has_device | 是否有空闲机器 |

---

### 6. 分配机器

- **接口地址**: `POST /1890213833127940098`

**请求参数**:

| 名称 | 类型 | 必填 | 说明 |
|------|------|------|------|
| batch_id | string | 是 | 批次ID |
| callback_failed_url | string | 否 | 失败回调地址 |

---

## 状态枚举

### 批次状态

| 值 | 说明 |
|----|------|
| NEW | 新建 |
| ASSIGNED | 已分配 |
| LOGIN | 账号登录中 |
| CHECKING | 账号自检中 |
| CHECKED | 账号自检完成 |
| UNDERWAY | 取证中 |
| SUCCESSED | 取证成功 |
| FAILED | 取证失败 |

### 任务状态

| 值 | 说明 |
|----|------|
| NEW | 新建 |
| RUNNING | 执行中 |
| SUCCESSED | 成功 |
| FAILED | 失败 |

### 取证类型

| 值 | 说明 |
|----|------|
| 1 | 拼多多电商取证 |
| 3 | 淘宝电商取证 |
| 4 | 抖音电商取证 |

path: references/sign-api.md
content: # 电子签章组件 API 文档

## 接口地址及配置

| 环境 | 接口请求地址 |
|------|-------------|
| 线上环境 | `https://api.dataqin.com` |

## 认证方式

同链开放能力，使用 HMAC-SHA256 签名认证。

## 业务流程

### 创建印章流程

```
创建主体（企业/个人）→ 上传印章文件（可选）→ 创建印章
```

### 签署合同流程

```
上传文件 → 创建合同模板 → 配置模板 → 创建签署流程 → 获取签署链接 → 用户签署 → 回调通知
```

---

## 接口列表

### 文件模块

#### 1. 上传文件（Base64）

- **接口地址**: `POST /1952208013663731714`
- **描述**: 上传文件，文件大小不超过 10M

**请求参数**:

| 名称 | 类型 | 必填 | 说明 |
|------|------|------|------|
| inputSource | string | 是 | Base64 编码的文件内容 |
| fileName | string | 是 | 文件名 |
| fileExt | string | 是 | 文件后缀 |

---

#### 2. Word 转 PDF（Base64）

- **接口地址**: `POST /1952207666752847874`
- **描述**: 只接受 docx/doc 文档，不超过 10M

**响应**: 返回临时下载链接，有效期 1 小时

---

#### 3. 文件下载

- **接口地址**: `POST /1952189609007448065`
- **响应**: 返回文件流

---

#### 4. 上传印章文件

- **接口地址**: `POST /1952189892311711746`
- **描述**: 文件大小不超过 5M

**请求参数**:

| 名称 | 类型 | 必填 | 说明 |
|------|------|------|------|
| inputSource | string | 是 | Base64 编码的文件内容 |
| type | integer | 是 | 0=企业，1=个人 |
| fileName | string | 是 | 文件名 |
| fileExt | string | 是 | 文件后缀 |

---

### 企业主体管理

#### 1. 创建企业主体

- **接口地址**: `POST /1952190928459988993`

**请求参数**:

| 名称 | 类型 | 必填 | 说明 |
|------|------|------|------|
| principalName | string | 是 | 企业名称 |
| unifiedSocialCreditCode | string | 是 | 统一社会信用代码 |
| isTemp | integer | 否 | 是否临时：0=否，1=是 |

---

#### 2. 查询企业列表

- **接口地址**: `POST /1952209023635357697`

---

#### 3. 查询企业详情

- **接口地址**: `POST /1952191519106072578`

---

#### 4. 删除企业主体

- **接口地址**: `POST /1952191933721411586`

---

### 个人主体管理

#### 1. 创建个人主体

- **接口地址**: `POST /1952192210109267969`

**请求参数**:

| 名称 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | string | 是 | 姓名 |
| idCard | string | 是 | 身份证号 |
| isTemp | integer | 否 | 是否临时 |

---

#### 2. 查询个人列表

- **接口地址**: `POST /1952209453136281602`

---

#### 3. 查询个人详情

- **接口地址**: `POST /1952192663647748097`

---

#### 4. 删除个人主体

- **接口地址**: `POST /1952193335533305858`

---

### 企业印章管理

#### 1. 创建企业印章

- **接口地址**: `POST /1952193656280121346`

**请求参数**:

| 名称 | 类型 | 必填 | 说明 |
|------|------|------|------|
| userId | integer | 是 | 企业用户ID |
| sealName | string | 是 | 印章名称 |
| enterpriseSealType | integer | 是 | 0=模板印章，1=自定义印章 |
| sealMode | integer | 是 | 印章样式 |
| sealPath | string | 否 | 自定义印章图片路径 |
| licenseProtocol | string | 否 | 授权协议文件路径 |

---

### 个人印章管理

#### 1. 创建个人签名

- **接口地址**: `POST /1952196969243410434`

---

### 合同模板管理

#### 1. 创建合同模板

- **接口地址**: `POST /1952206872632688641`

**请求参数**:

| 名称 | 类型 | 必填 | 说明 |
|------|------|------|------|
| templateName | string | 是 | 模板名称 |
| completeCallbackUrl | string | 否 | 配置完成回调URL |
| fileList | array | 是 | 文件列表 |

---

### 签署流程管理

#### 1. 创建签署流程

- **接口地址**: `POST /1952209757391093762`

**请求参数**:

| 名称 | 类型 | 必填 | 说明 |
|------|------|------|------|
| templateId | integer | 是 | 模板ID |
| fileList | array | 是 | 文件列表（含签署方和控件值） |
| createCallbackUrl | string | 否 | 创建完成回调URL |
| signCallbackUrl | string | 否 | 签署完成回调URL |
| signerCallbackUrl | string | 否 | 签署方完成回调URL |

**响应参数**:

| 名称 | 说明 |
|------|------|
| signFlowId | 签署流程ID |
| signFlowNo | 签署流程编号 |

---

#### 2. 获取预览/签署链接

- **接口地址**: `POST /1952205868046548994`

**响应参数**:

| 名称 | 说明 |
|------|------|
| previewUrl | PC 端预览链接 |
| signUrl | H5 签署链接 |

---

## 回调通知

### 签名规则

1. 参数按 key 字典序排序（排除 sign 字段）
2. 拼接 key=value，用 & 连接
3. 末尾追加 &appSecret=xxx
4. MD5 加密，转小写

### 回调类型

| 回调 | 触发时机 |
|------|---------|
| 模板配置完成 | 人工配置完模板后 |
| 创建流程完成 | 签署流程创建完成 |
| 签署完成 | 所有签署方签署完成 |
| 签署方完成 | 单个签署方签署完成 |

---

## 状态码

| 状态码 | 说明 |
|--------|------|
| 200 | 成功 |
| 500 | 程序异常 |

path: lib/auth.js
content: /**
 * HMAC-SHA256 签名模块
 * 用于生成 X-HMAC-SIGNATURE 和 X-HMAC-DIGEST
 */

const crypto = require('crypto');

/**
 * 生成 GMT 格式时间
 * @returns {string} GMT格式时间，如 "Tue, 06 Aug 2024 07:15:26 GMT"
 */
function getGMTDate() {
  return new Date().toUTCString();
}

/**
 * 生成全局签名 X-HMAC-SIGNATURE
 * 签名公式: HMAC-SHA256-HEX(appSecret, signingString)
 * signingString = HTTP Method + \n + URI + \n + "" + \n + appKey + \n + Date + \n
 * 
 * @param {string} method - HTTP方法，如 "POST"
 * @param {string} uri - 请求路径，如 "/1848921633505587202"
 * @param {string} appKey - 应用 appKey
 * @param {string} date - GMT格式时间
 * @param {string} appSecret - 应用 appSecret
 * @returns {string} Base64编码的签名
 */
function signRequest(method, uri, appKey, date, appSecret) {
  const signingString = `${method}\n${uri}\n\n${appKey}\n${date}\n`;
  
  return crypto
    .createHmac('sha256', appSecret)
    .update(signingString)
    .digest('base64');
}

/**
 * 生成 Body 签名 X-HMAC-DIGEST
 * 签名公式: HMAC-SHA256-HEX(appSecret, body)
 * 
 * @param {object|string} body - 请求体
 * @param {string} appSecret - 应用 appSecret
 * @returns {string} Base64编码的签名
 */
function signBody(body, appSecret) {
  const bodyStr = typeof body === 'string' ? body : JSON.stringify(body);
  
  return crypto
    .createHmac('sha256', appSecret)
    .update(bodyStr)
    .digest('base64');
}

/**
 * 构建完整的请求头
 * 
 * @param {string} method - HTTP方法
 * @param {string} uri - 请求路径
 * @param {object|string} body - 请求体
 * @param {object} config - 配置对象 { appKey, appSecret, productId }
 * @returns {object} 完整的请求头对象
 */
function buildHeaders(method, uri, body, config) {
  const date = getGMTDate();
  const bodyStr = typeof body === 'string' ? body : JSON.stringify(body);
  
  return {
    'X-HMAC-ACCESS-KEY': config.appKey,
    'X-APP-PRODUCT-ID': config.productId,
    'X-HMAC-ALGORITHM': 'hmac-sha256',
    'X-HMAC-SIGNATURE': signRequest(method, uri, config.appKey, date, config.appSecret),
    'X-HMAC-DIGEST': signBody(bodyStr, config.appSecret),
    'Date': date,
    'Content-Type': 'application/json'
  };
}

module.exports = {
  buildHeaders,
  signRequest,
  signBody,
  getGMTDate
};

path: lib/callback.js
content: /**
 * 回调验签模块（MD5）
 * 用于验证电子签章组件的回调签名
 */

const crypto = require('crypto');

/**
 * 生成回调签名
 * 签名规则：
 * 1. 参数按 key 字典序排序（排除 sign 字段）
 * 2. 拼接 key=value，用 & 连接
 * 3. 末尾追加 &appSecret=xxx
 * 4. MD5 加密，转小写
 * 
 * @param {object} params - 回调参数
 * @param {string} appSecret - 应用 appSecret
 * @returns {string} MD5签名（小写）
 */
function generateCallbackSign(params, appSecret) {
  const sortedKeys = Object.keys(params)
    .filter(key => key !== 'sign')
    .sort();
  
  const str = sortedKeys
    .map(key => `${key}=${params[key]}`)
    .join('&') + `&appSecret=${appSecret}`;
  
  return crypto.createHash('md5').update(str).digest('hex').toLowerCase();
}

/**
 * 验证回调签名
 * @param {object} params - 回调参数（包含 sign 字段）
 * @param {string} appSecret - 应用 appSecret
 * @returns {boolean} 验签通过返回 true
 */
function verifyCallback(params, appSecret) {
  const { sign } = params;
  
  if (!sign) {
    return false;
  }
  
  const expectedSign = generateCallbackSign(params, appSecret);
  return sign === expectedSign;
}

/**
 * 解析回调数据
 * @param {object} params - 回调参数
 * @param {string} appSecret - 应用 appSecret
 * @returns {object|null} 验签成功返回解析后的数据，失败返回 null
 */
function parseCallback(params, appSecret) {
  if (!verifyCallback(params, appSecret)) {
    return null;
  }
  
  // 解析 signerList（可能是 JSON 字符串）
  if (params.signerList && typeof params.signerList === 'string') {
    try {
      params.signerList = JSON.parse(params.signerList);
    } catch (e) {
      // 保持原样
    }
  }
  
  return {
    signFlowId: params.signFlowId,
    signStatus: params.signStatus,
    createStatus: params.createStatus,
    templateId: params.templateId,
    templateStatus: params.templateStatus,
    fileId: params.fileId,
    signerList: params.signerList,
    timestamp: params.timestamp,
    nonce: params.nonce,
    appKey: params.appKey
  };
}

module.exports = {
  generateCallbackSign,
  verifyCallback,
  parseCallback
};

path: lib/client.js
content: /**
 * 统一 HTTP 客户端
 */

const { buildHeaders } = require('./auth');
const { validateConfig, getEndpointConfig } = require('./validate');

let _config = null;

/**
 * 初始化配置
 * @param {object} config - 配置对象
 */
function init(config) {
  validateConfig(config);
  _config = config;
}

/**
 * 获取当前配置
 * @returns {object} 配置对象
 */
function getConfig() {
  if (!_config) {
    throw new Error('未初始化，请先调用 init(config)');
  }
  return _config;
}

/**
 * 获取凭证
 * @returns {object} { appKey, appSecret }
 */
function getCredentials() {
  const config = getConfig();
  
  if (!config.appKey || !config.appSecret) {
    throw new Error(
      '\n❌ 凭证未配置\n' +
      '   请在 config.json 中配置 appKey 和 appSecret\n'
    );
  }
  
  return {
    appKey: config.appKey,
    appSecret: config.appSecret
  };
}

/**
 * 统一请求方法
 * @param {string} product - 产品名称，如 'chain', 'evidence', 'certificate', 'sign'
 * @param {string} endpoint - 端点名称，如 'upload', 'queryResult'
 * @param {object} body - 请求体
 * @returns {Promise<object>} 响应数据
 */
async function request(product, endpoint, body) {
  const { path, productId } = getEndpointConfig(_config, product, endpoint);
  const credentials = getCredentials();
  
  const url = `${_config.baseUrl}${path}`;
  const headers = buildHeaders('POST', path, body, {
    appKey: credentials.appKey,
    appSecret: credentials.appSecret,
    productId
  });
  
  const response = await fetch(url, {
    method: 'POST',
    headers,
    body: JSON.stringify(body)
  });
  
  const data = await response.json();
  
  // 检查全局响应
  if (data.code !== 200) {
    const error = new Error(data.message || `API Error: ${data.code}`);
    error.code = data.code;
    error.response = data;
    throw error;
  }
  
  // 检查服务响应
  if (data.data && data.data.code !== undefined) {
    const serviceCode = data.data.code;
    // 兼容字符串和数字类型的状态码
    if (serviceCode !== 0 && serviceCode !== '0' && serviceCode !== '200' && serviceCode !== 200) {
      const error = new Error(data.data.msg || data.data.message || `Service Error: ${serviceCode}`);
      error.code = serviceCode;
      error.response = data;
      throw error;
    }
  }
  
  return data.data;
}

/**
 * 带重试的请求
 * @param {string} product - 产品名称
 * @param {string} endpoint - 端点名称
 * @param {object} body - 请求体
 * @param {number} retries - 重试次数，默认 3
 * @param {number} delay - 重试间隔（毫秒），默认 1000
 * @returns {Promise<object>} 响应数据
 */
async function requestWithRetry(product, endpoint, body, retries = 3, delay = 1000) {
  let lastError;
  
  for (let i = 0; i < retries; i++) {
    try {
      return await request(product, endpoint, body);
    } catch (error) {
      lastError = error;
      
      // 某些错误不应该重试
      if (error.code === 401 || error.code === 403) {
        throw error;
      }
      
      if (i < retries - 1) {
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }
  
  throw lastError;
}

/**
 * 下载文件
 * @param {string} product - 产品名称
 * @param {string} endpoint - 端点名称
 * @param {object} body - 请求体
 * @returns {Promise<{buffer: Buffer, contentType: string}>}
 */
async function downloadFile(product, endpoint, body) {
  const { path, productId } = getEndpointConfig(_config, product, endpoint);
  const credentials = getCredentials();
  
  const url = `${_config.baseUrl}${path}`;
  const headers = buildHeaders('POST', path, body, {
    appKey: credentials.appKey,
    appSecret: credentials.appSecret,
    productId
  });
  
  const response = await fetch(url, {
    method: 'POST',
    headers,
    body: JSON.stringify(body)
  });
  
  if (!response.ok) {
    throw new Error(`Download failed: ${response.status}`);
  }
  
  const buffer = await response.arrayBuffer();
  const contentType = response.headers.get('content-type');
  
  return {
    buffer: Buffer.from(buffer),
    contentType
  };
}

module.exports = {
  init,
  getConfig,
  getCredentials,
  request,
  requestWithRetry,
  downloadFile
};

path: lib/validate.js
content: /**
 * 配置校验模块
 */

/**
 * 校验配置是否完整
 * @param {object} config - 配置对象
 * @returns {boolean} 校验通过返回 true
 * @throws {Error} 配置不完整时抛出错误（包含详细的配置指导）
 */
function validateConfig(config) {
  const errors = [];
  const warnings = [];
  
  // 检查基础配置 - baseUrl
  if (!config.baseUrl) {
    errors.push({
      field: 'baseUrl',
      message: 'baseUrl 未配置',
      guide: '请配置接口地址，测试环境: https://test-apisix-gateway.shuzhi.shuqinkeji.cn'
    });
  }
  
  // appKey 和 appSecret 检查（必需）
  if (!config.appKey) {
    errors.push({
      field: 'appKey',
      message: 'appKey 未配置',
      guide: '请在 config.json 中填入数秦开放平台提供的 appKey'
    });
  }
  if (!config.appSecret) {
    errors.push({
      field: 'appSecret',
      message: 'appSecret 未配置',
      guide: '请在 config.json 中填入数秦开放平台提供的 appSecret'
    });
  }
  
  // 检查各产品配置（全部可选，按需配置）
  if (!config.products) {
    errors.push({
      field: 'products',
      message: 'products 未配置',
      guide: '请在 config.json 中配置 products 对象'
    });
  } else {
    // 区块链API服务（可选）
    if (config.products.chain) {
      const chainEndpoints = config.products.chain.endpoints || {};
      const endpoints = ['upload', 'queryResult'];
      for (const endpointKey of endpoints) {
        const endpoint = chainEndpoints[endpointKey];
        if (!endpoint || !endpoint.productId) {
          warnings.push({
            field: `products.chain.endpoints.${endpointKey}.productId`,
            message: `区块链API服务「${endpoint?.description || endpointKey}」的产品标识未配置`,
            guide: `如需使用区块链功能，请配置产品标识`
          });
        }
      }
    }
    
    // 自动化取证（可选）
    if (config.products.evidence && !config.products.evidence.productId) {
      warnings.push({
        field: 'products.evidence.productId',
        message: '自动化取证的产品标识未配置',
        guide: '如需使用取证功能，请配置产品标识'
      });
    }
    
    // 保管单组件（可选）
    if (config.products.certificate && !config.products.certificate.productId) {
      warnings.push({
        field: 'products.certificate.productId',
        message: '保管单组件的产品标识未配置',
        guide: '如需使用保管单功能，请配置产品标识'
      });
    }
    
    // 电子签章（可选）
    if (config.products.sign && !config.products.sign.productId) {
      warnings.push({
        field: 'products.sign.productId',
        message: '电子签章的产品标识未配置',
        guide: '如需使用电子签章功能，请配置产品标识'
      });
    }
  }
  
  // 生成错误消息（只有 errors 会阻止运行）
  if (errors.length > 0) {
    const errorLines = ['\n❌ 配置错误，请按以下指导完成配置：\n'];
    
    errors.forEach((err, index) => {
      errorLines.push(`${index + 1}. ${err.message}`);
      errorLines.push(`   → ${err.guide}`);
      errorLines.push(`   配置路径: config.json → ${err.field}`);
      errorLines.push('');
    });
    
    if (warnings.length > 0) {
      errorLines.push('\n⚠️  以下配置项未完成（可选功能）：\n');
      warnings.forEach((warn) => {
        errorLines.push(`   - ${warn.message}`);
      });
    }
    
    errorLines.push('\n📖 详细配置说明请参考 SKILL.md 或 references/ 目录下的 API 文档\n');
    
    throw new Error(errorLines.join('\n'));
  }
  
  // 显示警告但不阻止运行
  if (warnings.length > 0) {
    console.log('\n⚠️  以下功能未配置，按需启用：');
    warnings.forEach((warn) => {
      console.log(`   - ${warn.message}`);
    });
    console.log('');
  }
  
  return true;
}

/**
 * 获取端点配置
 * @param {object} config - 配置对象
 * @param {string} product - 产品名称
 * @param {string} endpoint - 端点名称
 * @returns {object} { path, productId }
 */
function getEndpointConfig(config, product, endpoint) {
  const productConfig = config.products[product];
  if (!productConfig) {
    throw new Error(`产品 ${product} 未配置`);
  }
  
  // 检查是否有 endpoints 配置
  if (productConfig.endpoints) {
    const endpointConfig = productConfig.endpoints[endpoint];
    if (!endpointConfig) {
      throw new Error(`端点 ${endpoint} 未配置`);
    }
    // endpoint 配置可能是字符串（路径）或对象 { path, productId }
    const path = typeof endpointConfig === 'string' ? endpointConfig : endpointConfig.path;
    return {
      path,
      productId: productConfig.productId
    };
  }
  
  // 其他产品使用 productId
  return {
    productId: productConfig.productId
  };
}

module.exports = {
  validateConfig,
  getEndpointConfig
};

path: lib/modules/certificate.js
content: /**
 * CAP-保管单组件模块
 * 用于生成和下载保管单 PDF 文件
 */

const { request } = require('../client');

/**
 * 生成保管单
 * @param {object} params - 保管单参数
 * @returns {Promise<{certNo: string}>} 存证编号
 */
async function create(params) {
  const result = await request('certificate', 'create', params);
  return result.data;
}

/**
 * 获取保管单下载地址
 * @param {string} certNo - 存证编号
 * @returns {Promise<{url: string}>}
 */
async function download(certNo) {
  const result = await request('certificate', 'download', { certNo });
  return {
    url: result.data
  };
}

/**
 * 获取客户模板列表
 * @returns {Promise<Array>}
 */
async function listTemplates() {
  const result = await request('certificate', 'listTemplates', {});
  return result.data;
}

/**
 * 生成保管单并等待完成
 * @param {object} params - 保管单参数
 * @param {number} [timeout] - 超时时间（毫秒）
 * @returns {Promise<{certNo: string, url: string}>}
 */
async function createAndGetUrl(params, options = {}) {
  const { timeout = 60000 } = options;
  
  const certNo = await create(params);
  
  if (!certNo) {
    throw new Error('生成保管单失败：未返回存证编号');
  }
  
  const startTime = Date.now();
  let lastError = null;
  
  while (Date.now() - startTime < timeout) {
    try {
      const downloadResult = await download(certNo);
      if (downloadResult.url) {
        return {
          certNo,
          url: downloadResult.url
        };
      }
    } catch (error) {
      lastError = error;
    }
    
    await new Promise(resolve => setTimeout(resolve, 2000));
  }
  
  return {
    certNo,
    url: null,
    error: lastError?.message || '获取下载地址超时'
  };
}

module.exports = {
  create,
  download,
  listTemplates,
  createAndGetUrl
};

path: lib/modules/chain.js
content: /**
 * 区块链API服务模块
 * 支持数据上链、批量查询上链结果
 */

const { request, getConfig } = require('../client');

/**
 * 生成请求ID
 * @returns {string} UUID格式的请求ID
 */
function generateRequestId() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    const r = Math.random() * 16 | 0;
    const v = c === 'x' ? r : (r & 0x3 | 0x8);
    return v.toString(16);
  });
}

/**
 * 数据上链
 * @param {string} data - 业务数据（JSON字符串或对象）
 * @param {object} options - 可选参数
 * @param {string[]} [options.business] - 业务名，如 ['EVIDENCE_PRESERVATION']
 * @param {string[]} [options.source] - 来源，如 ['BQ_INTERNATIONAL']
 * @param {string} [options.requestId] - 请求ID，不传则自动生成
 * @param {string[]} [options.extension] - 扩展内容
 * @returns {Promise<{index: string, requestId: string}>} 交易索引和请求ID
 * 
 * @example
 * const result = await chain.upload('{"ano":"BQ123","sha256":"xxx"}');
 * console.log(result.index); // 'd670b1db-926c-4e71-bf65-92df6c3b8aeb'
 */
async function upload(data, options = {}) {
  const { 
    business, 
    source, 
    requestId = generateRequestId(),
    extension 
  } = options;
  
  // 构建请求体
  const body = {
    data: typeof data === 'object' ? JSON.stringify(data) : data,
    requestId
  };
  
  // 添加可选字段
  if (business) body.business = business;
  if (source) body.source = source;
  if (extension) body.extension = extension;
  
  const result = await request('chain', 'upload', body);
  
  return {
    index: result.data.index,
    requestId
  };
}

/**
 * 批量查询上链结果
 * @param {string[]} indexList - 交易索引号列表
 * @returns {Promise<Array>} 上链结果列表
 * 
 * @example
 * const result = await chain.queryResult(['d670b1db-926c-4e71-bf65-92df6c3b8aeb']);
 * // result[0].chain_results[0].status: 1=成功
 */
async function queryResult(indexList) {
  const result = await request('chain', 'queryResult', { index_list: indexList });
  return result.data;
}

/**
 * 等待上链完成（轮询）
 * @param {string} index - 交易索引
 * @param {object} options - 选项
 * @param {number} options.interval - 轮询间隔（毫秒），默认 5000
 * @param {number} options.timeout - 超时时间（毫秒），默认 300000
 * @returns {Promise<object>} 上链结果
 */
async function waitUntilUploaded(index, options = {}) {
  const { interval = 5000, timeout = 300000 } = options;
  const startTime = Date.now();
  
  while (Date.now() - startTime < timeout) {
    const results = await queryResult([index]);
    
    if (results && results.length > 0) {
      const chainResults = results[0].chain_results;
      if (chainResults && chainResults.length > 0) {
        const status = chainResults[0].status;
        
        // 1 = 上链成功
        if (status === 1) {
          return results[0];
        }
        
        // 0 = 上链失败
        if (status === 0) {
          throw new Error(`上链失败，状态: ${status}`);
        }
      }
    }
    
    await new Promise(resolve => setTimeout(resolve, interval));
  }
  
  throw new Error('等待上链超时');
}

/**
 * 获取链类型列表
 * @returns {string[]} 支持的链类型
 */
function getSupportedChains() {
  const config = getConfig();
  return config.products?.chain?.chains || ['tritium', 'conflux'];
}

/**
 * 获取状态说明
 * @param {number} status - 状态码
 * @returns {string} 状态说明
 */
function getStatusText(status) {
  const config = getConfig();
  const statusMap = config.products?.chain?.status || {};
  return statusMap[status] || `未知状态(${status})`;
}

module.exports = {
  upload,
  queryResult,
  waitUntilUploaded,
  getSupportedChains,
  getStatusText,
  generateRequestId
};

path: lib/modules/evidence.js
content: /**
 * 自动化取证模块
 * 支持拼多多、淘宝、抖音、公众号取证
 */

const { request, getConfig } = require('../client');

/**
 * 创建批量取证任务
 * @param {string} batchId - 批次ID
 * @param {Array<object>} tasks - 任务列表
 * @returns {Promise<{batch_id: string, ws_url: string, url: string, bind_time: string}>}
 */
async function createTask(batchId, tasks) {
  const result = await request('evidence', 'createTask', { 
    batch_id: batchId, 
    tasks 
  });
  return result.data;
}

/**
 * 创建公众号取证任务
 * @param {Array<object>} tasks - 任务列表
 * @returns {Promise<object>}
 */
async function createWechatTask(tasks) {
  const result = await request('evidence', 'createWechatTask', { tasks });
  return result.data;
}

/**
 * 查询批次状态
 * @param {string} batchId - 批次ID
 * @returns {Promise<object>}
 */
async function queryBatch(batchId) {
  const result = await request('evidence', 'queryBatch', { batch_id: batchId });
  return result.data;
}

/**
 * 查询单个任务
 * @param {string} attId - 任务ID
 * @returns {Promise<object>}
 */
async function queryTask(attId) {
  const result = await request('evidence', 'queryTask', { att_id: attId });
  return result.data;
}

/**
 * 获取证据包下载地址
 * @param {string} attId - 任务ID
 * @returns {Promise<{url: string, expire_time: string}>}
 */
async function downloadZip(attId) {
  const result = await request('evidence', 'downloadZip', { att_id: attId });
  return result.data;
}

/**
 * 检查是否有空闲机器
 * @returns {Promise<{has_device: boolean}>}
 */
async function checkDevice() {
  const result = await request('evidence', 'checkDevice', {});
  return result.data;
}

/**
 * 分配机器
 * @param {string} batchId - 批次ID
 * @param {string} [callbackFailedUrl] - 失败回调URL
 * @returns {Promise<{ws_url: string, assigned_time: string}>}
 */
async function assignDevice(batchId, callbackFailedUrl) {
  const body = { batch_id: batchId };
  if (callbackFailedUrl) {
    body.callback_failed_url = callbackFailedUrl;
  }
  const result = await request('evidence', 'assignDevice', body);
  return result.data;
}

/**
 * 轮询等待任务完成
 * @param {string} attId - 任务ID
 * @param {object} options - 选项
 * @returns {Promise<object>}
 */
async function waitForComplete(attId, options = {}) {
  const { interval = 5000, timeout = 600000, onProgress } = options;
  const startTime = Date.now();
  
  while (Date.now() - startTime < timeout) {
    const task = await queryTask(attId);
    
    if (onProgress) {
      onProgress(task);
    }
    
    if (task.status === 'SUCCESSED') {
      return task;
    }
    
    if (task.status === 'FAILED') {
      throw new Error(`取证失败: ${task.error_msg || '未知错误'}`);
    }
    
    await new Promise(resolve => setTimeout(resolve, interval));
  }
  
  throw new Error('等待取证超时');
}

/**
 * 轮询等待批次完成
 * @param {string} batchId - 批次ID
 * @param {object} options - 选项
 * @returns {Promise<object>}
 */
async function waitForBatchComplete(batchId, options = {}) {
  const { interval = 10000, timeout = 1800000, onProgress } = options;
  const startTime = Date.now();
  
  while (Date.now() - startTime < timeout) {
    const batch = await queryBatch(batchId);
    
    if (onProgress) {
      onProgress(batch);
    }
    
    if (batch.status === 'SUCCESSED') {
      return batch;
    }
    
    if (batch.status === 'FAILED') {
      throw new Error(`批次失败: ${batch.message || '未知错误'}`);
    }
    
    await new Promise(resolve => setTimeout(resolve, interval));
  }
  
  throw new Error('等待批次超时');
}

/**
 * 获取取证类型说明
 * @param {number} type - 类型代码
 * @returns {string} 类型说明
 */
function getTypeText(type) {
  const config = getConfig();
  const types = config.products?.evidence?.types || {};
  return types[type] || `未知类型(${type})`;
}

/**
 * 获取批次状态说明
 * @param {string} status - 状态码
 * @returns {string} 状态说明
 */
function getBatchStatusText(status) {
  const config = getConfig();
  const statusMap = config.products?.evidence?.batchStatus || {};
  return statusMap[status] || status;
}

/**
 * 获取任务状态说明
 * @param {string} status - 状态码
 * @returns {string} 状态说明
 */
function getTaskStatusText(status) {
  const config = getConfig();
  const statusMap = config.products?.evidence?.taskStatus || {};
  return statusMap[status] || status;
}

module.exports = {
  createTask,
  createWechatTask,
  queryBatch,
  queryTask,
  downloadZip,
  checkDevice,
  assignDevice,
  waitForComplete,
  waitForBatchComplete,
  getTypeText,
  getBatchStatusText,
  getTaskStatusText
};

path: lib/modules/sign.js
content: /**
 * 电子签章组件模块
 * 包含文件管理、企业/个人主体管理、印章管理、合同模板管理、签署流程管理
 */

const { request } = require('../client');

// ==================== 文件模块 ====================

const file = {
  /**
   * 上传文件（Base64）
   * @param {string} inputSource - Base64编码的文件内容
   * @param {string} fileName - 文件名
   * @param {string} fileExt - 文件后缀，如 '.pdf'
   * @returns {Promise<{fileId: number, fileName: string, filePath: string}>}
   */
  async upload(inputSource, fileName, fileExt) {
    return request('sign', 'fileUpload', { inputSource, fileName, fileExt });
  },

  /**
   * Word转PDF（Base64）
   * @param {string} inputSource - Base64编码的Word文件内容
   * @param {string} fileName - 文件名
   * @param {string} fileExt - 文件后缀，如 '.docx'
   * @returns {Promise<string>} PDF临时下载链接（有效期1小时）
   */
  async wordToPdf(inputSource, fileName, fileExt) {
    return request('sign', 'wordToPdf', { inputSource, fileName, fileExt });
  },

  /**
   * 文件下载
   * @param {string} fileName - 文件名
   * @returns {Promise<Buffer>} 文件流
   */
  async download(fileName) {
    // 注意：此接口返回文件流，需要特殊处理
    const result = await request('sign', 'fileDownload', { fileName });
    return result;
  },

  /**
   * 上传印章文件（Base64）
   * @param {string} inputSource - Base64编码的印章图片
   * @param {number} type - 类型：0=企业，1=个人
   * @param {string} fileName - 文件名
   * @param {string} fileExt - 文件后缀，如 '.png'
   * @returns {Promise<{fileId: number}>}
   */
  async uploadSeal(inputSource, type, fileName, fileExt) {
    return request('sign', 'sealFileUpload', { inputSource, type, fileName, fileExt });
  },

  /**
   * 获取文件详情
   * @param {string|number} dscFileId - 文件ID
   * @returns {Promise<object>}
   */
  async getDetail(dscFileId) {
    return request('sign', 'fileDetail', { dscFileId });
  }
};

// ==================== 企业主体管理 ====================

const enterprise = {
  /**
   * 创建企业主体信息
   * @param {string} principalName - 企业名称
   * @param {string} unifiedSocialCreditCode - 统一社会信用代码
   * @param {number} [isTemp=0] - 是否临时：0=否，1=是
   * @returns {Promise<{userId: number, principalName: string}>}
   */
  async create(principalName, unifiedSocialCreditCode, isTemp = 0) {
    return request('sign', 'enterpriseCreate', { 
      principalName, 
      unifiedSocialCreditCode, 
      isTemp 
    });
  },

  /**
   * 查询企业主体列表
   * @param {string} [principalName] - 企业名称（模糊查询）
   * @param {string} [unifiedSocialCreditCode] - 统一社会信用代码
   * @returns {Promise<Array>}
   */
  async list(principalName, unifiedSocialCreditCode) {
    return request('sign', 'enterpriseList', { 
      principalName, 
      unifiedSocialCreditCode 
    });
  },

  /**
   * 查询企业主体详情
   * @param {string|number} userId - 用户ID
   * @returns {Promise<object>}
   */
  async detail(userId) {
    return request('sign', 'enterpriseDetail', { userId });
  },

  /**
   * 删除企业主体
   * @param {string|number} userId - 用户ID
   * @returns {Promise<boolean>}
   */
  async delete(userId) {
    return request('sign', 'enterpriseDelete', { userId });
  }
};

// ==================== 个人主体管理 ====================

const person = {
  /**
   * 创建个人主体信息
   * @param {string} name - 姓名
   * @param {string} idCard - 身份证号
   * @param {number} [isTemp=0] - 是否临时
   * @returns {Promise<{userId: string, name: string}>}
   */
  async create(name, idCard, isTemp = 0) {
    return request('sign', 'personCreate', { name, idCard, isTemp });
  },

  /**
   * 查询个人主体列表
   * @param {string} [name] - 姓名（模糊查询）
   * @param {string} [idCard] - 身份证号
   * @returns {Promise<Array>}
   */
  async list(name, idCard) {
    return request('sign', 'personList', { name, idCard });
  },

  /**
   * 查询个人主体详情
   * @param {string|number} userId - 用户ID
   * @returns {Promise<object>}
   */
  async detail(userId) {
    return request('sign', 'personDetail', { userId });
  },

  /**
   * 删除个人主体
   * @param {string|number} userId - 用户ID
   * @returns {Promise<boolean>}
   */
  async delete(userId) {
    return request('sign', 'personDelete', { userId });
  }
};

// ==================== 企业印章管理 ====================

const enterpriseSeal = {
  /**
   * 创建企业印章
   * @param {object} params - 印章参数
   * @param {number} params.userId - 企业用户ID
   * @param {string} params.sealName - 印章名称
   * @param {number} params.enterpriseSealType - 印章类型：0=模板印章，1=自定义印章
   * @param {number} params.sealMode - 印章样式
   * @param {string} [params.sealPath] - 自定义印章图片路径（自定义印章时必填）
   * @param {string} [params.licenseProtocol] - 授权协议文件路径
   * @returns {Promise<{sealId: string, sealCode: string}>}
   */
  async create(params) {
    return request('sign', 'enterpriseSealCreate', params);
  },

  /**
   * 获取企业印章详情
   * @param {string|number} sealId - 印章ID
   * @returns {Promise<object>}
   */
  async detail(sealId) {
    return request('sign', 'enterpriseSealDetail', { sealId });
  },

  /**
   * 获取企业印章列表
   * @param {object} params - 查询参数
   * @param {string} [params.principalName] - 企业名称
   * @param {string} [params.unifiedSocialCreditCode] - 统一社会信用代码
   * @param {string} [params.sealName] - 印章名称
   * @param {number} [params.enterpriseSealType] - 印章类型
   * @returns {Promise<Array>}
   */
  async list(params) {
    return request('sign', 'enterpriseSealList', params);
  },

  /**
   * 删除企业印章
   * @param {string|number} sealId - 印章ID
   * @returns {Promise<boolean>}
   */
  async delete(sealId) {
    return request('sign', 'enterpriseSealDelete', { sealId });
  }
};

// ==================== 个人印章管理 ====================

const personSeal = {
  /**
   * 创建个人签名
   * @param {object} params - 签名参数
   * @param {number} params.userId - 个人用户ID
   * @param {string} params.sealName - 签名名称
   * @param {number} params.sealMode - 签名样式：0=模板签名，1=自定义签名
   * @param {string} [params.sealPath] - 自定义签名图片路径
   * @returns {Promise<{sealId: string, sealCode: string}>}
   */
  async create(params) {
    return request('sign', 'personSealCreate', params);
  },

  /**
   * 获取个人签名详情
   * @param {string|number} sealId - 印章ID
   * @returns {Promise<object>}
   */
  async detail(sealId) {
    return request('sign', 'personSealDetail', { sealId });
  },

  /**
   * 获取个人签名列表
   * @param {string} [name] - 姓名
   * @param {string} [idCard] - 身份证号
   * @returns {Promise<Array>}
   */
  async list(name, idCard) {
    return request('sign', 'personSealList', { name, idCard });
  },

  /**
   * 删除个人签名
   * @param {string|number} sealId - 印章ID
   * @returns {Promise<boolean>}
   */
  async delete(sealId) {
    return request('sign', 'personSealDelete', { sealId });
  }
};

// ==================== 合同模板管理 ====================

const template = {
  /**
   * 创建合同模板
   * @param {object} params - 模板参数
   * @param {string} params.templateName - 模板名称
   * @param {string} [params.completeCallbackUrl] - 配置完成回调URL
   * @param {Array} params.fileList - 文件列表
   * @returns {Promise<{templateId: number, previewUrl: string}>}
   */
  async create(params) {
    return request('sign', 'templateCreate', params);
  },

  /**
   * 获取合同模板详情
   * @param {string|number} templateId - 模板ID
   * @returns {Promise<object>}
   */
  async detail(templateId) {
    return request('sign', 'templateDetail', { templateId });
  },

  /**
   * 获取模板预览链接
   * @param {string|number} templateId - 模板ID
   * @returns {Promise<{templateId: number, previewUrl: string}>}
   */
  async preview(templateId) {
    return request('sign', 'templatePreview', { templateId });
  },

  /**
   * 删除合同模板
   * @param {string|number} templateId - 模板ID
   * @returns {Promise<boolean>}
   */
  async delete(templateId) {
    return request('sign', 'templateDelete', { templateId });
  }
};

// ==================== 签署流程管理 ====================

const signFlow = {
  /**
   * 创建签署流程
   * @param {object} params - 流程参数
   * @param {number} params.templateId - 模板ID
   * @param {Array} params.fileList - 文件列表（包含签署方和控件值）
   * @param {string} [params.createCallbackUrl] - 创建完成回调URL
   * @param {string} [params.signCallbackUrl] - 签署完成回调URL
   * @param {string} [params.signerCallbackUrl] - 签署方签署完成回调URL
   * @returns {Promise<{signFlowId: string, signFlowNo: string}>}
   */
  async create(params) {
    return request('sign', 'signFlowCreate', params);
  },

  /**
   * 获取签署流程详情
   * @param {string} signFlowId - 签署流程ID
   * @returns {Promise<object>}
   */
  async detail(signFlowId) {
    return request('sign', 'signFlowDetail', { signFlowId });
  },

  /**
   * 获取预览链接和签署链接
   * @param {string} signFlowId - 签署流程ID
   * @returns {Promise<{signFlowId: string, previewUrl: string, signUrl: string}>}
   */
  async preview(signFlowId) {
    return request('sign', 'signFlowPreview', { signFlowId });
  },

  /**
   * 获取签署流程的所有合同文件
   * @param {string} signFlowId - 签署流程ID
   * @returns {Promise<Array>}
   */
  async files(signFlowId) {
    return request('sign', 'signFlowFiles', { signFlowId });
  },

  /**
   * 获取签署流程的所有签署方
   * @param {string} signFlowId - 签署流程ID
   * @returns {Promise<Array>}
   */
  async signers(signFlowId) {
    return request('sign', 'signFlowSigners', { signFlowId });
  }
};

// ==================== 业务流程封装 ====================

const workflow = {
  /**
   * 快速创建企业印章（创建主体 + 创建印章）
   * @param {string} principalName - 企业名称
   * @param {string} creditCode - 统一社会信用代码
   * @param {number} [sealType=0] - 印章类型：0=模板印章，1=自定义印章
   * @param {string} [sealName] - 印章名称（默认使用企业名称）
   * @returns {Promise<{userId: number, sealId: string}>}
   */
  async createEnterpriseWithSeal(principalName, creditCode, sealType = 0, sealName) {
    // 创建企业主体
    const entResult = await enterprise.create(principalName, creditCode);
    
    // 创建印章
    const sealResult = await enterpriseSeal.create({
      userId: entResult.userId,
      sealName: sealName || principalName + '印章',
      enterpriseSealType: sealType,
      sealMode: 0
    });
    
    return {
      userId: entResult.userId,
      sealId: sealResult.sealId,
      sealCode: sealResult.sealCode
    };
  },

  /**
   * 快速创建个人签名
   * @param {string} name - 姓名
   * @param {string} idCard - 身份证号
   * @param {number} [sealMode=0] - 签名样式
   * @param {string} [sealName] - 签名名称
   * @returns {Promise<{userId: string, sealId: string}>}
   */
  async createPersonWithSeal(name, idCard, sealMode = 0, sealName) {
    // 创建个人主体
    const personResult = await person.create(name, idCard);
    
    // 创建签名
    const sealResult = await personSeal.create({
      userId: personResult.userId,
      sealName: sealName || name + '签名',
      sealMode
    });
    
    return {
      userId: personResult.userId,
      sealId: sealResult.sealId,
      sealCode: sealResult.sealCode
    };
  },

  /**
   * 等待签署完成（轮询）
   * @param {string} signFlowId - 签署流程ID
   * @param {object} options - 选项
   * @returns {Promise<object>}
   */
  async waitSigned(signFlowId, options = {}) {
    const { interval = 10000, timeout = 86400000, onProgress } = options;
    const startTime = Date.now();
    
    while (Date.now() - startTime < timeout) {
      const flow = await signFlow.detail(signFlowId);
      
      if (onProgress) {
        onProgress(flow);
      }
      
      // signStatus: 1 = 已完成
      if (flow.signStatus === 1) {
        return flow;
      }
      
      await new Promise(resolve => setTimeout(resolve, interval));
    }
    
    throw new Error('等待签署超时');
  }
};

module.exports = {
  file,
  enterprise,
  person,
  enterpriseSeal,
  personSeal,
  template,
  signFlow,
  workflow
};

Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)