一个偶然的机会,认识了node-red。这种拖拽控件编写代码的方式给了我很深刻的印象。能够通过简单的拖拽实现mqtt,http,websocket,tcp的服务,作为基于网络的业务流程demon非常方便。最近用它实践了一把,给自己做了一个企业微信消息推送的流程,还是很方便的。

Node-RED背景介绍

• Node-Red是IBM公司开发的一个可视化的编程工具。它允许程序员通过组合各部件来编写应用程序。这些部件可以是硬件设备(如:Arduino板子)、Web API(如:WebSocket in和WebSocket out)、功能函数(如:range)或者在线服务(如:email)。
• Node-Red提供基于网页的编程环境。通过拖拽已定义node到工作区并用线连接node创建数据流来实现编程。程序员通过点击‘Deploy’按钮实现一键保存并执行。程序以JSON字符串的格式保存,方便用户分享、修改。
• Node-Red基于Node.js,它的执行模型和Node.js一样,也是事件驱动非阻塞的。理论上,Node.js的所有模块都可以被封装成Node-Red的一个或几个node。(Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。 Node.js 的包管理器 npm是全球最大的开源库生态系统。)

安装Node-red

还是采用docker安装,docker hub 原始地址

docker run -it -p 1880:1880 -v myNodeREDdata:/data --name mynodered nodered/node-red

通过1880端口即可访问web配置界面,比如: http://192.168.1.22:1880

先看下效果

推送的消息,我在jenkins,openwrt软路由,群晖nas里配置了消息通知

群晖7.0支持webhook推送,可以直接配置使用
6.2.3 可以通过自定义短信推送服务实现,将我们自己这个推送接口伪装成一个短信服务,但短信推送的默认触发条件较少,可以在群晖的推送的高级配置里自己选更多场景

准备企业微信

个人也可以开通企业微信,开通企业微信后,添加一个子定义应用,通过这个应用的消息推送接口向用户发送消息。

企业微信开通及应用申请申请流程

流程简单说明

  • 提供两个post和get节点,做为请求入口
  • 处理,格式化请求消息,记录请求时间
  • 从文件读取accesstoken,判断过期时间,如果过期则进入重新请求accesstoken流程
  • 异常处理节点,监听读取文件异常,发生异常就进入重新请求accesstoken流程
  • 获取新accesstoken,设置企业微信企业id,应用密钥,调用微信接口获取accesstoken,设置当前时间。将报文保存到文件里
  • 如果读取accesstoken正常,且未过期,则调用微信的消息推送接口发生消息。

接口地址: http://bbb.xxx.net:1880/send?msg=2222&title=334&url=https://nas.good365.net:5151/

我只接受了三个参数,title,msg,url。post可以支持markdown格式

流程json

[{"id": "e12bd28c78ef39c0","type": "tab","label": "流程 1","disabled": false,"info": "","env": []},{"id": "f148c0acd769ebef","type": "http in","z": "e12bd28c78ef39c0","name": "收到post请求","url": "/send","method": "post","upload": false,"swaggerDoc": "","x": 110,"y": 40,"wires": [["4f91d762766100d0"]]},{"id": "e9e926d7401a4479","type": "file in","z": "e12bd28c78ef39c0","name": "读取accesstoken文件","filename": "/data/accesstoken.json","format": "utf8","chunk": false,"sendError": false,"encoding": "none","allProps": false,"x": 1180,"y": 160,"wires": [["e95dacdaff16ad7a"]]},{"id": "9d919d103ee25af7","type": "http request","z": "e12bd28c78ef39c0","name": "请求新accesstoken","method": "GET","ret": "obj","paytoqs": "ignore","url": "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={{corpid}}&corpsecret={{corpsecret}}","tls": "","persist": false,"proxy": "","authType": "","senderr": false,"x": 950,"y": 600,"wires": [["43c6a5df6cb8bd83"]]},{"id": "dc7285ddba731a99","type": "file","z": "e12bd28c78ef39c0","name": "存储accesstoken文件","filename": "/data/accesstoken.json","appendNewline": true,"createDir": false,"overwriteFile": "true","encoding": "none","x": 1500,"y": 600,"wires": [[]]},{"id": "d2c5926ca0673808","type": "http response","z": "e12bd28c78ef39c0","name": "返回响应","statusCode": "","headers": {},"x": 1120,"y": 40,"wires": []},{"id": "774bd7eb3a39c99d","type": "catch","z": "e12bd28c78ef39c0","name": "读取accesstoken文件异常","scope": ["e9e926d7401a4479"],"uncaught": false,"x": 370,"y": 600,"wires": [["28a3c108d1c14b6f"]]},{"id": "1465025fdf4a4e2b","type": "http request","z": "e12bd28c78ef39c0","name": "调用微信接口","method": "POST","ret": "txt","paytoqs": "ignore","url": "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={{access_token}}","tls": "","persist": false,"proxy": "","authType": "","senderr": false,"x": 1540,"y": 500,"wires": [[]]},{"id": "2e438fa49d1c61d0","type": "function","z": "e12bd28c78ef39c0","name": "整理接口报文","func": "var sendmsg = flow.get(\"SEND_MSG\")\nvar sendtitle = flow.get(\"SEND_TITLE\")\nvar sendurl = flow.get(\"SEND_URL\")\n\nvar mk = flow.get(\"SEND_MK\")\n\nnode.log(\"msg:\"+sendmsg);\nnode.log(\"sendtitle:\"+sendtitle);\nnode.log(\"sendurl:\"+sendurl);\n\nnode.log(JSON.stringify(mk));\n\nvar newmsg = {\n    access_token: msg.payload.access_token,\n};\nif(mk){\n    newmsg.payload = {\n        agentid:\"1000002\",\n        touser : \"@all\",\n        msgtype : \"markdown\",\n        markdown : {content:mk.content}\n    };\n}else{\n    if(sendurl){\n    newmsg.payload = {\n        agentid:\"1000002\",\n        touser : \"@all\",\n        msgtype : \"textcard\",\n        textcard : {\n            title : sendtitle,\n            description : sendmsg,\n            url : sendurl,\n            btntxt:\"更多\"\n        }\n    };\n}else{\n    newmsg.payload = {\n        agentid:\"1000002\",\n        touser : \"@all\",\n        msgtype : \"text\",\n        text : {\n            content : sendtitle+\"\\n\"+sendmsg\n        }\n    };\n}\n}\n\n\nnode.log(newmsg.access_token);\n\nreturn newmsg;\n","outputs": 1,"noerr": 0,"initialize": "","finalize": "","libs": [],"x": 1220,"y": 500,"wires": [["1465025fdf4a4e2b"]]},{"id": "e95dacdaff16ad7a","type": "json","z": "e12bd28c78ef39c0","name": "转json对象","property": "payload","action": "","pretty": false,"x": 690,"y": 320,"wires": [["bc6f68aab4cfa651"]]},{"id": "4f91d762766100d0","type": "function","z": "e12bd28c78ef39c0","name": "格式化请求消息","func": "if(msg.payload.text!=undefined){\n    if(typeof msg.payload.text == 'object'){\n        msg.payload.text = JSON.stringify(msg.payload.text);\n    }\nmsg.payload.text = msg.payload.text.replace(/<\\/b>/g,\"\");\nmsg.payload.text = msg.payload.text.replace(/<b>/g,\"\");\n}\n\nflow.set(\"SEND_MSG\",msg.payload.text);\nflow.set(\"SEND_TITLE\",msg.payload.title!=undefined?msg.payload.title:\"通知\");\nflow.set(\"SEND_URL\",msg.payload.url!=undefined?msg.payload.url:\"\");\n\nflow.set(\"SEND_MK\",msg.payload.markdown);\n\nvar sendmsg = flow.get(\"SEND_MSG\")\n\n\nreturn msg;","outputs": 1,"noerr": 0,"initialize": "","finalize": "","libs": [],"x": 300,"y": 100,"wires": [["4745e8a209395e11"]]},{"id": "43c6a5df6cb8bd83","type": "change","z": "e12bd28c78ef39c0","name": "设定时间戳","rules": [{"t": "set","p": "payload.time","pt": "msg","to": "","tot": "date"}],"action": "","property": "","from": "","to": "","reg": false,"x": 1210,"y": 600,"wires": [["dc7285ddba731a99"]]},{"id": "e18066622fbcb8c8","type": "switch","z": "e12bd28c78ef39c0","name": "判断accesstoken过期","property": "expires","propertyType": "flow","rules": [{"t": "neq","v": "1","vt": "num"},{"t": "eq","v": "1","vt": "num"}],"checkall": "true","repair": false,"outputs": 2,"x": 720,"y": 440,"wires": [["2e438fa49d1c61d0"],["28a3c108d1c14b6f"]]},{"id": "4745e8a209395e11","type": "change","z": "e12bd28c78ef39c0","name": "记录请求时间","rules": [{"t": "set","p": "reqtime","pt": "flow","to": "","tot": "date"}],"action": "","property": "","from": "","to": "","reg": false,"x": 480,"y": 100,"wires": [["217079256df21e74","212125fe0ceebd22"]]},{"id": "28a3c108d1c14b6f","type": "function","z": "e12bd28c78ef39c0","name": "设置微信密钥","func": "msg.corpid = \"ww*************\";\nmsg.corpsecret = \"************************************************\";\n\nreturn msg;","outputs": 1,"noerr": 0,"initialize": "","finalize": "","libs": [],"x": 700,"y": 600,"wires": [["9d919d103ee25af7"]]},{"id": "bc6f68aab4cfa651","type": "function","z": "e12bd28c78ef39c0","name": "计算过期时间","func": "var sendmsg = flow.get(\"reqtime\")\n\nsendmsg = sendmsg - 7200000; \n\nnode.log(msg.payload.time);\nif( msg.payload.time==undefined || msg.payload.time< sendmsg){\n    flow.set(\"expires\",1);\n    node.log(\"expires\");\n}else{\n    flow.set(\"expires\",0);\n    node.log(\"not expires\");\n}\n\nreturn msg;","outputs": 1,"noerr": 0,"initialize": "","finalize": "","libs": [],"x": 920,"y": 320,"wires": [["e18066622fbcb8c8"]]},{"id": "a97da9563e8ee19d","type": "change","z": "e12bd28c78ef39c0","name": "消息赋值","rules": [{"t": "set","p": "payload.timestamp","pt": "msg","to": "","tot": "date"},{"t": "set","p": "payload.success","pt": "msg","to": "1","tot": "str"},{"t": "delete","p": "payload.msg","pt": "msg"}],"action": "","property": "","from": "","to": "","reg": false,"x": 880,"y": 40,"wires": [["d2c5926ca0673808"]]},{"id": "217079256df21e74","type": "function","z": "e12bd28c78ef39c0","name": "","func": "msg.payload = {\n    timestamp:0,\n    success:0\n};\n\nreturn msg;","outputs": 1,"noerr": 0,"initialize": "","finalize": "","libs": [],"x": 690,"y": 40,"wires": [["a97da9563e8ee19d"]]},{"id": "b3c58bd87bb9ebdb","type": "http in","z": "e12bd28c78ef39c0","name": "","url": "/send","method": "get","upload": false,"swaggerDoc": "","x": 100,"y": 240,"wires": [["4f91d762766100d0"]]},{"id": "5be2793473ab0a12","type": "complete","z": "e12bd28c78ef39c0","name": "存储完accesstoken重新读取","scope": ["dc7285ddba731a99"],"uncaught": false,"x": 400,"y": 240,"wires": [["212125fe0ceebd22"]]},{"id": "3fb5dc7b0e082a78","type": "switch","z": "e12bd28c78ef39c0","name": "执行次数控制","property": "count","propertyType": "msg","rules": [{"t": "lte","v": "3","vt": "str"}],"checkall": "true","repair": false,"outputs": 1,"x": 900,"y": 160,"wires": [["e9e926d7401a4479"]]},{"id": "212125fe0ceebd22","type": "function","z": "e12bd28c78ef39c0","name": "执行计数","func": "if(msg.count==undefined){\n    msg.count=1;\n}else{\n    msg.count++;\n}\nreturn msg;","outputs": 1,"noerr": 0,"initialize": "","finalize": "","libs": [],"x": 680,"y": 160,"wires": [["3fb5dc7b0e082a78"]]}
]

Node-Red 实践:企业微信消息推送相关推荐

  1. Spring Boot 实现企业微信消息推送

    1 Maven依赖 <!-- 阿里JSON解析器 --><dependency><groupId>com.alibaba</groupId><ar ...

  2. 企业微信消息推送接口大全

    该类存放接口所用到的企业微信的接口地址 package com.common;public final class QyapiUrl {/*** 请求nginx转发服务器地址*/public stat ...

  3. 企业微信消息推送卡片按钮互动的使用

    企业微信回调推送消息互动模板 前置条件 # 企业微信后台中设置了url 按以上设置 # 回调url: http://www.baidu.com# token: xxxxx# EncodingAESKe ...

  4. (二)企业微信消息推送

    1.所需信息 CorpID:企业ID AgentID:应用ID Secret:应用密钥 2.功能实现 参考内容:企业微信-发送应用消息 public static class QYWXHelper{/ ...

  5. 【corpwechat-bot】一个好用的企业微信消息推送python接口库

    文章目录 前言 Requirements Result Features Usage 前言 本文同步发表在blog.gentlecp.com corpwechat-bot是一个python封装的企业机 ...

  6. C# 企业微信消息推送对接,实现天气预报推送

    准备工作 需要获取的东西1. 企业Id,2.应用secret 和 应用ID 获取企业id 注册完成后,在我的企业=>企业信息=>最下面企业id 获取应用secret 和  应用ID 发送微 ...

  7. 微信接收不到企业微信消息推送

    使用微信提供的接口,给公司微信企业号的相关人员推送消息时. 一般是微信端的企业号和企业微信都能接收到推送的消息,但有一次企业微信版本更新后发现微信端的推送消息没有接收到. 原因找了很久,最终发现在企业 ...

  8. 【Java开发】Java实现企业微信消息推送,通过应用发送

    企业微信机器人发送消息 一.可能需要的依赖 二.必须的信息 三.效果展示 四.具体代码 一.可能需要的依赖 之前导依赖的时候没有特别标记,现在分不清哪个是哪个了,应该就在这些里面 <depend ...

  9. 企业微信消息推送(一)接收消息服务器URL

    1.点击左上角头像,打开微信管理平台 2.创建应用 3.获取五个参数 3.1获取应用的AgentId.Secret 3.2获取企业ID 配置接收消息服务器URL 企业微信限制过多,公司域名所有权检验不 ...

最新文章

  1. 【JOURNAL】城邦组诗之十二 - 心肠
  2. [转载]VC6下安装STLport-5.2.1
  3. java核心(十五):List、Set、Map功能分析
  4. Java+MyEclipse+Tomcat (三)配置MySQL及查询数据显示在JSP网页中
  5. 2016 linux发行版排行_选择困难症必看!云服务器如何选择操作系统,Windows和Linux哪个更好?...
  6. 网络服务器分为文件服务器通信服务器和,近代中国落后、贫困的根本原因是()...
  7. vue路由+ elementUI表格组件:loop文章列表页enter内容页(vue路由传参userid)- 代码篇
  8. css过度效果 从无到有,求教,对于CSS3 transition和transform实现的一个特效原理不是太明白?...
  9. php对象转换为字符串,php – 类的对象..无法转换为字符串
  10. 剑指 Offer59-I-滑动窗口的最大值
  11. 【读书笔记《Android游戏编程之从零开始》】11.游戏开发基础(SurfaceView 游戏框架、View 和 SurfaceView 的区别)...
  12. 求导计算机在线,求导计算器
  13. Windows照片查看器无法显示此图片
  14. win10系统安装到服务器失败,win10安装失败怎么办?
  15. 汤臣倍健营销云与畅捷通T+系统对接方案
  16. 无线监控安ftp服务器,手把手教你将监控摄像头视频FTP上传到路由器
  17. JavaScript网页制作--五秒后自动跳转页面
  18. mysql登陆错误2003(hy000)_mysql远程无法登陆出现ERROR2003(HY000)...解决方法_MySQL
  19. 【面试】2019.7.10京东校招提前批
  20. Springboot实现图书馆座位预约管理系统之自习室预约占座系统源码

热门文章

  1. 格式转换——heic转换成jpg的一种快捷方法
  2. 深度学习AI美颜系列---AI美颜磨皮算法二
  3. Vue返回上一个页面时如何触发上一个页面的方法
  4. 空中客车获中国航空器材集团300架飞机采购订单
  5. 【Android笔记】Android MediaPlayer的生命周期
  6. JS实现数字的分隔符,美元分隔符(12,300)
  7. sockaddr与sockaddr_in结构体简介
  8. 在北京买房子-搞笑版(转)
  9. [收藏]Steve Jobs的人生哲学
  10. 迎新年html,迎接新年句子精选十条