飞书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. 极客时间「大师课·深度剖析 RocketMQ5.0」上线啦,欢迎免费领取!

    从初代开源消息队列崛起,到 PC 互联网.移动互联网爆发式发展,再如今 IoT.云计算.云原生引领了新的技术趋势,消息中间件的发展已经走过了 30 多个年头. 目前,消息中间件在国内许多行业的关键应用 ...

  2. CentOS8安装RabbitMQ3.8.16

    之前安装过旧版的RabbitMQ和Erlang,先卸载. ①:卸载RabbitMQ /sbin/service rabbitmq-server stop yum list | grep rabbitm ...

  3. pwn V8入门

    V8入门 && StarCTF oob 搭建环境的步骤如下: 环境搭建 #depot_tools git clone https://chromium.googlesource.com ...

  4. 经典强化学习算法:分层强化学习算法—options算法2(理解篇)

    论文地址: https://people.cs.umass.edu/~barto/courses/cs687/Sutton-Precup-Singh-AIJ99.pdf 例子: 这是一个寻路问题,该问 ...

  5. 3-3 C++ vector类型

    目录 3.3.0 模板(Template) vector说明 模板简介 3.3.1 vector的定义和初始化 初始化的方式 总结初始化 3.3.2 往vector中添加元素 3.3.3 vector ...

  6. 轻松上云怎么操作?IoT_CLOUD之中移OneNET

    ​  最近来了很多新朋友,也经常被问:可以多讲些云平台的操作吗?当然可以!文末留言你想要了解的云平台,优先安排~ 接下来,本文将以Air780E+LuatOS作为示例,教你使用合宙IoT_CLOUD连 ...

  7. NOIP2023模拟9联测30 T3 高爸

    NOIP2023模拟9联测30 T3 高爸 三分啊,三分-- 思路 设现在的平均力量值为 \(x\),大于 \(x\) 力量值的龙有 \(n\) 条,小于等于的龙有 \(m\) 条,花费为: \[a( ...

  8. 5. Spring Cloud OpenFeign 声明式 WebService 客户端的超详细使用

    5. Spring Cloud OpenFeign 声明式 WebService 客户端的超详细使用 @ 目录 5. Spring Cloud OpenFeign 声明式 WebService 客户端 ...

  9. AbstractQueuedSynchronizer源码解析之ReentrantLock(二)

    上篇文章分析了ReentrantLock的lock,tryLock,unlock方法,继续分析剩下的方法,首先开始lockInterruptibly,先看其API说明:lockInterruptibl ...

  10. Django消息队列之django-rq

    github:https://github.com/rq/django-rq RQ(Redis Queue),人如其名,用 redis 做的队列任务 redis ,众所周知, 它的列表可以做队列,rq ...