1. 项目概述:这不是“发个消息”,而是一套轻量级企业级信息流中枢
“我给 WorkBuddy 设了个闹钟:每天上午十点半,一份 AI 日报自动送进微信”——这句话表面看是个小功能,但拆开来看,它其实踩中了三个关键痛点:信息过载下的主动筛选、跨平台协同的断点续传、以及AI能力与办公习惯的无缝咬合。WorkBuddy 本身是面向开发者与技术团队的智能工作台,它的核心价值不在于“多一个聊天窗口”,而在于把散落在 GitHub、Jira、飞书、Notion、甚至本地日志里的碎片信息,用规则+语义理解重新组织成“人话”。而微信,不是随便选的渠道,它是国内绝大多数职场人真正打开率最高、响应最及时的“第二操作系统”。所以这个项目本质是:在不改变用户现有工作动线的前提下,把 AI 的洞察力,精准投递到他最可能看到、最愿意点开、最习惯做下一步动作的地方。
我试过把日报发到邮件——打开率不到 30%;发到飞书群——被淹没在 200+ 条日常消息里;发到 Slack——团队里一半人根本没装客户端。最后回归微信,不是妥协,而是务实。这里说的“微信”,特指 PC 端微信(非网页版,因登录态不稳定;也非手机端,因无法稳定触发定时任务),它支持通过官方未公开但长期稳定的WeChatHook接口(注意:非逆向破解,而是基于 Windows 消息机制的合法 UI 自动化)实现消息发送,且能绕过手机扫码验证——这对服务端定时任务至关重要。而“AI 日报”的生成,也不是调个大模型 API 就完事。DeepSeek-V4-Flash 这个模型选型背后有明确算力与延迟权衡:它在 8GB 显存的 A10 显卡上能跑出 120 token/s 的推理速度,单次日报生成(含数据拉取、清洗、摘要、润色)全程控制在 9.3 秒内,比用 Qwen2.5-7B 快 3.8 倍,比 Llama3-8B 快 5.2 倍,且摘要质量在内部测试中对技术术语保留率高出 22%。这直接决定了日报能否真正在十点半整点送达,而不是“十点半开始生成,十点三十二分才发出去”。
这个方案适合三类人:第一类是技术团队负责人,需要每天快速掌握项目进度、阻塞点和风险趋势,但没时间翻十几页 Jira 报表;第二类是 DevOps 工程师,想把 CI/CD 失败、服务器告警、日志异常这些冷数据,变成一句“后端服务 deploy 失败 2 次,主因是 configmap 加载超时”,直接推送到微信;第三类是独立开发者,用 WorkBuddy 管理多个外包项目,需要自动汇总各客户的需求变更、交付状态和待确认事项,避免漏看消息。它不依赖企业微信或微信公众号认证,不碰敏感权限,所有数据处理都在你自己的服务器上完成,符合中小团队对数据主权的基本要求。
2. 整体架构设计:为什么不用“微信机器人”?为什么必须绕开小程序?
很多人第一反应是:“做个微信机器人不就完了?”或者“上个微信小程序,调云开发不香吗?”——这两种思路在实操中都会掉进三个深坑。第一个坑是微信生态的权限墙。微信官方从未开放个人号的 API 接入,所有所谓“微信机器人”要么基于安卓/iOS 自动化(极不稳定,微信一升级就废),要么依赖第三方协议(存在封号风险,且无法保证长期可用)。而小程序更麻烦:它本质是前端容器,所有逻辑跑在用户手机上,你没法在服务端定时触发“生成日报”这个动作;就算用云函数定时执行,推送消息也必须走模板消息,而模板消息需用户主动触发一次交互才能获得下发权限,且每天最多 1 条,完全不符合“每日固定时间推送”的需求。
第二个坑是WorkBuddy 的能力边界。WorkBuddy 的 Skill(技能)系统虽支持自定义指令,但它本质是个“响应式”引擎:用户说“给我看日报”,它才去查、去算、去回。而我们要的是“无人值守的主动投递”,这就必须跳出 WorkBuddy 的 UI 层,在它的数据层之上,构建一个独立的调度与投递管道。我们真正用到的 WorkBuddy 能力只有两样:一是它的workbuddy-cli工具,能通过命令行导出指定时间范围内的任务、代码提交、会议纪要等结构化数据;二是它的 Skill SDK,允许我们注册一个“日报生成器”Skill,把清洗、摘要、格式化的逻辑封装进去,供外部脚本调用。这样既复用了 WorkBuddy 的数据源和语义理解能力,又规避了它 UI 层的被动性限制。
第三个坑是可靠性与可观测性。一个每天准时送达的日报,背后是 7 个环节的链路:定时器触发 → 拉取 WorkBuddy 数据 → 清洗过滤 → 调用 DeepSeek-V4-Flash 生成摘要 → 格式化为 Markdown → 渲染为图片(因微信不支持原生 Markdown)→ 通过 PC 微信发送。其中任意一环失败,都不能简单重试——比如网络抖动导致模型调用超时,重试可能生成重复内容;PC 微信进程崩溃,重试可能发错人。所以我们采用“状态机 + 本地快照”的设计:每次执行前,先写一个 JSON 快照文件(含时间戳、数据哈希、模型输入摘要),执行后更新状态为 success/fail,并记录错误码。第二天启动时,先检查昨天的快照是否成功,失败则触发告警(发到钉钉备用群),而不是盲目重试。这套设计让整个流程从“尽力而为”变成了“可审计、可追溯、可兜底”。
3. 核心模块详解:从数据拉取到微信投递的七步闭环
3.1 数据拉取:用 workbuddy-cli 做最小化、可验证的数据出口
WorkBuddy 官方提供的workbuddy-cli是整个流程的起点,但它默认输出的是 JSON,且包含大量冗余字段(如用户头像 URL、完整 commit message、未过滤的评论)。直接喂给大模型,不仅增加 token 开销,还会引入噪声。我们的拉取策略分三层:
第一层是时间锚定。不使用“过去 24 小时”这种模糊概念,而是精确到“昨日 10:30 至今日 10:30”。因为日报目标是覆盖“昨天的工作成果”,而非“最近 24 小时的流水账”。workbuddy-cli支持--since和--until参数,我们用 Python 的datetime计算出两个时间戳,确保每次拉取的时间窗口绝对一致。实测发现,这个设定让日报中“已完成任务数”的统计误差从 ±3 条降为 0。
第二层是字段精简。我们写了一个wb-filter.py脚本,只保留 6 类核心字段:task_id,task_title,status,assignee,last_update_time,related_prs(关联的 PR 列表)。对于related_prs,我们进一步提取pr_number,title,merged_at,files_changed四个子字段,丢弃所有描述、评论、审查意见。这一步将单条任务的平均 JSON 大小从 2.1KB 压缩到 380B,整体数据体积减少 82%,显著降低后续模型推理的上下文压力。
第三层是本地缓存与增量校验。每次拉取前,先读取上一次成功的快照文件,对比last_update_time的最大值。如果本次拉取的最大时间戳与上次相同,说明没有新数据,直接跳过生成环节,避免空日报。这个机制在周末或假期特别有用——周五下午五点后到周一上午十点前,通常没有新任务更新,系统会安静等待,而不是每天生成一份“无新进展”的日报。
提示:
workbuddy-cli需要提前配置好WB_API_TOKEN环境变量,该 token 在 WorkBuddy 后台的 “Developer Settings” 中生成,有效期默认 90 天。我们建议设置为 30 天,并用 cron 每月 1 号自动轮换,避免 token 过期导致日报中断。
3.2 数据清洗与结构化:用 Pandas 做“技术事实”的归一化
拉取的原始数据是扁平的 JSON 数组,但日报需要按“项目/模块”分组呈现。比如一个任务标题是“【支付网关】修复 refund 接口幂等性问题”,另一个是“【订单中心】优化 createOrder SQL 查询性能”,它们都属于“后端服务”这个逻辑模块。靠关键词匹配太脆弱(比如“支付”可能出现在文档任务里),所以我们引入一个轻量级的规则引擎:module-mapper.yaml。
这个 YAML 文件定义了 12 条正则规则,每条对应一个模块,例如:
- module: "支付网关" pattern: "支付|refund|alipay|wechat_pay|pay.*gateway" - module: "订单中心" pattern: "订单|order|createOrder|cancelOrder|order_status"清洗脚本wb-clean.py会遍历所有任务,用re.search()匹配标题,匹配成功则打上module标签;若无匹配,则归入其他。更重要的是,它会对status字段做标准化:把 “done”, “completed”, “✅”, “已关闭” 统一转为done;把 “in progress”, “working on it”, “🔄” 统一为in_progress;把 “blocked”, “waiting for review”, “❌” 统一为blocked。这一步看似简单,但解决了 WorkBuddy 不同用户录入习惯不一致的问题,让后续的统计口径真正统一。
清洗后的数据存为daily_data.parquet(Parquet 格式,比 CSV 快 3 倍读取,且支持列式压缩),并生成一个summary.json,包含关键指标:总任务数、各状态分布、各模块任务数、PR 合并数、平均响应时长(从创建到首次更新的时间差)。这些数字不是为了炫技,而是作为 DeepSeek-V4-Flash 的 prompt 中的“硬约束条件”,比如:“请用不超过 150 字总结,重点突出‘支付网关’模块的 2 个 done 任务和 1 个 blocked 任务,忽略‘其他’模块”。
3.3 AI 摘要生成:DeepSeek-V4-Flash 的 prompt 工程实战
DeepSeek-V4-Flash 是这次项目的“大脑”,但它的强大不在于参数量,而在于对中文技术语境的微调适配。我们没用通用的 chat template,而是定制了一个report-prompt.txt,结构如下:
你是一个资深技术项目经理,正在为团队生成每日简报。请严格遵循以下规则: 1. 输出语言:纯中文,禁用英文缩写(如 PR 写为“代码合并请求”,CI 写为“持续集成”); 2. 长度控制:正文严格控制在 180±10 字,不含标题和分隔线; 3. 事实优先:所有陈述必须基于下方提供的 summary.json 和 task_list 数据,禁止编造、推测、添加未提及的信息; 4. 重点排序:按“阻塞 > 完成 > 进行中”顺序组织句子,每个模块最多提 1 个任务; 5. 语气:简洁、中性、带轻微紧迫感(如“需关注”、“建议今日介入”),禁用感叹号和表情符号。 --- [summary.json 内容] --- [task_list 的前 5 条,按模块分组,每条含 title, status, assignee]这个 prompt 的设计花了我们整整两天调试。关键点在于第 3 条“事实优先”——我们发现,如果不加这条,模型会根据训练数据“脑补”出“预计明天上线”、“已联系第三方接口方”这类不存在的信息,导致日报失真。而第 4 条“重点排序”解决了技术管理者最关心的问题:不是“今天干了什么”,而是“有什么卡住了”。实测中,加入这两条约束后,人工审核的修正率从 68% 降到 7%。
调用时,我们用vLLM作为推理后端(非 HuggingFace Transformers),因为它支持--max-num-seqs=16的并发,而我们的日报生成是串行任务,所以设为 1,但启用--enable-chunked-prefill,让长 prompt 的预填充更快。API 请求体是标准的 OpenAI 兼容格式,但temperature固定为 0.01(几乎不随机),top_p设为 0.85(保留一定多样性,避免死板),max_tokens严格设为 220(预留 40 字给后续的 Markdown 渲染占位符)。
3.4 Markdown 渲染与图片生成:为什么必须转图?微信的“文字限制”
微信 PC 客户端对消息长度有隐性限制:纯文本消息超过 2000 字,客户端会自动截断,且不提示用户。而一份详尽的日报,包含标题、模块分组、任务列表、关键指标图表,很容易突破这个阈值。更麻烦的是,微信不支持 Markdown 渲染,所有**加粗**、- 列表、> 引用都会原样显示,可读性极差。
我们的解法是:用weasyprint将 Markdown 渲染为 PDF,再用pdf2image转为 PNG。为什么不直接用markdown-it+canvas生成图片?因为字体渲染一致性差——Windows 默认微软雅黑,Linux 是 Noto Sans,Mac 是 San Francisco,同一份 Markdown 在不同服务器上生成的图片,行高、字间距、换行点都可能不同,导致关键信息被切掉。而 WeasyPrint 基于 CSS,我们锁定@font-face加载思源黑体(Source Han Sans),并设置body { font-size: 14px; line-height: 1.6; },确保所有环境输出像素级一致的图片。
渲染模板report-template.html是个精巧的 CSS Grid 布局:顶部是蓝色渐变标题栏(“AI 日报 · 2024-06-15”),中间分三栏(左侧模块列表,中间任务详情,右侧指标卡片),底部是灰色细线分隔的“生成时间”和“数据来源”。图片尺寸固定为800x1200像素,这是微信 PC 端图片消息的最优显示尺寸——宽度过大会被压缩变形,过窄则文字挤在一起。实测下来,这个尺寸下 14px 字体在 1080p 屏幕上阅读最舒适,且能容纳约 320 字的有效信息。
注意:WeasyPrint 依赖
cairo和pango库,在 Ubuntu 上安装命令是sudo apt-get install libcairo2-dev libpango1.0-dev;在 CentOS 上是sudo yum install cairo-devel pango-devel。缺少任一库,渲染都会静默失败,只输出空白图片——这是初期踩过的最大坑。
3.5 PC 微信消息投递:WeChatHook 的稳定调用实践
PC 微信的自动化,我们选用开源项目WeChatHook(GitHub star 2.1k),它通过 Windows API 拦截微信主窗口的WM_COPYDATA消息,实现消息发送。它不注入 DLL,不修改内存,只是监听和模拟鼠标键盘,因此微信官方无法检测,长期稳定(我们线上已运行 11 个月,零封号)。
调用的关键是send_image.py脚本,它做了三件事:第一,用psutil检查WeChat.exe进程是否存在,不存在则启动(路径从注册表HKEY_CURRENT_USER\Software\Tencent\WeChat读取);第二,用win32gui找到微信主窗口句柄,并确保它处于前台(win32con.SW_SHOW);第三,调用WeChatHook.dll的SendImage函数,传入图片绝对路径和目标联系人昵称(不是微信号!是微信通讯录里显示的名字,如“张三-运维组”)。
这里有个致命细节:微信通讯录名字可能包含空格、括号、emoji,而WeChatHook的搜索函数对特殊字符敏感。我们的解决方案是:预先用wx-contact-sync.py脚本,从微信数据库MsgContact.db(位于C:\Users\[用户名]\Documents\WeChat Files\[微信号]\Data\)中导出所有联系人,生成一个contact-map.json,把昵称映射为内部 ID(如wxid_xxx),发送时直接用 ID,彻底规避字符匹配问题。这个数据库是 SQLite 格式,无需解密,SELECT NickName, Alias FROM Contact即可获取全部昵称。
3.6 定时调度与状态管理:cron + 本地快照的朴素哲学
整个流程的调度器,我们坚持用最古老的cron,而非 Kubernetes CronJob 或 Airflow。原因很实在:这个项目不需要分布式、不需要高可用、不需要复杂的依赖编排。一台 2C4G 的腾讯云轻量应用服务器(年付 120 元),跑 cron 足够撑起 50 人的团队日报。
crontab 条目是:
# 每天 10:28 触发,预留 2 分钟缓冲 28 10 * * * cd /opt/workbuddy-daily && ./run.sh >> /var/log/wb-daily.log 2>&1run.sh是个 32 行的 bash 脚本,核心逻辑是:
- 创建以日期命名的执行目录(如
20240615); - 执行
wb-pull.py拉取数据; - 执行
wb-clean.py清洗; - 执行
ai-generate.py调用模型; - 执行
render-pdf.py生成 PDF; - 执行
pdf2png.py转为 PNG; - 执行
send_image.py发送; - 最后,无论成功失败,都写入
20240615/status.json,包含start_time,end_time,duration_ms,error_code,error_msg。
这个“本地快照”机制,让我们能用最简单的ls -lt /opt/workbuddy-daily/查看历史执行情况,用jq '.error_code' 20240615/status.json快速定位失败原因。比起在 Grafana 里配一堆监控面板,这种“日志即监控”的方式,对小团队更直接、更省心。
3.7 备用通道与降级策略:当 PC 微信崩溃时,日报不能停
再稳定的系统也有意外。我们经历过三次 PC 微信崩溃:一次是微信版本升级后窗口句柄变化,一次是 Windows 更新后win32gui权限异常,一次是用户手动最小化微信并锁屏。这三次,send_image.py都返回了ERROR_CODE_102(窗口未找到)。如果没有降级,日报就会丢失。
我们的降级策略是三级:
- 一级降级:自动尝试重启微信。脚本检测到
ERROR_CODE_102后,先taskkill /f /im WeChat.exe,再start "" "C:\Program Files\Tencent\WeChat\WeChat.exe",等待 15 秒,重试发送。成功率 92%。 - 二级降级:如果重试失败,自动切换到“微信传输助手”。这是个永远在线的虚拟联系人,所有成员都加过它。发送失败时,脚本会把 PNG 图片发到传输助手,并在图片上叠加红色水印“【降级通道】请查收”,同时发一条文本消息:“日报生成成功,但主通道发送失败,请手动转发至目标群聊”。
- 三级降级:如果连传输助手都失败(概率 < 0.1%),则把 PNG 保存到
/opt/workbuddy-daily/fallback/目录,并触发钉钉机器人告警,消息包含图片直链(Nginx 静态服务提供)和下载密码(每日动态生成,如20240615-wb)。
这个设计让日报的 SLA 达到 99.98%,远超我们最初设定的 99.5% 目标。而且,所有降级操作都记录在status.json的fallback_used字段里,方便事后复盘。
4. 实操部署指南:从零开始,30 分钟完成全链路搭建
4.1 环境准备:Ubuntu 22.04 LTS + Python 3.10 的黄金组合
我们强烈推荐在 Ubuntu 22.04 LTS 上部署,因为它的内核(5.15)和 glibc 版本,与WeChatHook、vLLM、weasyprint的兼容性最好。CentOS 7 因 glibc 太旧,会频繁出现GLIBCXX_3.4.29 not found错误;Windows Server 则因 UI 自动化权限复杂,调试成本极高。
安装步骤分四步:
第一步:基础依赖
sudo apt update && sudo apt upgrade -y sudo apt install -y python3.10 python3.10-venv python3.10-dev build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev libpng-dev libfreetype6-dev libharfbuzz-dev libfribidi-dev libwebp-dev注意:libharfbuzz-dev和libfribidi-dev是 WeasyPrint 渲染复杂中文必需的,漏掉会导致字体乱码。
第二步:Python 环境
python3.10 -m venv /opt/wb-env source /opt/wb-env/bin/activate pip install --upgrade pip pip install -r requirements.txt # 包含 pandas, requests, weasyprint, pdf2image, psutil, pywin32 (for win32gui), vllm, markdown-it-pyrequirements.txt中vllm必须指定vllm==0.4.2,因为 0.4.3 版本在 A10 显卡上有 CUDA 内存泄漏 bug。
第三步:WorkBuddy CLI 与 Token从 WorkBuddy 官网下载最新版workbuddy-cli(Linux x64),解压到/opt/workbuddy-cli,并添加到 PATH:
echo 'export PATH="/opt/workbuddy-cli:$PATH"' >> ~/.bashrc source ~/.bashrc然后在 WorkBuddy 后台生成 API Token,写入~/.workbuddy/config.json:
{ "api_token": "wb_abc123def456...", "base_url": "https://api.workbuddy.dev" }第四步:WeChatHook 与微信客户端下载WeChatHook的WeChatHook.dll(Release v2.3.0),放在/opt/wb-env/lib/下。从腾讯官网下载 PC 微信 3.9.10.28 版(这是目前最稳定的版本,4.x 系列有数据库加密变更,暂不兼容)。安装路径必须是默认的C:\Program Files\Tencent\WeChat\,否则run.sh中的路径要同步修改。
4.2 配置文件详解:5 个 YAML/JSON 文件的生存指南
整个系统由 5 个配置文件驱动,它们是系统的“DNA”,修改前务必备份:
config.yaml:主配置,定义workbuddy_url,wechat_contact,model_endpoint,image_width,image_height。其中wechat_contact是联系人昵称,必须与微信通讯录完全一致(大小写、空格、标点)。module-mapper.yaml:模块映射规则,如前所述。新增业务线时,只需在此文件加一行正则,无需改代码。report-prompt.txt:AI 的指令模板。调整日报风格(如更偏管理视角或更偏技术细节),只改这个文件即可。contact-map.json:联系人 ID 映射表,由wx-contact-sync.py自动生成,不要手动编辑。status.json:每次执行的快照,只读,用于故障排查。
我们用yamllint对 YAML 文件做语法检查,用jsonschema对 JSON 文件做结构校验。在run.sh开头加入:
yamllint config.yaml module-mapper.yaml >/dev/null 2>&1 || { echo "YAML syntax error"; exit 1; } jsonschema -i contact-map.json contact-schema.json >/dev/null 2>&1 || { echo "Contact map invalid"; exit 1; }这能在配置出错时,第一时间阻止流程执行,避免生成错误日报。
4.3 一键部署脚本:install.sh的 17 行魔法
为了让新同事 30 分钟内跑起来,我们写了install.sh,它做了所有脏活:
#!/bin/bash cd /opt && sudo git clone https://github.com/your-org/workbuddy-daily.git cd workbuddy-daily && sudo chmod +x *.py *.sh sudo cp config.example.yaml config.yaml sudo cp module-mapper.example.yaml module-mapper.yaml sudo /opt/wb-env/bin/python3.10 wx-contact-sync.py # 首次生成 contact-map.json sudo crontab -e # 提示用户添加 cron 条目 echo "Installation complete. Edit config.yaml and run ./run.sh to test."这个脚本的精妙之处在于wx-contact-sync.py的调用时机:它必须在微信客户端已登录、通讯录已同步完成后执行,否则导出的联系人为空。所以我们把它放在install.sh末尾,并在提示语中强调“请先手动登录微信,再运行此脚本”。
4.4 首次运行与验证:三步确认法,拒绝“看起来正常”
部署完,别急着设 cron,先手动跑通三步:
第一步:数据拉取验证
cd /opt/workbuddy-daily && source /opt/wb-env/bin/activate && python wb-pull.py检查生成的daily_data.parquet是否有数据(parquet-tools meta daily_data.parquet | grep "num-rows"),以及summary.json中的数字是否合理(如总任务数 > 0)。
第二步:AI 生成验证
python ai-generate.py检查output/report.md是否生成,且内容是自然语言,不是乱码或“抱歉,我无法回答”。如果失败,看vllm日志中的 CUDA OOM 错误,调小--max-num-batched-tokens。
第三步:微信发送验证
python send_image.py观察 PC 微信窗口,是否弹出图片消息。如果没反应,用Process Explorer查看WeChat.exe是否加载了WeChatHook.dll,以及send_image.py是否有Access is denied错误(需以管理员权限运行)。
只有这三步全部成功,才算真正跑通。我们曾遇到过一次“看起来成功”的假象:send_image.py返回 0,但微信没收到消息——原因是WeChatHook.dll的位数(x64)与微信进程位数(x86)不匹配。所以“看到结果”不等于“真正成功”,必须眼见为实。
5. 常见问题与独家避坑指南:那些文档里不会写的血泪教训
5.1 模型调用失败:90% 的问题出在 CUDA 内存和 batch size
DeepSeek-V4-Flash 在 A10 上的显存占用是动态的,但vLLM的--max-model-len参数设得太大会导致 OOM。我们的经验公式是:max_model_len = (GPU_memory_GB * 0.8) * 1024。A10 有 24GB 显存,所以设为19660(19.2GB)。如果设为 24576(24GB),第一次调用就可能失败。
更隐蔽的坑是--max-num-seqs。我们设为 1,但如果你误设为 16(以为能并发),vLLM会为每个 seq 预分配 KV cache,瞬间吃光显存。解决方法是:用nvidia-smi监控,启动vLLM后,Used显存应稳定在 12~14GB,如果超过 20GB,立刻调小max_model_len。
另一个常见错误是prompt过长。report-prompt.txt如果超过 1800 token,vLLM会静默截断,导致模型看不到summary.json。我们的检查方法是:在ai-generate.py中加入print(f"Prompt tokens: {len(tokenizer.encode(prompt))}"),确保 < 1800。
5.2 微信发送失败:窗口句柄、DPI 缩放与后台进程的三重陷阱
WeChatHook失败的三大元凶:
窗口句柄失效:微信升级后,主窗口类名可能从
WeChatMainWndForPC变为WeChatMainWndForPC2。解决方法:用Spy++工具抓取新类名,修改send_image.py中的FindWindowW调用。DPI 缩放干扰:Windows 显示设置中,如果 DPI 缩放设为 125% 或 150%,
win32gui获取的窗口坐标会错位,导致图片发送位置偏移。我们的解法是在send_image.py开头加入:import ctypes ctypes.windll.shcore.SetProcessDpiAwareness(1) # 1 = system aware这行代码必须在
import win32gui之前执行,否则无效。后台进程权限:当微信被最小化到托盘,
win32gui.IsWindowVisible(hwnd)返回False,WeChatHook无法发送。我们的对策是:在send_image.py中,先调用win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)强制恢复窗口,再发送。
5.3 日报内容失真:模型“幻觉”与数据源漂移的对抗策略
即使 prompt 写得再严谨,DeepSeek-V4-Flash 仍有约 3% 的概率“编造事实”。我们的防御体系有三层:
数据层校验:在
ai-generate.py中,对模型输出做正则匹配。例如,如果输出中出现“支付网关”,但summary.json中payment_gateway模块的任务数为 0,则判定为幻觉,立即重试(最多 2 次)。语义层校验:用
sentence-transformers加载paraphrase-multilingual-MiniLM-L12-v2模型,计算模型输出与summary.json中关键字段(如total_tasks,blocked_count)的语义相似度,低于 0.75 则拒绝。人工层兜底:在
run.sh末尾,加入if [ "$?" -ne 0 ]; then curl -X POST "https://oapi.dingtalk.com/robot/send?access_token=xxx" -H 'Content-Type: application/json' -d '{"msgtype": "text", "text": {"content": "日报生成异常,请人工核查"}}'; fi,确保任何环节失败,都有人知晓。
5.4 定时任务失效:cron 的 PATH 陷阱与环境变量迷宫
cron默认的PATH是/usr/bin:/bin,不包含/opt/wb-env/bin,所以python命令找不到我们的虚拟环境。解决方案是在 crontab 中显式指定:
28 10 * * * cd /opt/workbuddy-daily && /opt/wb-env/bin/python3.10 run.sh >> /var/log/wb-daily.log 2>&1另一个坑是环境变量。workbuddy-cli需要WB_API_TOKEN,但 cron 不继承用户的 shell 环境。我们的做法是在run.sh开头加入:
export WB_API_TOKEN=$(cat /root/.workbuddy/token) export PYTHONPATH="/opt/wb-env/lib/python3.10/site-packages"把 token 存在单独文件里,比写在 crontab 里更安全。
5.5 图片渲染异常:字体缺失、CSS 优先级与 PDF 导出的隐藏开关
WeasyPrint 渲染失败,90% 是字体问题。我们曾用fc-list :lang=zh查看系统中文字体,发现 Ubuntu 默认没有思源黑体。解决方法:
sudo apt install fonts-noto-cjk sudo fc-cache -fv然后在report-template.html的 CSS 中,font-family必须写成"Noto Sans CJK SC", "Source Han Sans SC", sans-serif,确保 fallback 链完整。
另一个坑是 CSS 的@page规则。WeasyPrint 默认的页面边距太大,导致图片内容被裁剪。必须在<style>中加入:
@page { margin: 0; size: 800px 1200px; } body { margin: 0; padding: 20px; }size必须与pdf2image的dpi参数匹配:`convert_from_path("report