<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>实战 on WYF 的技术博客</title><link>https://saflyer.cn/tags/%E5%AE%9E%E6%88%98/</link><description>Recent content in 实战 on WYF 的技术博客</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Mon, 14 Sep 2026 10:03:15 +0800</lastBuildDate><atom:link href="https://saflyer.cn/tags/%E5%AE%9E%E6%88%98/index.xml" rel="self" type="application/rss+xml"/><item><title>模型安全检测 09：自定义原子能力（起服务 → 注册 → DAG 引用）</title><link>https://saflyer.cn/posts/joysafety-custom-function/</link><pubDate>Mon, 14 Sep 2026 10:03:15 +0800</pubDate><guid>https://saflyer.cn/posts/joysafety-custom-function/</guid><description>&lt;img src="https://saflyer.cn/posts/joysafety-custom-function/cover.webp" alt="Featured image of post 模型安全检测 09：自定义原子能力（起服务 → 注册 → DAG 引用）" /&gt;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;&lt;strong&gt;状态&lt;/strong&gt;：✅ 已完成（2026-09-13，文中实验全部在本机真实环境跑通，返回为真实输出摘录）&#10;&lt;strong&gt;前置阅读&lt;/strong&gt;：&lt;a href="https://saflyer.cn/posts/joysafety-dag-engine/"&gt;04-DAG编排引擎深入&lt;/a&gt;（Function/Router/Node 三要素、ref 引用的入口）；配置版本化流水线与坑 4/坑 5 见 &lt;a href="https://saflyer.cn/posts/joysafety-mac-deploy/"&gt;02-Mac本地部署实操&lt;/a&gt; 附录 C 与第三章&#10;&lt;strong&gt;读者定位&lt;/strong&gt;：想把&amp;quot;自己的检测逻辑&amp;quot;接进 JoySafety 的人。读完能回答三个问题：我的服务要长成什么样？注册后配置怎么流到执行引擎？超时/降级行为由哪行代码决定？&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;h2 id="学习目标"&gt;&lt;a href="#%e5%ad%a6%e4%b9%a0%e7%9b%ae%e6%a0%87" class="header-anchor"&gt;&lt;/a&gt;学习目标&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;完整走通 &lt;strong&gt;起服务 → admin 注册 → online → DAG ref 引用 → upgrade → 验证&lt;/strong&gt; 的能力扩展全流程&lt;/li&gt;&#10;&lt;li&gt;从源码层面证明&amp;quot;&lt;strong&gt;新增能力不改一行 Java 代码&lt;/strong&gt;&amp;ldquo;的架构承诺，并掌握协议契约、type 选择、两层超时、连接池与 warmup 的行为细节&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="一句话原理与全局认识"&gt;&lt;a href="#%e4%b8%80%e5%8f%a5%e8%af%9d%e5%8e%9f%e7%90%86%e4%b8%8e%e5%85%a8%e5%b1%80%e8%ae%a4%e8%af%86" class="header-anchor"&gt;&lt;/a&gt;〇、一句话原理与全局认识&#10;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;JoySafety 的&amp;quot;原子能力&amp;quot;本质是一份存在 MySQL 里的配置模板&lt;/strong&gt;：&lt;code&gt;type&lt;/code&gt; 决定由哪个 Java 类执行（框架内置、不可插拔），&lt;code&gt;conf&lt;/code&gt; 里的 &lt;code&gt;url&lt;/code&gt; 决定 HTTP 调谁。所以&amp;quot;自定义能力&amp;quot;的真实含义是——&lt;strong&gt;框架的 Java 代码一行不动，你只需：写一个满足统一协议的 HTTP 服务 + 在 admin 注册一份配置 + 在 DAG 里 &lt;code&gt;ref&lt;/code&gt; 它&lt;/strong&gt;：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-0-1"&gt;&lt;a class="lnlinks" href="#hl-0-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-2"&gt;&lt;a class="lnlinks" href="#hl-0-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-3"&gt;&lt;a class="lnlinks" href="#hl-0-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-4"&gt;&lt;a class="lnlinks" href="#hl-0-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-5"&gt;&lt;a class="lnlinks" href="#hl-0-5"&gt;5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-6"&gt;&lt;a class="lnlinks" href="#hl-0-6"&gt;6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-7"&gt;&lt;a class="lnlinks" href="#hl-0-7"&gt;7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-8"&gt;&lt;a class="lnlinks" href="#hl-0-8"&gt;8&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;配置期 你的脚本 ──POST──► safety-admin:8006 ──写入──► MySQL(function_conf 表)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▲&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ① api 每 60s 轮询 /config/defense/api/conf&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;执行期 safety-api:8007 ─────┘&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ② 配置变更 → 重建 DAG（doMerge 展开 ref → 按 type 反射实例化 Function）&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ ③ 请求来了：Function 用 OkHttpClient POST 你的服务&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 你的检测服务（Flask/任意语言，无状态）&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;hr&gt;&#10;&lt;h2 id="一原子能力的契约统一-http-协议"&gt;&lt;a href="#%e4%b8%80%e5%8e%9f%e5%ad%90%e8%83%bd%e5%8a%9b%e7%9a%84%e5%a5%91%e7%ba%a6%e7%bb%9f%e4%b8%80-http-%e5%8d%8f%e8%ae%ae" class="header-anchor"&gt;&lt;/a&gt;一、原子能力的契约：统一 HTTP 协议&#10;&lt;/h2&gt;&lt;h3 id="11-type-注册表能力的类型是写死的枚举"&gt;&lt;a href="#11-type-%e6%b3%a8%e5%86%8c%e8%a1%a8%e8%83%bd%e5%8a%9b%e7%9a%84%e7%b1%bb%e5%9e%8b%e6%98%af%e5%86%99%e6%ad%bb%e7%9a%84%e6%9e%9a%e4%b8%be" class="header-anchor"&gt;&lt;/a&gt;1.1 type 注册表：能力的&amp;quot;类型&amp;quot;是写死的枚举&#10;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;safety/safety-api/src/main/java/com/jd/security/llmsec/service/execute/FunctionRegistry.java:20&lt;/code&gt;：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-1-1"&gt;&lt;a class="lnlinks" href="#hl-1-1"&gt; 1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-2"&gt;&lt;a class="lnlinks" href="#hl-1-2"&gt; 2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-3"&gt;&lt;a class="lnlinks" href="#hl-1-3"&gt; 3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-4"&gt;&lt;a class="lnlinks" href="#hl-1-4"&gt; 4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-5"&gt;&lt;a class="lnlinks" href="#hl-1-5"&gt; 5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-6"&gt;&lt;a class="lnlinks" href="#hl-1-6"&gt; 6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-7"&gt;&lt;a class="lnlinks" href="#hl-1-7"&gt; 7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-8"&gt;&lt;a class="lnlinks" href="#hl-1-8"&gt; 8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-9"&gt;&lt;a class="lnlinks" href="#hl-1-9"&gt; 9&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-10"&gt;&lt;a class="lnlinks" href="#hl-1-10"&gt;10&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-11"&gt;&lt;a class="lnlinks" href="#hl-1-11"&gt;11&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RiskCheckType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AbstractFunction&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FUNC_MAP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RiskCheckType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DummyFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RiskCheckType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;KeywordFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RiskCheckType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;single_label_pred&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SingleLabelPredFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RiskCheckType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;multi_label_pred&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MultiLablePreFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RiskCheckType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;kb_search&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;KbSearchFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RiskCheckType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;rag_answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RagAnswerFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RiskCheckType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;multi_turn_detect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MultiTurnDetectFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RiskCheckType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parallel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ParallelFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RiskCheckType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AsyncFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}};&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;类比&lt;/strong&gt;：&lt;code&gt;type&lt;/code&gt; 是&amp;quot;电器插头的制式&amp;rdquo;，&lt;code&gt;url&lt;/code&gt; 是&amp;quot;插在哪个插座上&amp;quot;。你只能选现有制式，不能发明新插头——除非改这段 Java 代码并重新构建镜像。&lt;/p&gt;&#10;&lt;p&gt;9 个 &lt;code&gt;type&lt;/code&gt; 里真正适合自定义业务能力的只有 &lt;strong&gt;6 个远程调用型&lt;/strong&gt;（keyword / single_label_pred / kb_search / rag_answer / multi_turn_detect / multi_label_pred），其中 &lt;code&gt;multi_label_pred&lt;/code&gt; 是个坑：注册了但 &lt;strong&gt;&lt;code&gt;MultiLablePreFunction.doRun()&lt;/code&gt; 是空实现&lt;/strong&gt;（&lt;code&gt;.../functions/MultiLablePreFunction.java:33&lt;/code&gt;，方法体只有 &lt;code&gt;// todo 后续支持; return null&lt;/code&gt;），注册它不会报错，但运行时永远返回 null。&lt;code&gt;dummy/parallel/async/mixed&lt;/code&gt; 是编排内部类型，不对外。&lt;/p&gt;&#10;&lt;h3 id="12-single_label_pred-协议一个协议吃掉四种模型"&gt;&lt;a href="#12-single_label_pred-%e5%8d%8f%e8%ae%ae%e4%b8%80%e4%b8%aa%e5%8d%8f%e8%ae%ae%e5%90%83%e6%8e%89%e5%9b%9b%e7%a7%8d%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;1.2 single_label_pred 协议：一个协议吃掉四种模型&#10;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;SingleLabelPredFunction&lt;/code&gt; 用 &lt;code&gt;modelType&lt;/code&gt; 区分四种模型（&lt;code&gt;safety-basic/.../core/conf/funcs/ModelType.java:8&lt;/code&gt;：&lt;code&gt;bert/fasttext/textcnn/llm&lt;/code&gt;），但看代码会发现&lt;strong&gt;四种 modelType 走的是同一段 HTTP 调用逻辑&lt;/strong&gt;——&lt;code&gt;doInit&lt;/code&gt;（第 59&lt;del&gt;63 行）和 &lt;code&gt;doRun&lt;/code&gt;（第 105&lt;/del&gt;110 行）的 switch 都是四分支贯通（fall-through），全部落到同一个 &lt;code&gt;runFasttext()&lt;/code&gt;。&lt;code&gt;modelType&lt;/code&gt; 目前只是元数据标签，不影响请求格式。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;请求方&lt;/strong&gt;（&lt;code&gt;.../service/execute/functions/SingleLabelPredFunction.java:116&lt;/code&gt; 起）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-2-1"&gt;&lt;a class="lnlinks" href="#hl-2-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-2-2"&gt;&lt;a class="lnlinks" href="#hl-2-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-2-3"&gt;&lt;a class="lnlinks" href="#hl-2-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-2-4"&gt;&lt;a class="lnlinks" href="#hl-2-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-2-5"&gt;&lt;a class="lnlinks" href="#hl-2-5"&gt;5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-2-6"&gt;&lt;a class="lnlinks" href="#hl-2-6"&gt;6&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;SingleLabelPredCheck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SkillsReq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SingleLabelPredCheck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SkillsReq&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setBusiness_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBusinessConf&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 业务名&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setRequest_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstReq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRequestId&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 请求ID&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setSession_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstReq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessageInfo&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getSessionId&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 会话ID&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setText_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Lists&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newArrayList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCheckContent&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;&lt;span class="c1"&gt;// 待检文本，定长1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// POST extra.url，body 即上面的 req&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;即你的服务会收到（字段定义见 &lt;code&gt;safety-basic/.../core/check/SingleLabelPredCheck.java:61&lt;/code&gt; 的 &lt;code&gt;SkillsReq&lt;/code&gt;）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-3-1"&gt;&lt;a class="lnlinks" href="#hl-3-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;business_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;test&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;request_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;xxx&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;session_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;yyy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;text_list&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;待检测文本&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;响应方&lt;/strong&gt;必须返回（&lt;code&gt;ResponseMessage&amp;lt;List&amp;lt;SkillsResp&amp;gt;&amp;gt;&lt;/code&gt;，&lt;code&gt;safety-basic/.../core/ResponseMessage.java:11&lt;/code&gt;）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-4-1"&gt;&lt;a class="lnlinks" href="#hl-4-1"&gt; 1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-4-2"&gt;&lt;a class="lnlinks" href="#hl-4-2"&gt; 2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-4-3"&gt;&lt;a class="lnlinks" href="#hl-4-3"&gt; 3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-4-4"&gt;&lt;a class="lnlinks" href="#hl-4-4"&gt; 4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-4-5"&gt;&lt;a class="lnlinks" href="#hl-4-5"&gt; 5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-4-6"&gt;&lt;a class="lnlinks" href="#hl-4-6"&gt; 6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-4-7"&gt;&lt;a class="lnlinks" href="#hl-4-7"&gt; 7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-4-8"&gt;&lt;a class="lnlinks" href="#hl-4-8"&gt; 8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-4-9"&gt;&lt;a class="lnlinks" href="#hl-4-9"&gt; 9&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-4-10"&gt;&lt;a class="lnlinks" href="#hl-4-10"&gt;10&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-4-11"&gt;&lt;a class="lnlinks" href="#hl-4-11"&gt;11&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;code&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 必须 0，否则 api 抛 inner_error&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;success&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="c1"&gt;// 数组非空，api 只取 data[0]&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;riskCode&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 必须显式返回；0=正常，非0=有风险（hasRisk() 唯一判据）&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;riskMessage&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;文本超长&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;probability&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 置信度，供 groovy 阈值表判定，框架本身不判&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;原文&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 可选，回显&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;detail&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;riskCode&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;riskMessage&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;文本超长&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;probability&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.99&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="c1"&gt;// 可选，全标签分布&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;逐字段语义（都有代码依据）：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;字段&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;语义&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;代码依据&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;code&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;必须是 0&lt;/strong&gt;，否则 api 侧抛 &lt;code&gt;inner_error&lt;/code&gt;，该节点按异常处理&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;SingleLabelPredFunction.java:131&lt;/code&gt; 的 &lt;code&gt;respMessage.success()&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;data&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;数组且非空，只取 &lt;code&gt;data[0]&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;SingleLabelPredFunction.java:136/141&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;riskCode&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;必须显式返回&lt;/strong&gt;。默认/缺失时不是&amp;quot;正常&amp;quot;而是&amp;quot;有风险&amp;quot;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;RiskCheckResult.java:16&lt;/code&gt; 默认 &lt;code&gt;-1&lt;/code&gt;；&lt;code&gt;hasRisk()&lt;/code&gt; 仅判 &lt;code&gt;riskCode != 0&lt;/code&gt;（&lt;code&gt;RiskCheckResult.java:28&lt;/code&gt;）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;probability&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;置信度。策略层阈值判定用；框架本身不判&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;groovy 脚本里的 &lt;code&gt;thresholds_xxx[riskCode]&lt;/code&gt; 表（演示策略）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;detail&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;全标签概率分布，调试用；演示策略用它做&amp;quot;正常置信度太低也拦截&amp;quot;的反查&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;SingleLabelPredCheck.java:27&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;参考实现：Python 侧 BERT 服务的组包代码 &lt;code&gt;safety/safety-skills/safety-bert/app/model/bert_infer.py:55&lt;/code&gt;（riskCode/riskMessage/probability/text/detail 五件套），FastText 服务的 Flask 返回 &lt;code&gt;safety/safety-skills/safety-fasttext/app/model/models.py:42&lt;/code&gt;（&lt;code&gt;{&amp;quot;code&amp;quot;:0,&amp;quot;message&amp;quot;:&amp;quot;ok&amp;quot;,&amp;quot;cost&amp;quot;:...,&amp;quot;data&amp;quot;:[...]}&lt;/code&gt;）。&lt;/p&gt;&#10;&lt;h3 id="13-两个内建的后处理机关"&gt;&lt;a href="#13-%e4%b8%a4%e4%b8%aa%e5%86%85%e5%bb%ba%e7%9a%84%e5%90%8e%e5%a4%84%e7%90%86%e6%9c%ba%e5%85%b3" class="header-anchor"&gt;&lt;/a&gt;1.3 两个内建的后处理机关&#10;&lt;/h3&gt;&lt;p&gt;注册配置里还有两个容易忽略的字段（&lt;code&gt;safety-basic/.../core/conf/funcs/SingleLabelPredConf.java:20&lt;/code&gt;）：&lt;code&gt;ignoreRiskCode&lt;/code&gt; 是 api 侧后处理——你的服务返回的 riskCode 若在这个列表里，会被&lt;strong&gt;改写成 &lt;code&gt;defaultRisk&lt;/code&gt;&lt;/strong&gt;（默认 &lt;code&gt;(0,&amp;quot;正常文本&amp;quot;)&lt;/code&gt;，&lt;code&gt;LabelUtil.java:13&lt;/code&gt;），见 &lt;code&gt;SingleLabelPredFunction.java:157&lt;/code&gt; 的 &lt;code&gt;doIgnore()&lt;/code&gt;；&lt;code&gt;defaultRisk&lt;/code&gt; 则可自定义&amp;quot;忽略后改成什么&amp;quot;。演示策略里 bert 节点挂 &lt;code&gt;ignoreRiskCode: [1006]&lt;/code&gt; 就是这个用途：模型判&amp;quot;辱骂&amp;quot;时这个节点当作没看见（辱骂由关键词节点负责）。&lt;/p&gt;&#10;&lt;p&gt;另外注意：&lt;strong&gt;最终返回给业务方的 &lt;code&gt;riskMessage&lt;/code&gt; 会被 api 拼接上 riskCode 前缀&lt;/strong&gt;——&lt;code&gt;FunctionExecutorImpl.java:341&lt;/code&gt;：若 riskMessage 首字符不是数字，则改写为 &lt;code&gt;riskCode + riskMessage&lt;/code&gt;。所以服务返回&amp;quot;文本超长&amp;quot;，业务方收到的是 &lt;code&gt;&amp;quot;9001文本超长&amp;quot;&lt;/code&gt;（本文实验实测如此）。&lt;/p&gt;&#10;&lt;h3 id="14-type-选择策略什么时候复用什么时候才需要新类型"&gt;&lt;a href="#14-type-%e9%80%89%e6%8b%a9%e7%ad%96%e7%95%a5%e4%bb%80%e4%b9%88%e6%97%b6%e5%80%99%e5%a4%8d%e7%94%a8%e4%bb%80%e4%b9%88%e6%97%b6%e5%80%99%e6%89%8d%e9%9c%80%e8%a6%81%e6%96%b0%e7%b1%bb%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;1.4 type 选择策略：什么时候复用，什么时候才需要新类型&#10;&lt;/h3&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;你的能力形态&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;选择&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;给文本打一个标签 + 置信度（分类器、规则打分、调外部 API 后的单结论）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;&lt;code&gt;single_label_pred&lt;/code&gt;&lt;/strong&gt;（首选，协议最简单）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;黑白名单/正则词匹配&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;keyword&lt;/code&gt;（但通常直接用官方 keywords 服务即可）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;需要向量检索返回命中文档&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;kb_search&lt;/code&gt;；需要检索+代答&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;需要会话历史的多轮判定&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;multi_turn_detect&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;要给一条文本打&lt;strong&gt;多个&lt;/strong&gt;标签&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;开源版没有可用实现（&lt;code&gt;multi_label_pred&lt;/code&gt; 是空壳），两个选择：拆成多个 &lt;code&gt;single_label_pred&lt;/code&gt; 能力并行，或改 Java&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;协议字段完全不够用（如要传图片、要流式）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;只能改 Java：加一个 &lt;code&gt;RiskCheckType&lt;/code&gt; + 一个 &lt;code&gt;AbstractFunction&lt;/code&gt; 子类 + 注册表一行，重新构建 safety-api 镜像&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;&lt;strong&gt;结论&lt;/strong&gt;：90% 的自定义检测逻辑都能塞进 &lt;code&gt;single_label_pred&lt;/code&gt;——它的 &lt;code&gt;riskCode&lt;/code&gt; 是开放整数空间，你的服务自定一套风险码（避开官方的 0/201/1001~1008/2001/3002/4002/5001/5002 即可），判定逻辑全部下沉到你的服务里，框架只负责调度与透传。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="二注册链路源码解剖配置怎么流转"&gt;&lt;a href="#%e4%ba%8c%e6%b3%a8%e5%86%8c%e9%93%be%e8%b7%af%e6%ba%90%e7%a0%81%e8%a7%a3%e5%89%96%e9%85%8d%e7%bd%ae%e6%80%8e%e4%b9%88%e6%b5%81%e8%bd%ac" class="header-anchor"&gt;&lt;/a&gt;二、注册链路源码解剖：配置怎么流转&#10;&lt;/h2&gt;&lt;h3 id="21-全链路时序"&gt;&lt;a href="#21-%e5%85%a8%e9%93%be%e8%b7%af%e6%97%b6%e5%ba%8f" class="header-anchor"&gt;&lt;/a&gt;2.1 全链路时序&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-5-1"&gt;&lt;a class="lnlinks" href="#hl-5-1"&gt; 1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-5-2"&gt;&lt;a class="lnlinks" href="#hl-5-2"&gt; 2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-5-3"&gt;&lt;a class="lnlinks" href="#hl-5-3"&gt; 3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-5-4"&gt;&lt;a class="lnlinks" href="#hl-5-4"&gt; 4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-5-5"&gt;&lt;a class="lnlinks" href="#hl-5-5"&gt; 5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-5-6"&gt;&lt;a class="lnlinks" href="#hl-5-6"&gt; 6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-5-7"&gt;&lt;a class="lnlinks" href="#hl-5-7"&gt; 7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-5-8"&gt;&lt;a class="lnlinks" href="#hl-5-8"&gt; 8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-5-9"&gt;&lt;a class="lnlinks" href="#hl-5-9"&gt; 9&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-5-10"&gt;&lt;a class="lnlinks" href="#hl-5-10"&gt;10&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-5-11"&gt;&lt;a class="lnlinks" href="#hl-5-11"&gt;11&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-5-12"&gt;&lt;a class="lnlinks" href="#hl-5-12"&gt;12&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;注册请求&lt;/span&gt; &lt;span class="n"&gt;POST&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;online&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;▼&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;safety&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt; &lt;span class="err"&gt;──►&lt;/span&gt; &lt;span class="n"&gt;MySQL&lt;/span&gt; &lt;span class="n"&gt;function_conf&lt;/span&gt; &lt;span class="err"&gt;表（版本化状态机&lt;/span&gt; &lt;span class="n"&gt;edit&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;online&lt;/span&gt;&lt;span class="err"&gt;）&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;▲&lt;/span&gt; &lt;span class="err"&gt;▲&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt; &lt;span class="err"&gt;每&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="err"&gt;聚合&lt;/span&gt; &lt;span class="n"&gt;online&lt;/span&gt; &lt;span class="err"&gt;配置到内存（&lt;/span&gt;&lt;span class="n"&gt;DefenseConfigServiceImpl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refreshConf&lt;/span&gt;&lt;span class="err"&gt;）&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;①&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="err"&gt;每&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="err"&gt;轮询&lt;/span&gt; &lt;span class="n"&gt;POST&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;defense&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;conf&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;DynamicConfigService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loadConf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Scheduled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fixedRate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;initialDelay&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;②&lt;/span&gt; &lt;span class="n"&gt;shouldUpdate&lt;/span&gt;&lt;span class="err"&gt;：逐业务比对&lt;/span&gt; &lt;span class="n"&gt;lastChangeTimestamp&lt;/span&gt;&lt;span class="err"&gt;，变了才继续&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;▼&lt;/span&gt; &lt;span class="err"&gt;全量替换内存&lt;/span&gt; &lt;span class="n"&gt;name2BusinessConf&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;nodeConfMap&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;functionConfMap&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;③&lt;/span&gt; &lt;span class="err"&gt;回调监听器&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;FunctionChainBuilderImpl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loadConf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="err"&gt;逐业务重建&lt;/span&gt; &lt;span class="n"&gt;DAG&lt;/span&gt;&lt;span class="err"&gt;：&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;▼&lt;/span&gt; &lt;span class="n"&gt;YAML节点&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;doMerge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ref展开&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="err"&gt;反射&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Function&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="err"&gt;构建&lt;/span&gt;&lt;span class="n"&gt;Router&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;rootMap&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="err"&gt;④&lt;/span&gt; &lt;span class="n"&gt;Function&lt;/span&gt; &lt;span class="err"&gt;构造函数里：建&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;复用&lt;/span&gt; &lt;span class="n"&gt;OkHttpClient&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;注册&lt;/span&gt; &lt;span class="n"&gt;warmup&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;▼&lt;/span&gt; &lt;span class="err"&gt;执行期请求按新&lt;/span&gt; &lt;span class="n"&gt;DAG&lt;/span&gt; &lt;span class="err"&gt;流转&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;h3 id="22-admin-侧版本化状态机与字段校验"&gt;&lt;a href="#22-admin-%e4%be%a7%e7%89%88%e6%9c%ac%e5%8c%96%e7%8a%b6%e6%80%81%e6%9c%ba%e4%b8%8e%e5%ad%97%e6%ae%b5%e6%a0%a1%e9%aa%8c" class="header-anchor"&gt;&lt;/a&gt;2.2 admin 侧：版本化状态机与字段校验&#10;&lt;/h3&gt;&lt;p&gt;接口文档 &lt;code&gt;safety/safety-admin/docs/api/function_api.md&lt;/code&gt;，控制器 &lt;code&gt;safety-admin/.../contoller/business/FunctionManageController.java:24&lt;/code&gt;。核心实现 &lt;code&gt;FunctionConfigServiceImpl.java&lt;/code&gt;，几个&lt;strong&gt;会咬人的细节&lt;/strong&gt;：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;行为&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;代码&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;后果&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;new&lt;/code&gt; 强制 &lt;code&gt;status=edit&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;FunctionConfigServiceImpl.java:45&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;skills_init.py 里传的 &lt;code&gt;&amp;quot;status&amp;quot;:&amp;quot;online&amp;quot;&lt;/code&gt; 其实被忽略，&lt;strong&gt;必须再调一次 online&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;desc&lt;/code&gt; 必填&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;第 60 行 &lt;code&gt;checkArgument(desc非空, &amp;quot;desc为空&amp;quot;)&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;文档没写但缺了报 400&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;同 group+name 已有 online 则拒绝 new&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;第 47~50 行&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;重复注册报错&amp;quot;function已经存在，可以更新已有的&amp;quot;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;online 状态禁止 update&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;第 171~173 行 &lt;code&gt;cannotUpdateOnline&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;改配置必须走 newVersion→update→upgrade&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;newVersion 只能从 online 版本复制&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;第 236 行&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;流水线的入口固定是 active&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;upgrade 原子切换：旧 offline + 新 online，双双刷新 update_time&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;第 279~283 行 &lt;code&gt;offlineAndOnline&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;这就是触发 api 感知的&amp;quot;信号量&amp;quot;（见下）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;&lt;code&gt;single_label_pred&lt;/code&gt; 类型的 conf 校验在第 133~147 行：&lt;code&gt;modelType&lt;/code&gt;、&lt;code&gt;name&lt;/code&gt; 必填，四种 modelType 都要求 &lt;code&gt;extra.url&lt;/code&gt; 非空。&lt;/p&gt;&#10;&lt;h3 id="23-变更感知lastchangetimestamp-的精确计算"&gt;&lt;a href="#23-%e5%8f%98%e6%9b%b4%e6%84%9f%e7%9f%a5lastchangetimestamp-%e7%9a%84%e7%b2%be%e7%a1%ae%e8%ae%a1%e7%ae%97" class="header-anchor"&gt;&lt;/a&gt;2.3 变更感知：lastChangeTimestamp 的精确计算&#10;&lt;/h3&gt;&lt;p&gt;api 不是无条件全量重载。admin 组装配置时（&lt;code&gt;DefenseConfigServiceImpl.java:274~291&lt;/code&gt;），每个业务的 &lt;code&gt;lastChangeTimestamp&lt;/code&gt; = &lt;strong&gt;业务记录、DAG 记录、该业务 DAG 所引用 function 记录&lt;/strong&gt;三者的 &lt;code&gt;update_time&lt;/code&gt; 最大值。api 侧 &lt;code&gt;DynamicConfigService.java:142~146&lt;/code&gt; 逐业务比对这个时间戳，变了才触发重载。&lt;/p&gt;&#10;&lt;p&gt;由此推出两个重要结论：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;只注册+online 一个新能力但不改 DAG，api 永远感知不到&lt;/strong&gt;——新能力不在任何业务的引用集合里，所有业务的 lastChangeTimestamp 都不变。必须配合 DAG 变更（或重启 api）。&lt;/li&gt;&#10;&lt;li&gt;反过来说，&lt;strong&gt;改了被引用能力的配置（如 timeout），对应业务的 DAG 会被自动重建&lt;/strong&gt;——那 02 笔记坑 5 为什么还要重启？答案在下面的连接池机制里。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="24-ref-引用与深合并模板覆盖"&gt;&lt;a href="#24-ref-%e5%bc%95%e7%94%a8%e4%b8%8e%e6%b7%b1%e5%90%88%e5%b9%b6%e6%a8%a1%e6%9d%bf%e8%a6%86%e7%9b%96" class="header-anchor"&gt;&lt;/a&gt;2.4 ref 引用与深合并：模板+覆盖&#10;&lt;/h3&gt;&lt;p&gt;DAG 节点里 &lt;code&gt;- ref: text_length_guard&lt;/code&gt; 是个&amp;quot;引用&amp;quot;，构建时被展开。入口 &lt;code&gt;FunctionChainBuilderImpl.java:92&lt;/code&gt; 调 &lt;code&gt;FunctionUtil.doMerge()&lt;/code&gt;；parallel 节点的子能力同样在 &lt;code&gt;ParallelFunction.java:71&lt;/code&gt; 逐个 doMerge。&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;doMerge&lt;/code&gt;（&lt;code&gt;safety-api/.../utils/FunctionUtil.java:39&lt;/code&gt;）逐行批注：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-6-1"&gt;&lt;a class="lnlinks" href="#hl-6-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-6-2"&gt;&lt;a class="lnlinks" href="#hl-6-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-6-3"&gt;&lt;a class="lnlinks" href="#hl-6-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;FunctionConfig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;copyFrom&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConfigService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;functionConfMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRef&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 44: 按名字查注册的能力模板&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copyFrom&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;function ref 不存在: &amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRef&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 46: ref 打错字=DAG 构建失败&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mergedConf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mergeProperties&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;copyTo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConf&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConf&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 51: 深合并&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;code&gt;mergeProperties&lt;/code&gt;（第 58~94 行）的规则：&lt;strong&gt;引用处（custome）覆盖模板（template）；双方都有的 key 且 value 是 Map 时递归下钻；否则 custome 直接赢&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-7-1"&gt;&lt;a class="lnlinks" href="#hl-7-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-7-2"&gt;&lt;a class="lnlinks" href="#hl-7-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-7-3"&gt;&lt;a class="lnlinks" href="#hl-7-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-7-4"&gt;&lt;a class="lnlinks" href="#hl-7-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-7-5"&gt;&lt;a class="lnlinks" href="#hl-7-5"&gt;5&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;模板（注册时） 引用处（DAG 里） 合并结果&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;conf: conf: conf:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; extra.url: A ignoreRiskCode: [1006] extra.url: A&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; modelType: bert modelType: bert&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ignoreRiskCode: [] ignoreRiskCode: [1006] ← 引用处赢&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;注意一个边界&lt;/strong&gt;：合并只对 Map 递归，&lt;strong&gt;List 是整体替换&lt;/strong&gt;（第 88 行 else 分支）。所以 &lt;code&gt;ignoreRiskCode: [1006]&lt;/code&gt; 会把模板的 &lt;code&gt;[]&lt;/code&gt; 整个换掉，不存在&amp;quot;追加&amp;quot;。&lt;/p&gt;&#10;&lt;h3 id="25-连接池复用与-warmup热连接的实现"&gt;&lt;a href="#25-%e8%bf%9e%e6%8e%a5%e6%b1%a0%e5%a4%8d%e7%94%a8%e4%b8%8e-warmup%e7%83%ad%e8%bf%9e%e6%8e%a5%e7%9a%84%e5%ae%9e%e7%8e%b0" class="header-anchor"&gt;&lt;/a&gt;2.5 连接池复用与 warmup：热连接的实现&#10;&lt;/h3&gt;&lt;p&gt;每个远程 Function 初始化时（&lt;code&gt;SingleLabelPredFunction.java:54~93&lt;/code&gt;）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-8-1"&gt;&lt;a class="lnlinks" href="#hl-8-1"&gt; 1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-2"&gt;&lt;a class="lnlinks" href="#hl-8-2"&gt; 2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-3"&gt;&lt;a class="lnlinks" href="#hl-8-3"&gt; 3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-4"&gt;&lt;a class="lnlinks" href="#hl-8-4"&gt; 4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-5"&gt;&lt;a class="lnlinks" href="#hl-8-5"&gt; 5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-6"&gt;&lt;a class="lnlinks" href="#hl-8-6"&gt; 6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-7"&gt;&lt;a class="lnlinks" href="#hl-8-7"&gt; 7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-8"&gt;&lt;a class="lnlinks" href="#hl-8-8"&gt; 8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-9"&gt;&lt;a class="lnlinks" href="#hl-8-9"&gt; 9&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-10"&gt;&lt;a class="lnlinks" href="#hl-8-10"&gt;10&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-11"&gt;&lt;a class="lnlinks" href="#hl-8-11"&gt;11&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-12"&gt;&lt;a class="lnlinks" href="#hl-8-12"&gt;12&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-13"&gt;&lt;a class="lnlinks" href="#hl-8-13"&gt;13&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;150&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 54: 兜底的默认超时&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getTimeoutMilliseconds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;conf&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getTimeoutMilliseconds&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 56: 注册时的 timeoutMilliseconds&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FunctionResourcePool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fasttextConf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUrl&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 68: 按 type+url 查池&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fasttextClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OkHttpClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 70: 命中直接复用&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OkHttpClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="na"&gt;callTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MILLISECONDS&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 73: 新建&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uniqResource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FunctionResourcePool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addFuncIfAbsent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uniqResource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 76: 确认自己是&amp;#34;首创者&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ScheduleWarmup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;appendTask&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 77: 注册周期性预热任务&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;池与超时归属&lt;/strong&gt;：&lt;code&gt;FunctionResourcePool.java:14&lt;/code&gt; 是静态 ConcurrentMap，key = &lt;code&gt;type + &amp;quot;$&amp;quot; + url&lt;/code&gt;（第 16/21 行），同一 URL 的多个能力&lt;strong&gt;共享同一个 OkHttpClient&lt;/strong&gt;，且&lt;strong&gt;池子全生命周期不清空&lt;/strong&gt;（类里没有 remove 方法）。OkHttp 的 &lt;code&gt;callTimeout&lt;/code&gt; 在 client 创建时定死——&lt;strong&gt;谁先在池里注册了这个 URL，就用谁的 timeoutMilliseconds&lt;/strong&gt;，后续同 URL 能力改超时无效。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;warmup&lt;/strong&gt;：&lt;code&gt;ScheduleWarmup.java:40&lt;/code&gt; 的 &lt;code&gt;@Scheduled(initialDelay=5000, fixedRate=60000)&lt;/code&gt;——从应用启动 5 秒后开始，&lt;strong&gt;每 60 秒把 tasks 里所有任务各跑一遍&lt;/strong&gt;（3 线程池，线程名 &lt;code&gt;warm-up-N&lt;/code&gt;）。预热请求是真实的 HTTP POST：&lt;code&gt;{&amp;quot;text_list&amp;quot;: [&amp;quot;warmup&amp;quot;], &amp;quot;businessName&amp;quot;: &amp;quot;warmup&amp;quot;}&lt;/code&gt;（&lt;code&gt;SingleLabelPredFunction.java:81~83&lt;/code&gt;）。配合 &lt;code&gt;HttpHelper.keepaliveHeader()&lt;/code&gt;（&lt;code&gt;HttpHelper.java:87&lt;/code&gt;：&lt;code&gt;Connection: Keep-Alive&lt;/code&gt;、&lt;code&gt;Keep-Alive: timeout=20, max=5000&lt;/code&gt;），保持到能力服务的长连接不断、远端服务不退磁（对有 JIT/惰性加载的模型服务尤其重要）。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;&lt;strong&gt;你的服务必须容忍 warmup 请求&lt;/strong&gt;：&lt;code&gt;text_list=[&amp;quot;warmup&amp;quot;]&lt;/code&gt;，且注意 warmup 的 body 里业务字段叫 &lt;code&gt;businessName&lt;/code&gt;，而真实请求里叫 &lt;code&gt;business_id&lt;/code&gt;（源码两处字段名不一致，实测如此）——别对字段做强校验，宽松解析即可。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;这就是 02 笔记坑 5 的源码级根因&lt;/strong&gt;：修改已存在能力的 &lt;code&gt;timeoutMilliseconds&lt;/code&gt; → admin 侧 upgrade 成功 → api 60s 内感知、DAG 重建、Function 重新实例化——&lt;strong&gt;但 &lt;code&gt;doInit&lt;/code&gt; 里 &lt;code&gt;getFunc(type, url)&lt;/code&gt; 命中了池里的旧 client（旧超时）&lt;/strong&gt;，新超时根本没生效。只有 &lt;code&gt;docker restart safety-api&lt;/code&gt; 让 JVM 重启、静态池清零，新 client 才会带着新超时被创建。&lt;strong&gt;改 URL（哪怕只改路径）可以绕过&lt;/strong&gt;，因为新 URL 会建一个新 client。&lt;/p&gt;&#10;&lt;h3 id="26-两层超时的完整图景02-笔记坑-4-的代码答案"&gt;&lt;a href="#26-%e4%b8%a4%e5%b1%82%e8%b6%85%e6%97%b6%e7%9a%84%e5%ae%8c%e6%95%b4%e5%9b%be%e6%99%af02-%e7%ac%94%e8%ae%b0%e5%9d%91-4-%e7%9a%84%e4%bb%a3%e7%a0%81%e7%ad%94%e6%a1%88" class="header-anchor"&gt;&lt;/a&gt;2.6 两层超时的完整图景（02 笔记坑 4 的代码答案）&#10;&lt;/h3&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;层&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;配置位置&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;生效代码&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;超时后果&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;① 整体 DAG&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;业务配置 user/robotCheckConf.timeoutMilliseconds（默认 1000ms，&lt;code&gt;CheckConf.java:14&lt;/code&gt;）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;FunctionExecutorImpl.java:297&lt;/code&gt; 每节点前 &lt;code&gt;context.timeout()&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;抛 exec_timeout，链路中断&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;② DAG 节点（parallel）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;策略 YAML 节点的 timeoutMilliseconds&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;ParallelFunction.java:119&lt;/code&gt; 起：总预算递减，&lt;code&gt;remainCost &amp;lt; 0&lt;/code&gt; 时退化为 1ms&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;剩余子项结果被丢弃&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;③ 原子能力 HTTP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;注册能力的 timeoutMilliseconds&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;创建 OkHttpClient 的 callTimeout（&lt;code&gt;SingleLabelPredFunction.java:73&lt;/code&gt;）；parallel 内还经过 &lt;code&gt;Future.get(该function超时)&lt;/code&gt;（&lt;code&gt;ParallelFunction.java:123~130&lt;/code&gt;）双重生效&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;该能力结果被丢弃（异常→resultMap 缺该项）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;02 笔记踩坑时改的是 ② 和 ③；接慢能力时三层都要心里有数。&lt;/p&gt;&#10;&lt;h3 id="27-失败语义能力挂掉--弃权不是拦截"&gt;&lt;a href="#27-%e5%a4%b1%e8%b4%a5%e8%af%ad%e4%b9%89%e8%83%bd%e5%8a%9b%e6%8c%82%e6%8e%89--%e5%bc%83%e6%9d%83%e4%b8%8d%e6%98%af%e6%8b%a6%e6%88%aa" class="header-anchor"&gt;&lt;/a&gt;2.7 失败语义：能力挂掉 = 弃权，不是拦截&#10;&lt;/h3&gt;&lt;p&gt;parallel 节点里子 function 抛异常时（&lt;code&gt;ParallelFunction.java:132~135&lt;/code&gt;）只记日志和监控埋点，&lt;strong&gt;该 function 的结果不进 resultMap&lt;/strong&gt;。groovy router 里拿到的是 null，通常写法 &lt;code&gt;lenGuard != null &amp;amp;&amp;amp; lenGuard.hasRisk()&lt;/code&gt; 直接放过。加上节点级 &lt;code&gt;ignoreError: true&lt;/code&gt;（&lt;code&gt;NodeConfig.java:17&lt;/code&gt; 默认就是 true，&lt;code&gt;FunctionExecutorImpl.java:312&lt;/code&gt;），单个能力宕机不阻断整体链路。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;这是安全系统的取舍：可用性优先于拦截率&lt;/strong&gt;。生产化时需要用监控埋点（&lt;code&gt;MonitorAlarmService.parallelIncr&lt;/code&gt;）把&amp;quot;弃权率&amp;quot;管起来，否则能力全挂了审核还是&amp;quot;正常放行&amp;quot;，形同虚设。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="三动手实验一个-flask-能力服务的全链路"&gt;&lt;a href="#%e4%b8%89%e5%8a%a8%e6%89%8b%e5%ae%9e%e9%aa%8c%e4%b8%80%e4%b8%aa-flask-%e8%83%bd%e5%8a%9b%e6%9c%8d%e5%8a%a1%e7%9a%84%e5%85%a8%e9%93%be%e8%b7%af" class="header-anchor"&gt;&lt;/a&gt;三、动手实验：一个 Flask 能力服务的全链路&#10;&lt;/h2&gt;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;实验环境即 02 笔记部署的本机环境（全部容器 Up，网络 &lt;code&gt;joysafety_joy_safety&lt;/code&gt;）。&lt;strong&gt;本节每一条命令都真实执行过，返回为真实输出摘录。&lt;/strong&gt;&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;h3 id="31-写演示服务文本长度检测"&gt;&lt;a href="#31-%e5%86%99%e6%bc%94%e7%a4%ba%e6%9c%8d%e5%8a%a1%e6%96%87%e6%9c%ac%e9%95%bf%e5%ba%a6%e6%a3%80%e6%b5%8b" class="header-anchor"&gt;&lt;/a&gt;3.1 写演示服务：文本长度检测&#10;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;/tmp/joysafety-demo/text_guard.py&lt;/code&gt;（完整可运行，依赖只有 flask）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-9-1"&gt;&lt;a class="lnlinks" href="#hl-9-1"&gt; 1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-2"&gt;&lt;a class="lnlinks" href="#hl-9-2"&gt; 2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-3"&gt;&lt;a class="lnlinks" href="#hl-9-3"&gt; 3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-4"&gt;&lt;a class="lnlinks" href="#hl-9-4"&gt; 4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-5"&gt;&lt;a class="lnlinks" href="#hl-9-5"&gt; 5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-6"&gt;&lt;a class="lnlinks" href="#hl-9-6"&gt; 6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-7"&gt;&lt;a class="lnlinks" href="#hl-9-7"&gt; 7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-8"&gt;&lt;a class="lnlinks" href="#hl-9-8"&gt; 8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-9"&gt;&lt;a class="lnlinks" href="#hl-9-9"&gt; 9&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-10"&gt;&lt;a class="lnlinks" href="#hl-9-10"&gt;10&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-11"&gt;&lt;a class="lnlinks" href="#hl-9-11"&gt;11&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-12"&gt;&lt;a class="lnlinks" href="#hl-9-12"&gt;12&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-13"&gt;&lt;a class="lnlinks" href="#hl-9-13"&gt;13&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-14"&gt;&lt;a class="lnlinks" href="#hl-9-14"&gt;14&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-15"&gt;&lt;a class="lnlinks" href="#hl-9-15"&gt;15&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-16"&gt;&lt;a class="lnlinks" href="#hl-9-16"&gt;16&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-17"&gt;&lt;a class="lnlinks" href="#hl-9-17"&gt;17&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-18"&gt;&lt;a class="lnlinks" href="#hl-9-18"&gt;18&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-19"&gt;&lt;a class="lnlinks" href="#hl-9-19"&gt;19&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-20"&gt;&lt;a class="lnlinks" href="#hl-9-20"&gt;20&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-21"&gt;&lt;a class="lnlinks" href="#hl-9-21"&gt;21&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-22"&gt;&lt;a class="lnlinks" href="#hl-9-22"&gt;22&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-23"&gt;&lt;a class="lnlinks" href="#hl-9-23"&gt;23&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-24"&gt;&lt;a class="lnlinks" href="#hl-9-24"&gt;24&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-25"&gt;&lt;a class="lnlinks" href="#hl-9-25"&gt;25&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-26"&gt;&lt;a class="lnlinks" href="#hl-9-26"&gt;26&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-27"&gt;&lt;a class="lnlinks" href="#hl-9-27"&gt;27&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-28"&gt;&lt;a class="lnlinks" href="#hl-9-28"&gt;28&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-29"&gt;&lt;a class="lnlinks" href="#hl-9-29"&gt;29&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jsonify&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;MAX_LEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="c1"&gt;# 演示阈值：超过 10 个字符判风险&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;RISK_CODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9001&lt;/span&gt; &lt;span class="c1"&gt;# 自定义风险码（避开框架已有的 0/201/1001~1008 等）&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/guard/length&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;guard_length&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;force&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;text_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text_list&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;[req] biz=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;business_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;businessName&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;text_list=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text_list&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;text_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;MAX_LEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;riskCode&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RISK_CODE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;riskMessage&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;文本超长&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;probability&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;detail&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;riskCode&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RISK_CODE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;riskMessage&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;文本超长&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;probability&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.99&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;riskCode&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;riskMessage&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;正常文本&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;probability&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;riskCode&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;riskMessage&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;正常文本&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;probability&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;detail&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;riskCode&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;riskMessage&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;正常文本&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;probability&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;code&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;success&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;cost&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;0.0.0.0&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;9000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;宿主机裸跑（本文采用的方式）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-10-1"&gt;&lt;a class="lnlinks" href="#hl-10-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-2"&gt;&lt;a class="lnlinks" href="#hl-10-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-3"&gt;&lt;a class="lnlinks" href="#hl-10-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-4"&gt;&lt;a class="lnlinks" href="#hl-10-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-5"&gt;&lt;a class="lnlinks" href="#hl-10-5"&gt;5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-6"&gt;&lt;a class="lnlinks" href="#hl-10-6"&gt;6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-7"&gt;&lt;a class="lnlinks" href="#hl-10-7"&gt;7&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir -p /tmp/joysafety-demo &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; /tmp/joysafety-demo&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 -m venv venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./venv/bin/pip install flask&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./venv/bin/python text_guard.py &lt;span class="p"&gt;&amp;amp;&lt;/span&gt; &lt;span class="c1"&gt;# 监听 0.0.0.0:9000&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 自测（真实输出）：短文本 riskCode=0，长文本 riskCode=9001&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -s -X POST http://127.0.0.1:9000/guard/length -H &lt;span class="s1"&gt;&amp;#39;Content-Type: application/json&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -d &lt;span class="s1"&gt;&amp;#39;{&amp;#34;request_id&amp;#34;:&amp;#34;r1&amp;#34;,&amp;#34;business_id&amp;#34;:&amp;#34;test&amp;#34;,&amp;#34;session_id&amp;#34;:&amp;#34;s1&amp;#34;,&amp;#34;text_list&amp;#34;:[&amp;#34;你好&amp;#34;]}&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;接入方式二选一&lt;/strong&gt;（URL 是注册时唯一关心的事）：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;方式&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;注册时 url 写法&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;说明&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;宿主机裸跑（本文）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;http://host.docker.internal:9000/guard/length&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Docker Desktop on Mac 内置该域名指向宿主机，容器内直连&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;容器化&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;http://text-guard:9000/guard/length&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;进 &lt;code&gt;joysafety_joy_safety&lt;/code&gt; 网络，用容器名互访&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;容器化方式（同样实测通过；镜像用 02 笔记同款的华为云代理源）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-11-1"&gt;&lt;a class="lnlinks" href="#hl-11-1"&gt; 1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-11-2"&gt;&lt;a class="lnlinks" href="#hl-11-2"&gt; 2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-11-3"&gt;&lt;a class="lnlinks" href="#hl-11-3"&gt; 3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-11-4"&gt;&lt;a class="lnlinks" href="#hl-11-4"&gt; 4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-11-5"&gt;&lt;a class="lnlinks" href="#hl-11-5"&gt; 5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-11-6"&gt;&lt;a class="lnlinks" href="#hl-11-6"&gt; 6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-11-7"&gt;&lt;a class="lnlinks" href="#hl-11-7"&gt; 7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-11-8"&gt;&lt;a class="lnlinks" href="#hl-11-8"&gt; 8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-11-9"&gt;&lt;a class="lnlinks" href="#hl-11-9"&gt; 9&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-11-10"&gt;&lt;a class="lnlinks" href="#hl-11-10"&gt;10&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-11-11"&gt;&lt;a class="lnlinks" href="#hl-11-11"&gt;11&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-11-12"&gt;&lt;a class="lnlinks" href="#hl-11-12"&gt;12&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-11-13"&gt;&lt;a class="lnlinks" href="#hl-11-13"&gt;13&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-11-14"&gt;&lt;a class="lnlinks" href="#hl-11-14"&gt;14&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /tmp/joysafety-demo&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &amp;gt; Dockerfile &lt;span class="s"&gt;&amp;lt;&amp;lt;&amp;#39;EOF&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;FROM swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/python:3.12-slim&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;WORKDIR /app&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;RUN pip install --no-cache-dir flask -i https://mirrors.aliyun.com/pypi/simple/&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;COPY text_guard.py .&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;CMD [&amp;#34;python&amp;#34;, &amp;#34;text_guard.py&amp;#34;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker build -t text-guard:demo .&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -d --name text-guard --network joysafety_joy_safety text-guard:demo&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 从 safety-api 容器内验证连通性（真实输出：长文本返回 9001）：&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; safety-api sh -c &lt;span class="s1"&gt;&amp;#39;curl -s -m 3 -X POST http://text-guard:9000/guard/length \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; -H &amp;#34;Content-Type: application/json&amp;#34; -d &amp;#34;{\&amp;#34;text_list\&amp;#34;:[\&amp;#34;容器网络内的连通性测试文本\&amp;#34;]}&amp;#34;&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 验证完清理：docker rm -f text-guard&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;h3 id="32-admin-注册并上线"&gt;&lt;a href="#32-admin-%e6%b3%a8%e5%86%8c%e5%b9%b6%e4%b8%8a%e7%ba%bf" class="header-anchor"&gt;&lt;/a&gt;3.2 admin 注册并上线&#10;&lt;/h3&gt;&lt;p&gt;照 &lt;code&gt;example/safety-skills/safety-knowledge/skills_init.py&lt;/code&gt;（注册 6 个官方能力的脚本，new + online 两段式）改造：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-12-1"&gt;&lt;a class="lnlinks" href="#hl-12-1"&gt; 1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-12-2"&gt;&lt;a class="lnlinks" href="#hl-12-2"&gt; 2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-12-3"&gt;&lt;a class="lnlinks" href="#hl-12-3"&gt; 3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-12-4"&gt;&lt;a class="lnlinks" href="#hl-12-4"&gt; 4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-12-5"&gt;&lt;a class="lnlinks" href="#hl-12-5"&gt; 5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-12-6"&gt;&lt;a class="lnlinks" href="#hl-12-6"&gt; 6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-12-7"&gt;&lt;a class="lnlinks" href="#hl-12-7"&gt; 7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-12-8"&gt;&lt;a class="lnlinks" href="#hl-12-8"&gt; 8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-12-9"&gt;&lt;a class="lnlinks" href="#hl-12-9"&gt; 9&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-12-10"&gt;&lt;a class="lnlinks" href="#hl-12-10"&gt;10&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -s -X POST http://localhost:8006/config/defense/manage/function/new &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Content-Type: application/json&amp;#39;&lt;/span&gt; -d &lt;span class="s1"&gt;&amp;#39;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;name&amp;#34;: &amp;#34;text_length_guard&amp;#34;, &amp;#34;group&amp;#34;: &amp;#34;default&amp;#34;, &amp;#34;desc&amp;#34;: &amp;#34;文本长度检测（演示）&amp;#34;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;type&amp;#34;: &amp;#34;single_label_pred&amp;#34;, &amp;#34;timeoutMilliseconds&amp;#34;: 500,&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;confObj&amp;#34;: {&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;name&amp;#34;: &amp;#34;text_length_guard&amp;#34;, &amp;#34;modelType&amp;#34;: &amp;#34;fasttext&amp;#34;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;extra&amp;#34;: {&amp;#34;url&amp;#34;: &amp;#34;http://host.docker.internal:9000/guard/length&amp;#34;},&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;ignoreRiskCode&amp;#34;: []&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; }&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;真实返回（&lt;strong&gt;注意 status 是 edit，请求里传 online 也没用&lt;/strong&gt;——2.2 节讲过 new 强制 edit）。拿到 &lt;code&gt;id=9&lt;/code&gt; 后上线：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-13-1"&gt;&lt;a class="lnlinks" href="#hl-13-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-13-2"&gt;&lt;a class="lnlinks" href="#hl-13-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-13-3"&gt;&lt;a class="lnlinks" href="#hl-13-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -s -X POST http://localhost:8006/config/defense/manage/function/online &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Content-Type: application/json&amp;#39;&lt;/span&gt; -d &lt;span class="s1"&gt;&amp;#39;{&amp;#34;id&amp;#34;: 9}&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# → 真实返回：code=0, data.status=online（version=1）&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;code&gt;modelType&lt;/code&gt; 填的是 &lt;code&gt;fasttext&lt;/code&gt;——填 &lt;code&gt;bert&lt;/code&gt; 完全一样（1.2 节：四分支贯通同一段代码），它只是元数据标签。&lt;/p&gt;&#10;&lt;h3 id="33-dag-里-ref-引用并-upgrade"&gt;&lt;a href="#33-dag-%e9%87%8c-ref-%e5%bc%95%e7%94%a8%e5%b9%b6-upgrade" class="header-anchor"&gt;&lt;/a&gt;3.3 DAG 里 ref 引用并 upgrade&#10;&lt;/h3&gt;&lt;p&gt;走 02 笔记附录 C 的流水线：&lt;code&gt;active → newVersion → update → upgrade&lt;/code&gt;。计划改动：在 &lt;code&gt;from_user_test1&lt;/code&gt; 并行节点里加 &lt;code&gt;- ref: text_length_guard&lt;/code&gt;，并在它的 groovy router 最前面加短路判定。保存为 &lt;code&gt;update_dag_add_guard.py&lt;/code&gt;：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-14-1"&gt;&lt;a class="lnlinks" href="#hl-14-1"&gt; 1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-2"&gt;&lt;a class="lnlinks" href="#hl-14-2"&gt; 2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-3"&gt;&lt;a class="lnlinks" href="#hl-14-3"&gt; 3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-4"&gt;&lt;a class="lnlinks" href="#hl-14-4"&gt; 4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-5"&gt;&lt;a class="lnlinks" href="#hl-14-5"&gt; 5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-6"&gt;&lt;a class="lnlinks" href="#hl-14-6"&gt; 6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-7"&gt;&lt;a class="lnlinks" href="#hl-14-7"&gt; 7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-8"&gt;&lt;a class="lnlinks" href="#hl-14-8"&gt; 8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-9"&gt;&lt;a class="lnlinks" href="#hl-14-9"&gt; 9&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-10"&gt;&lt;a class="lnlinks" href="#hl-14-10"&gt;10&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-11"&gt;&lt;a class="lnlinks" href="#hl-14-11"&gt;11&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-12"&gt;&lt;a class="lnlinks" href="#hl-14-12"&gt;12&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-13"&gt;&lt;a class="lnlinks" href="#hl-14-13"&gt;13&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-14"&gt;&lt;a class="lnlinks" href="#hl-14-14"&gt;14&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-15"&gt;&lt;a class="lnlinks" href="#hl-14-15"&gt;15&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-16"&gt;&lt;a class="lnlinks" href="#hl-14-16"&gt;16&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-17"&gt;&lt;a class="lnlinks" href="#hl-14-17"&gt;17&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-18"&gt;&lt;a class="lnlinks" href="#hl-14-18"&gt;18&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-19"&gt;&lt;a class="lnlinks" href="#hl-14-19"&gt;19&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-20"&gt;&lt;a class="lnlinks" href="#hl-14-20"&gt;20&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-21"&gt;&lt;a class="lnlinks" href="#hl-14-21"&gt;21&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-22"&gt;&lt;a class="lnlinks" href="#hl-14-22"&gt;22&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-23"&gt;&lt;a class="lnlinks" href="#hl-14-23"&gt;23&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-24"&gt;&lt;a class="lnlinks" href="#hl-14-24"&gt;24&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-25"&gt;&lt;a class="lnlinks" href="#hl-14-25"&gt;25&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-26"&gt;&lt;a class="lnlinks" href="#hl-14-26"&gt;26&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-27"&gt;&lt;a class="lnlinks" href="#hl-14-27"&gt;27&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-28"&gt;&lt;a class="lnlinks" href="#hl-14-28"&gt;28&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-29"&gt;&lt;a class="lnlinks" href="#hl-14-29"&gt;29&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-30"&gt;&lt;a class="lnlinks" href="#hl-14-30"&gt;30&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-31"&gt;&lt;a class="lnlinks" href="#hl-14-31"&gt;31&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-14-32"&gt;&lt;a class="lnlinks" href="#hl-14-32"&gt;32&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;urllib.request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;yaml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ADMIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;http://safety-admin:8006/config/defense/manage&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# post(path, body) 辅助函数与 02 笔记附录 A 相同，略&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/dag/active&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;group&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;businessName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;test&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;dag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;new_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/dag/newVersion&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dag&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]})[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;NEW_ROUTER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;import com.jd.security.llmsec.core.check.MixedCheck&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def retMap=ctx.curResult.resultMap&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def next=&amp;#34;from_user_test_bert2&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def kw = retMap.keyword&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def lenGuard = retMap.text_length_guard&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;if (lenGuard != null &amp;amp;&amp;amp; lenGuard.hasRisk()) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; ctx.curResult = lenGuard&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; return null&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;if (kw != null &amp;amp;&amp;amp; (kw.bwgLabel == 1 || kw.bwgLabel == 2 )) { ctx.curResult = kw; return null } else { return next }&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;conf_array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dag&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;confArray&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;conf_array&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;nodeId&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;from_user_test1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;functionConf&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;conf&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;functionConfs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ref&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;text_length_guard&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;routerConf&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;conf&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;script&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NEW_ROUTER&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;yaml_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;safe_dump&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;new_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;businessName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;test&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;group&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;desc&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;desc&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;test&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rootId&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dag&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;rootId&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;confArray&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;conf_array&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;allow_unicode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/dag/dagUpdateYaml&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yaml_body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;text/plain&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/dag/upgrade&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;new_id&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;执行（放 safety-knowledge 容器里跑，自带 PyYAML；宿主机跑则把 ADMIN 换成 &lt;code&gt;http://localhost:8006/...&lt;/code&gt;）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-15-1"&gt;&lt;a class="lnlinks" href="#hl-15-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-15-2"&gt;&lt;a class="lnlinks" href="#hl-15-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-15-3"&gt;&lt;a class="lnlinks" href="#hl-15-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker cp update_dag_add_guard.py safety-knowledge:/work/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; safety-knowledge python /work/update_dag_add_guard.py&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 真实输出：当前线上 DAG id=3 version=3；新版本 id=5；upgrade 后 version=4, status=online&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;变更后的 DAG 结构&lt;/strong&gt;：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-16-1"&gt;&lt;a class="lnlinks" href="#hl-16-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-16-2"&gt;&lt;a class="lnlinks" href="#hl-16-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-16-3"&gt;&lt;a class="lnlinks" href="#hl-16-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-16-4"&gt;&lt;a class="lnlinks" href="#hl-16-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-16-5"&gt;&lt;a class="lnlinks" href="#hl-16-5"&gt;5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-16-6"&gt;&lt;a class="lnlinks" href="#hl-16-6"&gt;6&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;start（按 fromRobot 分流）&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ robot → from_robot_test1: ref keyword → stupid_end&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─ user → from_user_test1: parallel(keyword, text_length_guard) ← 新能力在这里&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ router：lenGuard 有风险 → 短路返回 9001&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ 关键词命中黑/白 → 短路返回&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └────────→ from_user_test_bert2: parallel(bert_20250916, bert_prompt_injection_20250828)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;h3 id="34-观察配置同步与-warmup不用重启"&gt;&lt;a href="#34-%e8%a7%82%e5%af%9f%e9%85%8d%e7%bd%ae%e5%90%8c%e6%ad%a5%e4%b8%8e-warmup%e4%b8%8d%e7%94%a8%e9%87%8d%e5%90%af" class="header-anchor"&gt;&lt;/a&gt;3.4 观察配置同步与 warmup（不用重启！）&#10;&lt;/h3&gt;&lt;p&gt;DAG 的 upgrade 刷新了 dag 记录的 update_time → 该业务 lastChangeTimestamp 变化 → api 下一轮轮询感知（2.3 节）。api 日志（宿主机 &lt;code&gt;example/safety-api/logs/app.log&lt;/code&gt;）真实记录：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-17-1"&gt;&lt;a class="lnlinks" href="#hl-17-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-17-2"&gt;&lt;a class="lnlinks" href="#hl-17-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-17-3"&gt;&lt;a class="lnlinks" href="#hl-17-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;16:41:05 DynamicConfigService:97 | 配置更新成功，function={..., &amp;#34;text_length_guard&amp;#34;:{&amp;#34;conf&amp;#34;:{&amp;#34;extra&amp;#34;:{&amp;#34;url&amp;#34;:&amp;#34;http://host.docker.internal:9000/guard/length&amp;#34;}, ...}}, ...}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;16:41:10 SingleLabelPredFunction:77 | warm up fasttext, url=http://host.docker.internal:9000/guard/length ← 首次预热&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;16:42:10 SingleLabelPredFunction:77 | warm up fasttext, url=http://host.docker.internal:9000/guard/length ← 每 60s 一次&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;Flask 侧日志同步看到（能清楚区分 warmup 与真实流量，注意 warmup 的字段名是 businessName）：&lt;code&gt;[req] biz=warmup text_list=['warmup']&lt;/code&gt; 每 60s 一发，真实审核流量则是 &lt;code&gt;biz=test&lt;/code&gt;。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;本次实验是&amp;quot;&lt;strong&gt;新增&lt;/strong&gt;能力 + 新增 ref&amp;quot;，不需要重启 api（连接池里没有旧 client 占位）。什么时候必须重启，见 2.5 节末尾与第四章速查。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;h3 id="35-验证命中与不命中两条路径"&gt;&lt;a href="#35-%e9%aa%8c%e8%af%81%e5%91%bd%e4%b8%ad%e4%b8%8e%e4%b8%8d%e5%91%bd%e4%b8%ad%e4%b8%a4%e6%9d%a1%e8%b7%af%e5%be%84" class="header-anchor"&gt;&lt;/a&gt;3.5 验证：命中与不命中两条路径&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-18-1"&gt;&lt;a class="lnlinks" href="#hl-18-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-18-2"&gt;&lt;a class="lnlinks" href="#hl-18-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-18-3"&gt;&lt;a class="lnlinks" href="#hl-18-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-18-4"&gt;&lt;a class="lnlinks" href="#hl-18-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 路径一：不命中（2 个字符 ≤ 阈值 10）&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; safety-knowledge python /work/api_test.py &lt;span class="s1"&gt;&amp;#39;你好&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 路径二：命中（长度 22 &amp;gt; 阈值 10）&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; safety-knowledge python /work/api_test.py &lt;span class="s1"&gt;&amp;#39;这段文本长度明显超过十个字符用来触发自定义能力&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;两条路径的真实输出对比（截取关键字段）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-19-1"&gt;&lt;a class="lnlinks" href="#hl-19-1"&gt; 1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-19-2"&gt;&lt;a class="lnlinks" href="#hl-19-2"&gt; 2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-19-3"&gt;&lt;a class="lnlinks" href="#hl-19-3"&gt; 3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-19-4"&gt;&lt;a class="lnlinks" href="#hl-19-4"&gt; 4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-19-5"&gt;&lt;a class="lnlinks" href="#hl-19-5"&gt; 5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-19-6"&gt;&lt;a class="lnlinks" href="#hl-19-6"&gt; 6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-19-7"&gt;&lt;a class="lnlinks" href="#hl-19-7"&gt; 7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-19-8"&gt;&lt;a class="lnlinks" href="#hl-19-8"&gt; 8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-19-9"&gt;&lt;a class="lnlinks" href="#hl-19-9"&gt; 9&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-19-10"&gt;&lt;a class="lnlinks" href="#hl-19-10"&gt;10&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-19-11"&gt;&lt;a class="lnlinks" href="#hl-19-11"&gt;11&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-19-12"&gt;&lt;a class="lnlinks" href="#hl-19-12"&gt;12&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-19-13"&gt;&lt;a class="lnlinks" href="#hl-19-13"&gt;13&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 不命中：cost=3856ms —— 长度检测和关键词都放行，继续走完两个 BERT 才判正常&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;code&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;cost&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3856&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;checkedContent&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;你好&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;riskCode&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;riskMessage&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0正常文本&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;riskCheckType&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;mixed&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;riskCheckName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;none&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}]}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 命中：cost=28ms —— 在第一个并行节点被短路返回，根本没走到 BERT&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;code&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;cost&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;checkedContent&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;这段文本长度明显超过十个字符用来触发自定义能力&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;riskCode&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;riskMessage&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;9001文本超长&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;riskCheckType&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;single_label_pred&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;riskCheckName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;text_length_guard&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;riskCheckResult&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;single_label_pred&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;riskCode&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;srcName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;text_length_guard&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;cost&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;probability&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;detail&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;riskCode&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;riskMessage&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;文本超长&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;probability&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.99&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;riskCode&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}]}}]}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;3856ms vs 28ms 的对比就是纵深防御&amp;quot;便宜的能力放前面&amp;quot;的成本收益。对照 1.3 节：&lt;code&gt;riskMessage&lt;/code&gt; 被 api 拼成了 &lt;code&gt;&amp;quot;9001文本超长&amp;quot;&lt;/code&gt;；&lt;code&gt;detail&lt;/code&gt; 原样透传；&lt;code&gt;srcName&lt;/code&gt; 是注册的能力名。&lt;/p&gt;&#10;&lt;h3 id="36-降级实验服务挂掉时框架的行为"&gt;&lt;a href="#36-%e9%99%8d%e7%ba%a7%e5%ae%9e%e9%aa%8c%e6%9c%8d%e5%8a%a1%e6%8c%82%e6%8e%89%e6%97%b6%e6%a1%86%e6%9e%b6%e7%9a%84%e8%a1%8c%e4%b8%ba" class="header-anchor"&gt;&lt;/a&gt;3.6 降级实验：服务挂掉时框架的行为&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-20-1"&gt;&lt;a class="lnlinks" href="#hl-20-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-20-2"&gt;&lt;a class="lnlinks" href="#hl-20-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-20-3"&gt;&lt;a class="lnlinks" href="#hl-20-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pkill -f text_guard.py &lt;span class="c1"&gt;# 模拟能力宕机&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; safety-knowledge python /work/api_test.py &lt;span class="s1"&gt;&amp;#39;服务挂掉时这段超过十个字符的文本会被放行吗&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 真实输出：riskCode=0, riskCheckType=mixed —— 放行了！&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;完全符合 2.7 节的源码结论：parallel 节点内 HTTP 调用失败 → 该 function 结果不进 resultMap → groovy 里 &lt;code&gt;lenGuard == null&lt;/code&gt; → 继续走 BERT → 正常放行。&lt;strong&gt;自定义能力上线后，务必把&amp;quot;该能力最近一分钟有没有返回&amp;quot;纳入监控&lt;/strong&gt;，否则它挂了你看到的是&amp;quot;天下太平&amp;quot;。实验完恢复服务：&lt;code&gt;cd /tmp/joysafety-demo &amp;amp;&amp;amp; ./venv/bin/python text_guard.py &amp;amp;&lt;/code&gt;。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="四速查附录"&gt;&lt;a href="#%e5%9b%9b%e9%80%9f%e6%9f%a5%e9%99%84%e5%bd%95" class="header-anchor"&gt;&lt;/a&gt;四、速查附录&#10;&lt;/h2&gt;&lt;h3 id="附录-a原子能力管理接口速查admin8006"&gt;&lt;a href="#%e9%99%84%e5%bd%95-a%e5%8e%9f%e5%ad%90%e8%83%bd%e5%8a%9b%e7%ae%a1%e7%90%86%e6%8e%a5%e5%8f%a3%e9%80%9f%e6%9f%a5admin8006" class="header-anchor"&gt;&lt;/a&gt;附录 A：原子能力管理接口速查（admin，8006）&#10;&lt;/h3&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;接口&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;用途&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;关键入参&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;/config/defense/manage/function/new&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;注册（强制 edit 态，desc 必填）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;name/group/type/timeoutMilliseconds/confObj&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;/online&lt;/code&gt; &lt;code&gt;/offline&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;上/下线&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;id&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;/active&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;查线上版本&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;group + name&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;/newVersion&lt;/code&gt; → &lt;code&gt;/update&lt;/code&gt; → &lt;code&gt;/upgrade&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;改配置三连：复制新版本 → 改内容 → 原子切换上线&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;id（upgrade 用新版本的 id）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="附录-b注册-confobj-模板single_label_pred"&gt;&lt;a href="#%e9%99%84%e5%bd%95-b%e6%b3%a8%e5%86%8c-confobj-%e6%a8%a1%e6%9d%bfsingle_label_pred" class="header-anchor"&gt;&lt;/a&gt;附录 B：注册 confObj 模板（single_label_pred）&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-21-1"&gt;&lt;a class="lnlinks" href="#hl-21-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-21-2"&gt;&lt;a class="lnlinks" href="#hl-21-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;你的能力名&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;modelType&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;fasttext&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;extra&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;url&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;http://你的服务地址/路径&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;ignoreRiskCode&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;h3 id="附录-c常见症状自查"&gt;&lt;a href="#%e9%99%84%e5%bd%95-c%e5%b8%b8%e8%a7%81%e7%97%87%e7%8a%b6%e8%87%aa%e6%9f%a5" class="header-anchor"&gt;&lt;/a&gt;附录 C：常见症状自查&#10;&lt;/h3&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;症状&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;原因&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;解法&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;DAG 构建失败日志 &lt;code&gt;function ref 不存在&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;ref 名字打错 / 能力没 online&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;FunctionUtil.java:46&lt;/code&gt;；先 &lt;code&gt;/function/active&lt;/code&gt; 确认&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;注册了新能力但 api 像没看见&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;没有 DAG 变更，lastChangeTimestamp 不动（2.3 节）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;配合 DAG upgrade，或重启 api&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;改了能力的 timeoutMilliseconds 不生效&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;OkHttpClient 按 type+url 复用旧实例（2.5 节）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;docker restart safety-api&lt;/code&gt;（等 1~2 分钟），或改 url 路径绕过&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;本该命中的没命中、耗时卡在 2s&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;两层超时丢结果（02 笔记坑 4）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;DAG 节点层 + 能力注册层都调大&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;报&amp;quot;调用接口失败&amp;quot;，或没返回 riskCode 的文本全被判风险&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;code != 0&lt;/code&gt; / &lt;code&gt;data&lt;/code&gt; 空数组；riskCode 缺省 -1 → hasRisk() 为 true&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;协议见 1.2 节：code 必须为 0、riskCode 必须显式返回&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;能力挂掉后审核全放行&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;ignoreError 容错语义（2.7 节）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;加监控，不是改框架&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="附录-d本文引用的源码坐标"&gt;&lt;a href="#%e9%99%84%e5%bd%95-d%e6%9c%ac%e6%96%87%e5%bc%95%e7%94%a8%e7%9a%84%e6%ba%90%e7%a0%81%e5%9d%90%e6%a0%87" class="header-anchor"&gt;&lt;/a&gt;附录 D：本文引用的源码坐标&#10;&lt;/h3&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;主题&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;位置&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;type 注册表&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;safety/safety-api/src/main/java/com/jd/security/llmsec/service/execute/FunctionRegistry.java:20&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;统一远程调用&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;safety/safety-api/src/main/java/com/jd/security/llmsec/service/execute/functions/SingleLabelPredFunction.java:116&lt;/code&gt;（doInit 在 :49）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;ref 深合并&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;safety/safety-api/src/main/java/com/jd/security/llmsec/utils/FunctionUtil.java:39&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;连接池 / warmup&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;.../service/execute/functions/FunctionResourcePool.java:13&lt;/code&gt; / &lt;code&gt;.../service/ScheduleWarmup.java:40&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;请求/响应/配置 POJO&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;safety-basic/.../core/check/SingleLabelPredCheck.java:61&lt;/code&gt; / &lt;code&gt;.../core/conf/funcs/SingleLabelPredConf.java:20&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;admin 注册状态机&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;safety/safety-admin/src/main/java/com/jd/security/llmsec/service/business/FunctionConfigServiceImpl.java:41&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;lastChangeTimestamp 计算&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;safety/safety-admin/src/main/java/com/jd/security/llmsec/service/business/DefenseConfigServiceImpl.java:274&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;api 配置轮询&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;safety/safety-api/src/main/java/com/jd/security/llmsec/service/DynamicConfigService.java:65&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;接口文档 / 官方脚本&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;safety/safety-admin/docs/api/function_api.md&lt;/code&gt; / &lt;code&gt;example/safety-skills/safety-knowledge/skills_init.py&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;&lt;strong&gt;坐标勘误声明&lt;/strong&gt;（相对本篇原提纲，以实际代码为准）：提纲中 &lt;code&gt;engine/FunctionRegistry.java&lt;/code&gt; 实为 &lt;code&gt;service/execute/FunctionRegistry.java&lt;/code&gt;；&lt;code&gt;engine/functions/&lt;/code&gt; 实为 &lt;code&gt;service/execute/functions/&lt;/code&gt;；&lt;code&gt;util/FunctionUtil.java&lt;/code&gt; 实为 &lt;code&gt;utils/FunctionUtil.java&lt;/code&gt;；&lt;code&gt;ScheduleWarmup.java&lt;/code&gt; 在 &lt;code&gt;service/&lt;/code&gt; 下；&lt;code&gt;example/safety-skills/skills_init.py&lt;/code&gt; 实为 &lt;code&gt;example/safety-skills/safety-knowledge/skills_init.py&lt;/code&gt;。提纲给的行号（:20/:116/:39）经核实与当前代码一致。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;</description></item><item><title>模型安全检测 10：业务接入全流程（HMAC-SHA1 签名 + 限流实测）</title><link>https://saflyer.cn/posts/joysafety-business-onboarding/</link><pubDate>Mon, 14 Sep 2026 10:03:15 +0800</pubDate><guid>https://saflyer.cn/posts/joysafety-business-onboarding/</guid><description>&lt;img src="https://saflyer.cn/posts/joysafety-business-onboarding/cover.webp" alt="Featured image of post 模型安全检测 10：业务接入全流程（HMAC-SHA1 签名 + 限流实测）" /&gt;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;&lt;strong&gt;状态&lt;/strong&gt;：✅ 已完成（含 mybiz 动手实验实录）&#10;&lt;strong&gt;前置阅读&lt;/strong&gt;：02-Mac本地部署实操、03-技术架构与业务模块分析、04-DAG编排引擎深入、05-FreeTaxi异步检测模式&#10;&lt;strong&gt;日期&lt;/strong&gt;：2026-09-14&#10;&lt;strong&gt;素材来源&lt;/strong&gt;：safety-api / safety-basic / safety-admin 源码逐行核实 + 本地环境真实实验&#10;&lt;strong&gt;读者定位&lt;/strong&gt;：要把自己的业务接进 JoySafety 的读者；想搞懂&amp;quot;鉴权、限流、配置同步&amp;quot;在代码里怎么落地的读者&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="先建立全局认识接入一个业务--打通两条链路"&gt;&lt;a href="#%e5%85%88%e5%bb%ba%e7%ab%8b%e5%85%a8%e5%b1%80%e8%ae%a4%e8%af%86%e6%8e%a5%e5%85%a5%e4%b8%80%e4%b8%aa%e4%b8%9a%e5%8a%a1--%e6%89%93%e9%80%9a%e4%b8%a4%e6%9d%a1%e9%93%be%e8%b7%af" class="header-anchor"&gt;&lt;/a&gt;〇、先建立全局认识：接入一个业务 = 打通两条链路&#10;&lt;/h2&gt;&lt;p&gt;业务接入不是&amp;quot;调一个接口&amp;quot;那么简单，它是&lt;strong&gt;一条配置链路和一条请求链路&lt;/strong&gt;同时打通：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-0-1"&gt;&lt;a class="lnlinks" href="#hl-0-1"&gt; 1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-2"&gt;&lt;a class="lnlinks" href="#hl-0-2"&gt; 2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-3"&gt;&lt;a class="lnlinks" href="#hl-0-3"&gt; 3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-4"&gt;&lt;a class="lnlinks" href="#hl-0-4"&gt; 4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-5"&gt;&lt;a class="lnlinks" href="#hl-0-5"&gt; 5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-6"&gt;&lt;a class="lnlinks" href="#hl-0-6"&gt; 6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-7"&gt;&lt;a class="lnlinks" href="#hl-0-7"&gt; 7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-8"&gt;&lt;a class="lnlinks" href="#hl-0-8"&gt; 8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-9"&gt;&lt;a class="lnlinks" href="#hl-0-9"&gt; 9&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-10"&gt;&lt;a class="lnlinks" href="#hl-0-10"&gt;10&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-0-11"&gt;&lt;a class="lnlinks" href="#hl-0-11"&gt;11&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;【配置链路】（只跑一次，之后变更才重跑）&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 运维脚本 ──► safety-admin:8006 /config/defense/manage/business/new + online&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ──► MySQL business_info 表（版本化状态机）&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; safety-api:8007 每 60s HTTP 轮询 admin /config/defense/api/conf&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ──► 内存 volatile Map 全量替换 ──► 监听器重建该业务 DAG&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;【请求链路】（每次审核都跑）&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 业务方 ──HTTP+HMAC-SHA1──► POST /llmsec/api/defense/v2/{accessKey}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → checkParams 参数校验 → rateLimited 限流 → verify 验签 → preProcess 预处理&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 按 responseMode 分派（sync 直接跑 DAG / free_taxi·http·mq 异步入队）&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → DAG 逐节点调 skill 服务（keywords/bert/knowledge）→ 返回 riskCode&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;三个最关键的工程认知&lt;/strong&gt;（后面全部细节围绕它们）：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;api 不直连 MySQL&lt;/strong&gt;。业务配置（含 secretKey）由 admin 从 MySQL 读出，api 通过 60s 轮询拉到内存——所以新建业务后&lt;strong&gt;最长要等约 60 秒&lt;/strong&gt;才能调通，这是正常现象不是故障。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;鉴权三段式&lt;/strong&gt;：限流（按 accessKey 的 Guava RateLimiter）→ 时效（timestamp 超 &lt;code&gt;expireSeconds&lt;/code&gt; 拒绝）→ 签名（HmacSHA1+Base64）。accessKey 以 &lt;code&gt;test&lt;/code&gt; 开头整体跳过第三段。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;多业务隔离靠&amp;quot;按业务名索引&amp;quot;&lt;/strong&gt;：配置 Map、DAG root Map、限流器 Map、敏感词场景过滤，全部以业务名为 key，互不串扰；只有 &lt;code&gt;all&lt;/code&gt; 词库是全局的。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="一业务配置详解admin-侧"&gt;&lt;a href="#%e4%b8%80%e4%b8%9a%e5%8a%a1%e9%85%8d%e7%bd%ae%e8%af%a6%e8%a7%a3admin-%e4%be%a7" class="header-anchor"&gt;&lt;/a&gt;一、业务配置详解（admin 侧）&#10;&lt;/h2&gt;&lt;h3 id="11-创建接口与字段"&gt;&lt;a href="#11-%e5%88%9b%e5%bb%ba%e6%8e%a5%e5%8f%a3%e4%b8%8e%e5%ad%97%e6%ae%b5" class="header-anchor"&gt;&lt;/a&gt;1.1 创建接口与字段&#10;&lt;/h3&gt;&lt;p&gt;接口文档：&lt;code&gt;safety/safety-admin/docs/api/business_api.md&lt;/code&gt;；入口 &lt;code&gt;BusinessManageController.java:41&lt;/code&gt;（&lt;code&gt;/config/defense/manage/business/new&lt;/code&gt;）。必填字段：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;字段&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;说明&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;落地位置&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;name&lt;/code&gt; / &lt;code&gt;group&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;业务名 + 分组，&lt;strong&gt;确定后不可改&lt;/strong&gt;（&lt;code&gt;BusinessManageController.java:28&lt;/code&gt; 注释）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;business_info.name/group&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;type&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;toB / toC / toE&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;business_info.type&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;secretKey&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;业务密钥，验签用&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;business_info.secret_key&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;qpsLimit&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;总 QPS 限制&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;business_info.qps_limit&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;robotCheckConfObj&lt;/code&gt; / &lt;code&gt;userCheckConfObj&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;机器人/用户两方向的检测配置：&lt;code&gt;checkNum&lt;/code&gt;（上下文检测窗口大小）、&lt;code&gt;unit&lt;/code&gt;（slice_single/slice_multi/message_single/message_multi）、&lt;code&gt;timeoutMilliseconds&lt;/code&gt;（&lt;strong&gt;整条 DAG 的整体超时&lt;/strong&gt;）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;business_info.robot_check_conf/user_check_conf&lt;/code&gt;（JSON 串）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;建表 SQL 见 &lt;code&gt;safety/safety-admin/docs/tables.sql:33&lt;/code&gt;（&lt;code&gt;business_info&lt;/code&gt;），另有两列提纲里没提但很重要：&lt;code&gt;access_targets&lt;/code&gt;（授权接口集合，默认 &lt;code&gt;[&amp;quot;defenseV2&amp;quot;,&amp;quot;defenseResultV2&amp;quot;]&lt;/code&gt;，见 &lt;code&gt;BusinessInfoVO.java:17&lt;/code&gt;）和版本化三件套 &lt;code&gt;version/status/editor*&lt;/code&gt;。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;checkConf 的超时去哪了&lt;/strong&gt;：&lt;code&gt;SessionContext.timeout()&lt;/code&gt;（&lt;code&gt;safety-basic/.../core/session/SessionContext.java:36&lt;/code&gt;）按 fromRole 取对应 checkConf 的 &lt;code&gt;timeoutMilliseconds&lt;/code&gt;，DAG 主循环每跳一个节点前检查一次（&lt;code&gt;FunctionExecutorImpl.java:297&lt;/code&gt;），超时抛 &lt;code&gt;exec_timeout&lt;/code&gt;(50002)。这就是笔记-03 说的&amp;quot;三层超时&amp;quot;里最外层的业务级超时。&lt;/p&gt;&#10;&lt;h3 id="12-版本化上线才算数"&gt;&lt;a href="#12-%e7%89%88%e6%9c%ac%e5%8c%96%e4%b8%8a%e7%ba%bf%e6%89%8d%e7%ae%97%e6%95%b0" class="header-anchor"&gt;&lt;/a&gt;1.2 版本化：上线才算数&#10;&lt;/h3&gt;&lt;p&gt;业务配置同样走笔记-02 附录 C 的通用流水线：&lt;code&gt;new&lt;/code&gt;(status=edit) → &lt;code&gt;online&lt;/code&gt;；改配置走 &lt;code&gt;newVersion → update → upgrade&lt;/code&gt;（&lt;code&gt;upgrade&lt;/code&gt; 把旧版本置 offline、新版本置 online 原子切换）。&lt;strong&gt;只有 online 状态的记录会被 api 轮询到&lt;/strong&gt;——只 new 不 online 的业务，api 永远看不见。&lt;/p&gt;&#10;&lt;h3 id="13-官方示例"&gt;&lt;a href="#13-%e5%ae%98%e6%96%b9%e7%a4%ba%e4%be%8b" class="header-anchor"&gt;&lt;/a&gt;1.3 官方示例&#10;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;example/safety-skills/safety-knowledge/business_init.py&lt;/code&gt;（注意实际路径在 safety-knowledge 子目录下）建了 test 演示业务：&lt;code&gt;secretKey=123456&lt;/code&gt;、&lt;code&gt;qpsLimit=20&lt;/code&gt;、两侧 checkConf 都是 &lt;code&gt;checkNum=1 / timeoutMilliseconds=5000&lt;/code&gt;。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="二配置怎么流转admin--mysql--api-轮询--内存缓存--dag-重建"&gt;&lt;a href="#%e4%ba%8c%e9%85%8d%e7%bd%ae%e6%80%8e%e4%b9%88%e6%b5%81%e8%bd%acadmin--mysql--api-%e8%bd%ae%e8%af%a2--%e5%86%85%e5%ad%98%e7%bc%93%e5%ad%98--dag-%e9%87%8d%e5%bb%ba" class="header-anchor"&gt;&lt;/a&gt;二、配置怎么流转：admin → MySQL → api 轮询 → 内存缓存 → DAG 重建&#10;&lt;/h2&gt;&lt;p&gt;这是&amp;quot;新建业务等 60 秒&amp;quot;的完整答案，核心在 &lt;code&gt;safety-api/.../service/DynamicConfigService.java&lt;/code&gt;（注意：DefenseServiceImpl、RateLimitServiceImpl 都在 &lt;code&gt;service/&lt;/code&gt; 包下，&lt;strong&gt;不是&lt;/strong&gt; &lt;code&gt;service/impl/&lt;/code&gt;）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-1-1"&gt;&lt;a class="lnlinks" href="#hl-1-1"&gt; 1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-2"&gt;&lt;a class="lnlinks" href="#hl-1-2"&gt; 2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-3"&gt;&lt;a class="lnlinks" href="#hl-1-3"&gt; 3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-4"&gt;&lt;a class="lnlinks" href="#hl-1-4"&gt; 4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-5"&gt;&lt;a class="lnlinks" href="#hl-1-5"&gt; 5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-6"&gt;&lt;a class="lnlinks" href="#hl-1-6"&gt; 6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-7"&gt;&lt;a class="lnlinks" href="#hl-1-7"&gt; 7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-8"&gt;&lt;a class="lnlinks" href="#hl-1-8"&gt; 8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-9"&gt;&lt;a class="lnlinks" href="#hl-1-9"&gt; 9&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-10"&gt;&lt;a class="lnlinks" href="#hl-1-10"&gt;10&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-11"&gt;&lt;a class="lnlinks" href="#hl-1-11"&gt;11&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-12"&gt;&lt;a class="lnlinks" href="#hl-1-12"&gt;12&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-1-13"&gt;&lt;a class="lnlinks" href="#hl-1-13"&gt;13&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@PostConstruct&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;throws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;IOException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loadConf&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 启动时先全量加载一次&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@Scheduled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fixedRate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initialDelay&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 之后每 60s 轮询&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;loadConf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;throws&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;IOException&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConfigResponse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dynamicConf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;loadDynamic&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// HTTP POST admin /config/defense/api/conf&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shouldUpdate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shouldUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;preDynamicConf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dynamicConf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;shouldUpdate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;配置无变更&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 按 lastChangeTimestamp 比对&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;nodeConfMap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nodeConfMap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 全量替换 volatile Map（换对象不换内容，无锁）&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name2BusinessConf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name2BusinessConf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Runnable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;listeners&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 回调监听器 → 重建所有业务 DAG&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;逐环节批注：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;轮询目标&lt;/strong&gt;：&lt;code&gt;conf.dynamic.url=http://safety-admin:8006/config/defense/api/conf&lt;/code&gt;（&lt;code&gt;safety-api/docs/application_example.properties:5&lt;/code&gt;），admin 侧由 &lt;code&gt;DefenseConfigApiController.java:42&lt;/code&gt; 提供。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;变更判定&lt;/strong&gt;：admin 组装配置时取 业务表/DAG 表/关联原子能力表 三者 &lt;code&gt;update_time&lt;/code&gt; 的最大值作为 &lt;code&gt;lastChangeTimestamp&lt;/code&gt;（&lt;code&gt;DefenseConfigServiceImpl.java:274-291&lt;/code&gt;）；api 侧逐业务比对，没变就直接返回（&lt;code&gt;DynamicConfigService.java:113-151&lt;/code&gt;）。还有一个兜底：拉到的配置为空时&lt;strong&gt;拒绝更新&lt;/strong&gt;，防止 admin 故障把 api 的配置清空。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;DAG 重建&lt;/strong&gt;：&lt;code&gt;FunctionChainBuilderImpl.java:50&lt;/code&gt; 把自己注册成监听器，配置一变就 &lt;code&gt;loadConf()&lt;/code&gt; 重新构建每个业务的执行链（反射实例化 Function、编译 groovy Router、重建 OkHttp client），root 节点按业务名存进 &lt;code&gt;rootMap&lt;/code&gt;（&lt;code&gt;:64&lt;/code&gt;）。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;限流器同步刷新&lt;/strong&gt;：&lt;code&gt;RateLimitServiceImpl.java:37&lt;/code&gt; 同样 &lt;code&gt;@Scheduled(fixedRate = 60000)&lt;/code&gt; 重建全部 RateLimiter。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;&lt;strong&gt;实验结论&lt;/strong&gt;：新建业务 + 新 DAG，&lt;strong&gt;不用重启 api&lt;/strong&gt;，60s 轮询即可生效（本章实验实测）。笔记-02 坑 5 里&amp;quot;改原子能力超时要重启&amp;quot;是另一个问题（部分资源重建不彻底），不要混淆。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="三请求怎么流转一次请求的完整鉴权链路"&gt;&lt;a href="#%e4%b8%89%e8%af%b7%e6%b1%82%e6%80%8e%e4%b9%88%e6%b5%81%e8%bd%ac%e4%b8%80%e6%ac%a1%e8%af%b7%e6%b1%82%e7%9a%84%e5%ae%8c%e6%95%b4%e9%89%b4%e6%9d%83%e9%93%be%e8%b7%af" class="header-anchor"&gt;&lt;/a&gt;三、请求怎么流转：一次请求的完整鉴权链路&#10;&lt;/h2&gt;&lt;p&gt;入口 &lt;code&gt;safety-api/.../contoller/DefenseApiV2Controller.java&lt;/code&gt;（&lt;code&gt;contoller&lt;/code&gt; 是原仓库的拼写笔误）。两个端点：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;:37&lt;/code&gt; &lt;code&gt;POST /llmsec/api/defense/v2/{accessKey}&lt;/code&gt; —— 审核&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;:159&lt;/code&gt; &lt;code&gt;POST /llmsec/api/defenseResult/v2/{accessKey}&lt;/code&gt; —— 主动拉异步结果（free_taxi/http 模式用）&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="31-主线代码逐段批注"&gt;&lt;a href="#31-%e4%b8%bb%e7%ba%bf%e4%bb%a3%e7%a0%81%e9%80%90%e6%ae%b5%e6%89%b9%e6%b3%a8" class="header-anchor"&gt;&lt;/a&gt;3.1 主线代码逐段批注&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-2-1"&gt;&lt;a class="lnlinks" href="#hl-2-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-2-2"&gt;&lt;a class="lnlinks" href="#hl-2-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-2-3"&gt;&lt;a class="lnlinks" href="#hl-2-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-2-4"&gt;&lt;a class="lnlinks" href="#hl-2-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-2-5"&gt;&lt;a class="lnlinks" href="#hl-2-5"&gt;5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-2-6"&gt;&lt;a class="lnlinks" href="#hl-2-6"&gt;6&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;defenseService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;checkParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// :46 参数校验&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAccessTarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ConstantVals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;TARGET_DEFENSE_V2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// :57 强制写死 &amp;#34;defenseV2&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defenseService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;rateLimited&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;触发限流&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// :59 限流（在验签之前！）&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;accessKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;test&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;defenseService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// :74 test 开头免签名特赦&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;defenseService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;preProcess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// :78 OpenAI 格式解析/7000 字截断&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;free_taxi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// :97 按 responseMode 分派&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;注意两个设计细节：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;限流排在验签前面&lt;/strong&gt;——恶意请求连验签的 CPU 都消耗不到，先被限流挡掉；但这也意味着&lt;strong&gt;业务不存在（40002）的错误会在限流阶段就抛出&lt;/strong&gt;（&lt;code&gt;DefenseServiceImpl.java:84-87&lt;/code&gt;：拿不到 BusinessConf 直接抛 &lt;code&gt;token_error&lt;/code&gt;），轮询未同步时调新业务见到的就是它。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;accessTarget 由服务端写死&lt;/strong&gt;为 &lt;code&gt;defenseV2&lt;/code&gt;（&lt;code&gt;ConstantVals.java:12&lt;/code&gt;），客户端传不传都一样——签名串里的 accessTarget 也必须用这个值，否则验签必挂。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="32-checkparams硬性规则defenseserviceimpljava61"&gt;&lt;a href="#32-checkparams%e7%a1%ac%e6%80%a7%e8%a7%84%e5%88%99defenseserviceimpljava61" class="header-anchor"&gt;&lt;/a&gt;3.2 checkParams：硬性规则（&lt;code&gt;DefenseServiceImpl.java:61&lt;/code&gt;）&#10;&lt;/h3&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;规则&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;不满足的报错&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;content / responseMode / messageInfo / fromRole 必填&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;param_invalid(40001)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;非 sync 模式必须带 sessionId&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;同上&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;有 sliceId 必须有 messageId&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;同上&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="33-限流guava-ratelimiter-内存级ratelimitserviceimpljava"&gt;&lt;a href="#33-%e9%99%90%e6%b5%81guava-ratelimiter-%e5%86%85%e5%ad%98%e7%ba%a7ratelimitserviceimpljava" class="header-anchor"&gt;&lt;/a&gt;3.3 限流：Guava RateLimiter 内存级（&lt;code&gt;RateLimitServiceImpl.java&lt;/code&gt;）&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-3-1"&gt;&lt;a class="lnlinks" href="#hl-3-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-3-2"&gt;&lt;a class="lnlinks" href="#hl-3-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-3-3"&gt;&lt;a class="lnlinks" href="#hl-3-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-3-4"&gt;&lt;a class="lnlinks" href="#hl-3-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-3-5"&gt;&lt;a class="lnlinks" href="#hl-3-5"&gt;5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-3-6"&gt;&lt;a class="lnlinks" href="#hl-3-6"&gt;6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-3-7"&gt;&lt;a class="lnlinks" href="#hl-3-7"&gt;7&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;hasLimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;accessKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;accessTarget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// :77&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RateLimiter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rateLimiter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rateLimiterMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accessKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;accessTarget&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 先查子维度 accessKey$accessTarget&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rateLimiter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rateLimiter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rateLimiter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rateLimiterMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accessKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 没有就用业务总限流&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rateLimiter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rateLimiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tryAcquire&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 快速路径：拿到令牌直接放行&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;rateLimiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tryAcquire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;waitMillisecondsMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accessKey&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TimeUnit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MILLISECONDS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// :86 再等一个窗口&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;批注：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;key 结构：业务总限流用 &lt;code&gt;accessKey&lt;/code&gt;；&lt;code&gt;AuthControlConf.qpsLimits&lt;/code&gt;（&lt;code&gt;safety-basic/.../core/auth/AuthControlConf.java:39&lt;/code&gt;）可按 accessTarget 配子维度，key 为 &lt;code&gt;accessKey$accessTarget&lt;/code&gt;（&lt;code&gt;:72-73&lt;/code&gt;）。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;qpsLimit&lt;/code&gt; 不传或 ≤0 时&lt;strong&gt;默认 10 QPS&lt;/strong&gt;（&lt;code&gt;:46&lt;/code&gt;）。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;它不是&amp;quot;超限即拒&amp;quot;&lt;/strong&gt;：&lt;code&gt;tryAcquire()&lt;/code&gt; 失败后还会再等 &lt;code&gt;1000/qpsLimit + 10&lt;/code&gt; 毫秒（&lt;code&gt;:48&lt;/code&gt;），等到令牌照样放行。实验证实：qpsLimit=2 时串行压测根本打不出限流（请求被拖慢到 ~470ms 自动排队），必须并发突发才能看到 50001（见第七章）。这是&amp;quot;节流&amp;quot;而非&amp;quot;熔断&amp;quot;，对同步调用方很友好，但做容量评估时别误以为超 QPS 会直接报错。&lt;/li&gt;&#10;&lt;li&gt;纯内存实现、60s 全量重建——多实例部署时每个实例独立计数，实际全局限流 ≈ qpsLimit × 实例数。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="34-验签verify--businessconf--basiccheckauthrequestjava26"&gt;&lt;a href="#34-%e9%aa%8c%e7%ad%beverify--businessconf--basiccheckauthrequestjava26" class="header-anchor"&gt;&lt;/a&gt;3.4 验签：verify → BusinessConf → basicCheck（&lt;code&gt;AuthRequest.java:26&lt;/code&gt;）&#10;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;DefenseServiceImpl.verify()&lt;/code&gt;（&lt;code&gt;:92&lt;/code&gt;）很短：plainText 为空就回填 content → 按 accessKey 查 BusinessConf → &lt;code&gt;request.basicCheck(businessConf)&lt;/code&gt;。basicCheck 的&lt;strong&gt;实际执行顺序&lt;/strong&gt;（与常见直觉不同，以代码为准）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-4-1"&gt;&lt;a class="lnlinks" href="#hl-4-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-4-2"&gt;&lt;a class="lnlinks" href="#hl-4-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-4-3"&gt;&lt;a class="lnlinks" href="#hl-4-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-4-4"&gt;&lt;a class="lnlinks" href="#hl-4-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-4-5"&gt;&lt;a class="lnlinks" href="#hl-4-5"&gt;5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-4-6"&gt;&lt;a class="lnlinks" href="#hl-4-6"&gt;6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-4-7"&gt;&lt;a class="lnlinks" href="#hl-4-7"&gt;7&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;controlConf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;未授权&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// :27 业务不存在/未上线&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// :35-39 ① accessTarget 授权范围：配置了 accessTargets 且请求带 accessTarget 时，必须在集合内&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// :41-48 ② 必填校验：timestamp&amp;gt;0、requestId/accessKey/plainText/signature 非空&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentTimeMillis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;controlConf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getExpireSeconds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sign_expire&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// :50 ③ 防重放：超 expireSeconds(默认300s) 拒收&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SignUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;controlConf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// :54 ④ HMAC 验签&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sign_not_matched&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;即：&lt;strong&gt;授权范围 → 必填 → 时效 → 验签&lt;/strong&gt;。两点值得记：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;accessTarget 校验是有条件的（集合非空且请求非空才查），而 controller 已强制写入 &lt;code&gt;defenseV2&lt;/code&gt;，所以 defense 接口实际总会校验；默认授权集合 &lt;code&gt;[&amp;quot;defenseV2&amp;quot;,&amp;quot;defenseResultV2&amp;quot;]&lt;/code&gt; 覆盖了拉结果接口。&lt;/li&gt;&#10;&lt;li&gt;时效检查只看&amp;quot;比服务端晚多少&amp;quot;，&lt;strong&gt;不管未来时间&lt;/strong&gt;（timestamp 大于当前时间永远合法），客户端时钟快一点没事、慢 5 分钟必挂。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="35-签名算法本体safety-basiccoreutilsignutiljava"&gt;&lt;a href="#35-%e7%ad%be%e5%90%8d%e7%ae%97%e6%b3%95%e6%9c%ac%e4%bd%93safety-basiccoreutilsignutiljava" class="header-anchor"&gt;&lt;/a&gt;3.5 签名算法本体（&lt;code&gt;safety-basic/.../core/util/SignUtil.java&lt;/code&gt;）&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-5-1"&gt;&lt;a class="lnlinks" href="#hl-5-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-5-2"&gt;&lt;a class="lnlinks" href="#hl-5-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-5-3"&gt;&lt;a class="lnlinks" href="#hl-5-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-5-4"&gt;&lt;a class="lnlinks" href="#hl-5-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-5-5"&gt;&lt;a class="lnlinks" href="#hl-5-5"&gt;5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-5-6"&gt;&lt;a class="lnlinks" href="#hl-5-6"&gt;6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-5-7"&gt;&lt;a class="lnlinks" href="#hl-5-7"&gt;7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-5-8"&gt;&lt;a class="lnlinks" href="#hl-5-8"&gt;8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-5-9"&gt;&lt;a class="lnlinks" href="#hl-5-9"&gt;9&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AuthControlConf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;authConf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AuthRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;authRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// :36&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;StringBuilder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plainBuilder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;StringBuilder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plainBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;accessKey=&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authConf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAccessKey&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;amp;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;accessTarget=&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAccessTarget&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;amp;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;requestId=&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRequestId&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;amp;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;timestamp=&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTimestamp&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;amp;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;plainText=&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPlainText&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hmacSHA1Encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plainBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;authConf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSecretKey&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;签名串是&lt;strong&gt;带 key 的&lt;/strong&gt; &lt;code&gt;accessKey=..&amp;amp;accessTarget=..&amp;amp;requestId=..&amp;amp;timestamp=..&amp;amp;plainText=..&lt;/code&gt;（顺序固定），用业务 &lt;code&gt;secretKey&lt;/code&gt; 做 HmacSHA1，结果 Base64（&lt;code&gt;:62-73&lt;/code&gt;）。&lt;code&gt;verify()&lt;/code&gt;（&lt;code&gt;:57&lt;/code&gt;）就是&amp;quot;服务端重算一遍再字符串相等比较&amp;quot;——所以 secretKey 永不上网，网传的是签名。&lt;/p&gt;&#10;&lt;h3 id="36-test-特赦实测"&gt;&lt;a href="#36-test-%e7%89%b9%e8%b5%a6%e5%ae%9e%e6%b5%8b" class="header-anchor"&gt;&lt;/a&gt;3.6 test 特赦实测&#10;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;DefenseApiV2Controller.java:74&lt;/code&gt; 的 &lt;code&gt;startsWith(&amp;quot;test&amp;quot;)&lt;/code&gt; 让整个 verify 被跳过。实测：&lt;code&gt;timestamp=1&lt;/code&gt;（1970 年）、不带 signature 调 &lt;code&gt;/v2/test&lt;/code&gt; 依然返回 &lt;code&gt;code=0&lt;/code&gt;。&lt;strong&gt;生产业务千万不要用 test 开头的 accessKey&lt;/strong&gt;，且这个硬编码前缀意味着你无法通过配置关掉它。&lt;/p&gt;&#10;&lt;h3 id="37-preprocessdefenseserviceimpljava107"&gt;&lt;a href="#37-preprocessdefenseserviceimpljava107" class="header-anchor"&gt;&lt;/a&gt;3.7 preProcess（&lt;code&gt;DefenseServiceImpl.java:107&lt;/code&gt;）&#10;&lt;/h3&gt;&lt;p&gt;验签后执行：配置了 &lt;code&gt;openai.stream.business&lt;/code&gt; 的业务的 robot 消息按 OpenAI 流式格式解析；&lt;code&gt;openai.request.business&lt;/code&gt; 的 user 消息解析成 openaiMessages；content 超 7000 字截断（&lt;code&gt;GlobalConf.MAX_CHECK_LEN&lt;/code&gt;，&lt;code&gt;GlobalConf.java:49&lt;/code&gt;）、plainText 超 14000 截断。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="四签名算法与三语言复刻"&gt;&lt;a href="#%e5%9b%9b%e7%ad%be%e5%90%8d%e7%ae%97%e6%b3%95%e4%b8%8e%e4%b8%89%e8%af%ad%e8%a8%80%e5%a4%8d%e5%88%bb" class="header-anchor"&gt;&lt;/a&gt;四、签名算法与三语言复刻&#10;&lt;/h2&gt;&lt;p&gt;三份 demo 算法完全一致，只是语言壳不同。以 &lt;code&gt;quickstart/api_test.py&lt;/code&gt; 为准逐行批注：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-6-1"&gt;&lt;a class="lnlinks" href="#hl-6-1"&gt; 1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-6-2"&gt;&lt;a class="lnlinks" href="#hl-6-2"&gt; 2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-6-3"&gt;&lt;a class="lnlinks" href="#hl-6-3"&gt; 3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-6-4"&gt;&lt;a class="lnlinks" href="#hl-6-4"&gt; 4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-6-5"&gt;&lt;a class="lnlinks" href="#hl-6-5"&gt; 5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-6-6"&gt;&lt;a class="lnlinks" href="#hl-6-6"&gt; 6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-6-7"&gt;&lt;a class="lnlinks" href="#hl-6-7"&gt; 7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-6-8"&gt;&lt;a class="lnlinks" href="#hl-6-8"&gt; 8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-6-9"&gt;&lt;a class="lnlinks" href="#hl-6-9"&gt; 9&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-6-10"&gt;&lt;a class="lnlinks" href="#hl-6-10"&gt;10&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do_sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret_key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 毫秒时间戳（不是秒！）&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;timestamp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;accessTarget&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;defenseV2&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# 必须与服务端写死值一致&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;plainText&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;plainText&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;plainText&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;content&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# plainText 默认取 content&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;amp;&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;=&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;accessKey&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;accessTarget&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;requestId&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;timestamp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;plainText&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt; &lt;span class="c1"&gt;# 顺序固定&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gen_sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# hmac-sha1 → base64&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;signature&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sig&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;三语言对照（全部在 &lt;code&gt;safety/safety-demo/&lt;/code&gt; 下）：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;语言&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;文件&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;签名实现要点&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Python&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;python/demo.py:26&lt;/code&gt;、&lt;code&gt;quickstart/api_test.py:26&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;hmac.new(key, msg, hashlib.sha1).digest()&lt;/code&gt; → &lt;code&gt;base64.b64encode&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Go&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;golang/main.go:172&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;hmac.New(sha1.New, []byte(SK))&lt;/code&gt; → &lt;code&gt;base64.StdEncoding&lt;/code&gt;，参数顺序用数组固定&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Java&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;java/.../demo/DefenseApiDemo.java:110&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;直接复用框架类：&lt;code&gt;request.sign(authConf)&lt;/code&gt;（&lt;code&gt;DefenseApiRequest.java:57&lt;/code&gt; 内部调 SignUtil）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;&lt;strong&gt;跨语言坑&lt;/strong&gt;：为什么签名用 plainText 而不是整个 JSON body？&lt;code&gt;safety-api/docs/api.md:36&lt;/code&gt; 写得很坦白——不同语言对 JSON 默认值/序列化的处理不一致，对整个 body 签名跨语言必翻车，所以只签一个纯文本字段。自己写客户端时：&lt;strong&gt;plainText 传什么，服务端就签什么&lt;/strong&gt;，它和 content 不一致时以 plainText 为准。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="五四种-responsemode-怎么选"&gt;&lt;a href="#%e4%ba%94%e5%9b%9b%e7%a7%8d-responsemode-%e6%80%8e%e4%b9%88%e9%80%89" class="header-anchor"&gt;&lt;/a&gt;五、四种 responseMode 怎么选&#10;&lt;/h2&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;模式&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;源码行为（&lt;code&gt;DefenseApiV2Controller.java:97-127&lt;/code&gt;）&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;何时用&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;sync&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;同步跑完整 DAG 返回；若 checkNum&amp;gt;1 且带 sessionId/messageId，还会顺带捞回该会话之前未取的异步结果（&lt;code&gt;DefenseServiceImpl.java:157&lt;/code&gt;）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;输入审核&lt;/strong&gt;：内容发出去之前必须知道能不能发&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;free_taxi&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;任务异步入队后&lt;strong&gt;立即返回该会话之前累积的结果&lt;/strong&gt;（&amp;ldquo;搭便车&amp;rdquo;），带 sliceId 时按 messageId 过滤&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;流式输出审核&lt;/strong&gt;：不阻塞 token 吐出，结果滞后一条消息，会话结束发 content=&lt;code&gt;$&lt;/code&gt; 的 sync 请求收尾（机制详解见笔记-05）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;http&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;只入队立即返回空，客户端事后调 &lt;code&gt;defenseResult/v2/{accessKey}&lt;/code&gt; 拉结果&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;异步批量、离线审计&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;mq&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;同 http 入队，但开源版 MQ 投递是&lt;strong&gt;空实现 stub&lt;/strong&gt;（&lt;code&gt;MessageQueueServiceImpl.java&lt;/code&gt;），等于只入队不投递&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;预留，要用得自己实现&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;经验法则：&lt;strong&gt;默认 sync&lt;/strong&gt;；只在&amp;quot;审核不能阻塞主流程&amp;quot;（流式生成）时上 free_taxi；http/mq 是给不要求实时处置的场景。选 free_taxi/http 别忘了 checkParams 要求 sessionId 必填。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="六多业务隔离机制"&gt;&lt;a href="#%e5%85%ad%e5%a4%9a%e4%b8%9a%e5%8a%a1%e9%9a%94%e7%a6%bb%e6%9c%ba%e5%88%b6" class="header-anchor"&gt;&lt;/a&gt;六、多业务隔离机制&#10;&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-7-1"&gt;&lt;a class="lnlinks" href="#hl-7-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-7-2"&gt;&lt;a class="lnlinks" href="#hl-7-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-7-3"&gt;&lt;a class="lnlinks" href="#hl-7-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-7-4"&gt;&lt;a class="lnlinks" href="#hl-7-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-7-5"&gt;&lt;a class="lnlinks" href="#hl-7-5"&gt;5&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;name2BusinessConf (业务名 → BusinessConf：secretKey/qpsLimit/checkConf)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nodeConfMap (业务名 → DAG 节点配置)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rootMap (业务名 → DAG 执行链 root)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rateLimiterMap (accessKey / accessKey$accessTarget → RateLimiter)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;敏感词 (business_scene 列：精确匹配业务名，或 &amp;#34;all&amp;#34;/空 = 全局生效)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;配置按业务名索引&lt;/strong&gt;：上面四张 Map 各管一段，业务之间互不串扰（&lt;code&gt;DynamicConfigService.java:53-54&lt;/code&gt;、&lt;code&gt;FunctionChainBuilderImpl.java:46&lt;/code&gt;）。鉴权时按 URL 里的 accessKey 查出本业务的 secretKey——&lt;strong&gt;A 业务的签名过不了 B 业务的验签&lt;/strong&gt;，天然隔离。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;限流隔离&lt;/strong&gt;：每个业务独立 RateLimiter，mybiz 被打爆不影响 test。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;敏感词的全局例外&lt;/strong&gt;：keywords 服务按请求里的 &lt;code&gt;businessName&lt;/code&gt; 做场景过滤，&lt;code&gt;business_scene=&amp;quot;all&amp;quot;&lt;/code&gt;（或空）的词&lt;strong&gt;对所有业务生效&lt;/strong&gt;（&lt;code&gt;safety-keywords/.../SensitiveWordsManageServiceImpl.java:180-191&lt;/code&gt; 的 &lt;code&gt;isSceneMatch&lt;/code&gt;）；业务名通过 DAG 节点调用时透传（&lt;code&gt;KeywordFunction.java:71-76&lt;/code&gt;）。所以&amp;quot;全司通用黑词&amp;quot;灌到 all，业务专属词灌到自己名下。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;子业务&lt;/strong&gt;：&lt;code&gt;messageInfo.ext.subBizId&lt;/code&gt; 存在时按 &lt;code&gt;accessKey$subBizId&lt;/code&gt; 先查配置，查不到回退主业务（&lt;code&gt;ConfigService.java:33-43&lt;/code&gt;、&lt;code&gt;DynamicConfigService.java:223-227&lt;/code&gt;）——一套 secretKey 下挂多个子场景的玩法，开源版没有配套管理接口，需要手工造配置，了解即可。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="七动手实验mybiz-全流程实录"&gt;&lt;a href="#%e4%b8%83%e5%8a%a8%e6%89%8b%e5%ae%9e%e9%aa%8cmybiz-%e5%85%a8%e6%b5%81%e7%a8%8b%e5%ae%9e%e5%bd%95" class="header-anchor"&gt;&lt;/a&gt;七、动手实验：mybiz 全流程实录&#10;&lt;/h2&gt;&lt;p&gt;环境：笔记-02 部署的本地 Docker 环境（容器已运行 7 小时，未做任何重启）。目标：新建业务 &lt;code&gt;mybiz&lt;/code&gt;（secretKey=&lt;code&gt;mybiz-secret-2026&lt;/code&gt;，qpsLimit=2）→ 绑一条 keyword-only 策略 → Python 带签名调通 → 三种失败路径各验证一次。&lt;/p&gt;&#10;&lt;h3 id="71-建业务并上线"&gt;&lt;a href="#71-%e5%bb%ba%e4%b8%9a%e5%8a%a1%e5%b9%b6%e4%b8%8a%e7%ba%bf" class="header-anchor"&gt;&lt;/a&gt;7.1 建业务并上线&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-8-1"&gt;&lt;a class="lnlinks" href="#hl-8-1"&gt; 1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-2"&gt;&lt;a class="lnlinks" href="#hl-8-2"&gt; 2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-3"&gt;&lt;a class="lnlinks" href="#hl-8-3"&gt; 3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-4"&gt;&lt;a class="lnlinks" href="#hl-8-4"&gt; 4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-5"&gt;&lt;a class="lnlinks" href="#hl-8-5"&gt; 5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-6"&gt;&lt;a class="lnlinks" href="#hl-8-6"&gt; 6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-7"&gt;&lt;a class="lnlinks" href="#hl-8-7"&gt; 7&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-8"&gt;&lt;a class="lnlinks" href="#hl-8-8"&gt; 8&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-9"&gt;&lt;a class="lnlinks" href="#hl-8-9"&gt; 9&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-8-10"&gt;&lt;a class="lnlinks" href="#hl-8-10"&gt;10&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -s -X POST http://localhost:8006/config/defense/manage/business/new &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Content-Type: application/json&amp;#39;&lt;/span&gt; -d &lt;span class="s1"&gt;&amp;#39;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;name&amp;#34;: &amp;#34;mybiz&amp;#34;, &amp;#34;group&amp;#34;: &amp;#34;default&amp;#34;, &amp;#34;desc&amp;#34;: &amp;#34;学习笔记10实验业务&amp;#34;, &amp;#34;type&amp;#34;: &amp;#34;toC&amp;#34;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;secretKey&amp;#34;: &amp;#34;mybiz-secret-2026&amp;#34;, &amp;#34;qpsLimit&amp;#34;: 2,&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;robotCheckConfObj&amp;#34;: {&amp;#34;checkNum&amp;#34;: 1, &amp;#34;unit&amp;#34;: &amp;#34;slice_single&amp;#34;, &amp;#34;timeoutMilliseconds&amp;#34;: 6000},&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;userCheckConfObj&amp;#34;: {&amp;#34;checkNum&amp;#34;: 1, &amp;#34;unit&amp;#34;: &amp;#34;message_single&amp;#34;, &amp;#34;timeoutMilliseconds&amp;#34;: 6000}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; }&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 返回 id=2, version=1, status=edit —— 注意此时 api 还看不见它&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -s -X POST http://localhost:8006/config/defense/manage/business/online &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Content-Type: application/json&amp;#39;&lt;/span&gt; -d &lt;span class="s1"&gt;&amp;#39;{&amp;#34;id&amp;#34;: 2}&amp;#39;&lt;/span&gt; &lt;span class="c1"&gt;# status → online&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;h3 id="72-绑策略dag"&gt;&lt;a href="#72-%e7%bb%91%e7%ad%96%e7%95%a5dag" class="header-anchor"&gt;&lt;/a&gt;7.2 绑策略（DAG）&#10;&lt;/h3&gt;&lt;p&gt;mybiz_strategy.yml：&lt;code&gt;start&lt;/code&gt;(dummy+groovy 按 fromRole 分流) → &lt;code&gt;user_keyword&lt;/code&gt; / &lt;code&gt;robot_keyword&lt;/code&gt;（均 &lt;code&gt;ref: keyword&lt;/code&gt; + &lt;code&gt;stupid_end&lt;/code&gt;）。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-9-1"&gt;&lt;a class="lnlinks" href="#hl-9-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-2"&gt;&lt;a class="lnlinks" href="#hl-9-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-3"&gt;&lt;a class="lnlinks" href="#hl-9-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-9-4"&gt;&lt;a class="lnlinks" href="#hl-9-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -s -X POST http://localhost:8006/config/defense/manage/dag/dagUpdateYaml &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Content-Type: text/plain&amp;#39;&lt;/span&gt; --data-binary @mybiz_strategy.yml &lt;span class="c1"&gt;# 返回 id=4&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -s -X POST http://localhost:8006/config/defense/manage/dag/online &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s1"&gt;&amp;#39;Content-Type: application/json&amp;#39;&lt;/span&gt; -d &lt;span class="s1"&gt;&amp;#39;{&amp;#34;id&amp;#34;: 4}&amp;#39;&lt;/span&gt; &lt;span class="c1"&gt;# DAG 上线&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;DAG 与业务的绑定不靠外键、靠 &lt;code&gt;businessName&lt;/code&gt; 字段同名&lt;/strong&gt;（&lt;code&gt;execute_dag_conf.business_name&lt;/code&gt; = &lt;code&gt;business_info.name&lt;/code&gt;），admin 组装配置时按名字 join（&lt;code&gt;DefenseConfigServiceImpl.java:251-296&lt;/code&gt;）。&lt;/p&gt;&#10;&lt;h3 id="73-等配置同步--带签名调用"&gt;&lt;a href="#73-%e7%ad%89%e9%85%8d%e7%bd%ae%e5%90%8c%e6%ad%a5--%e5%b8%a6%e7%ad%be%e5%90%8d%e8%b0%83%e7%94%a8" class="header-anchor"&gt;&lt;/a&gt;7.3 等配置同步 + 带签名调用&#10;&lt;/h3&gt;&lt;p&gt;写完配置&lt;strong&gt;立刻调会报 40002&lt;/strong&gt;（轮询未到）。等约 60 秒后（Python 客户端完整代码见第四节 api_test.py 的改造版：URL 换 &lt;code&gt;localhost:8007&lt;/code&gt;、AK/SK 换 mybiz 的）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-10-1"&gt;&lt;a class="lnlinks" href="#hl-10-1"&gt;1&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-2"&gt;&lt;a class="lnlinks" href="#hl-10-2"&gt;2&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-3"&gt;&lt;a class="lnlinks" href="#hl-10-3"&gt;3&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-4"&gt;&lt;a class="lnlinks" href="#hl-10-4"&gt;4&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-5"&gt;&lt;a class="lnlinks" href="#hl-10-5"&gt;5&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-6"&gt;&lt;a class="lnlinks" href="#hl-10-6"&gt;6&lt;/a&gt;&#10;&lt;/span&gt;&lt;span class="lnt" id="hl-10-7"&gt;&lt;a class="lnlinks" href="#hl-10-7"&gt;7&lt;/a&gt;&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 mybiz_client.py ok &lt;span class="s2"&gt;&amp;#34;你好&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# {&amp;#34;code&amp;#34;: 0, &amp;#34;cost&amp;#34;: 336, &amp;#34;data&amp;#34;: [{&amp;#34;riskCode&amp;#34;: 0, &amp;#34;riskMessage&amp;#34;: &amp;#34;0正常文本&amp;#34;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# &amp;#34;riskCheckType&amp;#34;: &amp;#34;keyword&amp;#34;, &amp;#34;riskCheckResult&amp;#34;: {&amp;#34;hitWord&amp;#34;: &amp;#34;&amp;#34;, &amp;#34;bwgLabel&amp;#34;: 0, ...}}]}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 mybiz_client.py ok &lt;span class="s2"&gt;&amp;#34;你是王八蛋&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# {&amp;#34;code&amp;#34;: 0, &amp;#34;cost&amp;#34;: 73, &amp;#34;data&amp;#34;: [{&amp;#34;riskCode&amp;#34;: 1006, &amp;#34;riskMessage&amp;#34;: &amp;#34;1006辱骂&amp;#34;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# &amp;#34;riskCheckResult&amp;#34;: {&amp;#34;hitWord&amp;#34;: &amp;#34;王八蛋&amp;#34;, &amp;#34;bwgLabel&amp;#34;: 1, ...}}]}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;✅ 全程没有重启 safety-api——新业务接入靠 60s 轮询即可生效。响应字段解读：&lt;code&gt;riskCode=0&lt;/code&gt; 正常、非 0 即风险编码；&lt;code&gt;riskCheckType&lt;/code&gt; 是命中的检测类型（keyword）；&lt;code&gt;riskCheckResult&lt;/code&gt; 里 &lt;code&gt;hitWord&lt;/code&gt; 是命中词、&lt;code&gt;bwgLabel&lt;/code&gt; 1=黑 2=白 3=灰。&lt;/p&gt;&#10;&lt;h3 id="74-三种失败路径实测"&gt;&lt;a href="#74-%e4%b8%89%e7%a7%8d%e5%a4%b1%e8%b4%a5%e8%b7%af%e5%be%84%e5%ae%9e%e6%b5%8b" class="header-anchor"&gt;&lt;/a&gt;7.4 三种失败路径实测&#10;&lt;/h3&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;实验&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;做法&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;实际返回&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;对应代码&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;改错签名&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;签名末两位改成 &amp;ldquo;XX&amp;rdquo;（或用错 secretKey，效果相同）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;{&amp;quot;code&amp;quot;: 40004, &amp;quot;message&amp;quot;: &amp;quot;签名值错误&amp;quot;, &amp;quot;cost&amp;quot;: 1}&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;AuthRequest.java:54-57&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;超 timestamp&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;timestamp 减 301 秒（&amp;gt; 默认 expireSeconds=300s）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;{&amp;quot;code&amp;quot;: 40003, &amp;quot;message&amp;quot;: &amp;quot;签名过期&amp;quot;, &amp;quot;cost&amp;quot;: 0}&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;AuthRequest.java:50-52&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;超 QPS&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;20 个线程并发打 qpsLimit=2 的 mybiz&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;4 个成功，16 个 &lt;code&gt;{&amp;quot;code&amp;quot;: 50001, &amp;quot;message&amp;quot;: &amp;quot;触发限流&amp;quot;}&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;RateLimitServiceImpl.java:77-88&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;&lt;strong&gt;意外发现（限流的真实行为）&lt;/strong&gt;：先用串行循环连发 10 个请求，&lt;strong&gt;全部成功&lt;/strong&gt;，只是耗时从 ~60ms 被拖长到 ~470ms——因为 &lt;code&gt;hasLimit&lt;/code&gt; 在 &lt;code&gt;tryAcquire()&lt;/code&gt; 失败后还会等 &lt;code&gt;1000/qpsLimit+10&lt;/code&gt;=510ms 的窗口（第三节 3.3）。只有并发突发、等待窗口内也拿不到令牌时才真正返回 50001。想复现限流必须用并发，串行测不出来。&lt;/p&gt;&#10;&lt;p&gt;顺带验证的两个边界：调不存在的 accessKey → &lt;code&gt;40002 token错误/业务不存在&lt;/code&gt;（在限流阶段抛出，早于验签）；test 业务 timestamp=1、无签名 → &lt;code&gt;code=0&lt;/code&gt;（特赦生效）。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="八速查附录"&gt;&lt;a href="#%e5%85%ab%e9%80%9f%e6%9f%a5%e9%99%84%e5%bd%95" class="header-anchor"&gt;&lt;/a&gt;八、速查附录&#10;&lt;/h2&gt;&lt;h3 id="81-错误码表safety-basiccoreresponsecodejava"&gt;&lt;a href="#81-%e9%94%99%e8%af%af%e7%a0%81%e8%a1%a8safety-basiccoreresponsecodejava" class="header-anchor"&gt;&lt;/a&gt;8.1 错误码表（&lt;code&gt;safety-basic/.../core/ResponseCode.java&lt;/code&gt;）&#10;&lt;/h3&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;code&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;含义&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;常见触发&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;成功&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;—&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;40001&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;参数非法&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;content/responseMode/sessionId 缺失等&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;40002&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;token错误/业务不存在&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;业务未 online、轮询未同步、accessKey 打错&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;40003&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;签名过期&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;timestamp 与服务端差超 expireSeconds（默认 300s），检查客户端时钟&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;40004&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;签名值错误&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;secretKey 错、签名串顺序/拼写错、accessTarget 不是 defenseV2&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;50001&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;触发限流&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;并发超 qpsLimit 且等待窗口内拿不到令牌&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;50002&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;内部执行超时&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;DAG 整体耗时超 checkConf.timeoutMilliseconds&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="82-接入一个新业务的最小清单"&gt;&lt;a href="#82-%e6%8e%a5%e5%85%a5%e4%b8%80%e4%b8%aa%e6%96%b0%e4%b8%9a%e5%8a%a1%e7%9a%84%e6%9c%80%e5%b0%8f%e6%b8%85%e5%8d%95" class="header-anchor"&gt;&lt;/a&gt;8.2 接入一个新业务的最小清单&#10;&lt;/h3&gt;&lt;ol&gt;&#10;&lt;li&gt;&lt;code&gt;business/new&lt;/code&gt; + &lt;code&gt;business/online&lt;/code&gt;（拿到 accessKey=name、自配 secretKey/qpsLimit）&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;dag/dagUpdateYaml&lt;/code&gt; + &lt;code&gt;dag/online&lt;/code&gt;（businessName 与业务同名）&lt;/li&gt;&#10;&lt;li&gt;等 ≤60s（api 轮询）→ 带签名调 &lt;code&gt;POST /llmsec/api/defense/v2/{accessKey}&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;客户端签名：&lt;code&gt;accessKey=..&amp;amp;accessTarget=defenseV2&amp;amp;requestId=..&amp;amp;timestamp=毫秒&amp;amp;plainText=content&lt;/code&gt;，HmacSHA1(secretKey) → Base64&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="83-源码坐标速查"&gt;&lt;a href="#83-%e6%ba%90%e7%a0%81%e5%9d%90%e6%a0%87%e9%80%9f%e6%9f%a5" class="header-anchor"&gt;&lt;/a&gt;8.3 源码坐标速查&#10;&lt;/h3&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;主题&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;位置&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;审核/拉结果入口&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;safety/safety-api/src/main/java/com/jd/security/llmsec/contoller/DefenseApiV2Controller.java:37&lt;/code&gt; / &lt;code&gt;:159&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;test 免签名特赦&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;同上 &lt;code&gt;:74&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;参数校验/预处理&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;safety-api/.../service/DefenseServiceImpl.java:61&lt;/code&gt; / &lt;code&gt;:107&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;限流&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;safety-api/.../service/RateLimitServiceImpl.java:77&lt;/code&gt;（60s 刷新在 &lt;code&gt;:37&lt;/code&gt;）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;验签主流程&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;safety/safety-basic/src/main/java/com/jd/security/llmsec/core/auth/AuthRequest.java:26&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;签名算法&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;safety-basic/.../core/util/SignUtil.java:36&lt;/code&gt;（verify 在 &lt;code&gt;:57&lt;/code&gt;）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;配置轮询&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;safety-api/.../service/DynamicConfigService.java:65&lt;/code&gt;（变更比对 &lt;code&gt;:113&lt;/code&gt;）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;DAG 重建监听&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;safety-api/.../service/execute/FunctionChainBuilderImpl.java:50&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;admin 配置下发&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;safety-admin/.../contoller/business/DefenseConfigApiController.java:42&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;业务管理接口文档&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;safety/safety-admin/docs/api/business_api.md&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;business_info 建表&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;safety/safety-admin/docs/tables.sql:33&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;建 test 业务示例&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;example/safety-skills/safety-knowledge/business_init.py&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Python/Go/Java 客户端&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;quickstart/api_test.py&lt;/code&gt;、&lt;code&gt;safety/safety-demo/{python,golang,java}&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;敏感词 all 全局生效&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;safety-skills/safety-keywords/.../SensitiveWordsManageServiceImpl.java:180&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="84-与提纲直觉不符以代码为准的事实"&gt;&lt;a href="#84-%e4%b8%8e%e6%8f%90%e7%ba%b2%e7%9b%b4%e8%a7%89%e4%b8%8d%e7%ac%a6%e4%bb%a5%e4%bb%a3%e7%a0%81%e4%b8%ba%e5%87%86%e7%9a%84%e4%ba%8b%e5%ae%9e" class="header-anchor"&gt;&lt;/a&gt;8.4 与提纲/直觉不符、以代码为准的事实&#10;&lt;/h3&gt;&lt;ul&gt;&#10;&lt;li&gt;basicCheck 顺序是 &lt;strong&gt;授权范围→必填→时效→验签&lt;/strong&gt;，不是&amp;quot;必填→时效→授权→验签&amp;quot;（&lt;code&gt;AuthRequest.java:35-57&lt;/code&gt;）。&lt;/li&gt;&#10;&lt;li&gt;签名串带 &lt;code&gt;key=&lt;/code&gt; 前缀（&lt;code&gt;accessKey=xx&amp;amp;...&lt;/code&gt;），不是纯值拼接（&lt;code&gt;SignUtil.java:36-52&lt;/code&gt;）。&lt;/li&gt;&#10;&lt;li&gt;DefenseServiceImpl / RateLimitServiceImpl 位于 &lt;code&gt;service/&lt;/code&gt; 包，&lt;strong&gt;没有&lt;/strong&gt; &lt;code&gt;service/impl/&lt;/code&gt; 这一层。&lt;/li&gt;&#10;&lt;li&gt;限流是&amp;quot;节流&amp;quot;不是&amp;quot;熔断&amp;quot;：超限先等 510ms（qpsLimit=2 时）再拒，串行压测打不出 50001。&lt;/li&gt;&#10;&lt;li&gt;business_init.py 实际在 &lt;code&gt;example/safety-skills/safety-knowledge/&lt;/code&gt; 下。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item></channel></rss>