用 Python 把考勤提醒做成“静默守护”:一次小工具的工程化复盘
一、需求:把"每天查一次"变成"只在异常时打扰"
每天早上出门前,我都会打开考勤系统确认一句"今天打了没"。这件事本身只要 30 秒,但它每天都要发生,而且经常是在最赶时间的时刻。于是我给自己定了一个目标:把它做成一个只在出问题时才说话的守护进程。
三条硬约束先立好:
- 只读:只查询状态和发提醒,绝不代替本人打卡;
- 不落凭据:密码不写进代码、不进 Git、不进聊天记录;
- 失败要能自愈:能自己恢复的故障不许打扰人,恢复不了的必须说清楚原因。
二、整体架构:四层就够
拆开看,这类小工具其实就是四层,每层只做一件事:
- 认证层:通过统一身份认证(SSO)拿到业务系统会话;
- 数据层:调用查询接口取"今日打卡状态";
- 调度层:定时任务,每天固定时间触发;
- 告警层:成功保持静默,失败输出可读原因。
听起来简单,真正花时间的都在"认证层"——也就是会话怎么活下来。
三、关键实现
1. 会话其实有两层,别混为一谈
踩过坑才明白:登录后拿到的不是一个 cookie,而是两类凭据。
- 业务会话:业务域自己的会话 cookie,通常几十分钟到一两小时就过期;
- SSO 长期票:认证域签发的票据(例如 TGT 类 cookie),寿命长得多。
业务会话过期时,不需要重新登录:拿 SSO 票再走一次服务跳转,就能静默换出一张新的业务会话。这一步做对之后,工具从"每天要人工续一次"变成"几周不用管"。
业务会话失效
→ GET 业务域 /index/islogin(自动 302 到认证域)
→ 认证域用 SSO 票签发新 ticket
→ 302 回业务域 ?ticket=ST-xxx,下发新会话 cookie
→ 校验接口返回 200 即续期成功
2. 失败必须分类,不要只报一句"失败了"
同一句"请求失败",背后可能是完全不同的原因。不做分类,排查会浪费大量时间:
302 → /common/404.html:常见于会话失效,或 UA 与 cookie 不匹配(WAF 会把两者绑定);403 not login:请求到了业务接口,但会话没被认出来;401 / "登录错误":凭据本身不对;427:一次性票据已被消费,必须重新获取。
把这几类写进错误信息里,告警才有价值——否则人只会看到"又失败了"。
3. 告警去重:同一故障 6 小时只提醒一次
守护进程最大的反面教材,是每 30 分钟发一条"失败了",最后被当成垃圾消息忽略。做法很简单:把"最近一次状态 + 时间戳"写进一个状态文件,同一故障在窗口期内只推送一次;从失败恢复时再补一条"已恢复"。
4. 静默即成功
调度层用"脚本模式"最省资源:输出为空 = 不推送,非零退出 = 告警。于是"一切正常"就真的等于没有消息,而不是每天一条"今日正常"的噪音。
四、踩过的三个坑
- UA 与 cookie 绑定:换了 cookie 却没换 User-Agent,直接 302 到 404 页面。两者必须成对更新。
- 扫码登录的状态机:
new → authorizing → authorized → 427。到了authorized必须当拍用返回的 redirect_url 去兑换,慢一步票据就作废。 - 跨域票据交换少一步:只完成业务页登录还不够,数据接口所在域要再走一次
/index/islogin?ticket=,否则接口一直 404 或 not login。
五、安全边界比功能更重要
- 凭据只放在本机配置文件(权限 600),不进代码仓库、不进对话;
- 工具只做"查"和"提醒",不代打卡——考勤是本人责任,自动化不能越界;
- 账号仅限本人使用,不共享、不外借;
- 失败要留痕:日志里能看出是哪一层出的问题,而不是一个笼统的异常。
六、效果
现在的状态是:每天早上固定时间跑一次,正常时零消息;会话过期时先静默续期再重试,仍然失败才发一条带原因和修复命令的告警。连续运行下来,人工干预次数从"每天一次"降到"几周一次"。
这类小工具的价值不在于技术多复杂,而在于把一次确定性的重复劳动,变成一个不会遗忘、也不会吵闹的后台任务。定时任务放在一台常驻的 Linux 主机上(例如华为云 ECS)就够了,成本极低,收益是每天早上的那 30 秒。
- 点赞
- 收藏
- 关注作者
评论(0)