通用型一次性脚本 / CLI 执行流程 — 可套用模板
把"一次性(oneshot)命令行程序"从入口到退出的通用流程讲清楚。这份文档只讲流程:做了什么、为什么这么设计,不含任何代码。每一段都用本项目的续火花场景对号入座,帮你把抽象流程落到具体例子上。
适用:定时/手动触发、跑完即退、无常驻服务的脚本(发消息、批量处理、拉报表、单跑爬虫)。不适用常驻 Web 服务。
七段骨架总览
[入口] → [准备:参数→配置→把关] → [初始化资源] → [核心循环] → [收尾落盘/通知] → [退出码] → 进程退出
│ │ │ │ │ └─ 信号交 cron/systemd/CI
│ └─ 任何致命错误 → 立即跳收尾,仍保证"落盘+通知+正确退出码"
└─ 每阶段都可能失败;成败哲学决定继续还是止损(见 §7)
[!note] 怎么读这份文档 每一段都先看 [流程] 这段要做什么(纯讲做什么、为什么),再看 ➤ 续火花这么走(同一段流程套到本项目,也是纯白话)。全程没有代码。
通用段 ↔ 本项目流程对照速查:
| 通用段 | 流程上对应(续火花) | 深入看哪篇笔记 |
|---|---|---|
| ① 入口 | 程序的启动壳,只派活不干活 | [[Main]] |
| ② 参数 | 先问清"试跑还是真发、用哪份配置" | [[Main]] |
| ③ 配置把关 | 读登录态+任务清单,启动即校验 | [[Config-Models]] |
| ④ 抢锁 | 确认没有别的任务在跑(防重复) | [[History]] |
| ⑤ 起资源 | 建目录、开日志、打开浏览器登进抖音 | [[Browser]] |
| ⑥ 核心循环 | 挨个好友发消息+去重+成败分级 | [[Main]]/[[Sender]] |
| ⑦ 收尾/退出码 | 写结果、尽力通知、给外部结论 | [[Notifier]] |
① 入口(Entry)—— 极薄,别装业务
[流程] 程序的门口。它不干活,只负责三件事:
- 接住你的命令和参数;
- 把活派给真正的业务部分;
- 把业务部分的结论转成"这趟成没成"的信号(退出码)。
☞ 为什么这么设计?把"接命令"和"干活"分开——想给"干活"部分单独做验证时,不用真的去敲命令行起一次,方便、省事、好测。
➤ 续火花这么走:你发起一次运行 → 进入启动壳 → 启动壳立刻把"发消息"这件正事交给主程序去办。启动壳本人很轻:只负责"接收指令、派活、中途出了情况就换算成'这趟是成功还是失败'"。真正逐条发消息的那些动作,它一概不碰。
② 参数解析(Argument Parsing)—— 先问清楚"这趟怎么跑"
[流程] 程序一起动,做的第一件决策:这趟到底怎么跑?主要确认两件事:
- 是不是只检查、不真动手(试运行)。这是整个设计里最重要的一道安全阀,尤其对象是那种"会真实改动外部世界"的操作——没试运行就放出去,一个失误就可能真的发出去 / 真的删掉 / 真的下单。
- 用哪份配置。不同环境(本地、生产)配置可能不同,需要能指定用哪一份。
这两点定下来,才继续往下。
➤ 续火花这么走:
- 试运行:先只验证"能不能登进抖音、能不能定位到你列的那些好友",绝不把消息真正发出去。你改了好友名单、换了问候语、调了表情后,一定要先这样过一遍,看到"全部通过"再放心正式跑——这是防"改错却直接发给真人"的护身符。
- 指定配置:本地调试用一套配置、正式定时用另一套,靠一个开关切换,不用来回改文件。
③ 配置加载与校验(Config Load + Fail-Fast)—— 提前把关,绝不带病上路
[流程] 动手之前,先把"干活的依据"读进来,并从头到尾审查一遍。依据分两类:
- 运行设定:怎样跑(放哪、密钥、开关……);
- 任务内容:具体干些啥(对象清单、每项的细节)。
审查的原则叫 fail-fast(快失败):只要有一处不对,当场停下来、明明白白告诉你"哪一项为什么不行",绝不带着隐患继续。
☞ 为什么这么严?因为地方不对,后面注定失败——与其浪费资源、甚至误动外部世界,不如在动手前就拦住。这是"边界守把住、破口子不放行"的思维方式。
➤ 续火花这么走:程序去取登录凭证(之前登录存下的身份),再读任务清单(发给谁、每天发啥、间隔多久、要不要防重、出错要不要继续)。审查就做在这里:
- 名单一个好友都没写 → 停;
- 要发的原生表情没配上 → 停;
- 要发的图片文件不存在 → 停。 开浏览器之前就把这些都拦住,这趟的结论就是"配置有问题"。
④ 进程互斥(Single-Instance Lock)—— 确认没有第二个"我"在跑
[流程] 正式动手前,先抢一道"独占牌":当下有没有另一个同样的任务已经在跑了?
- 有 → 自己退出,别去凑热闹(否则两个一起会重复处理同一批对象)。
- 没有 → 牌到手,才放心继续。
☞ 这是防重复的第一道防线。定时任务的重复触发、手动和定时同时发生、双击误启动——全指望它兜住。
➤ 续火花这么走:程序启动时检查"是不是已经有一个发消息的任务在跑":
- 场景:你设了每天定点自动发消息,同时又手动点了一次——两个险些撞上 → 后启动的那个得知"已有在跑"→ 立刻退出,绝不允许两个进程同时对同一批好友发。
- 除此之外还有第二道保险:在 CI 平台上让同一个任务不并发。两层一起,把"重复发送"堵死。
⑤ 初始化资源(Resource Setup)—— 备好工具、签好"用完必还"
[流程] 确认没撞车后,程序开始置办干活的家伙:
- 建输出目录——结果、出问题的现场都放这里;
- 开日志——一路记下发生了什么;
- 若是会话型的操作,就打开那个"要打交道的资源"(浏览器/数据库/网络),并且从现在起就把"用完一定归还"的约定锁死:不管中途成没成,结束后都老老实实把资源按序还回去、关干净,绝不留下没关完的后台进程。
☞ 为什么"用完必还"这么郑重?因为资源(尤其浏览器、数据库连接)一旦泄漏,轻则占着不还,重则在后台越攒越多、拖垮机器。用"约定式归还"把它变成默认行为,而不是靠谁记得去关。
➤ 续火花这么走:程序建好输出目录、开好日志,然后打开一个真实浏览器,用登录状态登进抖音——这是整条流程里**唯一"碰得到抖音"**的东西。并且从一开始就用"用完必还"管住它:进去时一步一步启动浏览器、建页面、挂状态;无论这趟成没成,结束时都按序把页面、浏览器、驱动一一关干净,绝不在后台留个空浏览器进程。顺带把"去重记录簿"载入内存(等下判断"今天发过没"要用),并算好"今天"是哪天。
⑥ 核心循环(Main Loop)—— 挨个对象,干正事
[流程] 这是真正干活的地方,核心思路是"拆分 → 逐个跑 → 每个都记结局":
- 拆:把一件事拆成一个个独立的小单元。
- 逐个跑:每个单元都走"准备 → 动手 → 确认"的小循环。
- 记结局:每个单元跑完,记成一个明确的"结果"(成功/失败/跳过/未知),攒成一张清单,供收尾用。
另有两条贯穿的重要规则:
- 防重(幂等):给每个单元一个稳定编号,并持久化记"处理过没"。已处理过的,下次直接跳过,绝不重复做。
- 成败分级:
- 大问题(凭据、安全这类全局性坏了)→ 判定所有单元都做不成了 → 立即刹车,后面的都不做;
- 小问题(单个单元出错)→ 记一笔,默认继续下一个。
- 节流:对象之间随机歇几秒,让整趟节奏像"人"而不是"机器"。
➤ 续火花这么走:一位好友 = 一个单元。对每一位好友走:找到他(搜索→点开聊天)→ 把他今天该发的每条消息发出去 → 确认真的收到。
- 稳定编号:好友名 + 今天的日期 + 那条消息内容算出的代号,并成一个唯一键,用来认"这一条"。
- 防重:开防重后,发"某位好友的某条消息"前先查——今天发过没? 发过就跳过;没发过,先占个位 → 复查登录 → 真正发 → 发完记"已发"。
- ☞ 关键拿捏:如果发完后没法确认到底发没发出去,程序绝不再发一次——因为它怕"其实发了、只是没看到,再发就重复"。它宁可把这条记成"状态未知",下次当"已处理"跳过。目的:不留任何重复的可能。
- 节流:两位好友之间随机停几秒,让整趟动作像真人手点,不易被系统判成机器行为。
- 成败分级:登录失效/风控这类全局问题 → 直接刹车、今天剩下的全不发了;单个好友找不到/发不出 → 记失败,默认继续下一位(可切换成"严格模式:一个失败就全停")。
⑦ 收尾与退出码(Finalize + Exit Code)—— 无论成败都给个交代
[流程] 不管这趟全成功、有失败、还是中途爆了,程序都要收尾,做三件事:
- 把结果落成一个文件——谁成功、谁失败、失败原因,机器可读,供排查和自动化用;
- 尽力通知——往指定渠道(邮件/IM/webhook)报个信:成了几个、败几个、为啥。通知是尽力而为:就算没发出去,也不掩盖任务本身的结论;
- 给外部一个明确结论(退出码)——让定时调度器 / CI 能据此判断要不要告警、要不要重试。
➤ 续火花这么走:
- 结果文件:记下这次是不是试运行、什么时候跑完、每位好友的成败与发出条数。
- 通知(钉钉):列成功名单、失败名单、失败原因、失败截图、CI 运行链接;通知发不出去,也不影响任务本身成败。
- 结论(退出码),正好有四种典型结局:
| 结论 | 续火花里代表什么 |
|---|---|
| 成功 | 所有好友都发好了 |
| 有失败 | 某些好友失败,但不致命 |
| 致命 | 登录失效 / 风控 / 配置错 / 已锁定 |
| 取消 | 你手动中断了任务 |
外部就靠这四个结论决定"要不要报警、要不要重跑、要不要人工看看"。
这七段 "为什么这么设计"(一句话版)
| 阶段的动作 | 那句"流程大白话" |
|---|---|
| 入口 | 启动壳只派活,不掺和干活,方便单独验证干活部分 |
| 参数 | 大风险操作必须先有"试运行"这道安全阀 |
| 配置把关 | 不行立刻停,绝不带病上路 |
| 抢锁 | 确认没人抢,免得同一批做两遍 |
| 起资源 | 工具备好、资源打开且约好"用完必还" |
| 核心循环 | 挨个做、每个记结局;小事记账继续、大事刹车 |
| 收尾/退出码 | 成败都交代结果、尽力通知、给外部明确结论 |
贯穿全程的三个通用特点
- 稳:每步等"该出现的真出现"再动手,不抢跑。
- 不重复:抢锁 + 去重记录 + "没确认就不重做",三层防重复。
- 有现场:任何一步出问题,都留下"日志 / 记录 / 现场",能快速定位挂在哪。
写你自己脚本时的核对清单
- 入口只做:接命令 → 派活 → 异常转结论
- 大风险操作带 试运行 开关
- 配置先解析成结构、动手前 快失败校验
- 有 单实例锁(或说明为何不需要)
- 运行设定 / 任务内容分开管理
- 核心循环:逐单元 + 结果分类 + 成败分级
- 单元有稳定编号,支持防重 / 幂等
- 打外部资源时,单元间节流 / 随机间隔
- 收尾无论成败都写结果 + 尽力通知
- 通知文本做转义 + 长度裁剪
- 退出码(成功/失败/致命/取消…)约定清晰并文档化
- 资源用"用完必还"包住,留收尾兜底
续火花业务流程(完整版)
到这里为止是通用骨架;这一整章是把它落到本项目——一次"续火花"任务从头到尾到底怎么走,全程白话、零代码。可以当作整篇文档的"活例子"来读:上面七个阶段的理解,都可以在这条业务流里看到影子。
一张图看懂
发起
│
▼
① 入口:决定这趟怎么跑
② 准备配置:读设定 + 读任务清单,提前把关
③ 抢锁:确认没有别的任务在跑
④ 起资源:建目录、开日志、打开浏览器登进抖音
⑤ 进私信主页 & 验明正身(掉线/风控→停)
⑥ 逐位好友处理(找到人→发消息→确认;小事记账、大事刹车)
⑦ 收尾:写结果文件 + 尽力通知
⑧ 退出:关浏览器,给外部一个"成/败/大错/取消"的结论
① 入口 — 决定这趟怎么跑
程序被启动,第一件事是搞清楚"这趟的规矩":
- 是不是只检查不真发(试运行)?试运行时,只验证"能不能登进去、能不能找到好友",绝不真正发消息——这是改动后做安全验证用的。
- 用哪份配置?允许指定不同的配置来源(本地/生产换着用)。
一句话:先告诉我试跑还是真发、用哪份配置,我再继续。
→ 对应通用骨架的 ①入口 + ②参数。
② 准备配置 — 提前把关
程序取两样东西:
- 登录凭证:之前登录存下的身份,让自己能进抖音。
- 任务清单:发给谁、每天发什么、间隔多久、要不要防重、出错是否继续。
关键 = 提前把关。 只要清单里有一处不对——比如要发的表情没配上、要发的图片文件不存在——程序当场停,根本不会去打开浏览器。因为它清楚:配置错了后面必失败,早停省时省力、还不白碰账号。
一句话:先检查配置,不行立刻停,绝不带病上路。
→ 对应通用骨架的 ③配置把关。
③ 抢锁 — 确认只有我一个在跑
程序要确认:现在是不是已经有一个同样的任务在跑?
- 有 → 两个并发会重复发送 → 直接退出,不掺和。
- 没有 → 拿到独占权,才往下。
防重是这条流反复出现的主题:抢锁只是第一道防线,后面发消息阶段还有去重记录这道,双保险都为"不重复"。
一句话:确认没人抢,我才动手,免得同一批消息发两遍。
→ 对应通用骨架的 ④抢锁。
④ 起资源 — 建目录、开日志、打开浏览器
程序把"干活的家伙"都备好:
- 建输出目录——结果、出问题的现场都放这;
- 开日志——记这一趟发生了什么;
- 打开一个真实浏览器,用登录状态登进抖音——这是全程里**唯一"碰得到抖音"**的东西。
这时还没发任何消息,只是"工具就绪、人已到门口"。用"用完必还"管着浏览器:结束了一定按序关干净,不留后台进程。
一句话:目录和日志就绪,浏览器打开,我站到了抖音门口。
→ 对应通用骨架的 ⑤起资源。
⑤ 进私信主页 & 验明正身
浏览器打开抖音私信主页后,先做安全检查,回答三个问题:
- 页面是不是在催"重新登录"?
- 是不是在要"安全验证"(风控)?
- 有没有真的进到私信界面,而不是停在别的页?
只要任一异常 → 拍下现场、直接停,今天就不发。 逻辑很硬:账号状态不对时硬来,轻则发不出,重则触发更多风控、伤号。明知状态不对,就不硬上。
一句话:我确认自己还正常登着、没被风控,才敢往下一步走。
→ 对应通用骨架 ⑤ 前半的"验明正身"。
⑥ 逐位好友处理 — 核心中的核心
这才是真正"续火花"的地方。对清单里每一位好友,一轮一轮做四件事:
6a 找到这位好友
搜索框输入名字 → 在结果里点开这位朋友的聊天窗口。
这是整条流最脆弱的一步——抖音前端经常改版。所以程序准备了好几套不同的找法:这套找不到换一套,总要把它找出来。实在找不到,拍下现场、记为失败、告诉你。
6b 发消息
对该好友今天该发的每条消息(可能一句问候 + 一个表情):
- 先复核一次登录状态没掉(防这轮处理太久中途出意外);
- 查去重:这条今天是不是已经发过(可能是上次没跑完留下的标记)→ 发过就跳过,不重复;
- 真正发出去;
- 发完确认:回去看聊天记录里这条是不是真出现了。
☞ 这里有个必须讲清楚的"确认拿捏":
- 确认发出了 → 记下"今天这条已发",安心。
- 没确认到 → 绝不重发。因为万一其实发出去了、只是没看到,重发不就成了重复?所以宁可"这条算了、不发了",记成"状态未知"。下次再跑,这个"未知"会被当成"已处理过"跳过——为的就是不留任何重复的可能。
6c 记成败
- 正常发出 → 记"成功",数几条;
- 这位好友操作出小问题(找不到、发不出)→ 记"失败 + 原因",不耽误下一个;
- 但登录失效、风控这种全局性问题 → 所有好友都发不成了 → 整个流程立刻刹车,后面的都不处理。
一句话区分: 小事"记一笔、继续";大事"刹车、全停"。
6d 节奏控制
处理完一个好友,随机停几秒钟再处理下一个——让整趟看起来像一个人在慢慢发,而不是机器狂点,降低被系统盯上的风险。
一句话:挨个找人、把今天的话发出去并确认收到;小事记一笔不误下一个,大事直接刹车;中间还故意歇口气、装得像真人。
→ 对应通用骨架的 ⑥核心循环。
⑦ 收尾 — 无论成没成都会做
不管这趟全成功、有失败、还是中途爆了,程序都要收摊:
- 把结果写成一个文件:谁成功、谁失败、每个失败的原因;
- 配了通知(如钉钉)就发条消息给你:"成功 x 个,失败 x 个" + 失败原因。
通知是**"尽力而为"**:就算没发出去,也不影响这趟任务本身成没成。更像"事后递个话",递不到就算了。
一句话:跑完或中途停,我都把结果记好、尽力告诉你一声。
→ 对应通用骨架的 ⑦收尾/通知。
⑧ 退出 & 交结论
程序关掉浏览器,然后给外部一个明确的信号——这趟到底成没成:
| 信号 | 这意味着 | 外部会怎么反应 |
|---|---|---|
| 成功 | 全部好友都发好 | 视为正常,不需处理 |
| 有失败 | 有好友失败,但不致命 | 可告警,可人工看 |
| 大错 | 登录失效 / 风控 / 配置错 / 撞锁 | 重点排查,通知负责人 |
| 取消 | 你手动按了中断 | 看情况决定要不要重跑 |
这个信号交给定时调度器(定时任务、CI)——它据此判断要不要报警、要不要重试。
一句话:关掉浏览器,给外部一个"成/败/大错/取消"的明确结论。
→ 对应通用骨架的 ⑦退出码。
一条线串起续火花全程
发起 → 说清试跑还是真发 → 读配置并提前把关 → 抢锁防重 → 起目录开浏览器 → 进私信页验明正身 → 挨个好友:找到人 → 发消息 → 确认收到,小事记一笔大事刹车,中间随机歇口气 → 写结果 + 尽力通知 → 关浏览器,交给外部一个"成/败/大错/取消"的结论。
续火花的三个特点
- 稳:每一步等"该出现的东西真的出现"再动手,不瞎等、不抢跑;
- 不重复:抢锁 + 去重记录 + "没确认就不重发"三层,拼命避免发两遍;
- 有现场:任何一步出问题,都留下"截图 / 日志 / 记录",让你能快速定位掉在哪。
续火花明细(每个阶段看哪篇笔记)
| 阶段 | 深入主题 | 笔记 |
|---|---|---|
| 入口/编排 | 主程序如何串起七段 | [[Main]] |
| 配置把关 | Settings/TaskConfig | [[Config-Models]] |
| 抢锁/去重 | run.lock + 防重记录 | [[History]] |
| 开浏览器/验明正身 | 登录态与风控检测 | [[Browser]] |
| 找到好友 | 多套找法兜底 | [[DouyinChat]] |
| 发消息/确认 | 发送与"没确认就不重发" | [[Sender]] |
| 通知 | 钉钉 markdown | [[Notifier]] |
通用骨架见本文档上方各节;续火花完整流程也可直接看 [[Request-Flow]]。使用说明见
README.md。