# 外部系统风险评估报告接口 ## 1. 文档说明 本文档定义外部业务系统调用蝶雅风险预警平台生成新品风险评估 Markdown 报告的接口协议。 > 外部接口代码已实现。启用前需执行数据库迁移、配置系统调用密钥并开通本地钉钉用户;测试和正式地址由部署方提供。本接口与内部 `POST /api/v1/risk-predictions/preview` 分离,不依赖内部用户 JWT 或聊天会话。 ### 1.1 接口用途 外部系统提交产品、包装、供应商和开发阶段等风险评估参数,蝶雅平台完成以下处理: 1. 校验调用方系统身份和请求签名。 2. 根据钉钉用户标识,或管理员预先批准的外部账号映射,匹配蝶雅平台用户。 3. 使用蝶雅平台风险数据库、标准库和评分模型完成风险评估。 4. 生成 Markdown 格式的风险评估报告并直接返回。 ### 1.2 当前数据边界 外部系统与蝶雅平台使用不同数据库时,蝶雅平台默认只使用自己的历史异常案例、采购订单、供应商主数据和标准规则库。外部系统数据库中的历史数据不会自动参与评分;如需使用,双方另行约定数据同步或 `externalEvidence` 扩展协议。 ## 2. 接口信息 | 项目 | 内容 | |---|---| | 请求方式 | `POST` | | 请求路径 | `/api/v1/integrations/risk-assessments/report` | | 请求类型 | `application/json` | | 成功响应类型 | `text/markdown; charset=utf-8` | | 字符编码 | UTF-8 | | 接口超时时间建议 | 120 秒 | | 幂等依据 | `sourceSystem + requestId`;`Idempotency-Key` 必须与 `requestId` 一致 | 生产环境完整地址由蝶雅平台提供,例如: ```text https://risk.example.com/api/v1/integrations/risk-assessments/report ``` ## 3. 调用鉴权 接口使用“调用方系统身份 + HMAC-SHA256 签名”鉴权。外部系统不使用蝶雅平台用户 JWT,也不直接传入蝶雅平台本地 `userId`。 ### 3.1 请求头 | 请求头 | 必填 | 说明 | |---|---:|---| | `Content-Type` | 是 | 固定为 `application/json` | | `X-Client-Id` | 是 | 蝶雅平台分配的调用方系统编号,例如 `dieya-plm` | | `X-Timestamp` | 是 | Unix 时间戳,单位为秒 | | `X-Nonce` | 是 | 8~64 位随机字符串,仅允许字母、数字、下划线、短横线,单次请求唯一 | | `X-Signature` | 是 | HMAC-SHA256 签名,固定使用标准 Base64 编码(带填充 `=`) | | `Idempotency-Key` | 是 | 与请求体中的 `requestId` 保持一致 | ### 3.2 签名计算 蝶雅平台为每个 `X-Client-Id` 分配独立密钥 `clientSecret`。POST 调用使用以下原文计算签名: ```text X-Timestamp + "\n" + X-Nonce + "\n" + 原始请求Body ``` 伪代码: ```python message = f"{timestamp}\n{nonce}\n{raw_body}" signature = base64.b64encode( hmac.new(client_secret.encode("utf-8"), message.encode("utf-8"), hashlib.sha256).digest() ).decode("ascii") ``` 注意事项: - 签名必须基于发送时的原始 JSON 字节内容计算,不能先格式化后再计算。 - 服务端默认只接受时间误差不超过 5 分钟的请求。 - 已认证请求的 `X-Nonce` 在数据库中保留 10 分钟,跨进程和重启后仍拒绝重复使用;正常 JSON 的字段校验失败也会消耗 Nonce。 - `clientSecret` 只通过双方约定的安全渠道保存,不放在前端代码、日志或请求体中。 - 生产环境建议叠加 IP 白名单;高安全场景可使用 mTLS。 ### 3.3 用户身份映射 请求中的用户身份使用钉钉标识: - `dingtalkUnionId`:首选跨系统关联标识。 - `dingtalkUserId`:钉钉用户 ID,作为辅助校验和追踪字段。 - `employeeNo`、`name`:展示和审计信息,不能单独作为身份依据。 蝶雅平台按以下顺序处理: 1. 按 `dingtalkUnionId` 查找本地用户。 2. 必要时结合 `dingtalkUserId` 校验用户身份。 3. 用户不存在、停用或 UnionId 匹配到多个本地用户时返回 `403`,不自动创建账号。 4. 评分、审计和报告记录使用蝶雅平台本地用户 ID;该 ID 不返回给外部系统作为跨库主键。 用户需要拥有本地 `risk.assess` 操作权限(管理员直接允许)。如传入 `dingtalkUserId`,必须与本地用户一致。报告中的姓名使用本地用户资料,不使用调用方提交的姓名作为认证或显示依据。查询报告时再次校验原操作人的账号状态及权限。 ### 3.4 非钉钉操作人 没有钉钉身份的用户可使用 `operator.externalUserId`,前提是风险平台管理员已为当前调用系统批准映射。两种身份模式必须且只能选一种;外部账号模式不传 `dingtalkUnionId`、`dingtalkUserId`,可以省略或传 `null`。不允许按姓名、员工编号自动匹配用户。 例如 PLM 账号 `superadmin`(周子墨)的操作人参数为: ```json { "externalUserId": "superadmin", "name": "周子墨" } ``` 这个对象替换请求中的整个 `operator`,其他字段及系统签名要求不变。PLM 后端必须从当前已登录用户取出该账号标识,不能让前端任意指定别人的 `externalUserId`。平台通过 `X-Client-Id + externalUserId` 查映射并检查本地账号及权限,未批准映射返回 `403`。外部系统的管理员角色不授予风险平台管理员权限。 GET 查询时也会重新校验记录中的外部账号映射;撤销映射或停用本地账号后禁止查询。 如果两个钉钉应用返回的 UnionId 不一致,双方需要先确定统一的钉钉身份源或增加统一身份映射表。 ## 4. 请求参数 ### 4.1 请求体 ```json { "requestId": "PLM-D-FT-003-0001", "sourceSystem": "dieya-plm", "operator": { "dingtalkUnionId": "ding_union_xxx", "dingtalkUserId": "ding_user_xxx", "employeeNo": "A10001", "name": "张三" }, "assessment": { "subjectId": "SKU-20261007-001", "productName": "新品洗发水", "productCategory": "洗发水/洗护类", "specification": "500ml", "materialType": "PET瓶/泵头", "supplier": "某某供应商", "factory": "某某加工厂", "stageNew": "产品开发", "defectLevel": null, "description": "新品上市前风险评估", "asOfDate": "2026-10-07" } } ``` ### 4.2 顶层字段 | 字段 | 类型 | 必填 | 说明 | 校验建议 | |---|---|---:|---|---| | `requestId` | string | 是 | 调用方请求唯一编号,用于幂等 | 1~128 个字符,仅允许字母、数字、`_`、`.`、`:`、`-` | | `sourceSystem` | string | 是 | 来源系统编号 | 必须是已登记的 `X-Client-Id` | | `operator` | object | 是 | 发起评估的钉钉用户 | 见下表 | | `assessment` | object | 是 | 风险评估数据 | 见下表 | ### 4.3 `operator` 字段 | 字段 | 类型 | 必填 | 说明 | |---|---|---:|---| | `dingtalkUnionId` | string/null | 条件必填 | 钉钉用户模式必填,与 `externalUserId` 二选一 | | `externalUserId` | string/null | 条件必填 | 非钉钉用户的调用方稳定账号标识,1~128 字符,必须在当前调用系统下预先批准映射 | | `dingtalkUserId` | string | 建议 | 钉钉用户 ID | | `employeeNo` | string | 否 | 员工编号,用于审计展示 | | `name` | string | 否 | 用户姓名,用于报告展示 | ### 4.4 `assessment` 字段 | 字段 | 类型 | 必填 | 说明 | |---|---|---:|---| | `subjectId` | string | 是 | 产品、SKU 或评估对象编号 | | `productName` | string | 否 | 产品名称 | | `productCategory` | string | 是 | 按当前风险评分的八类传值,完整清单及兼容规则见 4.6 | | `specification` | string | 否 | 规格或容量,例如 `500ml`、`100g` | | `materialType` | string | 是 | 包装材质或物料组合,例如 `PET瓶/泵头` | | `supplier` | string | 否 | 供应商名称 | | `factory` | string | 否 | 加工厂名称 | | `stageNew` | string | 否 | 开发阶段 | | `defectLevel` | string/null | 否 | 缺陷等级,可取 `A`、`B`、`C` | | `description` | string | 否 | 评估背景或补充说明 | | `asOfDate` | string/null | 否 | 评估数据截止日期,格式 `YYYY-MM-DD`,不早于 1900-01-01、不允许未来日期;省略时使用首次受理的上海日期 | `stageNew` 建议使用以下枚举值: ```text 需求发现 产品立项 产品开发 量产上市 产品生命管理 ``` ### 4.5 字段约束 - `productCategory`、`materialType`、`subjectId` 缺失时,接口返回 `422`。 - `description` 仅作为评估背景,不直接作为已确认的检测结果或历史事实。 - 调用方不要提交最终风险分数、风险等级或模型版本;这些字段由蝶雅平台统一计算。 - 当前版本不接收外部数据库连接信息、SQL、文件路径或可执行脚本。 - 可选字段允许省略或传 `null`。`stageNew`、`defectLevel` 非空时必须符合枚举;未知字段(包括调用方提交的分数、角色)返回 `422`。 - 参数长度:`subjectId` 128、`productName` 200、`productCategory` 100、`specification` 100、`materialType` 200、`supplier/factory` 300、`description` 2000;`dingtalkUnionId` 64、`dingtalkUserId` 50、`employeeNo` 64、`name` 50。请求体最大 64KB。 ### 4.6 产品品类字典(当前评分口径) 本次 PLM 对接采用现有七维风险评分服务 `normalize_category()` 的八类口径。以下为当前评分分类清单,并非数据库字典接口的返回结果。 PLM 优先将产品品类映射成下表的标准值后提交 `assessment.productCategory`,并保存映射关系。无法确定具体类别时,不应仅凭 PLM 的宽泛业务大类强行映射。 | 标准值(建议提交值) | 当前兼容识别关键词 | |---|---| | `洗发水/洗护类` | 洗发、洗护、洗发水、洗发露、洗发乳 | | `沐浴露类` | 沐浴、沐浴露 | | `面霜/护肤类` | 面霜、乳液、精华、面膜、护肤 | | `牙膏/口腔护理类` | 牙膏、口腔 | | `清洁剂类` | 清洁剂、清洁、洗洁 | | `脱毛类` | 脱毛 | | `彩妆/唇部类` | 彩妆、口红、粉底、眼影 | | `香氛/止汗类` | 香氛、香水、止汗 | 匹配规则及评分边界: - 当前按上表顺序进行关键词子串匹配,多个类别同时命中时取第一类。 - 空值仍按本接口必填规则返回 `422`。非空值无法识别时,按现有评分逻辑将“产品品类”维度设为默认 50 分,并在报告中注明无法归类;这不代表产品风险已经由历史数据验证。 - 已识别品类的历史异常案例少于 10 条时,该维度按 45 分处理;其余情况按现有异常数量归一化规则计算。 - 当前 `防晒`、`洁面`、`护发素` 等名称本身没有专门的评分匹配规则;包含其他兼容关键词的完整名称仍按现有规则归类,例如“防晒乳液”命中“乳液”。 - `个护清洁` 会命中“清洁”并归为“清洁剂类”,不能将该 PLM 大类整体作为防晒等产品的评分分类。 - 报告应同时展示调用方提交的品类和实际评分归类;无法识别时注明默认分及原因。 历史案例的品类维度统计优先归一化案例的 `product_category`,无法识别时再尝试案例的 `product_name`。本对接口径采用评分规则;看板中的“身体护理类”“洁面/卸妆类”“头发护理类”“包材/物料专项”“待补标品类”不属于当前八类评分字典。 ## 5. 成功响应 ### 5.1 响应头 ```http HTTP/1.1 200 OK Content-Type: text/markdown; charset=utf-8 X-Request-Id: PLM-D-FT-003-0001 X-Assessment-Id: 10086 X-Model-Version: database-seven-dimension-v1 X-Risk-Score: 72.50 X-Risk-Level: moderately_low ``` ### 5.2 响应体 响应体直接为 Markdown 文本,不再包裹 `data`、`code` 等 JSON 字段: ```markdown # 新品风险评估报告 ## 一、评估信息 | 项目 | 内容 | |---|---| | 评估对象 | 新品洗发水 | | 对象编号 | SKU-20261007-001 | | 产品品类 | 洗发水/洗护类 | | 规格 | 500ml | | 包装材质 | PET瓶/泵头 | | 供应商 | 某某供应商 | | 加工厂 | 某某加工厂 | | 开发阶段 | 产品开发 | | 评估人 | 张三 | | 评估时间 | 2026-10-07 | ## 二、综合风险结论 - 综合得分:72.50 - 风险等级:较低风险 - 模型版本:database-seven-dimension-v1 ## 三、七维风险评分 | 风险维度 | 得分 | 权重 | 数据状态 | 说明 | |---|---:|---:|---|---| | 供应商表现 | 80.00 | 25% | database | 近三年采购订单和异常案例统计 | | 物料品类 | 75.00 | 20% | database | 包装材质历史异常统计 | | 品类历史 | 70.00 | 15% | database | 产品品类历史案例统计 | ## 四、相似异常案例 暂无直接匹配案例,建议结合相似品类进行验证。 ## 五、标准规则 | 风险点 | 判定规则 | 来源 | 状态 | |---|---|---|---| | 密封性 | 按标准要求完成密封性验证 | 标准文件.xlsx | 待检测 | ## 六、风险预防建议 1. 打样阶段完成内容物与包装材料相容性测试。 2. 增加密封性和加速老化测试。 3. 首单加强来料检验和过程巡检。 ## 七、数据口径说明 本报告基于蝶雅风险平台本地数据库中的历史案例、采购订单和标准库生成。 部分维度无实际数据时,按系统配置的中性值计算。 ``` 报告中的分数是安全得分,分数越高代表风险越低。风险等级阈值以蝶雅平台当前模型配置为准,调用方不要自行重新计算等级。 `X-Risk-Score` 为 0~100 的安全分,保留两位小数。`X-Risk-Level` 为 `low`(低风险)、`moderately_low`(较低风险)、`medium`(中等风险)、`high`(高风险)、`very_high`(极高风险)。`X-Assessment-Id` 按字符串存储。GET 和幂等重试返回原报告及相同结论响应头。 ## 6. 幂等规则 `requestId` 是调用方生成的业务请求唯一编号,同一个 `sourceSystem + requestId` 只能对应一次评估请求。 ### 首次请求 返回 `200` 和 Markdown 报告。 ### 相同请求重复提交 如果请求体和原请求一致,服务端返回同一份已生成报告,仍返回 `200`,并在响应头中带回原始 `X-Assessment-Id`。 ### 同一编号提交不同内容 如果 `requestId` 已经使用,但请求内容摘要不同,返回 `409`,避免覆盖历史报告。 ### 处理中、失败及重试 - 幂等比较基于校验后的 JSON 对象摘要,忽略 JSON 键顺序和排版,省略的可选字段与 `null` 等价;变更任意有效字段视为不同请求。 - 重试保留原请求体(包括操作人和 `asOfDate`),重新生成时间戳、Nonce 和签名。主动重新评估必须使用新 `requestId`。 - 同一请求仍在生成时,POST 或 GET 返回 `202` JSON 和 `Retry-After: 5`,不会重复计算。 - 每次生成最多 110 秒(可配置为 10~110 秒),超时或失败记录为 failed,返回 `503`;相同请求内容可以重新 POST 重试。生成权租约为 150 秒,进程异常退出后租约到期可重新 POST 接管。 - 调用方网络超时并不代表服务端已经停止。应先 GET 查询,再决定是否使用原请求重试。 - 省略 `asOfDate` 时首次受理日期单独持久化,跨天重试仍沿用首次日期。 处理中响应示例: ```json {"code":202,"message":"报告正在生成,请稍后查询","data":{"requestId":"PLM-D-FT-003-0001"}} ``` ### 报告查询 ```http GET /api/v1/integrations/risk-assessments/report?requestId=PLM-D-FT-003-0001 ``` 查询参数必须且只能包含一个 `requestId`;GET 不携带 Body,也不要求 `Idempotency-Key`。仅能查询与 `X-Client-Id` 相同来源的记录。成功返回 `200` Markdown,处理中返回 `202`,失败返回 `503`,不存在返回 `404`。 GET 签名原文为: ```text "GET" + "\n" + 请求路径 + "\n" + 原始查询字符串 + "\n" + X-Timestamp + "\n" + X-Nonce + "\n" ``` 请求路径包含 `/api/v1`,不含域名、查询字符串;原始查询字符串不含 `?`,包括实际发送的 URL 编码,签名后不得重新编码。网关不能重写对外接口路径。以 UTF-8 字节计算 HMAC-SHA256,再以标准 Base64 编码。报告持久化保存,当前未启用自动清理策略。 ## 7. 错误响应 错误响应统一使用 JSON: ```json { "code": 422, "message": "产品品类不能为空", "data": null } ``` | HTTP 状态码 | `code` | 场景 | 调用方处理建议 | |---:|---:|---|---| | `400` | `400` | 请求格式错误或签名原文不合法 | 修正请求格式后重试 | | `401` | `401` | 缺少签名、签名错误、时间戳过期 | 检查系统密钥和签名算法 | | `403` | `403` | 调用方未开通接口或用户无风险评估权限 | 联系平台管理员开通权限 | | `404` | `404` | 当前调用系统未找到该请求 | 核对 requestId 和调用系统 | | `409` | `409` | `requestId` 重复但内容不一致 | 更换请求编号或查询原请求 | | `422` | `422` | 参数缺失或字段值不符合约束 | 修正参数后重试 | | `429` | `429` | 超过调用频率限制 | 按 `Retry-After` 延迟重试 | | `500` | `500` | 平台内部处理异常 | 使用同一 `requestId` 重试并联系平台方 | | `503` | `503` | 风险数据库、模型或依赖服务暂不可用 | 稍后重试,不要生成新的业务编号 | ## 8. 调用示例 ### 8.1 cURL ```bash curl -X POST 'https://risk.example.com/api/v1/integrations/risk-assessments/report' \ -H 'Content-Type: application/json' \ -H 'X-Client-Id: dieya-plm' \ -H 'X-Timestamp: 1791331200' \ -H 'X-Nonce: 8f3d4c1a9e' \ -H 'X-Signature: Base64HmacSha256Value' \ -H 'Idempotency-Key: PLM-D-FT-003-0001' \ --data-raw '{ "requestId": "PLM-D-FT-003-0001", "sourceSystem": "dieya-plm", "operator": { "dingtalkUnionId": "ding_union_xxx", "dingtalkUserId": "ding_user_xxx", "employeeNo": "A10001", "name": "张三" }, "assessment": { "subjectId": "SKU-20261007-001", "productName": "新品洗发水", "productCategory": "洗发水/洗护类", "specification": "500ml", "materialType": "PET瓶/泵头", "supplier": "某某供应商", "factory": "某某加工厂", "stageNew": "产品开发", "description": "新品上市前风险评估", "asOfDate": "2026-10-07" } }' \ --output risk-report.md ``` ### 8.2 Python 签名示例 ```python import base64 import hashlib import hmac import json import secrets import time body = { "requestId": "PLM-D-FT-003-0001", "sourceSystem": "dieya-plm", "operator": {"dingtalkUnionId": "ding_union_xxx"}, "assessment": { "subjectId": "SKU-20261007-001", "productCategory": "洗发水/洗护类", "materialType": "PET瓶/泵头", }, } raw_body = json.dumps(body, ensure_ascii=False, separators=(",", ":")).encode("utf-8") timestamp = str(int(time.time())) nonce = secrets.token_urlsafe(16) message = timestamp.encode() + b"\n" + nonce.encode() + b"\n" + raw_body signature = base64.b64encode( hmac.new(client_secret.encode(), message, hashlib.sha256).digest() ).decode() ``` 调用方必须使用计算签名时的同一份 `raw_body` 发送请求。 ## 9. 评分和报告处理说明 平台内部处理逻辑建议统一复用现有七维风险评分服务: - 供应商表现 - 物料品类 - 品类历史 - 加工厂质量 - 标准符合性 - 节点阶段 - 缺陷严重度 平台会在报告中标注数据状态。没有真实数据库数据的维度不得伪造历史结论,按模型配置的中性值处理并在报告中说明。 外部接口使用结构化参数直接评分,不再通过聊天语义提取参数。沿用现有评分区间起点(截止年份往前 3 年的 1 月 1 日),区间终点为 `asOfDate`,不包含截止日期之后的案例及采购订单。相似案例使用现有检索服务的文本和业务字段排序,输入同一统计区间内的案例;本接口不调用向量嵌入服务。 包装组合沿用现有单标签评分口径,例如 `PET 瓶身 + PP 泵头` 命中“泵头”,不代表分别评估了所有组件。报告展示原文及评分标签。缺陷维度来自历史 A/B/C 记录,不预测新品实际缺陷等级。 AI 只用于生成预防建议,等待最多 20 秒。AI 不可用时仍返回数据库评分及固定验证建议,并在报告中标注;数据库评分失败则返回 `503`,不生成模拟分数。 报告建议包含: 1. 评估信息。 2. 综合风险得分和风险等级。 3. 七维评分明细。 4. 品类、供应商和物料历史统计。 5. 相似异常案例。 6. 标准规则匹配结果。 7. 风险预防建议。 8. 数据口径和模型版本。 ## 10. 联调前需要双方确认的事项 1. 正式接口域名、环境地址和接口版本。 2. `sourceSystem`、`X-Client-Id` 和密钥分配方式。 3. 钉钉 `unionId` 是否可以作为双方统一用户标识;如果不能,需要提供统一身份映射方案。 4. 操作人提前开通、UnionId 唯一映射及 `risk.assess` 权限;本接口已采用不存在返回 `403` 的策略。 5. 是否启用 IP 白名单、mTLS 和调用频率限制。 6. `stageNew`、`defectLevel` 枚举,以及 PLM 到第 4.6 节八类评分品类的映射关系;确认无法归类时的默认分提示。 7. 外部系统历史数据是否需要同步到蝶雅平台。 8. Markdown 报告的保存、下载和展示方式。 9. 超时、重试和 `requestId` 查询机制。 ## 11. 服务端启用配置 先执行迁移: ```powershell python -m alembic upgrade head ``` 新增表为 `integration_nonces` 和 `integration_assessments`,分别保存已认证随机串及请求快照、处理状态、评分结果和 Markdown 报告;成功评估同时写入操作审计。现有用户表不自动添加外部用户。报告记录通过本地用户编号逻辑关联,不对已有用户表创建外键,迁移账号不需要该表的 `REFERENCES` 权限;用户状态和权限仍由接口校验。 在服务端环境配置中设置: ```dotenv RISK_INTEGRATION_CLIENTS={"dieya-plm":"replace-with-at-least-32-random-characters"} RISK_INTEGRATION_REQUESTS_PER_MINUTE=60 RISK_INTEGRATION_REPORT_TIMEOUT_SECONDS=110 ``` 上面的密钥仅为占位,须替换为至少 32 个字符的随机密钥;测试、正式各使用一套。调用系统编号仅允许字母、数字、下划线和短横线,最长 64 位。默认 `{}` 不允许任何系统调用。配置后重启服务。 也可以在后端项目根目录使用启用工具生成密钥,工具会保留 `.env` 中的其他设置及已有系统密钥,不打印密钥、不自动轮换已有密钥: ```powershell .\.venv\Scripts\python.exe scripts/setup_external_risk.py configure --client-id dieya-plm ``` 让操作人在钉钉工作台中登录风险平台,正常免登会同步用户资料和 UnionId。然后使用 PLM 保存的真实 UnionId 检查映射与权限: ```powershell .\.venv\Scripts\python.exe scripts/setup_external_risk.py check-user --union-id "PLM操作人的真实UnionId" --dingtalk-user-id "同一人的真实UserId" ``` 工具只检查、不修改账号或权限。不存在时先通过钉钉免登开通;已停用或无权限时由账号管理员确认处理;映射不唯一或身份不一致时核对两边钉钉身份源,不能按姓名猜测绑定。当前启用的 admin、product_manager、reviewer、user 角色均有风险评估操作权限,以本地 `role_permissions` 表实际记录为准。 限流按调用系统统计已通过签名的请求,POST、GET、幂等重试共用滚动 60 秒窗口;达到阈值返回 `429` 和 `Retry-After: 60`。IP 白名单和 mTLS 可在网关配置,当前应用层不提供这两项配置。 ## 12. 联调调用工具 非钉钉用户需由风险平台管理员在本地显式开通,例如: ```powershell .\.venv\Scripts\python.exe scripts/setup_external_risk.py allow-user --client-id dieya-plm --external-user-id superadmin --employee-no plm_superadmin --name "周子墨" ``` 工具创建无密码、无钉钉绑定的普通本地账号,并写入 `.env`: ```dotenv RISK_INTEGRATION_OPERATOR_MAPPINGS='{"dieya-plm":{"superadmin":"plm_superadmin"}}' ``` 映射目标是本地 `emp_no`。默认 `{}` 禁止所有非钉钉账号;已有映射不会自动指向其他账号,已有账号不会自动更改角色、姓名或启用状态。配置后重启后端,无需数据库迁移。 项目提供 `scripts/call_external_risk_report.py`,自动生成 POST/GET 签名并保存 Markdown。调用方将本系统分配的密钥写入环境变量 `RISK_INTEGRATION_CLIENT_SECRET`(客户端单个密钥,与服务端 JSON 密钥表是不同配置)。`assessment.json` 使用本文第 4.1 节请求结构,填入已开通的真实 UnionId。 ```powershell python scripts/call_external_risk_report.py --url https://risk.example.com/api/v1/integrations/risk-assessments/report --client-id dieya-plm --body-file assessment.json --output risk-report.md ``` 查询原请求: ```powershell python scripts/call_external_risk_report.py --url https://risk.example.com/api/v1/integrations/risk-assessments/report --client-id dieya-plm --request-id PLM-D-FT-003-0001 --output risk-report.md ``` 只有 `200` 会保存报告;`202`/错误响应会展示 JSON 和重试等待时间,不会覆盖已有 Markdown。