| name | drpy-node-play-debug |
| description | 适用于 drpy-node 源播放链路排查与 lazy 修复。用户提到"播放不通""lazy 不对""play.html 被当直链""iframe 提取""m3u8 提取""parse:0/1 判断""假播放""站外解析""加密链接""webplay""不能播""播放空白""播放器打不开""花屏""解密""防盗链"时使用。专注判断播放是否真实可播;detail 不稳定、整体修源和仓库上传应分流。 |
⚠️ 已归档(2026-07-17):本 skill 已被 drpy-node-coder 取代。coder 融合了 4 个旧 skill(workflow/create/play-debug/repo-upload)的全部工作流,并自带 scripts/cli.js CLI 替代 drpy-node-mcp 服务——一个 skill、无需安装 MCP。本文件保留仅供历史参考,新工作请直接用 drpy-node-coder。
drpy-node Play Debug
快速索引
| 播放表现 | 优先判断 | 首选动作 |
|---|
detail 没有 vod_play_url | 不是播放问题 | 交回 workflow 修二级 |
parse:0 返回 /play/*.html | 假直链 | 改 parse:1 或提取 iframe/m3u8 |
player_* url 乱码 | encrypt/base64/sign | 先解密再判直链/解析 |
| 浏览器可播但静态 HTML 无 m3u8 | JS 运行时生成 | Playwright network 兜底 |
| 多线路部分可播 | 按 flag/input 分派 | 不要一刀切 lazy |
执行契约
- 输入:source_name、detail ids、play_url、flag、当前 lazy 返回值或错误表现。
- 输出:播放类型判断、lazy 修复方案、play 接口验证结果。
- 停手:detail 不稳定、一级/搜索同时异常、上传/评估整源诉求出现时交回 workflow。
模式闸门:先判断是否允许写入
| 用户模式 | 允许动作 | 禁止动作 |
|---|
| 只读 / 规划 / dry-run / 不要改文件 | 读取、诊断、判断 lazy 类型、给修复方案和复测计划 | drpy_edit_file、重写 lazy、仓库上传/改标签 |
| 需要确认后再改 | 读取、诊断、输出播放诊断确认 | 未确认前禁止改 lazy |
| 明确要求执行 | 按同一 play_url + flag 修改并复测 | detail 不稳定时不得深挖 lazy |
| 自主全流程 | detail 稳定时自动做低风险 lazy 修复、复测并回传 autonomous_next | 改 detail、强绕登录/验证码、高风险多分支重写 |
如果用户说“只判断 / 不要修改 / dry-run”,本 skill 只能输出播放诊断确认、lazy 修改建议和验证计划。
自主全流程调用约定
当 workflow/source-create 为“修到100 / 满分后上传 / 不要中途问我”调用本 skill,且已传入真实 source_name/ids/play_url/flag 并确认 detail 稳定时,本 skill 可自动执行低风险播放修复:
- 记录修改前 detail 摘要和 play 返回。
- 判断返回类型:直链 / 站外解析 / 播放页 / 特殊协议 / 假通过。
- 只修改 lazy 相关最小逻辑,不顺带改一级、二级、搜索。
- 用同一
play_url + flag 复测。
- 返回
blocker_type 和 autonomous_next 给 workflow。
必须停手的 blocker:
| blocker_type | 判定 | autonomous_next |
|---|
detail_unstable | detail 没有稳定 vod_play_from/vod_play_url | return_workflow |
hard_anti_bot | 验证码、强 headless 检测、DRM/WASM | stop_for_user |
missing_credentials | 播放需要 Cookie/Authorization/登录 | stop_for_user |
high_risk_change | 需要重写多条线路、删除模板 lazy、影响所有 flag | stop_for_user |
none | 低风险修复已完成并复测 | continue_evaluate |
Return Packet 必含:
source_name: ...
ids: ...
flag: ...
play_url: ...
修改前 play: {parse, jx, url}
修改后 play: {parse, jx, url} / 未修改
结论: 真实可播 / 假通过 / 需浏览器确认
blocker_type: none / detail_unstable / hard_anti_bot / missing_credentials / high_risk_change
autonomous_next: continue_evaluate / retry_lazy / return_workflow / stop_for_user
|---|---|
| common_lazy / def_lazy / cj_lazy | references/references-play-lazy-summary.md |
| encrypt/base64/player_* 判断 | references/references-play-lazy-summary.md |
| playParseAfter / parse/jx 后处理 | references/references-play-lazy-summary.md |
| 多线路 / 特殊协议 | references/references-play-lazy-summary.md |
调度优先级
当本地环境已安装本 Skill 时:
- 本 Skill 优先级 高于 drpy-node MCP 的通用调试 prompt
- 播放链排障必须优先按本 Skill 的判断顺序执行
30 秒速查决策树
当用户说"播放不通 / lazy 不对 / play.html 被当直链"时:
Step 1:检查前提
- 一级/二级/搜索也同时异常?→ 交回
drpy-node-source-workflow
- detail 还没稳定产出
vod_play_from/vod_play_url?→ 先修二级,不要判 lazy
工具调用(MCP 真实字段示例):
test_spider_interface(source_name='源名', interface='detail', ids='一级返回的真实 vod_id')
test_spider_interface(source_name='源名', interface='play', play_url='二级返回的真实播放地址', flag='线路名')
Step 2:判断当前 lazy 更接近哪类模板默认
| 类型 | 特征 | 参考 |
|---|
| common_lazy | 播放页有 player_* JSON,可能有 encrypt | references-play-lazy-summary.md §1 |
| def_lazy | 站点播放页交给解析系统,parse:1 | references-play-lazy-summary.md §2 |
| cj_lazy | 采集站/解析接口,有 parse_url | references-play-lazy-summary.md §3 |
Step 3:判断返回类型
| 类型 | 特征 | 正确返回 | 验真要点 |
|---|
| 直链媒体 | m3u8/mp4/m4a/mp3 | {parse:0, jx:0, url} | URL 应可请求且 content-type/内容像媒体或 m3u8 |
| 站外解析链接 | 外部解析器地址 | {parse:0, jx:1, url} | 不能把普通网页误当直链 |
| 网站播放页 | 当前站播放页 URL | {parse:1, url:input} | parse:1 是嗅探意图,不是失败本身 |
URL 类型快速判定表
| URL 特征 | 类型 | 正确返回格式 |
|---|
以 .m3u8 / .mp4 / .mp3 / .m4a 结尾 | 直链媒体 | {parse:0, jx:0, url} 或直接返回 URL 字符串 |
跨域域名,含 jx / 解析 / play / player / url= | 站外解析链接 | {parse:0, jx:1, url} |
本域名且路径含 /play/ / /vodplay/ | 网站播放页 | {parse:1, url:input} |
🛑 检查点:确认诊断后再修复
在编写或修改 lazy 前,先确认:
- detail 是否稳定产出
vod_play_from/vod_play_url
- 当前返回类型是直链/站外解析/播放页中的哪一种
- 当前是否属于假通过
- 预期应该返回什么类型
确认模板:
## 播放诊断确认
- detail 状态:稳定 / 不稳定
- 当前 play 返回:...
- 类型判断:直链 / 站外解析 / 网站播放页 / 特殊协议
- 假通过风险:有 / 无
- 拟修复 lazy:...
- 验证计划:test_spider_interface(play)
确认后再进入修复;自主全流程模式下,若 detail 稳定且属于低风险 lazy 修复,此处只记录诊断并继续执行。
lazy 编辑验证清单
修改 lazy 前后必须保留证据:
- 修改前:记录
detail 输出里的 vod_play_from/vod_play_url。
- 修改前:记录
play 当前返回值和假通过判定。
- 修改时:只改 lazy 相关逻辑,不顺带重写一级/二级。
- 修改后:重新测同一个
play_url + flag。
- 多线路:至少测一条直链线路和一条解析/网页线路。
🛑 检查点:确认修复结果
在闭环报告前,必须核对修改前后的 play 返回值:
## 修改验证确认
- 修改前 play 返回:{parse, jx, url}
- 修改后 play 返回:{parse, jx, url}
- 是否真实可播:是 / 否 / 需要浏览器确认
- 是否覆盖多线路:已测线路数 / 总线路数
- 是否回传 workflow:需要 / 不需要
同一 play_url + flag 修复后必须复测通过,多线路源至少每条线路测一条。未验证的修改不闭环。
强提醒
parse:1 + 播放页链接 不一定是错,可能是 def_lazy 的合理输出
- 拿到 http 链接 ≠ 拿到直链,先判断是不是 m3u8/mp4
- 遇到 player_ 配置优先查 encrypt*,很多站不是没链接而是没解密
假通过识别
test_spider_interface(play) 返回 success 不等于真实可播,必须继续看返回内容:
| 假通过表现 | 判定 | 下一步 |
|---|
parse:0 但 url 是 /play/...html、API 地址或普通 HTML 页 | 播放页/API 被误当直链 | 查 isVideo/sniffer,改为 parse:1 或提取真实 m3u8/iframe |
parse:0 但 url 不是 http/特殊协议/媒体后缀 | 加密或拼接错误 | 查 encrypt/base64/sign |
jx:1 但 url 是本站详情/播放页 | 站外解析误判 | 保留本站播放页走 parse:1 |
| 返回空 url 或原 input | lazy 没提取到有效目标 | 回到 player_* / iframe / 网络请求排查 |
如果 play 接口 success 但返回 URL 仍是网页/API,必须继续验证扩展名、content-type、响应内容或浏览器网络请求;必要时检查 play_json/play_parse/sniffer/isVideo 是否改变了最终判断。
以下情况必须二次确认后再改:
- lazy 需要重写超过一个分支或影响多条线路。
- 需要从 common_lazy 改为完全自定义 lazy。
- 需要引入 Playwright 抓到的签名 API 或动态 token。
- 需要删除模板继承的 lazy 或 parse_url。
真实可播验证清单
拿到 play 返回值后,按返回类型做验真,不要只看接口 success:
| 返回类型 | 必验项目 | 失败时动作 |
|---|
| m3u8 | 请求状态 200/206;响应或首段内容含 #EXTM3U;跳转后仍是 m3u8 | 补 headers/referer;若是播放页伪装则改 parse:1 |
| mp4/mp3/m4a | 状态 200/206;content-type 或 URL 后缀像媒体;非 HTML 文本 | 若返回 HTML/403,查防盗链或签名时效 |
| 站外解析 | jx:1 且 URL 不是本站详情/播放页;包含目标媒体参数 | 保留为解析,不要误设直链 parse:0 |
| 网站播放页 | parse:1;页面能让嗅探器或浏览器产生媒体请求 | 静态抓不到时走 Playwright 网络兜底 |
| 特殊协议 | pics:// / novel:// / push:// 格式完整 | 转对应内容类型检查,不套视频直链规则 |
如果媒体 URL 依赖 Referer/Cookie/User-Agent,要把 headers 放进返回结构或 lazy 请求链路里验证;不要只复制裸 URL。
参考与进阶
框架 playParseAfter 后处理机制
lazy 返回后,框架调用 playParseAfter() 做最终判断:
parse = SPECIAL_URL.test(playUrl) || /^(push:)/.test(playUrl) ||
/\.(m3u8|mp4|m4a|mp3)/.test(playUrl) ? 0 : 1;
jx = tellIsJx(playUrl);
这意味着:
- 只要 lazy 返回的 URL 以 m3u8/mp4/m4a/mp3 结尾 → 自动设
parse:0
- 即使 lazy 返回
{parse:1, url},但 url 是直链 → 框架会覆盖为 parse:0
tellIsJx(url) 判断站外解析(源码锚点:drpysParser.js 的 playParseAfter() 调用链)→ 自动设 jx:1
所以在 lazy 中返回字符串 "https://example.com/movie.m3u8" 也是安全的——框架会自动处理。
播放相关 rule 字段速查
| 字段 | 典型症状 | 检查重点 |
|---|
play_json | lazy 表面返回与最终 parse/jx/url 不一致 | 用 get_resolved_rule 看继承后的最终字段,再测 play |
play_parse | 是否解析/嗅探的行为不符合预期 | 确认不是用配置掩盖错误直链判断 |
sniffer | 页面 URL 需要嗅探真实媒体请求 | 静态工具找不到时用浏览器网络证据确认 |
isVideo | HTML/API 地址被误判为媒体,或媒体地址未识别 | 对照 URL 后缀、content-type、网络响应内容 |
这些字段会影响 lazy 前后处理,但不能替代真实播放验证;最终仍以 test_spider_interface(play) 加媒体证据为准。
tellIsJx 判断规则(站外解析识别)
框架的 tellIsJx(url) 通过 URL 特征判断是否为站外解析链接:
匹配站外解析的典型特征:
- 域名与当前站点 host 不同(跨域)
- URL 含常见解析关键字: jx, 解析, api, player, parse, open, url
- URL 指向已知解析服务商域名模式
手动判断时可参考此规则,但最终以框架 tellIsJx 正式行为为准。
特殊情况:webplay 线路
部分站点同时提供多条播放线路,其中某些线路是「webplay」需要特殊处理:
线路A: /play/1-1.html (普通播放页)
线路B: /webplay/1-1.html (webplay 模式)
对于 webplay 线路,通常需要在 lazy 中检测 URL 特征并做不同处理:
- 普通线路 → 模板默认 lazy(common_lazy 或 def_lazy)
- webplay 线路 → 可能需要 fetch 页面内容并提取 iframe/m3u8
模板默认 lazy 逻辑
完整代码与边界见 references/references-play-lazy-summary.md;本节只保留执行判断:
| 类型 | 触发 | 正确处理 |
|---|
| common_lazy | 播放页有 player_* JSON | 解 encrypt 后判断直链/站外解析/回退 input |
| def_lazy | 站点播放页交给嗅探 | {parse:1, url:input, js:''} |
| cj_lazy | 采集站或 parse_url | 检查直链、json: 解析接口或拼接 parse_url |
高级排查技术
iframe / m3u8 提取
iframe 提取
用 extract_iframe_src(url) 工具从播放页中提取 iframe 的 src。
m3u8 嗅探
用 fetch_spider_url(url) 请求播放页 URL,在响应中搜索 .m3u8 链接。
Playwright 网络兜底
如果 fetch_spider_url / extract_iframe_src 都找不到 m3u8,但浏览器里能播放,说明媒体 URL 可能由 JS 运行时/XHR 生成:
- 用
browser_navigate(play_url) 打开播放页。
- 用
browser_network_requests(filter='m3u8|mp4|api|play|url') 查看运行时请求。
- 若请求需要点击播放按钮,先
browser_snapshot 找按钮,再点击后重新看网络。
- 从网络请求中提取真实媒体 URL 或签名 API,再回写 lazy。
不要用静态 HTML 抓取失败直接判定站点无直链。
Playwright 失败降级:
如果 Playwright 也无法获取媒体请求(疑似 headless 检测/WASM 解密/DRM):
- 检查是否需要登录或验证码 → 需要则返回
missing_credentials 或 hard_anti_bot,不绕过
- 只允许做不规避保护的环境一致性复核(如使用正常 viewport 重新打开页面);确认存在 headless/验证码/DRM/登录墙后立即停手
- 若无强反爬但最终仍拿不到直链 → 在修复结论中标注“需浏览器确认”,不要强行返回空直链
- 单条线路失败不判定整源播放不可用 → 标为“某线路需浏览器确认”
player_* 配置提取
部分站的播放器配置(含视频地址)不在 HTML 元素属性中,而是嵌入在 <script> 标签的 JSON 对象里:
- 用
pdfa(html, 'script') 列出所有 script 标签内容
- 搜索含
player_ 前缀的变量(如 player_0、player_data、player_js)
- 找到后用
JSON.parse 或正则提取 url 字段
(若 player_* 配置在 HTML 属性中,仍优先用模板默认 common_lazy 处理)
多线路排障诊断流程
当用户报告"多线路部分能播部分不能"时,不要直接改 lazy。先做对比诊断:
Step 1: 分别测试每条线路
test_spider_interface(play, play_url='线路A真实URL', flag='线路A')
test_spider_interface(play, play_url='线路B真实URL', flag='线路B')
记录每条线路的返回 {parse, jx, url}。
Step 2: 对比能播与不能播线路的差异
- flag 名称是否暗示不同处理(如 "直链" vs "解析" / "线路1" vs "webplay")?
- input URL 路径/域名模式有何不同?
- 返回的 url 是什么类型(m3u8/mp4/play.html/跨域解析)?
Step 3: 检查 lazy 来源
get_resolved_rule(path) 确认当前 lazy 是模板继承还是自定义。
如果是模板继承(common_lazy/def_lazy/cj_lazy),先读 references/references-play-lazy-summary.md
确认该模板默认行为是否已覆盖其中一条线路。
Step 4: 判断停手条件
- 能播/不能播的差异在 input URL 模式上 → 本 skill 可修复(见下方"多线路混合处理")
- 不能播是因为 detail 未产出对应线路的 vod_play_url → 交回 workflow 修 detail
- 涉及修改 detail 的线路名/分组/选集生成逻辑 → 立即停手,交回 workflow
工具调用示例(分别测试两条线路):
# 先确认 detail 稳定产出所有线路
test_spider_interface(source_name='源名', interface='detail', ids='一级返回的真实 vod_id')
# 分别测试各线路
test_spider_interface(source_name='源名', interface='play', play_url='线路A的播放地址', flag='线路A')
test_spider_interface(source_name='源名', interface='play', play_url='线路B的播放地址', flag='线路B')
# 如果某线路返回假通过(parse:0 但 url 仍是 play.html),按"假通过识别"处理
🛑 检查点:确认多线路诊断方向
完成 Step 1-4 对比诊断后,向用户确认差异分析结论:
## 多线路诊断确认
- 能播线路:...(flag + 返回类型)
- 不能播线路:...(flag + 返回类型)
- 差异根因:input URL 模式 / detail 未产出 / 线路分组逻辑
- 是否本 skill 修复:是 / 否(转 workflow)
- 拟修改策略:按 flag 分派 / 统一 lazy / 不修改
确认后再进入多线路混合处理或回传 workflow。
多线路混合处理
当一个源有多条线路(如"线路A直链m3u8 + 线路B需要解析"),lazy 不应一刀切。
应根据 input 的 URL 特征分别处理:
lazy: async function () {
let {input} = this;
if (/\.m3u8/.test(input)) return {parse: 0, url: input};
if (/\.mp4/.test(input)) return {parse: 0, url: input};
return {parse: 1, url: input};
}
加密链接处理
部分站的播放链接是加密的(如 base64 编码的 m3u8 地址),需要在 lazy 中先解密:
lazy: async function () {
let {input} = this;
if (!input.startsWith('http')) {
try { input = base64Decode(input); } catch(e) {}
}
if (/\.m3u8/.test(input)) return {parse: 0, url: input};
return {parse: 1, url: input};
}
加密排查子流程(从乱码到解密)
当播放页有 player_* JSON 配置且 url 看起来像乱码时,按下述顺序排查:
1. 找到 player_* JSON → 查找 encrypt 字段的值
2. 如果 encrypt: '1' → 对 url 执行 unescape() 解码
3. 如果 encrypt: '2' → 对 url 执行 base64Decode() → unescape() 解码
4. 如果无 encrypt 字段 → url 可能是 base64 编码字符串,尝试 base64Decode()
5. 如果解码后仍是乱码 → 检查是否包含自定义签名(time/sign/key)
6. 验证:解码后的 URL 是否以 .m3u8/.mp4 结尾,或为有效 http 链接
常见加密模式(从真实源中总结)
| 加密方式 | 特征 | 处理 |
|---|
encrypt: '1' | player_* JSON 中 | unescape(url) |
encrypt: '2' | player_* JSON 中 | base64Decode(url) → unescape() |
| URL 非 http 开头 | 如 base64 字符串 | base64Decode(input) |
| 自定义签名 | URL 含 time/sign/key | 需逆向签名算法 |
| 动态解密 WASM | 页面加载 wasm 解密 | 需调用 wasm 或预解密映射 |
边界与合作
特殊协议处理
播放链不仅处理视频,还处理小说、漫画、音乐等内容类型:
comic (漫画) — pics:// 协议
lazy: async function () {
let html = await request(input);
let urls = pdfa(html, '.comic-pages&&img')
.map(it => pdfh(it, 'img&&data-src'));
return { parse: 0, url: 'pics://' + urls.join('&&'), js: '' };
}
novel (小说) — novel:// 协议
lazy: async function () {
let html = await request(content_url);
let json = JSON.parse(html);
let ret = JSON.stringify({ title, content: json.data.content });
return { parse: 0, url: 'novel://' + ret, js: '' };
}
audio (音乐) — 直链 m4a/mp3
lazy: async function () {
let html = await request(input);
let music = html.match(/var\s+music\s*=\s*(\{[\s\S]*?\})/)[1];
music = JSON5.parse(music);
return urljoin(input, music.file + ".m4a");
}
push (投屏) — push:// 协议
return { parse: 0, url: 'push://' + playUrl, js: '' };
与其他 Skill 的边界
本 Skill 负责
- 判断播放结果是否真实可播
- 修复 lazy 逻辑
- 判断 parse:0/1、jx:0/1
- 提取 iframe / m3u8 / 站外解析链接
本 Skill 不负责
- 从零写完整源
- 系统性重建首页/一级/二级规则
- 仓库上传决策
何时交回 workflow
- 一级/二级/搜索也同时异常
- 问题已超出播放链,涉及整体重建
- 用户目标是评估/上传/回滚
Return Packet to workflow
播放专项结束或停手时必须回传:
- source_name、ids、flag、play_url
- 修改前 play 返回
- 修改后 play 返回(如未修改则写“未修改”)
- 结论:真实可播 / 假通过 / 需浏览器确认
- blocker_type:none / detail_unstable / hard_anti_bot / missing_credentials / high_risk_change
- autonomous_next:continue_evaluate / retry_lazy / return_workflow / stop_for_user
- 是否需要整源 evaluate
强制停手检查点
出现以下任一情况时,暂停深挖 lazy,交回 workflow:
- detail 还没稳定产出 vod_play_from/vod_play_url
- 需要修改 detail 的线路名、分组或选集生成逻辑
- 问题已明显超出播放链
- 用户目标已变成"评估整份源是否可用"
最小化原则
本 Skill 默认目标是让播放链路可用,不是把整份源补全。
不要在播放排障时顺带做的
- 补年份/地区/演员/导演
- 美化搜索字段
- 整体源评估
这些属于源整体完善项,应交回 create/workflow。
实战参考
实战排查案例:从 play.html 到正确 lazy
场景
某源 play 测试返回 {"url": "/play/1-1.html", "parse": 0},播放器不工作。
排查步骤
1. 前提检查:test_spider_interface(detail) → detail 通,vod_play_url 有值 ✓
2. lazy 类型:源未写自定义 lazy → 走模板默认(mxpro → common_lazy)
3. 返回类型:url 是 /play/1-1.html → 网站播放页,不是直链
4. 根因诊断:common_lazy 预期提取 player_* JSON 输出直链,
但实际返回了 /play/1-1.html → 说明播放页可能没有 player_* 配置,
或 common_lazy 解析失败
5. 修复方案 A(简单):去掉自定义 lazy,让框架用 def_lazy 兜底
→ 返回 {parse:1, url:input},交由嗅探系统
6. 修复方案 B(直链):用 extract_iframe_src 提取 iframe → m3u8
→ 返回 {parse:0, url: 'https://...m3u8'}
7. 验证:test_spider_interface(play) 确认修复后返回值
关键教训
- 不要见 play.html 就判 lazy 写错 — 先区分是预期行为还是解析失败
- 修复应优先选简单方案 — 嗅探可用就不必强行提取直链
- 始终从 detail 状态开始排查 — detail 不通时排查 lazy 毫无意义
收尾输出模板
## 播放修复结果
- 源:...
- detail 状态:稳定 / 不稳定
- play_url / flag:...
- 根因:假直链 / encrypt / iframe / 站外解析 / 多线路 / 其他
- lazy 改动:...
- 验证结果:
- 修改前 play:...
- 修改后 play:...
- 是否真实可播:是 / 否 / 需要浏览器确认
- 下一步:交回 workflow / 继续播放专项 / 结束
排查顺序总结
1. detail 真通? → No → 交回 workflow
2. lazy 类型判断 → common_lazy / def_lazy / cj_lazy
├─ Step 2: 判断模板默认 lazy 类型
3. 返回类型判断 → 直链 / 站外 / 播放页
├─ Step 3: 用 URL 类型判定表判类型
├─ 🛑 检查点:确认诊断后再修复
4. 假通过识别 → 检查 parse/jx/url 是否真实
5. 加密检查 → encrypt 1/2 → base64Decode/unescape
6. 修复 lazy → 用正确 parse/jx 返回
├─ 编辑 → 验证 → 🛑 确认修复结果
7. 验证 → test_spider_interface(play)
8. 多线路? → 分别测试各线路 → 🛑 确认诊断方向 → 按 flag 分派
9. 闭环 → 收尾输出模板 / 交回 workflow