import requests
import json
from jira import JIRA
from datetime import datetime
import time
import re
import pendulum
import argparse
import urllib3
# 禁用不安全请求的警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# 全局配置
app_id = "cli_a777034febf3100b"
app_secret = "ppbxuNwzB1FTp7Ta3IwhnbRajiOhNd5m"
bitableToken = "YtHSwKBLaidtCEkU8HncCenanlg"
sheetToken = "Dh19wciIliMOTXkL1gYcWx9anXb"
jira_user = "DrivingSysBot"
jira_password = "DSBTdsbt@123456"
remote_server = 'http://10.64.250.107:8086/jira'
# 飞书API认证
def getAccessToken(app_id, app_secret):
url = "https://10.64.250.107:8090/open-apis/auth/v3/tenant_access_token/internal"
headers = {
"Content-Type": "application/json; charset=utf-8"
}
params = {
"app_id": app_id,
"app_secret": app_secret
}
# 禁用SSL验证
response = requests.post(url, headers=headers, json=params, verify=False)
message = json.loads(response.text)
tenant_access_token = ""
if message['code'] == 0:
print("获取AccessToken成功")
tenant_access_token = message['tenant_access_token']
else:
print("获取AccessToken失败")
print(message['code'])
print(message['msg'])
exit(0)
return tenant_access_token
# 获取知识库节点token
def getAppToken(AuthorizationCode, token):
url = "https://10.64.250.107:8090/open-apis/wiki/v2/spaces/get_node"
headers = {
"Authorization": AuthorizationCode,
"Content-Type": "application/json; charset=utf-8"
}
params = {
"token": token
}
# 禁用SSL验证
response = requests.get(url=url, headers=headers, params=params, verify=False)
message = json.loads(response.text)
obj_token = ""
if message['code'] == 0:
if message['data']['node']['obj_type'] == "bitable":
obj_token = message['data']['node']['obj_token']
if message['data']['node']['obj_type'] == "sheet":
obj_token = message['data']['node']['obj_token']
else:
print("获取知识库节点token失败")
print(message['code'])
print(message['msg'])
exit(0)
return obj_token
# 获取所有工作表
def getAllSheets(AuthorizationCode, app_token):
url = f"https://10.64.250.107:8090/open-apis/sheets/v3/spreadsheets/{app_token}/sheets/query"
headers = {
"Authorization": AuthorizationCode,
"Content-Type": "application/json; charset=utf-8"
}
# 禁用SSL验证
response = requests.get(url=url, headers=headers, verify=False)
message = json.loads(response.text)
AllSheets = {}
if message['code'] == 0:
for sheet in message['data']['sheets']:
AllSheets[sheet['title']] = sheet['sheet_id']
else:
print("获取数据表失败")
print(message['code'])
print(message['msg'])
exit(0)
return AllSheets
# 新增工作表
def addNewSheet(AuthorizationCode, app_token, sheetName, templateSheetId):
url = f"https://10.64.250.107:8090/open-apis/sheets/v2/spreadsheets/{app_token}/sheets_batch_update"
headers = {
"Authorization": AuthorizationCode,
"Content-Type": "application/json; charset=utf-8"
}
params = {
"requests": [
{
"copySheet": {
"source": {
"sheetId": templateSheetId
},
"destination": {
"title": sheetName
}
}
}
]
}
# 禁用SSL验证
response = requests.post(url, headers=headers, json=params, verify=False)
message = json.loads(response.text)
sheet_id = ""
if message['code'] == 0:
print("创建电子表格成功")
sheet_id = message['data']['replies'][0]['copySheet']['properties']['sheetId']
else:
print("创建电子表格失败")
print(message['code'])
print(message['msg'])
exit(0)
return sheet_id
# 提取step3结论
def extract_step3_conclusion(text):
# 定义正则表达式模式,用于匹配step3:问题结论->后的内容
pattern = r'step3:问题结论->(.*?)step4:'
# 使用非贪婪模式匹配,确保只匹配到最近的step4
match = re.search(pattern, text, re.DOTALL | re.IGNORECASE)
if match:
# 提取匹配的内容
content = match.group(1).strip()
# 按行分割内容
lines = content.split('\n')
# 过滤掉空行和不包含有效内容的行
valid_lines = [line for line in lines if line.strip().startswith('*')]
# 处理每行,移除星号和多余的空格
processed_lines = [line.strip('* ').strip() for line in valid_lines]
# 重新组合成字符串
return '\n'.join(processed_lines)
else:
if "未发现降级类问题" in text:
return "未发现降级类问题"
else:
# 如果没有匹配到,返回原字符串
return "结论异常请返回jira查看"
# 设置下拉列表
def set_dropdown_list(tenant_access_token, spreadsheet_token, sheet_id, start_row, end_row, column_index, dropdown_values, multiple_values=True):
"""为指定范围设置下拉列表"""
url = f"https://10.64.250.107:8090/open-apis/sheets/v2/spreadsheets/{spreadsheet_token}/dataValidation"
headers = {
"Authorization": f"Bearer {tenant_access_token}",
"Content-Type": "application/json"
}
# 构建数据验证规则
data = {
"range": {
"sheetId": sheet_id,
"startRowIndex": start_row,
"endRowIndex": end_row,
"startColumnIndex": column_index,
"endColumnIndex": column_index + 1
},
"dataValidation": {
"conditionValues": dropdown_values,
"options": {
"multipleValues": multiple_values,
}
}
}
try:
# 禁用SSL验证
response = requests.post(url, headers=headers, json=data, verify=False)
result = response.json()
if result.get("code") == 0:
print(f"成功为 {sheet_id} 第{start_row+1}-{end_row}行第{column_index+1}列设置下拉列表")
else:
print(f"为 {sheet_id} 设置下拉列表失败: {result}")
except Exception as e:
print(f"设置下拉列表时发生错误: {e}")
# 添加数据到工作表
def addSheetData(AuthorizationCode, app_token, sheet_id, JIRAs, start_row=1):
"""
将JIRA数据添加到表格中
Args:
AuthorizationCode: 授权码
app_token: 表格token
sheet_id: 工作表ID
JIRAs: JIRA对象列表
start_row: 开始写入的行号,默认为1(第二行)
"""
url = f"https://10.64.250.107:8090/open-apis/sheets/v2/spreadsheets/{app_token}/values"
headers = {
"Authorization": AuthorizationCode,
"Content-Type": "application/json; charset=utf-8"
}
# 对象转换为列表数据
records = []
count = start_row
# 收集所有可能的值用于下拉列表
func_values = set()
priority_values = set()
type_values = set()
module_values = set()
source_values = set()
for jira in JIRAs:
try:
temp = []
jiraLink = {
"text": jira.jira_id,
"link": jira.jira_link,
"type": "url"
}
if None == jira.conclusion:
raise ValueError("JIRA结论为空")
labels = jira.labels
print(labels)
description = jira.description
count += 1
temp.append(f'{count - 1}') # 序号
temp.append(jiraLink) # JIRA链接
timeHappen = labels[0]
temp.append(timeHappen) # 问题发生日期
line = ''
if "测试路线:" in jira.description:
line = jira.description.split("测试路线:")[1].split("\n")[0].strip()
elif "测试路线: " in jira.description:
line = jira.description.split("测试路线: ")[1].split("\n")[0].strip()
temp.append(line) # 测试路线
func = ''
if "【CNOA】" in jira.outline:
func = "CNOA"
elif "【HNOA】" in jira.outline:
func = "HNOA"
temp.append(func) # 功能
if func:
func_values.add(func)
temp.append(jira.outline) # 概要
temp.append(jira.priority) # 问题等级
priority_values.add(jira.priority)
type = ''
if "问题描述:" in jira.description:
type = jira.description.split("问题描述:")[1].split("\n")[0].strip()
if "急刹" in type:
type = "急刹"
elif "降级" in type:
type = "降级"
elif "画龙" in type:
type = "画龙"
else:
type = "接管"
temp.append(type) # 问题类型
type_values.add(type)
source = ''
pattern0 = r'云端接收'
pattern1 = r'日常压测'
if re.search(pattern0, jira.outline):
source = "云端回传"
elif re.search(pattern1, jira.outline):
source = "日常压测"
else :
# match = re.search(r'【([^】]+)】', jira.outline)
# if match:
# source = match.group(1)
source = "日常压测"
temp.append(source) # 问题来源
if source:
source_values.add(source)
temp.append(jira.vehicleType) # 车型
temp.append(jira.version) # 版本
temp.append(jira.reasonModule) # 模块
module_values.add(jira.reasonModule)
temp.append(extract_step3_conclusion(jira.conclusion)) # 结论
temp.append('') # 分析人
temp.append('') # 测试问题分析
temp.append('') # 问题模块
temp.append('') # 优化方案简述
temp.append('') # SPM
temp.append('') # 研发责任人
temp.append('') # 计划改善时间
# temp.append("分析中") # 问题状态
records.append(temp)
except Exception as e:
print(f"处理JIRA对象 {getattr(jira, 'jira_id', '未知ID')} 时出错: {str(e)}")
continue
# 接口限制,单次写入数据不得超过5000行、100列 API.qps=100
if not records:
print("没有数据需要写入")
return 0, set(), set(), set(), set(), set()
# 确定写入范围
start_index = start_row # 从第start_row+1行开始写入
# 分批写入数据
rows_written = 0
for i in range(0, len(records), 5000):
batch = records[i:i + 5000]
params = {
"valueRange": {
"range": f"{sheet_id}!A{start_index + i + 1}:X", # X是最后一列的列号
"values": batch
}
}
try:
# 禁用SSL验证
response = requests.put(url, headers=headers, json=params, verify=False)
message = json.loads(response.text)
if message['code'] == 0:
print(f"成功添加 {len(batch)} 条数据")
rows_written += len(batch)
else:
print("添加数据失败")
print(message['code'])
print(message['msg'])
exit(0)
except Exception as e:
print(f"API请求失败: {str(e)}")
exit(0)
# 返回写入的行数和收集的下拉列表值
return rows_written, func_values, priority_values, type_values, module_values, source_values
# 获取工作表已有数据行数
def getSheetData(AuthorizationCode, app_token, sheet_id):
"""获取表格已有数据的行数"""
url = f"https://10.64.250.107:8090/open-apis/sheets/v2/spreadsheets/{app_token}/values/{sheet_id}!A:A"
headers = {
"Authorization": AuthorizationCode,
"Content-Type": "application/json; charset=utf-8"
}
try:
# 禁用SSL验证
response = requests.get(url, headers=headers, verify=False)
message = json.loads(response.text)
if message['code'] == 0 and 'valueRange' in message['data']:
values = message['data']['valueRange'].get('values', [])
# 减去表头行
row_count = len(values) - 1
print(f"表格已有 {row_count} 行数据")
return row_count
else:
print("获取表格数据失败")
print(message.get('code', '未知错误'))
print(message.get('msg', '未知错误'))
return 0
except Exception as e:
print(f"获取表格数据时发生错误: {e}")
return 0
# 搜索JIRA问题
def searchJiras(jiraObj, jql_query):
# 首先获取总数量
issues_count = jiraObj.search_issues(jql_query, maxResults=0).total
print(f"共找到 {issues_count} 条JIRA")
# 分批获取所有JIRA
all_issues = []
start_at = 0
batch_size = 500 # 每批获取的数量
max_retries = 3 # 最大重试次数
while start_at < issues_count:
retries = 0
success = False
while retries < max_retries and not success:
try:
print(f"正在获取第 {start_at+1}-{min(start_at+batch_size, issues_count)} 条JIRA...")
issues = jiraObj.search_issues(jql_query, startAt=start_at, maxResults=batch_size)
all_issues.extend(issues)
start_at += len(issues)
# 如果没有获取到任何问题,可能是网络问题或查询有误
if not issues:
print("警告:没有获取到任何问题,可能存在网络问题或查询有误")
break
success = True
except Exception as e:
retries += 1
print(f"获取JIRA时发生错误 ({retries}/{max_retries}): {e}")
print(f"已成功获取 {len(all_issues)} 条JIRA")
if retries < max_retries:
print(f"等待30秒后重试...")
time.sleep(30)
else:
print("达到最大重试次数,退出")
break
# 如果尝试了所有重试仍然失败,则退出循环
if not success:
break
print(f"成功获取所有 {len(all_issues)} 条JIRA")
# 组装Jira对象
JIRAs = []
creationDates = set()
for issue in all_issues:
# 处理概要中包含的信息
outline = issue.fields.summary
# 创建正则表达式模板
pattern = f"{re.escape('【')}(.*?){re.escape('】')}"
# 使用findall方法找到所有匹配项
matches = re.findall(pattern, outline)
if len(matches) == 0:
creationDate = None
source = ""
else:
if ',' in matches[-1]:
creationDate = matches[-1].split(',')[0]
elif ',' in matches[-1]:
creationDate = matches[-1].split(',')[0]
else:
creationDate = None
if creationDate is not None:
try:
datetime.strptime(creationDate, "%Y/%m/%d")
except ValueError:
creationDate = None
if "品质" in outline:
source = "品质"
elif "售后" in outline:
source = "售后"
elif "PMD" in outline:
source = "PMD"
elif "工程院软测" in outline:
source = "工程院软测"
elif "量产车回传" in outline:
source = "量产车回传"
else:
source = "泛化"
if len(issue.fields.versions) == 0:
version = None
else:
version = issue.fields.versions[0].name
if issue.fields.resolutiondate is None:
fixDate = None
else:
fixDate = issue.fields.resolutiondate.split('T')[0]
if len(issue.fields.comment.comments) == 0:
conclusion = ""
else:
conclusion = issue.fields.comment.comments[-1].body
# 获取【DP分析结论】
for comment in reversed(issue.fields.comment.comments):
if "【DP分析结论】" in comment.body:
conclusion = comment.body
break
if ( "非问题" in conclusion and "可关闭" in conclusion ) or "用户踩刹车/手力矩超限退出功能" in conclusion:
isValid = "非问题"
else:
isValid = "是问题"
# 组装jira对象
jira = Jira(
jira_id=issue.key,
jira_link="http://10.4.35.29:8090/browse/" + issue.key,
creationDate=creationDate,
outline=outline,
source=source,
status=issue.fields.status.name,
vehicleType=issue.fields.customfield_10300.value,
version=version,
reasonModule=issue.fields.components[0].name,
dealer=issue.fields.assignee.name if issue.fields.assignee else "",
priority=issue.fields.customfield_10200.value,
fixDate=fixDate,
conclusion=conclusion,
isValid=isValid,
labels=issue.fields.labels,
description=issue.fields.description
)
JIRAs.append(jira)
if creationDate is not None:
creationDates.add(creationDate)
return JIRAs, creationDates
# Jira到飞书数据同步主函数
def CNOA_Jira2Feishu(jql_query, batch_size=500, sheet_name_prefix=None, append_to_existing=True, dayago=0):
"""
处理JIRA数据并导入飞书电子表格,支持追加数据到现有表格
Args:
jql_query: JIRA查询语句
batch_size: 每批处理的JIRA数量,默认为500
sheet_name_prefix: 表格名称前缀,默认为查询日期
append_to_existing: 是否追加到现有表格,默认为True
dayago: 前推的天数,用于生成表格名称
"""
# 认证信息
jira_user = "DrivingSysBot"
jira_password = "DSBTdsbt@123456"
server = remote_server
# 获取访问令牌
access_token = getAccessToken(app_id, app_secret)
AuthorizationCode = "Bearer " + access_token
# 获取知识库下的电子表格app_token
app_token = getAppToken(AuthorizationCode, sheetToken)
# 获取所有数据表
AllSheets = getAllSheets(AuthorizationCode, app_token)
template_sheet_id = AllSheets['template(勿删)']
# 连接JIRA并获取所有问题
jiraObj = JIRA(server=server, auth=(jira_user, jira_password))
all_JIRAs, creationDates = searchJiras(jiraObj, jql_query)
if len(all_JIRAs) == 0:
print("搜索到JIRA条数为0, 无需新增电子表格和数据")
return None
# 生成基于查询日期的表名
local_tz = pendulum.timezone("Asia/Shanghai")
query_date = pendulum.now(local_tz).subtract(days=dayago)
date_str = query_date.strftime("%Y-%m-%d")
base_sheet_name = sheet_name_prefix or f"{date_str}汇总表"
# 检查是否已存在同名表格
sheet_id = None
start_row = 1 # 从第2行开始写入(索引从0开始,表头在第1行)
if append_to_existing:
# 查找已存在的表格(精确匹配表名)
for sheet_name, sheet_id_found in AllSheets.items():
if sheet_name == base_sheet_name: # 修改为精确匹配
sheet_id = sheet_id_found
print(f"找到已存在的表格: {sheet_name}")
# 获取已有数据的行数
existing_rows = getSheetData(AuthorizationCode, app_token, sheet_id)
if existing_rows > 0:
start_row = existing_rows
print(f"将从第 {start_row + 1} 行开始追加数据")
break
# 如果没有找到现有表格或不追加到现有表格,则创建新表格
if sheet_id is None:
print(f"创建新表格: {base_sheet_name}")
sheet_id = addNewSheet(AuthorizationCode, app_token, base_sheet_name, template_sheet_id)
print(f"处理 {len(all_JIRAs)} 条JIRA...")
# 收集所有可能的下拉列表值
all_func_values = set()
all_priority_values = set()
all_type_values = set()
all_module_values = set()
all_source_values = set()
# 接口限制,分批写入数据
total_batches = (len(all_JIRAs) + batch_size - 1)
current_row = start_row
for i in range(0, len(all_JIRAs), batch_size):
batch_JIRAs = all_JIRAs[i:i + batch_size]
batch_index = i
print(f"写入第 {batch_index}/{total_batches} 批数据,共 {len(batch_JIRAs)} 条JIRA...")
# 添加数据并获取写入的行数和下拉列表值
rows_written, func_values, priority_values, type_values, module_values, source_values = addSheetData(
AuthorizationCode, app_token, sheet_id, batch_JIRAs, current_row
)
# 更新下拉列表值集合
all_func_values.update(func_values)
all_priority_values.update(priority_values)
all_type_values.update(type_values)
all_module_values.update(module_values)
all_source_values.update(source_values)
# 更新当前行
current_row += rows_written
# 避免请求过于频繁
if i + batch_size < len(all_JIRAs):
print("等待2秒后处理下一批...")
time.sleep(2)
# 如果是新建的表格或追加了新的数据,更新下拉列表
if current_row > start_row:
# 为每列设置下拉列表
if all_func_values:
set_dropdown_list(
AuthorizationCode.split(" ")[1],
app_token,
sheet_id,
1, # 从第2行开始
current_row + 1, # 到最后一行
4, # E列 (索引从0开始)
list(all_func_values),
multiple_values=False
)
if all_priority_values:
set_dropdown_list(
AuthorizationCode.split(" ")[1],
app_token,
sheet_id,
1,
current_row + 1,
6, # G列
list(all_priority_values),
multiple_values=False
)
if all_type_values:
set_dropdown_list(
AuthorizationCode.split(" ")[1],
app_token,
sheet_id,
1,
current_row + 1,
7, # H列
list(all_type_values),
multiple_values=False
)
if all_source_values:
set_dropdown_list(
AuthorizationCode.split(" ")[1],
app_token,
sheet_id,
1,
current_row + 1,
8, # I列
list(all_source_values),
multiple_values=False
)
if all_module_values:
set_dropdown_list(
AuthorizationCode.split(" ")[1],
app_token,
sheet_id,
1,
current_row + 1,
11, # L列
list(all_module_values),
multiple_values=False
)
# 返回工作表的链接
print(f"成功{'追加到' if append_to_existing else '创建'}工作表,链接:")
print(f"https://upiwgvvcb4.feishu.cn/wiki/{sheetToken}?sheet={sheet_id}")
return f"https://upiwgvvcb4.feishu.cn/wiki/{sheetToken}?sheet={sheet_id}"
# Jira数据模型
class Jira:
def __init__(self, jira_id, jira_link, creationDate, outline, source, status,
vehicleType, version, reasonModule, dealer, priority, fixDate, conclusion, isValid, labels, description):
self.jira_id = jira_id
self.jira_link = jira_link
self.creationDate = creationDate
self.outline = outline
self.source = source
self.status = status
self.vehicleType = vehicleType
self.version = version
self.reasonModule = reasonModule
self.dealer = dealer
self.priority = priority
self.fixDate = fixDate
self.conclusion = conclusion
self.isValid = isValid
self.labels = labels
self.description = description
# 命令行入口
def main():
# 创建命令行参数解析器
parser = argparse.ArgumentParser(description='Jira到飞书数据同步工具')
parser.add_argument('--dayago', type=int, default=0,
help='指定前推的天数,默认为0,表示今天')
parser.add_argument('--batch-size', type=int, default=500,
help='每批处理的JIRA数量,默认为500')
parser.add_argument('--sheet-prefix', type=str, default=None,
help='表格名称前缀,默认为查询日期汇总表')
parser.add_argument('--append', action='store_true', default=True,
help='是否追加到现有表格,默认为True')
parser.add_argument('--no-append', dest='append', action='store_false',
help='不追加到现有表格,创建新表格')
# 解析命令行参数
args = parser.parse_args()
# 根据命令行参数计算日期
local_tz = pendulum.timezone("Asia/Shanghai")
now = pendulum.now(local_tz)
prev_day = now.subtract(days=args.dayago)
check_date_str = f"{prev_day.year}/{prev_day.month:02d}/{prev_day.day:02d}"
tag_time = f'行车系统自动化tag: {check_date_str}'
# tag_time = f'行车系统自动化_地图特殊任务tag: {check_date_str}'
prev_day1 = now.subtract(days=1)
check_date_str1 = f"{prev_day1.year}/{prev_day1.month:02d}/{prev_day1.day:02d}"
# 构建JQL查询
jql_query = f'description ~ "{tag_time}"'
print(f"执行Jira到飞书数据同步...")
print(f"查询日期: {check_date_str}")
print(f"JQL查询: {jql_query}")
print(f"批处理大小: {args.batch_size}")
print(f"表格前缀: {args.sheet_prefix if args.sheet_prefix else check_date_str}")
print(f"追加模式: {'开启' if args.append else '关闭'}")
# 执行批量处理
start_time = time.time()
result_url = CNOA_Jira2Feishu(
jql_query,
batch_size=args.batch_size,
sheet_name_prefix=args.sheet_prefix,
append_to_existing=args.append,
dayago=args.dayago # 传递dayago参数到主函数
)
end_time = time.time()
print(f"\n处理完成!")
jiraObj = JIRA(server=remote_server, auth=(jira_user, jira_password))
jira_count = len(searchJiras(jiraObj, jql_query)[0])
print(f"总共处理了 {jira_count} 条JIRA")
print(f"使用了 {args.batch_size} 条/批的处理方式")
print(f"前推天数: {args.dayago}")
print(f"总耗时: {end_time - start_time:.2f} 秒")
if result_url:
print(f"工作表链接: {result_url}")
if 0 == jira_count:
#企微
req_json = {
"text": f'日期为{check_date_str1}的JIRA(B平台)自动化处理完成,未检测到有效生成结果,请@卫军排查原因!',
"wechat_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=50ae2304-e1b3-4f39-85fc-05c172382e8c"
}
requests.post("http://10.64.250.103:8011/wechat_remind", params=req_json)
#飞书
# webhook_url = "https://open.feishu.cn/open-apis/bot/v2/hook/352b7c16-8d98-40c9-a494-da1be076d73c" # 替换为实际的Webhook地址
# headers = {
# "Content-Type": "application/json"
# }
# data = {
# "msg_type": "text",
# "content": {
# "text": f'日期为{check_date_str1}的JIRA(B平台)自动化处理完成,未检测到有效生成结果,请@卫军排查原因!'
# }
# }
# response = requests.post(webhook_url, headers=headers, data=json.dumps(data))
else:
#企微
req_json = {
"text": f'日期为{check_date_str1}的JIRA(B平台)自动化处理完成\n共处理了{jira_count}条JIRA,生成的报表地址为->\n{result_url}',
"wechat_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=50ae2304-e1b3-4f39-85fc-05c172382e8c"
}
# requests.post("http://10.64.250.103:8011/wechat_remind", params=req_json)
#飞书
# webhook_url = "https://open.feishu.cn/open-apis/bot/v2/hook/352b7c16-8d98-40c9-a494-da1be076d73c" # 替换为实际的Webhook地址
# headers = {
# "Content-Type": "application/json"
# }
# data = {
# "msg_type": "text",
# "content": {
# "text": f'日期为{check_date_str1}的JIRA(B平台)自动化处理完成\n共处理了{jira_count}条JIRA,生成的报表地址为->\n{result_url}'
# }
# }
# response = requests.post(webhook_url, headers=headers, data=json.dumps(data))
print(f"当前csv和企微/飞书提醒执行完成!")
if __name__ == "__main__":
main()