飞书lark机器人 自动化发版

#1 介绍

  • 开发飞书机器人接收消息并调用构建接口, 实现自动化发版

  • 发送指令 -> 机器人接收指令 -> 调用jenkins-job远程构建与部署

  • jenkins配置,勾选job配置的触发远程构建并设置身份验证令牌

#测试 触发远程构建
curl -ks -u user:user_token -X POST \
jenkins_url/job/job_name/buildWithParameters?token=job_token

#2、创建机器人

#2.1 登录开放平台

飞书 https://open.feishu.cn/

lark https://open.larksuite.com/

lark是飞书国际版

#2.2 创建应用
  • 创建应用 -> 创建企业自建应用cici ->添加应用能力机器人
  • 凭证与基础信息 -> 复制App IDApp Secret
  • 事件与回调 -> 加密策略 -> 复制Verification Token
  • 权限管理,添加如下权限:
    • 获取与更新群组信息
    • 以应用的身份发消息
    • 接收群聊中@机器人消息事件
#2.3 运行机器人服务

配置环境变量文件.env_lark

#vim .env_lark
APP_ID=cli_a7e8508040f99999
APP_SECRET=0iD0HYbmUPrI9aHfHX0NyhL0fy699999
VERIFICATION_TOKEN=vk0SOUPy8MViGxVesPJSAeI5wA799999
ENCRYPT_KEY=""
LARK_HOST=https://open.larksuite.com
#FLASK_ENV=production
JenkinsBaseUrl=https://user:user_token@jenkins.elvin.vip/job/

使用docker启动机器人服务

docker rm -f robot-lark &>/dev/null
docker run -dit --name robot-lark \
--restart=always -h robot-lark --net=host\
-v $(pwd):/opt --env-file .env_lark \
registry.aliyuncs.com/elvin/python:lark-robot \
python3 /opt/lark-robot.py

lark-robot.py实例在https://gitee.com/alivv/elvin-demo

nignx配置域名和lark反向代理

#lark
location ~ ^/(url_verification|lark-cicd) {
proxy_pass http://127.0.0.1:8092;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
#2.4 发布应用

2.4.1 配置事件与回调

2.4.2 版本管理与发布,创建版本,申请线上发布


#3 发送消息测试

  • 创建lark群,添加机器人,发送消息测试


#4 源码

python实例如下:

#!/usr/bin/env python3
#lark-robot.py import os
import logging
import json
import uuid
from flask import Flask, request, jsonify
import lark_oapi as lark
from lark_oapi.api.im.v1 import CreateMessageRequest, CreateMessageRequestBody
from datetime import datetime, timedelta
import requests # 创建 Flask 应用实例
app = Flask(__name__) # 配置日志
logger = logging.getLogger('lark-robot')
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler) # 存储已经处理过的 request_id
processed_request_ids = set() # 处理所有请求的前置方法
@app.before_request
def handle_all_requests():
path = request.path
if path == '/url_verification' or path == '/lark-cicd':
return None # 让请求继续传递给相应的路由处理
else:
if 'X-Forwarded-For' in request.headers:
ip = request.headers['X-Forwarded-For'].split(',')[0]
else:
ip = request.remote_addr
return ip + "\n", 200, [("Server", "Go"), ("City", "Shanghai")] # URL 验证接口
@app.route('/url_verification', methods=["POST"])
def url_verification():
req = request.json
if req.get("token") != VERIFICATION_TOKEN:
raise Exception("VERIFICATION_TOKEN is invalid")
return jsonify({"challenge": req.get("challenge")}) # 主业务逻辑接口
@app.route('/lark-cicd', methods=["POST"])
def index():
req = request.json
request_id = str(uuid.uuid4())
# logger.info(f"Received request with ID: {request_id}, data: {req}")
header = req.get("header", {})
event_type = header.get("event_type")
create_time = header.get("create_time") if req.get("type") == "url_verification":
return url_verification()
elif event_type == "im.message.receive_v1":
event = req.get("event")
message = event.get("message")
group_id = message.get("chat_id")
msg_content = json.loads(message.get("content")).get("text").split('@')[0]
msg_content = msg_content.rstrip() # 检查 request_id 是否已经被处理过
if request_id in processed_request_ids:
logger.info(f"Request ID: {request_id} - Request already processed")
return "succeed"
else:
processed_request_ids.add(request_id) # 标记 request_id 为已处理
if create_time: #检查消息是否在10秒以内
try:
create_time_dt = datetime.fromtimestamp(int(create_time) / 1000) # 转换为datetime对象
current_time_dt = datetime.now()
if current_time_dt - create_time_dt > timedelta(seconds=10):
logger.info(f"Request: {request_id} - {msg_content} - Message is too old")
return "succeed"
except ValueError:
logger.error(f"Request ID: {request_id} - Invalid create_time format")
return "succeed"
else:
logger.error(f"Request ID: {request_id} - Missing create_time")
return "succeed" if msg_content: # 检查 msg_content 是否为空
msg_name = next((mention.get("name") for mention in message.get("mentions", []) if mention.get("name")), None)
logger.info(f"Msg: {msg_content} @{msg_name}")
response_content = f"已收到 \n{msg_content}"
# send_event_message(group_id, response_content)
msg_cicd(group_id, msg_content)
return "succeed"
else:
logger.warning(f"Request ID: {request_id} - message content is empty")
return "succeed"
else:
logger.warning(f"Request ID: {request_id} - Unsupported event type: {event_type}")
return "succeed" # 发送消息到群聊
def send_event_message(group_id, response_content):
client = lark.Client.builder() \
.app_id(APP_ID) \
.app_secret(APP_SECRET) \
.domain(LARK_HOST) \
.enable_set_token(True) \
.log_level(lark.LogLevel.ERROR) \
.build() request_body = CreateMessageRequestBody.builder() \
.receive_id(group_id) \
.msg_type("text") \
.content(json.dumps({"text": response_content})) \
.uuid(os.urandom(16).hex()) \
.build() request = CreateMessageRequest.builder() \
.receive_id_type("chat_id") \
.request_body(request_body) \
.build() response = client.im.v1.message.create(request) if not response.success():
lark.logger.error(
f"client.im.v1.message.create failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}, resp: \n{json.dumps(json.loads(response.raw.content), indent=4, ensure_ascii=False)}")
return lark.logger.info(lark.JSON.marshal(response.data, indent=4))
return "succeed" ##########
#cicd #筛选消息,执行指令
def msg_cicd(group_id,text):
msg = text
#print(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "msg->: ", msg) #check group
#test oc_492604c3bb7382afeb47448b726e0a7d
if group_id != "oc_492604c3bb7382afeb47448b726e0a7d__":
appInfoMap = dict(appProd, **appProdTest)
myMenu = {"help", "prod", "test"}
L = msg.split(" ")
L = list(filter(lambda x: x != '', L))
Len = len(L)
if msg in appInfoMap:
app_env = appInfoMap[msg][0]
app_name = appInfoMap[msg][1]
if msg.startswith("b"):
app_url = appInfoMap[msg][2] + appInfoMap[msg][0]
else:
app_url = appInfoMap[msg][2]
app_url = app_url + app_env + "&app_list=" + app_name
if app_env != "":
#执行通知
msg = "env: %s\napp %s" % (app_env, app_name)
send_event_message(group_id, msg) #向webhook发起post请求
head = { 'User-Agent': "webhook-robot" }
res = requests.post(url=app_url, headers=head)
print(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "webhook", app_env, app_name, res.reason)
return "succeed"
else:
print(msg, "nothing")
return "succeed"
elif msg in myMenu:
#打印命令列表
print(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "Send menu")
msgTitle = "#命令 名称\n"
if msg == "help":
msgTitle2 = "#命令 获取列表\n"
msg = msgTitle2 + "prod app-prod-list\ntest app-test-list"
elif msg == "prod":
msg = msgTitle
for i in appProd:
msg = msg + i + " " + appInfoMap[i][1] + "\n"
elif msg == "test":
msg = msgTitle
for i in appProdTest:
msg = msg + i + " " + appInfoMap[i][1] + "\n"
msg = msg.rstrip('\n')
send_event_message(group_id, msg)
return "succeed"
#多个app部署
elif Len > 1:
app = ""
apps = ""
app_env = ""
for n in L:
if n in appInfoMap:
app_name = appInfoMap[n][1]
app = app + app_name + " \n"
apps = apps + app_name + " "
app_env = appInfoMap[n][0]
app_url = appInfoMap[n][2]
if app_env != "":
#执行通知
app = app.rstrip('\n')
msg = f"env: {app_env}\napp-list: \n{app}"
send_event_message(group_id, msg) #向webhook发起post请求
app_url = app_url + app_env + "&app_list=" + app
head = { 'User-Agent': "webhook-robot" }
res = requests.post(url=app_url, headers=head)
print(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "webhook", app_env, apps, res.reason)
return "succeed"
else:
msg = f"已收到: \n{msg} \n发送 help@cici 查看支持指令"
send_event_message(group_id, msg)
return "succeed"
else:
msg = f"已收到: \n{msg} \n发送 help@cici 查看支持指令"
send_event_message(group_id, msg)
return "succeed" else:
print("group_id no found",group_id)
return "succeed" # 从环境变量加载配置
APP_ID = os.getenv("APP_ID")
APP_SECRET = os.getenv("APP_SECRET")
VERIFICATION_TOKEN = os.getenv("VERIFICATION_TOKEN")
LARK_HOST = os.getenv("LARK_HOST", "https://open.larksuite.com") ##########
#cicd list #webhook url for jenkins
JenkinsBaseUrl = os.getenv("JenkinsBaseUrl") #job
appDeploy = "test-app-deploy/buildWithParameters?token=cicdTest&app_branch=master&app_build=true&docker_build=true&create_git_tag=false&notice_msg=true&app_deploy=true&image_update=true&input_pass=true&deploy_tag=tag&deploy_env=" #ci url
appDeployUrl = JenkinsBaseUrl + appDeploy appProd = {
"#app-prod-k8s-list:": ["","", ""],
"s101": ["prod","app-web", appDeployUrl],
"s102": ["prod","app-svc", appDeployUrl],
"s103": ["prod","app-api", appDeployUrl],
"s104": ["prod","app-event", appDeployUrl],
"s105": ["prod","app-admin", appDeployUrl],
} appProdTest = {
"#app-test-k8s-list:": ["","", ""],
"s201": ["test","app-web", appDeployUrl],
"s202": ["test","app-svc", appDeployUrl],
"s203": ["test","app-api", appDeployUrl],
"s204": ["test","app-event", appDeployUrl],
"s205": ["test","app-admin", appDeployUrl],
} ########## # 启动 Flask 应用
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8092, debug=False)

source: https://gitee.com/alivv/elvin-demo

飞书lark机器人 自动化发版的更多相关文章

  1. CICD自动化发版系统设计简介

    第一篇. 版本迭代是每一个互联网公司必须经历的,尤其是中小型公司,相信不少人踩到过很多坑.接下来的一系列文章将介绍我设计的自动化发版系统! 很多公司没有把配置独立出去,代码的构建.发版通过一个Jenk ...

  2. CI/CD自动化发版系统设计简介

    转载自:https://www.cnblogs.com/wellful/archive/2004/01/13/10604151.html 版本迭代是每一个互联网公司必须经历的,尤其是中小型公司,相信不 ...

  3. 基于travis和git tag 实现npm自动化发版

    最近又把烂尾的开源项目alfred-femine拾起来了,这个项目旨在开发一系列前端常用的alfred workflow,提供前端开发的查询效率.时隔这么久,再次搞起,希望自己能够一直维护下去,也欢迎 ...

  4. Jenkins+Allure测试报告+飞书机器人发送通知

    一.前言 之前讲了jenkins如何设置定时任务执行脚本,结合实际情况,本篇讲述在jenkins构建成功后,如何生成测试报告,以及推送飞书(因为我公司用的是飞书,所以是发送到飞书机器人). 本次实践搞 ...

  5. Jenkins发版通知企业微信机器人

    1)开始通知 在Jenkins发版过程的第一步添加下面内容,调用下面脚本实现机器人发版通知(注意脚本路径和传参) ${BUILD_USER}是Jenkins内置变量,执行发布的用户名,需要安装插件-B ...

  6. JustAuth 1.15.9 版发布,支持飞书、喜马拉雅、企业微信网页登录

    新增 修复并正式启用 飞书 平台的第三方登录 AuthToken 类中新增 refreshTokenExpireIn 记录 refresh token 的有效期 PR 合并 Github #101:支 ...

  7. GPT接入飞书

    GPT接入飞书 在体验ChatGPT这方面,我算是晚的.使用下来,更多的是对于这种应用形式感到兴奋,而不是ChatGPT的专业能力. 得知OpenAI提供GPT3的Api接口后,我想到了将其接入团队飞 ...

  8. Python发送飞书消息

    #!/usr/bin/python3.8 # -*- coding:UTF-8 -*- import os, sys sys.path.append(os.path.dirname(os.path.a ...

  9. jenkins中通过git发版操作记录

    之前说到的jenkins自动化构建发版是通过svn方式,今天这里介绍下通过git方式发本的操作记录. 一.不管是通过svn发版还是git发版,都要首先下载svn或git插件.登陆jenkins,依次点 ...

  10. java生产环境增量发版陷阱【原】

    前言 在生产环境,我们为了降低发版风险,一般都只做增量发布,不做全量发布. 除非项目只有一到两人开发,对时间线和代码脉络结构一清二楚,才可全量发布. 然而增量发布也是有一定隐藏陷阱在里面的,以下就是笔 ...

随机推荐

  1. 什么是前后端分离应用(Full-stack Separation),想当然就会理解错

    前后端分离应用指的是将应用的前端部分(用户界面与交互逻辑)和后端部分(业务逻辑.数据处理.服务器响应)拆分成独立的模块,各自通过 API 进行通信.这种架构设计的目的是提高开发效率.增强可扩展性和灵活 ...

  2. PHP伪协议(PHP://、Pseudo-Protocols)和其他常用协议

    介绍 在PHP中,"伪协议" 是一种特殊的协议,它并不涉及传统的网络传输,而是用于访问特定的PHP功能或资源.这些伪协议通常以 php:// 开头,并用于操作数据流.内存.进程的输 ...

  3. 买游戏本玩战锤40K ?ToDesk云电脑教你2元升级旧电脑,省钱!

    <战锤40K:星际战士>终于出续作了!不得不说这款多人射击游戏的热度实在太高啦,刚发布两天就登顶Steam销量第一名. <战锤40K:星际战士2>不仅继承了前作的精髓,更在画面 ...

  4. 在昇腾Ascend 910B上运行Qwen2.5推理

    目前在国产 AI 芯片,例如昇腾 NPU 上运行大模型是一项广泛且迫切的需求,然而当前的生态还远未成熟.从底层芯片的算力性能.计算架构的算子优化,到上层推理框架对各种模型的支持及推理加速,仍有很多需要 ...

  5. 开源 - Ideal库 - 常用枚举扩展方法(二)

    书接上回,今天继续和大家享一些关于枚举操作相关的常用扩展方法. 今天主要分享通过枚举值转换成枚举.枚举名称以及枚举描述相关实现. 我们首先修改一下上一篇定义用来测试的正常枚举,新增一个枚举项,代码如下 ...

  6. SpringBoot进阶教程(八十三)Kaptcha

    Kaptcha是谷歌开源的一个可高度配置的比较老旧的实用验证码生成工具.它可以实现:(1)验证码的字体/大小颜色:(2)验证码内容的范围(数字,字母,中文汉字):(3)验证码图片的大小,边框,边框粗细 ...

  7. 国密SSL证书,为政务数据安全保驾护航

    随着数字化转型的加速,政务信息化建设已成为提升政府服务效率和质量的关键.近期,国家相关部门发布了<互联网政务应用安全管理规定>,为政务应用的安全管理提供了明确的规范和要求.该规定自2024 ...

  8. python数据结构的性能分析

    2.python数据结构的性能分析 一.引言 - 现在大家对 大O 算法和不同函数之间的差异有了了解.本节的目标是告诉你 Python 列表和字典操作的 大O 性能.然后我们将做一些基于时间的实验来说 ...

  9. json数据按照某一个相同键值进行分类成一个新的二维json数组

    1 formatTreeData(checkNodes){ 2 var map = {}, 3 targetData = []; 4 checkNodes.forEach(item => { 5 ...

  10. Rework:每个程序员都应该读的一本书

    来源: 萌萌的博客 每一个程序员都有改变世界的梦想,他们不甘平凡,他们想要与众不同,他们想要创立世界上最酷的公司,那具体该如何做呢?风靡全球的<Rework>将告诉你答案. 37signa ...