如何调用 JumpServer Runtime Store API 提交 Kael journal 记录并处理 revision 冲突【免费下载链接】jumpserverJumpServer is an open-source Privileged Access Management (PAM) platform that provides DevOps and IT teams with on-demand and secure access to SSH, RDP, Kubernetes, Database and RemoteApp endpoints through a web browser.项目地址: https://gitcode.com/GitHub_Trending/ju/jumpserver当你在为 JumpServer 的 Chat AI 运行时Go 服务 Kael做二次开发或联调时需要把 Kael 产生的对话状态落到 JumpServer Core 的持久化存储里。这一步走的是 Core 的内部 Runtime Store APIKael 通过POST /api/v1/chat-ai/runtime-store/追加一条 journal recordCore 在事务内做 revision 比对CAS并落库当多个调用方并发写入、或你上一次写入因为网络抖动没有确认结果时就会收到 revision 冲突需要按返回的current_revision重算后重提。本文给出从准备凭据、构造 record 与签名、提交写入到正确处理 409 冲突的完整路径。适用前提是你已经有一个绑定到typekael的 Terminal 的服务账号并且能拿到该服务账号的 Access Key接口只在 Core 的/api/v1/chat-ai/路由下可访问不单独暴露端口。一、前置条件服务账号与 Access KeyRuntime Store 是内部接口认证和授权都有硬性约束先核对清楚再动手认证方式为Access Key HTTP SignatureSignatureAuthentication。请求需携带Signature形式的Authorization头包含keyid、algorithm、signature三个字段且签名至少覆盖(request-target)和date两个头。keyid对应你的 Access Key。授权上要求认证用户必须是服务账号并且该账号绑定的 Terminal 类型必须是typekael。普通用户、其他组件的服务账号、以及用户委托票据X-JMS-AI-Delegation都不能访问这个接口。接口实现位于 RuntimeStoreView路由定义在 api/urls.py。确认要点你的 Access Key 属于一个服务账号、该账号挂了kael类型的 Terminal、Key 处于有效状态过期或被 IP 组限制会直接鉴权失败。这一步不满足时后续任何请求都会在鉴权层失败与本文后续的 record/签名无关。二、先读一次拿到当前 revision 起点写入前需要先知道 journal 当前的 revision。读接口GET /api/v1/chat-ai/runtime-store/?nonceuuidafter0limit1000nonce本次请求生成的 UUID必填。after默认0表示调用方已持久化到的 revisionlimit默认1000范围 1 到 1000。若after早于最新 snapshot响应会从该 snapshot 开始。单页 record 总量达到 64 MiB 时即使未达limit也会截断并返回has_moretrue。响应结构数值为文档示例仅作格式参考{ nonce: uuid, revision: 12, results: [ { revision: 12, commit_id: uuid, snapshot: false, record: jsonl-record } ], has_more: false, receipt: hmac-sha256 }revision字段即当前 head。把它作为下一次写入的expected_revision起点。响应带Cache-Control: no-storehas_moretrue时用本页最后一条 record 的revision继续翻页。receipt用当前请求 Access Key 的 secret 对 canonical 内容做 HMAC-SHA256调用方应校验它以确认响应未被篡改。三、构造写入请求record 结构与 integrity 签名写入请求体只有五个字段多一个都会被拒绝Unknown field(s): ...{ commit_id: uuid, expected_revision: 11, snapshot: false, record: jsonl-record, integrity: hmac-sha256 }各字段含义与约束commit_id本次提交的 UUID用于幂等见第四节。expected_revision你预期当前 head 的值非负整数是 CAS 的比对基准。snapshot布尔。写入 snapshot 时Core 会在同一事务中保存新 snapshot并删除它之前的 journal records。record必须恰好一行的 JSON 字符串且只能包含version、created_at、payload、checksum四个字段。当前version为1payload是 Base64 字符串checksum是 payload 解码后字节的 SHA-256。Core 会把record当不透明字符串保存但会验证 JSONL envelope、payload checksum、提交完整性和 revision。单条 record 上限 64 MiB。integrity用当前请求 Access Key 的 secret 对如下 canonical 内容各部分以换行拼接、无末尾换行执行 HMAC-SHA256kael-runtime-store-commit-v1 default commit_id expected_revision 0|1 snapshot sha256(record)其中0|1 snapshot是 snapshot 的位表示false→0true→1sha256(record)是record字符串的 SHA-256。下面这段 Python 仅演示如何按文档规则算出payload的checksum以及integrity其中的 UUID、时间戳、payload 都是占位值需要你替换成真实数据import base64, hashlib, hmac, json secret b你的 Access Key secret # 替换请求所用 Access Key 的 secret commit_id uuid # 替换本次提交的 UUID expected_revision 11 # 替换预期的当前 head snapshot False payload_bytes b你的 payload 原始字节 # 替换要持久化的业务数据 payload_b64 base64.b64encode(payload_bytes).decode() record json.dumps({ version: 1, created_at: ISO8601 时间字符串, # 替换如 2026-09-11T20:00:00Z payload: payload_b64, checksum: hashlib.sha256(payload_bytes).hexdigest(), }, separators(,, :), ensure_asciiFalse) def sign(parts): msg \n.join(str(p) for p in parts).encode(utf-8) return hmac.new(secret, msg, hashlib.sha256).hexdigest() record_hash hashlib.sha256(record.encode(utf-8)).hexdigest() integrity sign([ kael-runtime-store-commit-v1, default, commit_id, expected_revision, 1 if snapshot else 0, record_hash, ])注意record内层只允许那四个字段外层请求体也严格只允许那五个字段任何多余键都会触发校验失败。四、提交写入并处理 revision 冲突提交POST /api/v1/chat-ai/runtime-store/Core 在事务内锁定 revision 并执行 CAS两种结果成功HTTP 201返回{ revision: 12, commit_id: uuid, receipt: hmac-sha256 }成功receipt的 canonical 内容同样用 Access Key secret 做 HMAC-SHA256kael-runtime-store-receipt-v1 default commit_id expected_revision revision 0|1 snapshot sha256(record)调用方应校验该 receipt。revision 冲突HTTP 409返回{ code: runtime_store_revision_conflict, detail: The runtime store revision has changed., current_revision: 13 }冲突意味着在你读到expected_revision之后、真正落库之前head 已经被别的写入推进了。处理路径读取返回体里的current_revision这是服务端给出的最新 head不要自己猜。基于这个新 head 重新计算你的业务状态与 recordKael 负责对话状态的恢复、压缩和演进Core 不解析 payload 语义得到新的record与对应的sha256(record)。用新的expected_revision current_revision、并重新生成commit_id和integrity后重新POST。关于幂等重试这是冲突处理里最容易踩坑的一点commit_id支持网络失败后的重试但只有当 revision、snapshot、record 均相同且该提交仍是当前 head 时才会返回与首次相同的结果。也就是说网络超时但实际已写入的场景用同一个commit_id重发能拿回原结果一旦你在冲突后换成了新的 record 或 revision就必须用新的commit_id。判断是否可以直接重试的关键就是这三项revision/snapshot/record是否与上次完全一致。五、边界与限制以下限制直接影响调用能否成功联调前确认一遍反向代理的请求体限制必须高于 64 MiB以容纳 record 外层 JSON 和字符串转义开销否则会先被代理拦下到不了 Core。Runtime Store API 只能在 Core 的/api/v1/chat-ai/路由下访问不应单独暴露服务端口。Core 只把record作为不透明字符串保存不解析 Kael payload 的业务语义恢复、压缩和对话状态演进由 Kael 负责。Access Key 轮换不会重写历史 record读取和写入的 receipt 始终使用当前请求的 Access Key。落库表为chat_ai_runtime_storehead和chat_ai_runtime_store_recordrecord见 models.py。完成一次成功的POST后用GET重新读取确认新 revision 与 record 已可见即视为这条 journal 记录已正确落入 Runtime Store遇到 409 时按第四节的current_revision重算-重提流程收敛。更多接口细节可参考 Chat AIKael 与 JumpServer Core 桥接。【免费下载链接】jumpserverJumpServer is an open-source Privileged Access Management (PAM) platform that provides DevOps and IT teams with on-demand and secure access to SSH, RDP, Kubernetes, Database and RemoteApp endpoints through a web browser.项目地址: https://gitcode.com/GitHub_Trending/ju/jumpserver创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考