Node-RED企业微信发送—群文件

目录


一、功能目标

PLC下降沿登记任务,次日9点推送PDF报告

实验结束时,PDF 报告通常还没有生成。本项目中,PDF 由其他系统在凌晨自动生成,然后放到共享目录。

因此不能在 PLC 下降沿时立刻发送 PDF,而是:

text 复制代码
下降沿
    ↓
登记PDF发送任务
    ↓
第二天9点检查共享目录
    ↓
找到当天最新PDF
    ↓
发送到企业微信群

注意:企业微信发送文件不是一步完成的。

必须先上传文件拿到 media_id,再用 media_id 发送文件消息。

text 复制代码
PDF文件路径
    ↓
读取PDF二进制内容
    ↓
upload_media接口上传
    ↓
得到media_id
    ↓
webhook/send接口发送file消息

二、下降沿登记任务

新增 PDF发送任务登记 节点,和文本消息构造节点并联:

text 复制代码
边沿检测
    ├── 消息构造
    └── PDF发送任务登记

关键代码:

js 复制代码
if (msg.edgeType !== "falling") {
    return null;
}

// 在流上下文里登记一个【待发送任务】。
//    第二天早上9点的定时节点会读取这个标志。
flow.set('pdfSendTask', {
    pending: true,
    fallingTime: new Date().toISOString(),
    mode: flow.get('mode') || 0
});

return null;

这里不直接发送文件,只生成一个任务:

text 复制代码
pending = true

第二天 9 点检查任务时,只要看到 pendingtrue,才继续查找 PDF。

三、定时检查

新增一个 inject 节点:

text 复制代码
节点名称:每天9点检查PDF发送
crontab:00 09 * * *
topic:pdf_send_check

如果 PDF 生成可能延迟,可以把时间改成:

text 复制代码
05 09 * * *
10 09 * * *

四、配置共享目录与机器人KEY

使用流程环境变量

text 复制代码
PDF_SHARE_DIR:<PDF报告共享目录>
WECHAT_BOT_KEY:<企业微信机器人KEY>

注意:在 Node-RED 流程页签里配置两个环境变量(双击流程标签):

读取方式:

js 复制代码
const SHARE_DIR = env.get('PDF_SHARE_DIR');
const WECHAT_KEY = env.get('WECHAT_BOT_KEY');

容易写错的地方:

js 复制代码
// 错误:env.get里面不是写路径
const SHARE_DIR = env.get('<PDF报告共享目录>');

// 正确:env.get里面写变量名
const SHARE_DIR = env.get('PDF_SHARE_DIR');

五、查找最新PDF

检查逻辑:

  • 没有待发送任务,直接结束。
  • 有任务,扫描共享目录。
  • 只保留当天创建的 PDF。
  • 按创建时间倒序。
  • 取最新的一个。
  • 如果没有 PDF,清掉任务,不发送。

核心代码:

js 复制代码
// 1. 读取配置:共享文件夹路径和企业微信机器人key。
const SHARE_DIR = env.get('PDF_SHARE_DIR');
const WECHAT_KEY = env.get('WECHAT_BOT_KEY');

// 2. 只有下降沿登记过任务时,早上9点才继续处理。
const task = flow.get('pdfSendTask');
if (!task || !task.pending) {
    node.status({ fill: "grey", shape: "ring", text: "无待发送任务" });
    return null;
}

// 3. 把Date对象格式化成 yyyy-MM-dd,用来判断PDF是不是今天创建的。
function formatDate(date) {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const day = String(date.getDate()).padStart(2, '0');
    return `${year}-${month}-${day}`;
}

// 4. 配置没填时直接报错,不继续访问共享目录。
if (!SHARE_DIR) {
    node.error('请先配置 PDF_SHARE_DIR', msg);
    return null;
}

if (!WECHAT_KEY) {
    node.error('请先配置 WECHAT_BOT_KEY', msg);
    return null;
}

// 5. 扫描共享目录:只保留今天创建的PDF,并按创建时间倒序排列。
const files = fs.readdirSync(SHARE_DIR)
    .filter(name => name.toLowerCase().endsWith('.pdf'))
    .map(name => {
        const fullPath = path.join(SHARE_DIR, name);
        const stat = fs.statSync(fullPath);
        return { name, fullPath, stat };
    })
    .filter(file => file.stat.isFile() && formatDate(file.stat.birthtime) === today)
    .sort((a, b) => b.stat.birthtimeMs - a.stat.birthtimeMs);

// 6. 没有PDF就清掉任务,不发企业微信,也不反复等待。
if (files.length === 0) {
    flow.set('pdfSendTask', {
        ...task,
        pending: false,
        skippedAt: new Date().toISOString(),
        reason: 'no_pdf_created_today'
    });
    return null;
}

六、PDF上传与发送

PDF 文件发送已经单独调试完成,这里只记录架构,不重复展开细节。

企业微信文件发送分两步:

text 复制代码
1. upload_media 上传PDF,得到 media_id
2. webhook/send 用 media_id 发送 file 消息

架构:

text 复制代码
查找最新PDF并准备上传
    ↓
上传PDF到企业微信
    ↓
构造PDF文件消息
    ↓
消息队列
    ↓
企业微信HTTP发送

文件消息格式:

json 复制代码
{
  "msgtype": "file",
  "file": {
    "media_id": "<上传接口返回的media_id>"
  }
}

上传成功后,要清理临时 HTTP 字段,避免影响后面的发送节点:

js 复制代码
delete msg.method;
delete msg.url;
delete msg.headers;

七、最终架构

完整结构:

text 复制代码
S7 in
    ↓
switch变量分流
    ├── Mode变量 -> flow.set('mode')
    └── EndingFlag -> 边沿检测
                        ├── 消息构造 -> 消息队列 -> 企业微信发送
                        └── PDF发送任务登记

每天9点inject
    ↓
查找最新PDF
    ↓
上传PDF
    ↓
构造文件消息
    ↓
消息队列
    ↓
企业微信发送

八、现场复用清单

下次复用时,只需要重点检查:

  1. PLC 连接参数是否正确。
  2. PLC 变量地址是否正确。
  3. msg.topic 分流名称是否和 S7 节点变量名一致。
  4. 企业微信机器人 key 是否换成目标群。

【5】.PDF 共享目录是否能被 Node-RED 运行账号访问。

  1. 定时发送时间是否符合 PDF 实际生成时间。
  2. 如果一天可能多次实验,需要把 pdfSendTask 从单对象扩展为队列。

九、注意事项

1. 为什么上传后还要再发送一次

企业微信文件消息分两步:

第一步:上传文件到企业微信临时素材。

text 复制代码
upload_media

#文件上传接口

作用是把文件传给企业微信,返回 media_id

第二步:上传得到 media_id,发送文件消息到群里。

json 复制代码
{
  "msgtype": "file",
  "file": {
    "media_id": "xxxx"
  }
}

作用是把这个文件真正发送到群里。

所以"上传PDF到企业微信"不等于"群里已经收到文件"。

2. 没有PDF时不发送

早上 9 点如果没有找到当天创建的 PDF:

  • 清掉 pdfSendTask.pending
  • 不发送企业微信
  • 不报错刷屏

这样更适合现场长期运行。

十、最终效果

实验结束时:

text 复制代码
PLC下降沿 -> 登记PDF发送任务

第二天早上:

text 复制代码
9:00 -> 检查共享目录 -> 找到最新PDF -> 上传企业微信 -> 发送文件到群

如果当天没有 PDF:

text 复制代码
9:00 -> 检查共享目录 -> 未找到PDF -> 清掉任务 -> 不发送
相关推荐
garmin Chen1 小时前
Prompt工程入门:让AI按你的要求工作(2)--Prompt 高阶优化与结构化设计
java·人工智能·python·ai·prompt
whatever who cares1 小时前
android中fragment demo举例
android·java·开发语言
zhangphil1 小时前
Android将ImageView显示的图原样取出转换为Bitmap,Kotlin
android·kotlin
西凉的悲伤1 小时前
Guava类库——Range连续区间
java·算法·guava
武子康1 小时前
Java-17 深入浅出MyBatis Mapper Proxy 源码解析:从 getMapper 到 invoke 的完整链路
java·后端
plainGeekDev1 小时前
CountDownTimer → Flow
android·java·kotlin
心之伊始1 小时前
Java 后端 AI 应用网关实战:多模型路由、Fallback、超时和可观测性设计
java·spring boot·大模型·架构设计·ai网关
仙俊红1 小时前
如何优化 MySQL 深分页 SQL
android·sql·mysql
小锋java12342 小时前
【技术专题】LangChain4j 开发Java Agent智能体 - 嵌入模型与向量数据库
java·人工智能