资讯动态

基于PC微信自动化的企业级AI日报系统设计与实现

发布时间:2026/9/28 15:43:11 来源:尧图企业网站定制
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.parquetParquet 格式比 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-seqs16的并发而我们的日报生成是串行任务所以设为 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-itcanvas生成图片因为字体渲染一致性差——Windows 默认微软雅黑Linux 是 Noto SansMac 是 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 微信的自动化我们选用开源项目WeChatHookGitHub 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 21run.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必须指定vllm0.4.2因为 0.4.3 版本在 A10 显卡上有 CUDA 内存泄漏 bug。第三步WorkBuddy CLI 与 Token从 WorkBuddy 官网下载最新版workbuddy-cliLinux x64解压到/opt/workbuddy-cli并添加到 PATHecho 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.dllRelease 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.txtAI 的指令模板。调整日报风格如更偏管理视角或更偏技术细节只改这个文件即可。contact-map.json联系人 ID 映射表由wx-contact-sync.py自动生成不要手动编辑。status.json每次执行的快照只读用于故障排查。我们用yamllint对 YAML 文件做语法检查用jsonschema对 JSON 文件做结构校验。在run.sh开头加入yamllint config.yaml module-mapper.yaml /dev/null 21 || { echo YAML syntax error; exit 1; } jsonschema -i contact-map.json contact-schema.json /dev/null 21 || { 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 sizeDeepSeek-V4-Flash 在 A10 上的显存占用是动态的但vLLM的--max-model-len参数设得太大会导致 OOM。我们的经验公式是max_model_len (GPU_memory_GB * 0.8) * 1024。A10 有 24GB 显存所以设为1966019.2GB。如果设为 2457624GB第一次调用就可能失败。更隐蔽的坑是--max-num-seqs。我们设为 1但如果你误设为 16以为能并发vLLM会为每个 seq 预分配 KV cache瞬间吃光显存。解决方法是用nvidia-smi监控启动vLLM后Used显存应稳定在 12~14GB如果超过 20GB立刻调小max_model_len。另一个常见错误是prompt过长。report-prompt.txt如果超过 1800 tokenvLLM会静默截断导致模型看不到summary.json。我们的检查方法是在ai-generate.py中加入print(fPrompt 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)返回FalseWeChatHook无法发送。我们的对策是在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_tokenxxx -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 21另一个坑是环境变量。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 :langzh查看系统中文字体发现 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

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑