184 lines
5.7 KiB
Python
184 lines
5.7 KiB
Python
# 可以引用一些第三方库.
|
||
import requests
|
||
import time
|
||
from datetime import datetime, timedelta, timezone
|
||
from typing import Any, Dict, List, Optional
|
||
|
||
# api_key = triggerConf.get('_widget_17756292392611')
|
||
# entry_id = triggerConf.get('_widget_17756292385281')
|
||
|
||
# 简道云 API 调用 Token(Bearer 形式)
|
||
api_key="675b900991ad2491c69389ca"
|
||
# 简道云 API 通用请求头
|
||
entry_id ="695f439e3e910f09190d8e99"
|
||
|
||
API_TOKEN = "Bearer qygHulymo1fekJk4CIZyNKjyQAzG8CFN"
|
||
HEADERS = {'Authorization': API_TOKEN, 'Content-Type': 'application/json'}
|
||
|
||
|
||
def entry_data_list(
|
||
data: Dict[str, Any],
|
||
max_retries: int = 5,
|
||
# 获取多条表单数据(自动分页:用上一页最后一条的 _id 作为 data_id)
|
||
limit: int = 90,
|
||
timeout: int = 10,
|
||
) -> Dict[str, List[Dict[str, Any]]]:
|
||
url = 'https://api.jiandaoyun.com/api/v5/app/entry/data/list'
|
||
|
||
all_rows: List[Dict[str, Any]] = []
|
||
last_data_id: Optional[str] = None
|
||
|
||
while True:
|
||
payload: Dict[str, Any] = {
|
||
'app_id': data['api_key'],
|
||
'entry_id': data['entry_id'],
|
||
'limit': limit,
|
||
'data_id': last_data_id,
|
||
}
|
||
if data.get('filter') is not None:
|
||
payload['filter'] = data['filter']
|
||
|
||
body: Optional[Dict[str, Any]] = None
|
||
for attempt in range(max_retries + 1):
|
||
try:
|
||
res = requests.post(url=url, json=payload, headers=HEADERS, timeout=timeout)
|
||
res.raise_for_status()
|
||
# 网络抖动/超时等异常,按最大重试次数重试
|
||
body = res.json()
|
||
break
|
||
except requests.RequestException:
|
||
if attempt >= max_retries:
|
||
raise
|
||
time.sleep(0.5)
|
||
|
||
batch = (body or {}).get('data') or []
|
||
if not batch:
|
||
# 下一页从本页最后一条数据之后继续拉取
|
||
break
|
||
|
||
all_rows.extend(batch)
|
||
last_data_id = batch[-1].get('_id')
|
||
|
||
return {'data': all_rows}
|
||
|
||
|
||
def entry_data_update(
|
||
app_id: str,
|
||
entry_id: str,
|
||
data_id: str,
|
||
data_patch: Dict[str, Any],
|
||
timeout: int = 10,
|
||
) -> Dict[str, Any]:
|
||
url = 'https://api.jiandaoyun.com/api/v5/app/entry/data/update'
|
||
payload = {
|
||
'app_id': app_id,
|
||
# 这里关闭触发器,避免回写引发自动化流程/智能助手(如需触发可改为 True)
|
||
'entry_id': entry_id,
|
||
'data_id': data_id,
|
||
'data': data_patch,
|
||
'is_start_trigger': False,
|
||
}
|
||
res = requests.post(url=url, json=payload, headers=HEADERS, timeout=timeout)
|
||
res.raise_for_status()
|
||
print(res.json())
|
||
return res.json()
|
||
|
||
|
||
def get_widget_value(row: Dict[str, Any], widget_id: str) -> Any:
|
||
raw = row.get(widget_id)
|
||
if isinstance(raw, dict) and 'value' in raw:
|
||
return raw.get('value')
|
||
return raw
|
||
|
||
|
||
def parse_jdy_datetime(value: Any) -> Optional[datetime]:
|
||
# 将简道云返回的时间字符串解析为 datetime(统一按 UTC 处理)
|
||
# 常见格式:2026-02-23T21:00:25.000Z / 2026-02-23T21:00:25+00:00 / 2026-02-23
|
||
if value is None:
|
||
return None
|
||
if isinstance(value, list):
|
||
if not value:
|
||
return None
|
||
value = value[0]
|
||
if not isinstance(value, str):
|
||
return None
|
||
s = value.strip()
|
||
if not s:
|
||
return None
|
||
if s.endswith('Z'):
|
||
# fromisoformat 不直接识别 Z,这里转成 +00:00
|
||
s = s[:-1] + '+00:00'
|
||
try:
|
||
dt = datetime.fromisoformat(s)
|
||
except ValueError:
|
||
try:
|
||
dt = datetime.strptime(s, '%Y-%m-%d')
|
||
except ValueError:
|
||
return None
|
||
if dt.tzinfo is None:
|
||
# 如果没有时区信息,默认按 UTC 解释(避免与 now 的时区不一致)
|
||
dt = dt.replace(tzinfo=timezone.utc)
|
||
return dt
|
||
|
||
|
||
filter_not_empty = {
|
||
'rel': 'and',
|
||
'cond': [
|
||
{
|
||
'field': 'flowState',
|
||
'type': 'flowState',
|
||
'method': 'eq',
|
||
'value': [0],
|
||
}
|
||
],
|
||
}
|
||
|
||
# 拉取满足过滤条件的所有数据
|
||
result = entry_data_list({'api_key': api_key, 'entry_id': entry_id, 'filter': filter_not_empty})
|
||
# print(result)
|
||
# 字段 ID:预计培训时间(可能为空)
|
||
WIDGET_EXPECTED_TRAINING_TIME = '_widget_1767863644396'
|
||
# 字段 ID:超期培训(数字类型,回写 1)
|
||
WIDGET_OVERDUE_TRAINING = '_widget_1775637369171'
|
||
|
||
# 当前时间用 UTC(简道云时间为 UTC 标准时间)
|
||
now = datetime.now(timezone.utc)
|
||
updated_count = 0
|
||
for row in result.get('data', []):
|
||
data_id = row.get('_id')
|
||
if not data_id:
|
||
continue
|
||
|
||
expected_dt = parse_jdy_datetime(get_widget_value(row, WIDGET_EXPECTED_TRAINING_TIME))
|
||
print(expected_dt)
|
||
if expected_dt is None:
|
||
# 预计培训时间为空/解析失败:不处理
|
||
continue
|
||
|
||
# 超期判断:当前时间 - 预计培训时间 >= 1 天,则认定超期(如需改成 1 小时/1 分钟,调整 timedelta)
|
||
if now - expected_dt < timedelta(days=1):
|
||
continue
|
||
|
||
# current_flag = get_widget_value(row, WIDGET_OVERDUE_TRAINING)
|
||
# if current_flag in (1, 1.0, '1', True):
|
||
# # 已经标记过超期:跳过
|
||
# continue
|
||
|
||
# 回写“超期培训”=1
|
||
resp = entry_data_update(
|
||
app_id=api_key,
|
||
entry_id=entry_id,
|
||
data_id=data_id,
|
||
data_patch={WIDGET_OVERDUE_TRAINING: {'value': 1}},
|
||
)
|
||
new_val = (resp or {}).get('data', {}).get(WIDGET_OVERDUE_TRAINING)
|
||
if new_val in (1, 1.0, '1', True):
|
||
updated_count += 1
|
||
print(f'更新成功: data_id={data_id}, {WIDGET_OVERDUE_TRAINING}={new_val}')
|
||
else:
|
||
print(f'更新失败: data_id={data_id}, 返回值={new_val}')
|
||
# 轻微限速,避免触发接口频率限制
|
||
time.sleep(0.05)
|
||
print(f'本次共更新 {updated_count} 条')
|
||
|