【测试团队管理】Node版钉钉飞书机器人提醒团队成员每日缺陷状态

一、场景

日常一般会通过jira bug面板筛选条件筛选出bug,如:经办人、状态、标签等,但这需要人为主动去筛选搜索,如果有一个定时每日钉钉群、飞书群提醒每个人截止当日bug情况就比较直观,对测试团队成员进度把控也有一定辅助左右。

二、方案设计

群消息,对应人员点击消息链接,跳转具体页面。

这边是通过jira issue 筛选器获取到URL

status = 已解决 AND assignee in (康丽婷) ORDER BY priority DESC, updated DESC

 将jira issue页面筛选后的url进行解码,获得具体地址

UrlEncode编码/UrlDecode解码 - 站长工具

三、server RESTful API

(一)钉钉机器人 webhook 对接

1.封装

dingtalk-rebot.js

'use strict'
const assert = require('assert')
const exec = require( 'child_process' ).exec

class DingTalkRobot {
  constructor (accessToken) {
    assert(accessToken, 'accessToken is necessary!')
    this.accessToken = accessToken
  }
  
  /**
   * text 类型
   * text String 必填 文本内容
   * isAtAll 选填 是否抄送所有人
   */
  sendText(text = '', isAtAll = false) {
    this.send({
      msgtype: 'text',
      text: {
        content: text
      },
      at: {
        isAtAll: isAtAll
      }
    })
  }

  /**
   * link类型
   * text String 必填 文本内容
   * title String 必填 消息标题
   * picUrl String 必填 展示图片
   * messageUrl String 必填 点击消息跳转的URL
   */
  sendLink(linkObject) {
    this.send({
      msgtype: 'link',
      link: linkObject
    })
  }

  /**
   * 发布markdown 消息
   * {
   *  "title":"杭州天气",
   *  "text": "#### 杭州天气\n"
   *  }
   * @param markdownContent
   */
  sendMarkdown(markdownContent) {
    const { title = '无题', text = ''} = markdownContent
    this.send({
      msgtype: 'markdown',
      markdown: {
        title,
        text
      }
    })
  }

  send(contentBody) {
    exec( `
      curl 'https://oapi.dingtalk.com/robot/send?access_token=${ this.accessToken }' \
       -H 'Content-Type: application/json' \
       -d '${ JSON.stringify(contentBody) }'
    `)
  }
}

module.exports = DingTalkRobot

2.引入
dingtalk-rebot.js

var DingTalkRobot=require('../dingtalk-rebot')# 引入封装文件dingtalk-rebot.js
let robot = new DingTalkRobot('accesstoken')

robot.sendText('jirabug hello world')
// robot.sendText('hello ding talk', true) // 抄送所有人


robot.sendLink({
    text: 'jirabug hello dingtalk',
    title: 'hello wold',
    picUrl: 'https://placeholdit.imgix.net/~text?txtsize=14&txt=FreeGroup.org+Cool&w=800&h=600',
    messageUrl: 'http://baidu.com'
  })

accesstoken替换成钉钉机器人webhook url上面的accesstoken

(二)JIRA Client RESTful API

1.获取指定账号下jira bug数据

// With ES5
var JiraApi = require('jira-client');
var jira = '';
var fs = require('fs');
describe('JIRA API', async () => {
	beforeEach('Initialize', async () => {
		// Initialize
		jira = new JiraApi({
			protocol: 'https',
			host: 'jira.homeking365.com', // jira域名
			username: 'jira账号',
			password: 'jira密码',
			apiVersion: '2',
			strictSSL: true
		});
	});

	/**
	 * ES6
	 * @description 获取指定账号提交的issue (获取某个账号所提交的所有bug)
	 * @param username: string
	 * @param open: boolean issue状态是否打开
	 * @method getUsersIssues(username: string, open: boolean): *
	 * @returns object {"startAt":0,"maxResults":50,"total":0,"issues":[]}
	*/
	it('getUsersIssues', async () => {
		try {
			var result = await jira.getUsersIssues('liyc', false);
			// await console.log(`getUsersIssues: ${JSON.stringify(result.issues)}`);
			// 筛选bug
			var bugListOpen = []; // 激活
			var bugListClose = []; // 已关闭
			var bugListResolve = []; // 已解决
			var bugListReopen = []; // 重新打开
			// bug分类统计
					for (const iterator of result.issues) {
						switch (iterator.fields.status.name) {
							case '激活':
								bugListOpen.push(iterator);
								break;
							case '已关闭':
								bugListClose.push(iterator);
								break;
							case '已解决':
								bugListResolve.push(iterator);
								break;
							default:
								bugListReopen.push(iterator);
								break;
						}
					}
					// 激活 数量
					console.log('激活 bug数量:', bugListOpen.length);
					// 已解决 数量
					console.log('已解决 bug数量:', bugListResolve.length);
					// 已关闭 数量
					console.log('已关闭 bug数量:', bugListClose.length);
					// bugListReopen 数量
					console.log('bugListReopen bug数量:', bugListReopen.length);
		} catch (error) {
			await console.log(`getUsersIssues error:${error}`);
		}
	});
	
});

四、应用配置

(一)钉钉

1.钉钉机器人配置

(1)使用Webhook

(2)选择关键字,输入关键字

(3)获得WebHook 

(二)飞书机器人 webhook 对接

 1.新建自定义机器人

飞书群设置>添加机器人>自定义机器人>

2.安全设置

自定义关键词,当发送内容含有自定义关键词,才能成功发送钉钉机器人消息到群里!

3.lib库

(1)crypto.js

const crypto = require('crypto');
/**
 * 签名
 * @param {string} timestamp - 10位时间戳
 * @param {string} secret - 密钥
 * @returns {string} - 签名
 */
exports.genSign = function (timestamp, secret) {
    const key = `${timestamp}\n${secret}`;
    const sign = crypto.createHmac('sha256', key).update('').digest('base64');
    return sign;
}

(2)index.js

const Robot = require('./robot');
const utils = require('./utils');
module.exports = function (option) {

    checkInput(option);

    const { webhook, secret } = option;
    
    return new Robot(webhook, secret);
};

/**
 * 参数检查
 * @param {string} option.webhook
 * @param {string?} option.secret
 */
function checkInput(option) {
    const { webhook, secret } = option;
    if(!utils.exist(webhook) || !utils.isString(webhook)) {
        throw new TypeError('webhook is required and expect string');
    }
    try {
        new URL(webhook);
    } catch (err) {
        throw new TypeError('webhook expect URL');
    }
    if (utils.exist(secret) && !utils.isString(secret)) {
        throw new TypeError('secret expect string');
    }
}

(3)robot.js

const crypto = require('./crypto');
const utils = require('./utils');
const simpleClient = require('simple-client');



/**
 * 飞书群聊机器人
 * @param {string} webhook - webhook地址
 * @param {string} secret - 密钥
 * @return {Robot}
 */
function Robot(webhook, secret) {
    this.webhook = webhook;
    this.secret = secret;
}

/**
 * 发送文本消息
 * @param {string} text 文本
 * @return {Promise} - resolve({ statusCode, statusMessage, data }), reject(err);
 */
Robot.prototype.sendText = function (text) {
    const data = {
        msg_type: 'text',
        content: {
            text: text
        }
    };
    return this.send(data);
}

/**
 * 发送富文本消息
 * @param {string} title 标题
 * @param {array[]} texts 富文本消息按照飞书格式数组中每个元素是数字,代表一行数据 element: [{ tag, text, ... }]
 * @param {string} language - 语言默认中文zh_cn
 * @return {Promise} - resolve({ statusCode, statusMessage, data }), reject(err);
 */
Robot.prototype.sendRickText = function (title, texts, language) {
    const defaultLanguage = 'zh_cn';
    language = utils.exist(language) ? language : defaultLanguage;
    const data = {
        msg_type: 'post',
        content: {
            post: {
                [language]: {
                    title: title,
                    content: texts
                }
            }
        }
    };
    return this.send(data);
}

/**
 * 发送消息
 * @param {object} data - 对应飞书消息格式
 * @returns {Promise} - resolve({ statusCode, data }), reject(err);
 */
Robot.prototype.send = function(data) {
    if (utils.exist(this.secret)) {
        const timestamp = utils.genTimeStamp();
        const sign = crypto.genSign(timestamp, this.secret);
        data.timestamp = timestamp;
        data.sign = sign;
    }
    return simpleClient.post(this.webhook, { body: data });
}

module.exports = Robot;

(4)utils.js

/**
 * @method 生成10位时间戳
 * @returns string 时间戳
 */
 function genTimeStamp() {
    let timestamp = Date.now();
    return String(timestamp).slice(0, 10);
}

function exist(value) {
    return null != value;
}

function isString(value) {
    return typeof value === 'string';
}

module.exports = {
    genTimeStamp,
    exist,
    isString,
}

(5)test-feishu-robot.js

// 飞书群机器人webhook
var webhook;
// 引入飞书机器人
var robot;

// setup
before("setup", () => {
	// 飞书群机器人webhook
	webhook = 'https://open.feishu.cn/open-apis/bot/v2/hook/xxx';
	// 引入飞书机器人
	robot = require('../../src/feishu/lib/index.js')({
		webhook
	});
});

// teardown
after("teardown",()=>{
    console.log("done")
})

describe('发送消息', async () => {
	it('普通文本', async () => {
		try {
			var data = await robot.sendText('JIRA bug');
			console.log(data);
		} catch (error) {
			console.log(error);
		}
	});
	it('富文本', async () => {
		var message = {
			title: 'JIRA bug 日常提醒',
			texts: [
				[
					{ tag: 'text', text: '张三:其余标签请' },
					{
						tag: 'a',
						text: '点击链接',
						href: 'https://open.feishu.cn/document/ukTMukTMukTM/uMDMxEjLzATMx4yMwETM'
					}
				]
			]
		};

		try {
			var data = robot.sendRickText(message.title, message.texts, 'en_us');
			console.log(data);
		} catch (error) {
			console.log(error);
		}
	});
});

// 富文本
/**
(async () => {
	try {
		var data = robot.sendRickText(message.title, message.texts, 'en_us');
		console.log(data);
	} catch (error) {
		console.log(error);
	}
})()
 *
 */

4.相关文档

(1)旧版

发送消息卡片

发送文本

发送图片

发送富文本

查询消息已读状态

(2)新版

飞书开放平台

(三)其他应用配置方式

【语雀】

https://jingyan.baidu.com/article/c1a3101ebcc3d19f656deb8c.html

【gitee】

https://gitee.com/help/articles/4135

【github】

https://blog.csdn.net/Q563573095/article/details/79580249

【gitlab】

https://blog.csdn.net/keep_learn/article/details/105136144

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liyinchi1988

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值