5cde7f852a
2.新增项目批量停用、材料批量修改功能
841 lines
31 KiB
Python
841 lines
31 KiB
Python
"""
|
|
简道云 API 接口封装模块
|
|
|
|
本模块封装了简道云的所有 API 接口,包括:
|
|
- 表单数据操作(获取、创建、更新、删除)
|
|
- 表单字段获取
|
|
- 工作流操作(获取实例、审批、转交)
|
|
- 文件操作(获取上传凭证、上传文件)
|
|
|
|
特性:
|
|
- 支持失败重试机制(默认最多重试20次)
|
|
- 支持字段替换(将字段ID替换为标签名)
|
|
- 支持批量操作(批量创建、更新、删除)
|
|
- 支持数据类型转换(NumPy、Decimal等)
|
|
- 完善的错误处理和日志记录
|
|
"""
|
|
import requests
|
|
import json
|
|
import time
|
|
import logging
|
|
from typing import Optional, List, Dict, Any
|
|
from decimal import Decimal
|
|
import numpy as np
|
|
from app.config import Config
|
|
|
|
# 获取日志记录器
|
|
logger = logging.getLogger('app')
|
|
error_logger = logging.getLogger('app.error') # 错误日志记录器
|
|
|
|
|
|
class NpEncoder(json.JSONEncoder):
|
|
"""
|
|
NumPy 数据类型 JSON 编码器
|
|
|
|
用于将 NumPy 数据类型转换为 JSON 可序列化的类型。
|
|
支持:
|
|
- np.integer -> int
|
|
- np.floating -> float
|
|
- np.ndarray -> list
|
|
"""
|
|
def default(self, obj):
|
|
if isinstance(obj, np.integer):
|
|
return int(obj)
|
|
elif isinstance(obj, np.floating):
|
|
return float(obj)
|
|
elif isinstance(obj, np.ndarray):
|
|
return obj.tolist()
|
|
else:
|
|
return super(NpEncoder, self).default(obj)
|
|
|
|
|
|
def replace_decimals(obj):
|
|
"""
|
|
递归替换 Decimal 类型为 float
|
|
|
|
递归遍历数据结构,将所有 Decimal 类型替换为 float 类型,
|
|
用于 JSON 序列化。
|
|
|
|
Args:
|
|
obj: 要处理的对象(可以是 dict、list 或其他类型)
|
|
|
|
Returns:
|
|
处理后的对象,所有 Decimal 类型已替换为 float
|
|
"""
|
|
if isinstance(obj, dict):
|
|
return {k: replace_decimals(v) for k, v in obj.items()}
|
|
elif isinstance(obj, list):
|
|
return [replace_decimals(item) for item in obj]
|
|
elif isinstance(obj, Decimal):
|
|
return float(obj)
|
|
return obj
|
|
|
|
|
|
class API:
|
|
"""
|
|
简道云 API 接口封装类
|
|
|
|
提供简道云所有 API 接口的封装,包括表单操作、工作流操作、文件操作等。
|
|
所有方法都支持失败重试机制,提高可靠性。
|
|
"""
|
|
|
|
def entry_data_get(self, data: dict, replace: bool = False, max_retries: int = 20) -> Dict:
|
|
"""
|
|
获取单条表单数据
|
|
|
|
根据应用ID、表单ID和数据ID获取单条表单数据。
|
|
|
|
Args:
|
|
data: 包含应用ID(api_key)、表单ID(entry_id)、数据ID(data_id)的字典
|
|
replace: 是否替换字段(将字段ID替换为标签名),默认为False
|
|
max_retries: 最大重试次数,默认为20次
|
|
|
|
Returns:
|
|
Dict: 表单数据字典
|
|
|
|
Raises:
|
|
Exception: 如果重试max_retries次后仍然失败
|
|
"""
|
|
url = 'https://api.jiandaoyun.com/api/v5/app/entry/data/get'
|
|
|
|
headers = {
|
|
'Authorization': Config.JIANDAOYUN_API_TOKEN,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
|
|
payload = json.dumps({
|
|
"app_id": data['api_key'],
|
|
"entry_id": data['entry_id'],
|
|
"data_id": data['data_id']
|
|
})
|
|
|
|
retries = 0
|
|
while retries <= max_retries:
|
|
try:
|
|
res = requests.post(url=url, data=payload, headers=headers, timeout=10)
|
|
res.raise_for_status()
|
|
data_get = res.json()
|
|
print(data_get)
|
|
|
|
if replace:
|
|
data_get = self.field_replacement(data, data_get)
|
|
|
|
return data_get
|
|
except requests.exceptions.RequestException as e:
|
|
logger.warning(f"请求异常: {e}, 将重新请求")
|
|
retries += 1
|
|
if retries <= max_retries:
|
|
time.sleep(0.1)
|
|
|
|
if retries > max_retries:
|
|
error_logger.error(f"任务 {data.get('data_id')} 连续{max_retries}次请求失败,放弃此次请求。")
|
|
raise Exception(f"获取单条表单数据失败,已重试{max_retries}次")
|
|
|
|
def entry_data_list(self, data: dict, replace: bool = True, max_retries: int = 20) -> Dict:
|
|
"""
|
|
获取多条表单数据
|
|
:param max_retries: 最大重试次数
|
|
:param replace: 是否替换字段,默认为True(保持向后兼容)
|
|
:param data: 简道云插件发送过来的data,包含应用id、表单id等信息
|
|
:return: 表单数据列表
|
|
"""
|
|
url = 'https://api.jiandaoyun.com/api/v5/app/entry/data/list'
|
|
|
|
headers = {
|
|
'Authorization': Config.JIANDAOYUN_API_TOKEN,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
|
|
all_data_batches = []
|
|
last_data_id = None
|
|
exit_flag = False
|
|
|
|
while True:
|
|
payload = json.dumps({
|
|
"app_id": data['api_key'],
|
|
"entry_id": data['entry_id'],
|
|
"limit": 100,
|
|
"data_id": last_data_id
|
|
})
|
|
|
|
retries = 0
|
|
while retries <= max_retries:
|
|
try:
|
|
res = requests.post(url=url, data=payload, headers=headers, timeout=10)
|
|
res.raise_for_status()
|
|
data_get = res.json()
|
|
|
|
if data_get.get("data"):
|
|
all_data_batches.extend(data_get['data'])
|
|
last_data_id = data_get['data'][-1].get('_id')
|
|
print(f"已获取 {len(all_data_batches)} 条数据")
|
|
break
|
|
else:
|
|
if 'data' not in data_get or len(data_get['data']) == 0:
|
|
exit_flag = True
|
|
break
|
|
logger.warning(f"请求异常, 将重新请求")
|
|
retries += 1
|
|
time.sleep(0.1)
|
|
except requests.exceptions.RequestException as e:
|
|
logger.warning(f"请求异常: {e}, 将重新请求")
|
|
retries += 1
|
|
time.sleep(0.1)
|
|
|
|
if retries > max_retries:
|
|
error_logger.error(f"任务 {last_data_id}组 连续{max_retries}次请求失败,放弃此次请求。")
|
|
all_data_batches.append(None)
|
|
|
|
if exit_flag:
|
|
break
|
|
|
|
final_data = {
|
|
'data': all_data_batches
|
|
}
|
|
|
|
logger.info(f"获取了{len(all_data_batches)}条数据")
|
|
|
|
if replace:
|
|
print("进行了替换")
|
|
return self.field_replacement(data, final_data)
|
|
else:
|
|
return final_data
|
|
|
|
@staticmethod
|
|
def entry_widget_list(data: dict, max_retries: int = 20) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
获取表单字段
|
|
:param max_retries: 最大重试次数
|
|
:param data: 简道云插件发送过来的data,包含应用id、表单id等信息
|
|
:return: 表单字段信息
|
|
"""
|
|
url = 'https://api.jiandaoyun.com/api/v5/app/entry/widget/list'
|
|
|
|
headers = {
|
|
'Authorization': Config.JIANDAOYUN_API_TOKEN,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
|
|
payload = json.dumps({
|
|
"app_id": data['api_key'],
|
|
"entry_id": data['entry_id'],
|
|
})
|
|
|
|
retries = 0
|
|
while retries <= max_retries:
|
|
try:
|
|
res = requests.post(url=url, data=payload, headers=headers, timeout=10)
|
|
res.raise_for_status()
|
|
return res.json()
|
|
except requests.exceptions.RequestException as e:
|
|
logger.warning(f"请求异常: {e}, 将重新请求")
|
|
retries += 1
|
|
if retries <= max_retries:
|
|
time.sleep(0.1)
|
|
|
|
if retries > max_retries:
|
|
error_logger.error(f"获取表单字段失败,已重试{max_retries}次")
|
|
return None
|
|
|
|
def field_replacement(self, data: dict, data_get: dict) -> dict:
|
|
"""
|
|
字段替换,将id替换为标签名,即唯一值替换为表单中显示字段的名字
|
|
:param data: 简道云插件发送过来的data,包含表单id、数据id、应用id
|
|
:param data_get: 简道云请求的数据,一般是根据数据id获取到表单的数据
|
|
:return: 替换后的数据
|
|
"""
|
|
widget_list = self.entry_widget_list(data)
|
|
|
|
if not widget_list or 'widgets' not in widget_list or not isinstance(widget_list['widgets'], list):
|
|
raise ValueError("映射表没有接受到数据")
|
|
|
|
name_to_label = {widget['name']: widget['label'] for widget in widget_list['widgets']}
|
|
|
|
def replace_keys(obj):
|
|
"""递归替换字典中的键名"""
|
|
if isinstance(obj, dict):
|
|
new_dict = {}
|
|
for key, value in obj.items():
|
|
new_key = name_to_label.get(key, key)
|
|
new_dict[new_key] = replace_keys(value)
|
|
return new_dict
|
|
elif isinstance(obj, list):
|
|
return [replace_keys(item) for item in obj]
|
|
else:
|
|
return obj
|
|
|
|
data_get_copy = json.loads(json.dumps(data_get))
|
|
|
|
if 'data' in data_get_copy:
|
|
data_get_copy['data'] = replace_keys(data_get_copy['data'])
|
|
|
|
return data_get_copy
|
|
|
|
@staticmethod
|
|
def data_batch_create(data: dict, max_retries: int = 20) -> Optional[Dict]:
|
|
"""
|
|
新建单条表单数据
|
|
:param max_retries: 最大重试次数
|
|
:param data: 应该包含应用id、表单id,以及新建的数据data['data']
|
|
:return: 返回创建后简道云返回的信息
|
|
"""
|
|
url = 'https://api.jiandaoyun.com/api/v5/app/entry/data/create'
|
|
|
|
headers = {
|
|
'Authorization': Config.JIANDAOYUN_API_TOKEN,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
|
|
payload = json.dumps({
|
|
"app_id": data['api_key'],
|
|
"entry_id": data['entry_id'],
|
|
"data": data['data'],
|
|
"is_start_workflow": data.get('is_start_workflow', "false"),
|
|
"is_start_trigger": data.get('is_start_trigger', "false"),
|
|
"transaction_id": data.get('transaction_id', "")
|
|
})
|
|
|
|
retries = 0
|
|
while retries <= max_retries:
|
|
try:
|
|
res = requests.post(url=url, data=payload, headers=headers, timeout=10)
|
|
res.raise_for_status()
|
|
data_get = res.json()
|
|
if res.status_code == 200:
|
|
return data_get
|
|
else:
|
|
logger.warning(f"请求异常, 将重新请求")
|
|
retries += 1
|
|
time.sleep(3)
|
|
except requests.exceptions.RequestException as e:
|
|
logger.warning(f"请求异常: {e}, 将重新请求")
|
|
retries += 1
|
|
time.sleep(3)
|
|
|
|
if retries > max_retries:
|
|
error_logger.error(f"任务 {data.get('data')} 连续{max_retries}次请求失败,放弃此次请求。")
|
|
return None
|
|
|
|
@staticmethod
|
|
def entry_data_batch_create(
|
|
data: dict,
|
|
chunk_size: int = 90,
|
|
max_retries: int = 20
|
|
) -> List[Optional[Dict]]:
|
|
"""
|
|
新建多条数据
|
|
:param max_retries: 最大重试次数
|
|
:param data: 应包含数据id、表单id、以及需要新建的信息,新建信息应该是一个列表
|
|
:param chunk_size: 简道云限制批量新建一次最多100条,这里默认值设置为90条一次
|
|
:return: 返回请求后的结果
|
|
"""
|
|
data = replace_decimals(data)
|
|
url = 'https://api.jiandaoyun.com/api/v5/app/entry/data/batch_create'
|
|
|
|
headers = {
|
|
'Authorization': Config.JIANDAOYUN_API_TOKEN,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
|
|
total_length = len(data['data_list'])
|
|
logger.info(f"多数据写入行数: {total_length}")
|
|
|
|
num_chunks = (total_length + chunk_size - 1) // chunk_size
|
|
data_get_list = []
|
|
|
|
for i in range(num_chunks):
|
|
start_index = i * chunk_size
|
|
end_index = min(start_index + chunk_size, total_length)
|
|
payload = json.dumps({
|
|
"app_id": data['api_key'],
|
|
"entry_id": data['entry_id'],
|
|
"data_list": data['data_list'][start_index:end_index],
|
|
"is_start_workflow": data.get('is_start_workflow', "false"),
|
|
"is_start_trigger": data.get('is_start_trigger', "false"),
|
|
}, cls=NpEncoder)
|
|
|
|
retries = 0
|
|
while retries <= max_retries:
|
|
try:
|
|
res = requests.post(url=url, data=payload, headers=headers, timeout=10)
|
|
res.raise_for_status()
|
|
data_get = res.json()
|
|
if data_get.get("status") == "success":
|
|
data_get_list.append(data_get)
|
|
break
|
|
else:
|
|
logger.warning(f"请求异常,将重新请求")
|
|
retries += 1
|
|
time.sleep(3)
|
|
except requests.exceptions.RequestException as e:
|
|
logger.warning(f"请求异常: {e}, 将重新请求")
|
|
retries += 1
|
|
time.sleep(0.1)
|
|
|
|
if retries > max_retries:
|
|
error_logger.error(
|
|
f"任务 {data['data_list'][start_index:end_index]} 连续{max_retries}次请求失败,放弃此次请求。")
|
|
data_get_list.append(None)
|
|
|
|
return data_get_list
|
|
|
|
@staticmethod
|
|
def entry_data_update(data: dict, max_retries: int = 20) -> dict:
|
|
"""
|
|
修改数据
|
|
:param max_retries: 最大重试次数
|
|
:param data: 简道云插件发送过来的data,包含应用id、表单id、数据id等信息
|
|
:return: 修改数据后简道云返回的结果
|
|
"""
|
|
url = 'https://api.jiandaoyun.com/api/v5/app/entry/data/update'
|
|
|
|
headers = {
|
|
'Authorization': Config.JIANDAOYUN_API_TOKEN,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
|
|
payload = json.dumps({
|
|
"app_id": data['api_key'],
|
|
"entry_id": data['entry_id'],
|
|
"data_id": data['data_id'],
|
|
"data": data['data']
|
|
})
|
|
|
|
data_get = None
|
|
retries = 0
|
|
while retries <= max_retries:
|
|
try:
|
|
res = requests.post(url=url, data=payload, headers=headers, timeout=10)
|
|
res.raise_for_status()
|
|
data_get = res.json()
|
|
if res.status_code == 200:
|
|
break
|
|
else:
|
|
logger.warning(f"请求异常, 将重新请求")
|
|
retries += 1
|
|
time.sleep(3)
|
|
except requests.exceptions.RequestException as e:
|
|
logger.warning(f"请求异常: {e}, 将重新请求")
|
|
retries += 1
|
|
time.sleep(10)
|
|
|
|
if retries > max_retries:
|
|
error_logger.error(f"任务 {data.get('data_id')} 连续{max_retries}次请求失败,放弃此次请求。")
|
|
|
|
return data_get
|
|
|
|
@staticmethod
|
|
def entry_data_banch_update(data: dict, max_retries: int = 20, chunk_size: int = 90) -> List[dict]:
|
|
"""
|
|
批量修改数据
|
|
:param chunk_size: 批量修改块大小
|
|
:param max_retries: 最大重试次数
|
|
:param data: 简道云插件发送过来的data,包含应用id、表单id、数据id等信息
|
|
:return: 修改数据后简道云返回的结果列表
|
|
"""
|
|
url = 'https://api.jiandaoyun.com/api/v5/app/entry/data/batch_update'
|
|
|
|
headers = {
|
|
'Authorization': Config.JIANDAOYUN_API_TOKEN,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
|
|
total_length = len(data['data_ids'])
|
|
logger.info(f"多数据修改行数: {total_length}")
|
|
|
|
num_chunks = (total_length + chunk_size - 1) // chunk_size
|
|
data_get_list = []
|
|
|
|
for i in range(num_chunks):
|
|
start_index = i * chunk_size
|
|
end_index = min(start_index + chunk_size, total_length)
|
|
payload = {
|
|
"app_id": data['api_key'],
|
|
"entry_id": data['entry_id'],
|
|
"data_ids": data['data_ids'][start_index:end_index],
|
|
"data": data['data']
|
|
}
|
|
|
|
if "transaction_id" in data:
|
|
payload["transaction_id"] = data["transaction_id"]
|
|
payload = json.dumps(payload, cls=NpEncoder)
|
|
|
|
retries = 0
|
|
while retries <= max_retries:
|
|
try:
|
|
res = requests.post(url=url, data=payload, headers=headers, timeout=10)
|
|
res.raise_for_status()
|
|
data_get = res.json()
|
|
if res.status_code == 200:
|
|
data_get_list.append(data_get)
|
|
break
|
|
else:
|
|
logger.warning(f"请求异常, 将重新请求")
|
|
retries += 1
|
|
time.sleep(3)
|
|
except requests.exceptions.RequestException as e:
|
|
logger.warning(f"请求异常: {e}, 将重新请求")
|
|
retries += 1
|
|
time.sleep(10)
|
|
|
|
if retries > max_retries:
|
|
error_logger.error(f"任务 {data.get('data_ids')} 连续{max_retries}次请求失败,放弃此次请求。")
|
|
continue
|
|
|
|
return data_get_list
|
|
|
|
@staticmethod
|
|
def entry_data_delete(data: dict, max_retries: int = 20) -> dict:
|
|
"""
|
|
删除单条数据
|
|
:param data: 应包含应用ID、表单ID、数据ID
|
|
:param max_retries: 最大重试次数,默认20
|
|
:return: 删除结果
|
|
"""
|
|
url = 'https://api.jiandaoyun.com/api/v5/app/entry/data/delete'
|
|
|
|
headers = {
|
|
'Authorization': Config.JIANDAOYUN_API_TOKEN,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
|
|
payload = json.dumps({
|
|
"app_id": data['api_key'],
|
|
"entry_id": data['entry_id'],
|
|
"data_id": data['data_id'],
|
|
})
|
|
|
|
retries = 0
|
|
delete_status = None
|
|
|
|
while retries <= max_retries:
|
|
try:
|
|
res = requests.post(url=url, data=payload, headers=headers, timeout=10)
|
|
delete_status = res.json()
|
|
|
|
# 手动处理状态码 4001(数据不存在)
|
|
if delete_status == {
|
|
"code": 4001,
|
|
"msg": "Data does not exist."
|
|
}:
|
|
logger.info(f"返回结果: {delete_status}")
|
|
break
|
|
|
|
res.raise_for_status()
|
|
|
|
if res.status_code == 200:
|
|
break
|
|
else:
|
|
logger.warning(f"请求异常, 将重新请求")
|
|
retries += 1
|
|
time.sleep(3)
|
|
except requests.exceptions.RequestException as e:
|
|
logger.warning(f"请求异常: {e}, 将重新请求")
|
|
retries += 1
|
|
time.sleep(10)
|
|
|
|
if retries > max_retries:
|
|
error_logger.error(f"任务 {data.get('data_id')} 连续{max_retries}次请求失败,放弃此次请求。")
|
|
continue
|
|
|
|
return delete_status
|
|
|
|
@staticmethod
|
|
def entry_data_batch_delete(
|
|
data: dict,
|
|
chunk_size: int = 90,
|
|
max_retries: int = 20
|
|
) -> List[Optional[Dict]]:
|
|
"""
|
|
批量删除数据
|
|
:param data: 应包含应用ID、表单ID、数据ID列表
|
|
:param chunk_size: 单次删除最大条数,默认90
|
|
:param max_retries: 重试次数,默认20
|
|
:return: 删除结果列表
|
|
"""
|
|
data = replace_decimals(data)
|
|
url = 'https://api.jiandaoyun.com/api/v5/app/entry/data/batch_delete'
|
|
|
|
headers = {
|
|
'Authorization': Config.JIANDAOYUN_API_TOKEN,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
|
|
total_length = len(data['data_ids'])
|
|
logger.info(f"多数据删除行数: {total_length}")
|
|
|
|
num_chunks = (total_length + chunk_size - 1) // chunk_size
|
|
data_get_list = []
|
|
|
|
for i in range(num_chunks):
|
|
start_index = i * chunk_size
|
|
end_index = min(start_index + chunk_size, total_length)
|
|
payload = json.dumps({
|
|
"app_id": data['api_key'],
|
|
"entry_id": data['entry_id'],
|
|
"data_ids": data['data_ids'][start_index:end_index],
|
|
}, cls=NpEncoder)
|
|
|
|
retries = 0
|
|
while retries <= max_retries:
|
|
try:
|
|
res = requests.post(url=url, data=payload, headers=headers, timeout=10)
|
|
res.raise_for_status()
|
|
data_get = res.json()
|
|
logger.info(f"{i}页 返回结果: {data_get}")
|
|
if data_get.get("status") == "success":
|
|
data_get_list.append(data_get)
|
|
break
|
|
else:
|
|
logger.warning(f"请求异常, 将重新请求")
|
|
retries += 1
|
|
time.sleep(3)
|
|
except requests.exceptions.RequestException as e:
|
|
logger.warning(f"请求异常: {e}, 将重新请求")
|
|
retries += 1
|
|
time.sleep(0.1)
|
|
|
|
if retries > max_retries:
|
|
error_logger.error(
|
|
f"批量删除任务第{i+1}批 连续{max_retries}次请求失败,放弃此次请求。")
|
|
data_get_list.append(None)
|
|
|
|
return data_get_list
|
|
|
|
@staticmethod
|
|
def workflow_instance_get(data: dict, max_retries: int = 20) -> dict:
|
|
"""
|
|
查询实例流程信息
|
|
:param max_retries: 最大重试次数
|
|
:param data: 简道云插件发送过来的data,包含应用id
|
|
:return: 查询简道云流程实例信息返回的结果
|
|
"""
|
|
url = 'https://api.jiandaoyun.com/api/v5/workflow/instance/get'
|
|
|
|
headers = {
|
|
'Authorization': Config.JIANDAOYUN_API_TOKEN,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
|
|
payload = json.dumps({
|
|
"instance_id": data['data_id'],
|
|
"tasks_type": 1
|
|
})
|
|
|
|
data_get = None
|
|
retries = 0
|
|
while retries <= max_retries:
|
|
try:
|
|
res = requests.post(url=url, data=payload, headers=headers, timeout=10)
|
|
res.raise_for_status()
|
|
data_get = res.json()
|
|
if res.status_code == 200:
|
|
break
|
|
else:
|
|
logger.warning(f"请求异常, 将重新请求")
|
|
retries += 1
|
|
time.sleep(3)
|
|
except requests.exceptions.RequestException as e:
|
|
logger.warning(f"请求异常: {e}, 将重新请求")
|
|
retries += 1
|
|
time.sleep(0.1)
|
|
|
|
if retries > max_retries:
|
|
error_logger.error(f"任务 {data.get('data_id')} 连续{max_retries}次请求失败,放弃此次请求。")
|
|
|
|
return data_get
|
|
|
|
@staticmethod
|
|
def workflow_task_approve(data: dict, max_retries: int = 20) -> dict:
|
|
"""
|
|
流程待办提交
|
|
:param max_retries: 最大重试次数
|
|
:param data: 应包含username、instance_id(data_id)、task_id等信息
|
|
:return: 返回简道云流程待办提交的结果
|
|
"""
|
|
url = 'https://api.jiandaoyun.com/api/v1/workflow/task/approve'
|
|
|
|
headers = {
|
|
'Authorization': Config.JIANDAOYUN_API_TOKEN,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
|
|
payload = json.dumps({
|
|
"username": data["username"],
|
|
"instance_id": data["instance_id"],
|
|
"task_id": data['task_id'],
|
|
"comment": data.get("comment", "自动转交")
|
|
})
|
|
|
|
retries = 0
|
|
while retries <= max_retries:
|
|
try:
|
|
res = requests.post(url=url, data=payload, headers=headers, timeout=10)
|
|
res.raise_for_status()
|
|
if res.status_code == 200:
|
|
return res.json()
|
|
else:
|
|
logger.warning(f"请求异常, 将重新请求")
|
|
retries += 1
|
|
time.sleep(3)
|
|
except requests.exceptions.RequestException as e:
|
|
logger.warning(f"请求异常: {e}, 将重新请求")
|
|
retries += 1
|
|
time.sleep(3)
|
|
|
|
if retries > max_retries:
|
|
error_logger.error(f"任务 {data.get('task_id')} 连续{max_retries}次请求失败,放弃此次请求。")
|
|
return {}
|
|
|
|
@staticmethod
|
|
def workflow_task_hand_over(data: dict, max_retries: int = 10) -> Optional[dict]:
|
|
"""
|
|
流程待办转交
|
|
:param max_retries: 最大重试次数
|
|
:param data: 应包含username、instance_id(data_id)、task_id、transfer_username等信息
|
|
:return: 返回简道云流程待办转交的结果
|
|
"""
|
|
url = 'https://api.jiandaoyun.com/api/v1/workflow/task/transfer'
|
|
|
|
headers = {
|
|
'Authorization': Config.JIANDAOYUN_API_TOKEN,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
|
|
payload = json.dumps({
|
|
"username": data["username"],
|
|
"instance_id": data["instance_id"],
|
|
"task_id": data['task_id'],
|
|
"transfer_username": data['transfer_username'],
|
|
"comment": data.get("comment", "转交")
|
|
})
|
|
|
|
retries = 0
|
|
while retries <= max_retries:
|
|
try:
|
|
res = requests.post(url=url, data=payload, headers=headers, timeout=10)
|
|
res.raise_for_status()
|
|
if res.status_code == 200:
|
|
return res.json()
|
|
else:
|
|
logger.warning(f"请求异常, 将重新请求")
|
|
retries += 1
|
|
time.sleep(3)
|
|
except requests.exceptions.RequestException as e:
|
|
logger.warning(f"请求异常: {e}, 将重新请求")
|
|
retries += 1
|
|
time.sleep(3)
|
|
|
|
if retries > max_retries:
|
|
error_logger.error(f"任务转交失败,已重试{max_retries}次")
|
|
return None
|
|
|
|
@staticmethod
|
|
def get_upload_token(data: dict, max_retries: int = 10) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
获取文件上传凭证
|
|
:param max_retries: 最大重试次数
|
|
:param data: 应包含应用ID、表单ID、事务ID
|
|
:return: 返回upload_url、upload_token
|
|
"""
|
|
url = 'https://api.jiandaoyun.com/api/v5/app/entry/file/get_upload_token'
|
|
headers = {
|
|
'Authorization': Config.JIANDAOYUN_API_TOKEN,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
|
|
payload = json.dumps({
|
|
"app_id": data['api_key'],
|
|
"entry_id": data['entry_id'],
|
|
"transaction_id": data['transaction_id'],
|
|
})
|
|
|
|
retries = 0
|
|
while retries <= max_retries:
|
|
try:
|
|
res = requests.post(url=url, data=payload, headers=headers, timeout=10)
|
|
res.raise_for_status()
|
|
res_j = res.json()
|
|
|
|
# 检查 token_and_url_list 是否存在且不为空
|
|
token_list = res_j.get('token_and_url_list', [])
|
|
if not token_list or len(token_list) == 0:
|
|
logger.warning(f"未获取到上传凭证列表,将重新请求")
|
|
retries += 1
|
|
time.sleep(3)
|
|
continue
|
|
|
|
token_item = token_list[0]
|
|
upload_url = token_item.get('url')
|
|
upload_token = token_item.get('token')
|
|
|
|
if not upload_url or not upload_token:
|
|
logger.warning(f"上传凭证信息不完整,将重新请求")
|
|
retries += 1
|
|
time.sleep(3)
|
|
continue
|
|
|
|
logger.info(f"返回结果: {upload_url}, {upload_token}")
|
|
if res.status_code == 200:
|
|
return {
|
|
'upload_url': upload_url,
|
|
'upload_token': upload_token
|
|
}
|
|
else:
|
|
logger.warning(f"请求异常, 将重新请求")
|
|
retries += 1
|
|
time.sleep(3)
|
|
except (requests.exceptions.RequestException, KeyError, IndexError, TypeError) as e:
|
|
logger.warning(f"请求异常: {e}, 将重新请求")
|
|
retries += 1
|
|
time.sleep(3)
|
|
|
|
if retries > max_retries:
|
|
error_logger.error(f"获取上传凭证失败,已重试{max_retries}次")
|
|
return None
|
|
|
|
@staticmethod
|
|
def upload_file(data: dict, max_retries: int = 10) -> Optional[Any]:
|
|
"""
|
|
上传文件
|
|
:param max_retries: 最大重试次数
|
|
:param data: 应包含上传文件路径、上传文件url、上传文件token
|
|
:return: 返回上传文件结果
|
|
"""
|
|
url = data['upload_url']
|
|
headers = {
|
|
'Authorization': Config.JIANDAOYUN_API_TOKEN,
|
|
}
|
|
file_path = data['file_path']
|
|
payload = {
|
|
"token": data['upload_token'],
|
|
}
|
|
|
|
retries = 0
|
|
result = None
|
|
|
|
while retries <= max_retries:
|
|
try:
|
|
with open(file_path, 'rb') as f:
|
|
files = {"file": f}
|
|
res = requests.post(url=url, data=payload, headers=headers, files=files, timeout=10)
|
|
res.raise_for_status()
|
|
data_get = res.json()
|
|
logger.info(f"返回结果: {data_get}")
|
|
if res.status_code == 200:
|
|
result = data_get
|
|
break
|
|
else:
|
|
logger.warning(f"请求异常, 将重新请求")
|
|
retries += 1
|
|
time.sleep(3)
|
|
except requests.exceptions.RequestException as e:
|
|
logger.warning(f"请求异常: {e}, 将重新请求")
|
|
retries += 1
|
|
time.sleep(3)
|
|
|
|
if retries > max_retries:
|
|
error_logger.error(f"上传文件失败,已重试{max_retries}次")
|
|
|
|
return result
|