数据推送上线 ,抢先体验,欢迎来用! 支持钉钉!

本文涉及的产品
实时计算 Flink 版,5000CU*H 3个月
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
大数据开发治理平台 DataWorks,不限时长
简介: 大多数业务都会有定期推送业务信息至钉钉、飞书、Teams 群的需求,有些信息要推三个群、要推两个群、有些信息要 at 人、有些要当天、有些要当月,不旦要管理多个推送的 Webhook,还要管理推送的内容、监控推送是否生效等等,DataWorks 新推出的数据推送能减轻以上问题,还能助力快速完成推送内容开发,支持规范的上线流程。

大多数业务都会有定期推送业务信息至钉钉、飞书、Teams 群的需求,有些信息要推三个群、要推两个群、有些信息要 at 人、有些要当天、有些要当月,不但要管理多个推送的 Webhook,还要管理推送的内容、监控推送是否生效等等,DataWorks 新推出的数据推送能减轻以上问题,还能助力快速完成推送内容开发,支持规范的上线流程,快来体验 --> 传送门





推送效果如下

富文本内容推送

支持多种数据源

目前支持 MySQL、PostgreSQL、Hologres、MaxCompute、ClickHouse 等数据源,撰写多段业务 SQL,利用 DataWorks 调度资源组,弹性设置推送周期,将数据推送至各个推送对象,数据源连接、调度问题,DataWorks 数据推送都封装处理,用户只需关注推送的内容即可。



高度弹性的变量设计

将业务数据推送至推送内容需要高度弹性的变量才能符合各式各样的内容设计,首先,DataWorks 数据推送支持调度资源组的调度参数并提供自动 SQL 参数解析与手动添加参数模式 ,让用户能自由定义参数,再者,我们基于 facebook 开源的 lexical,在富文本编辑器上提供选取参数的交互方式与高亮,降低使用参数的学习门槛。


支持调度参数 yyyymmddyyyymmdd{yyyymmdd} [yyyymmdd]


透过右侧面板定义参数值,支持调度参数格式,如 yyyymmddyyyymmdd代表今日,{yyyymmdd} 代表今日,{yyyymmdd-1} 代表前一天,除了能用在 SQL 的运行内容上,并能运用在推送内容上,用户能使用参数弹性组合内容。





支持自动解析 SQL 参数


当 SQL 编辑器开启自动解析参数 (上方工作条的蓝色开关) 后,能侦测 SQL 的输出参数 (如 select 的字段) 与赋值参数 (如 ${var}),并自动增加至右侧面版的参数栏,用户能在推送内容上选到侦测到的变量 (支持键盘提示与插入按钮)。





支持手动添加参数与常数


若参数不存在于 SQL 或参数值为常数,可关闭自动解析参数 (上方工作条的蓝色开关) 后,使用手动添加的方式,自由维护参数。





富文本编辑器支持 ${var} 格式


Markdown 编辑器一般 不支持 var使facebooklexicalvar格式,我们使用基于facebook开源的lexical来建构富文本编辑器,并增加对{var} 格式,我们使用基于 facebook 开源的 lexical 来建构富文本编辑器,并增加对 {var} 格式的支持




支持 Emoji 让内容更生动


不同的推送渠道对 Emoji 的支持也不一樣,如飞书與釘釘都能直接推送 Emoji 图标,但钉钉還可以用 [Emoji] 格式來展示釘釘自己的圖標,为了让用户能方便运用 Emoji,我们提供了插入通用 Emoji 按钮与插入钉钉 Emoji 按钮,用户只需要选择插入即可。




@ 你想 at


飞书支持 Markdown 里使用  与  的方式来 at 用户,我们修改 lexical 支持将上述两格式转成 @name 的格式方便用户识别。




支持查看源码


在富文本上方工作条的最右边提供切换富文本与源码模式按钮,能仔细查看推送的 Markdown 源码,方便排查问题,也支持想尝试进阶用法的用户,如图片链接,就能结合上述所说的变量与图表 API 结合函数计算玩出进阶应用 (下文描述)。



表格推送

适配多种渠道


不同渠道对表格的撰写方式不同,如飞书的表格需要使用定制的 JSON 格式,Teams 使用标准 Markdown 表格语法,钉钉 PC 版支持标准 Markdown 表格语法但手机版不支持。为了能最大化适配不一样的推送渠道,初期我们支持飞书表格标准 Markdown 表格语法,用户只需要增加表格组件,其馀由数据推送依据渠道类型判断。


用户编写的内容



推送的结果 (一样的设定,飞书与钉钉的推送内容如下)




简易的设定方式


数据推送的表格组件支持选用右侧面版所列的输出参数与赋值参数作为依赖字段,每个字段除提供拖拽排序外,还能进阶设定每一个字段展示的内容,如根据条件变换颜色附加标示符、加上前缀后缀修改表头展示名等。





可视化图表推送

可视化图表推送需要借助 Markdown 图片链接的方式,透过图片链接就能实现许多进阶玩法 (注: 飞书的部份无法直接使用外部的图片链接,需要先将图片上传取得图片 key,并以此做为图片链接)。


图表 API

Chart API 已是不是新的东西,GoogleQuickChart 都有出相关的产品 ( Google 的部份已表明不维护),若要自己搭建其难度也不高,以下使用函数计算 + Chart.js 为例,原理是将 Chart.js 绘制的 canvas 对象,在 Node  Server 上保存为图片并透过 HTTP 输出。


我们以 Bar Chart 为例,建立一个图表 API。新建一个函数,使用 Node.js 16 示例代码。



增加 HTTP GET 触发器。



于函数配置的环境变量增加字体配置, "FONTCONFIG_FILE": "/code/fonts.conf"



至线上 vscode,增加 fonts.conf 以及加入想要的字体,将字体 ttf 档拖拽进编辑器左侧目录树。

<match target="pattern">
  <test name="family">
    <string>sans-serif</string>
  </test>
  <edit name="family" mode="prepend" binding="strong">
    <string>Microsoft YaHei</string>
    <string>Open Sans Bold</string>
    <string>Noto Sans CJK SC</string>
    <string>Noto Sans</string>
    <string>Twemoji</string>
  </edit>
</match>



于 index.js 代码里让 canvas 注册字型。

const { registerFont } = require('canvas');
registerFont('./Sora-Bold.ttf', {
  family: 'Sora',
});
registerFont('./Microsoft YaHei.ttf', {
  family: 'Microsoft YaHei',
});


图表 API 完整的 index.js 如下。

const Chart = require('chart.js/auto');
const colorLib = require('@kurkle/color');
const fs = require('fs');
const express = require('express');
const bodyParser = require('body-parser');
const { createCanvas, registerFont } = require('canvas');
// registerFont('./Sora-Bold.ttf', {
//   family: 'Sora',
// });
// 如有用到中文需要放入中文字体
// registerFont('./Microsoft YaHei.ttf', {
//   family: 'Microsoft YaHei',
// });
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(bodyParser.raw());
const port = 9000;
const tempPath = '/tmp';
async function saveChartImage(filePath, canvas) {
  return new Promise(async (resolve, reject) => {
    if (!fs.existsSync(tempPath)) {
      fs.mkdirSync(tempPath, { recursive: true });
    }
    const out = fs.createWriteStream(filePath);
    const stream = canvas.createPNGStream({
      compressionLevel: 9, // PNG employs a lossless compression algorithm
      resolution: 300,
    });
    stream.pipe(out);
    out.on('finish', () => {
      console.log(`[${Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })}] The chart image was saved: ${filePath}`);
      resolve();
    });
    out.on('error', reject);
  });
}
const Utils = {
  transparentize(value, opacity) {
    var alpha = opacity === undefined ? 0.5 : 1 - opacity;
    return colorLib(value).alpha(alpha).rgbString();
  },
  CHART_COLORS: {
    red: 'rgb(255, 99, 132)',
    orange: 'rgb(255, 159, 64)',
    yellow: 'rgb(255, 205, 86)',
    green: 'rgb(75, 192, 192)',
    blue: 'rgb(54, 162, 235)',
    purple: 'rgb(153, 102, 255)',
    grey: 'rgb(201, 203, 207)'
  }
};
const getChartData = async ({
  part1Today,
  part1Yesterday,
  part2Today,
  part2Yesterday,
}) => {
  const data = [
    { date: 'yesterday', name: 'part1', value: part1Yesterday },
    { date: 'today', name: 'part1', value: part1Today },
    { date: 'yesterday', name: 'part2', value: part2Yesterday },
    { date: 'today', name: 'part2', value: part2Today },
  ];
  return (
    {
      labels: ['yesterday', 'today'],
      datasets: [
        {
          label: 'part1',
          data: data.filter((d) => d.name === 'part1').map(row => row.value),
          borderColor: Utils.CHART_COLORS.blue,
          backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
        },
        {
          label: 'part2',
          data: data.filter((d) => d.name === 'part2').map(row => row.value),
          borderColor: Utils.CHART_COLORS.red,
          backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
        }
      ]
    }
  );
};
// Bar Chart 的配置
const getChartConfigs = () => {
  return {
    type: 'bar',
    options: {
      responsive: true,
      plugins: {
        customCanvasBackgroundColor: {
          color: 'white',
        },
        legend: {
          position: 'top',
        },
        title: {
          display: true,
          text: 'Chart 展示'
        }
      }
    }
  };
};
app.get('/chart-api', async (req, res) => {
  const width = 300;
  const height = 200;
  const canvas = createCanvas(width, height);
  const ctx = canvas.getContext('2d');
  ctx.font = "43px Microsoft YaHei";
  // API 参数
  const part1Today = req.query['part1Today'];
  const part1Yesterday = req.query['part1Yesterday'];
  const part2Today = req.query['part2Today'];
  const part2Yesterday = req.query['part2Yesterday'];
  const data = await getChartData({
    part1Today,
    part1Yesterday,
    part2Today,
    part2Yesterday,
  });
  const chartConfig = {
    ...getChartConfigs(),
    data,
  };
  // 绘制图片
  await (async function () {
    new Chart(
      ctx,
      chartConfig,
    );
  })();
  const filePath = `${tempPath}/chart.png`;
  await saveChartImage(filePath, canvas);
  // 回送图表给请求方
  const contentType = 'image/png';
  // res.setStatusCode(200);
  res.setHeader('content-type', contentType);
  if (req.headers['Accept'] === '*/*' || req.headers['accept'] === '*/*') {
    res.send("success");
  } else {
    res.send(fs.readFileSync(filePath));
  }
});
app.listen(port, () => {
  console.log(`App listening on port ${port}`);
})
app.timeout = 0; // never timeout
app.keepAliveTimeout = 0; // keepalive, never timeout


层管理创建图表 API 需要的依赖资源。


{
  "dependencies": {
    "@kurkle/color": "0.3.2",
    "canvas": "2.11.2",
    "chart.js": "4.3.0"
  }
}





于函数配置的增加上述资源层。



部署代码进行测试。




至触发器管理取得公网 URL,并加上 /chart-api 进行 GET 请求,取得图片,如下例 (我们提供的示例代码挖了四个参数并填入图表,此部份可自由修改)。

https://url/chart-api?part1Today=10&part1Yesterday=20&part2Today=30&part2Yesterday=10 



有了图表 API 后,回到数据推送,将图片链接放入,并将参数的常数改为推送的参数即可。


https://url/chart-api?part1Today=${var1}&part1Yesterday=${var2}&part2Today=${var3}&part2Yesterday=${var4}




DataV Card

图表 API 的方式能为推送带来可视化的内容,但是可视化配置与参数设计不易,数据分析已经提供了 SQL + DataV Card 的能力,用户能在数据分析上取得分析的结果与图表,并打开 DataV Card 的分享链接,我们能透过函数计算建立一个截屏 API,将此链接的内容推送出去。


取得数据分析 DataV Card 的分享链接。




首先创建一个函数。



使用 Node.js 16,并选用截图示例代码。



设置 HTTP GET 触发器




新建后,部署函数并取得 HTTP 触发器的公网 URL,示例代码参数默认为 url,如下例取得百度首页截图。


https://url?url=www.baidu.com


因为示例代码没有做快取判断,下面代码增加快取代码判断,与增加 key 参数值来刷新快取 (key 为快取的 key),另外透过图片的创建时间来判断是否自动过期 (默认 1 天)。


https://url?url=www.baidu.com&key=20240505


具备快取能力的截图代码如下。

const fs = require('fs');
const puppeteer = require('puppeteer');
function autoScroll(page) {
  return page.evaluate(() => {
    return new Promise((resolve, reject) => {
      var totalHeight = 0;
      var distance = 100;
      var timer = setInterval(() => {
        var scrollHeight = document.body.scrollHeight;
        window.scrollBy(0, distance);
        totalHeight += distance;
        if (totalHeight >= scrollHeight) {
          clearInterval(timer);
          resolve();
        }
      }, 100);
    })
  });
}
module.exports.handler = function (request, response, context) {
  (async () => {
    const url = request?.query?.['url'] || request?.queries?.['url'];
    const key = (request?.query?.['key'] || request?.queries?.['key']) || 'default';
    const path = `/tmp/screenshot-${key}`;
    const contentType = 'image/png';
    const current = new Date();
    const screen = async (url) => {
      const browser = await puppeteer.launch({
        headless: true,
        args: [
          '--disable-gpu',
          '--disable-dev-shm-usage',
          '--disable-setuid-sandbox',
          '--no-first-run',
          '--no-zygote',
          '--no-sandbox',
          '--single-process'
        ]
      });
      if (!url.startsWith('https://') && !url.startsWith('http://')) {
        url = 'http://' + url;
      }
      const page = await browser.newPage();
      await page.emulateTimezone('Asia/Shanghai');
      await page.goto(url, {
        'waitUntil': 'networkidle2'
      });
      await page.setViewport({
        width: 800,
        height: 600
      });
      await autoScroll(page)
      await page.screenshot({ path: path, fullPage: true, type: 'png' });
      await browser.close();
      response.setStatusCode(200);
      response.setHeader('content-type', contentType);
      if (request.headers['Accept'] === '*/*' || request.headers['accept'] === '*/*') {
        response.send("success")
      } else {
        response.send(fs.readFileSync(path))
      }
    }
    if (url) {
      if (fs.existsSync(path)) {
        // exist
        const result = fs.statSync(path);
        // 透过图片创建的时间,来判断是否过期,内置为 1 天
        if (current.getTime() - result.ctimeMs < (1 * 24 * 60 * 60 * 1000)) {
          response.setStatusCode(200);
          response.setHeader('content-type', contentType);
          if (request.headers['Accept'] === '*/*' || request.headers['accept'] === '*/*') {
            response.send("success");
          } else {
            response.send(fs.readFileSync(path));
          }
          console.log('get from cache');
        } else {
          await screen(url);
        }
      } else {
        await screen(url);
      }
    } else {
      response.setStatusCode(404);
      response.send("failed");
    }
  })().catch(err => {
    response.setStatusCode(500);
    response.setHeader('content-type', 'text/plain');
    response.send(err.message);
  });
};


将图片链接放至数据推送的内容里,即可推送 DataV Card。




注:函数计算提供的 HTTP 触发器域名为阿里云的域名,其请求返回表头默认为下载行为,会影响图表或截图 API 在钉钉或飞书等渠道的展示行为(如钉钉 PC 版,点击图片后无法展开图片),需要使用自定义域名才能避免请求的返回表头为下载行为,参考相关文章设置自定义域名


支持多种渠道,推送对象、推送节点、与调度资源组分别管理

目前已开放钉钉飞书两个推送对象 ( Webhook ),以项目为单位统一管理推送对象,各个推送节点可以绑定多个推送对象,并选用一个调度资源组。



规范的节点开发流程:开发态、生产态、版本管理

推送节点具开发态 (草稿)、生产态 (已上线),透过版本管理查看各版本详情,并能回滚选用版本。规范的节点开发流程能帮助用户调试渠道的推送内容与不同类型渠道的内容适配,如开发态的节点推送至调试用途的推送对象 ( Webhook ),等内容确定后,再上线推送到正式的推送对象 ( Webhook )。



开发态测试



生产态测试



生产态节点管理



即将

数据推送仍然有许多功能正在开发中,如推送实例管理、更快上手的推送开发 (向导模式)、更简易的可视化图表推送 (封装上述函数计算设定的部份)、封装飞书需处理图片上传的过程、功能合并到数据分析与数据开发里、支持更多类型渠道等。也期待更多您的需求反馈:DataWorks 工单需求



快来体验数据推送吧! --> 传送门






相关实践学习
基于Hologres轻松玩转一站式实时仓库
本场景介绍如何利用阿里云MaxCompute、实时计算Flink和交互式分析服务Hologres开发离线、实时数据融合分析的数据大屏应用。
阿里云实时数仓实战 - 项目介绍及架构设计
课程简介 1)学习搭建一个数据仓库的过程,理解数据在整个数仓架构的从采集、存储、计算、输出、展示的整个业务流程。 2)整个数仓体系完全搭建在阿里云架构上,理解并学会运用各个服务组件,了解各个组件之间如何配合联动。 3&nbsp;)前置知识要求 &nbsp; 课程大纲 第一章&nbsp;了解数据仓库概念 初步了解数据仓库是干什么的 第二章&nbsp;按照企业开发的标准去搭建一个数据仓库 数据仓库的需求是什么 架构 怎么选型怎么购买服务器 第三章&nbsp;数据生成模块 用户形成数据的一个准备 按照企业的标准,准备了十一张用户行为表 方便使用 第四章&nbsp;采集模块的搭建 购买阿里云服务器 安装 JDK 安装 Flume 第五章&nbsp;用户行为数据仓库 严格按照企业的标准开发 第六章&nbsp;搭建业务数仓理论基础和对表的分类同步 第七章&nbsp;业务数仓的搭建&nbsp; 业务行为数仓效果图&nbsp;&nbsp;
目录
相关文章
|
7月前
在钉钉中,如何创建表单的业务实例数据?
在钉钉中,如何创建表单的业务实例数据?
69 1
|
5天前
|
安全 机器人 数据安全/隐私保护
Metasploit主机上线钉钉通知
Metasploit主机上线钉钉通知
17 0
|
7月前
|
分布式计算 API MaxCompute
钉钉的OA审批数据和Odps
钉钉的OA审批数据和Odps
188 2
|
5天前
|
JavaScript 数据可视化 前端开发
钉钉宜搭通过js触发子表单数据联动
钉钉宜搭通过js触发子表单数据联动
|
5月前
|
存储 安全 API
获取打卡结果(attendance/list)不返回用户姓名怎么办 钉钉考勤获取打卡结果列表返回数据中有userId,但是没有用户姓名
获取打卡结果(attendance/list)不返回用户姓名怎么办 钉钉考勤获取打卡结果列表返回数据中有userId,但是没有用户姓名
68 1
|
8月前
|
安全 机器人 Windows
Metasploit主机上线钉钉通知
Metasploit主机上线钉钉通知
47 0
|
8月前
|
监控 物联网 机器人
钉钉群中如何接收IoT温控器数据告警通知
本实验主要介绍如何将温控器设备以MQTT协议接入IoT物联网平台,通过云产品流转到函数计算FC,调用钉钉群机器人API,实时推送温湿度消息到钉钉群。
200 2
|
移动开发 小程序 安全
DingTalk「开发者说」— 钉钉数据授权开发实战
随着《中华人民共和国网络安全法》的实施,对个人信息的保护越来越严格,钉钉遵守安全法要求,对钉钉上个人信息的收集、使用、共享有严格的把关。本次分享主要介绍钉钉如何做个人数据把关,以及在个人选择同意原则下,如何对个人数据做授权流转,以及实践演练。 DingTalk「开发者说」是钉钉开发者最新上线的开发者栏目,联合阿里云ACE团队,分享钉应用开发解决方案、技术更新、实战技巧,致力于成为钉钉与开发者的桥梁与纽带,让更多的钉钉开发者传播技术、提升技能、分享观点。在数字化变革的时代,“云钉一体”“钉钉全面开放”战略之后,希望钉钉技术可以持续激发开发者的创造力,为组织数字化赋能。
2076 4
DingTalk「开发者说」— 钉钉数据授权开发实战
|
数据可视化 安全 前端开发
DingTalk「开发者说」 钉钉连接平台:打通企业数据孤岛的最佳实践
摘要:DingTalk「开发者说」是专为钉钉开发者打造的栏目,分享钉应用开发的实战技巧、技术架构、解决方案,致力于成为钉钉与开发者的连接桥梁。本篇主要介绍什么是钉钉连接平台,连接平台所具备的能力,可覆盖哪些高频场景的互联互通,以及连接平台的实战演示。
2007 1
DingTalk「开发者说」 钉钉连接平台:打通企业数据孤岛的最佳实践
|
安全 机器人
阿里云钉钉上线专属解决方案 助力800万保险代理人疫情期工作
阿里云钉钉上线专属解决方案 助力800万保险代理人疫情期工作
阿里云钉钉上线专属解决方案 助力800万保险代理人疫情期工作
http://www.vxiaotou.com