mirror of
https://mirror.skon.top/github.com/cft0808/edict
synced 2026-04-20 21:00:16 +08:00
P0: - 圣旨模板下旨真正创建任务: 新增 POST /api/create-task 前端 executeTemplate 改为 API 调用(降级仍可剪贴板复制) P1: - morning-config POST 字段校验: 检查 categories/keywords/feishu_webhook 类型 - 早报幂等锁支持 --force 强制采集: 看板手动刷新默认 force=true - sync_agent_config 补全 Copilot 模型列表(6个) P2: - utils.py 公共函数抽取: read_json/now_iso/validate_url/safe_name - refresh_live_data.py 改用 utils.read_json 消除重复定义 - apply_model_changes 回滚标记: 失败时 rolledBack=true 写入日志+前端展示 - 早报日期 API 兼容 YYYY-MM-DD 自动转换 + 格式校验 - Request logging: log_message 改为只记录 4xx/5xx 错误请求 - 飞书 Webhook URL 校验: 限制 https + open.feishu.cn 域名 P3: - 御批模式基础实现: Review/Menxia 状态显示准奏/封驳按钮 新增 POST /api/review-action(approve推进/reject退回中书省+轮次+1) 前端 reviewAction() + 变更日志回滚标记显示
55 lines
1.7 KiB
Python
55 lines
1.7 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
三省六部 · 公共工具函数
|
||
避免 read_json / now_iso 等基础函数在多个脚本中重复定义
|
||
"""
|
||
import json, pathlib, datetime
|
||
|
||
|
||
def read_json(path, default=None):
|
||
"""安全读取 JSON 文件,失败返回 default"""
|
||
try:
|
||
return json.loads(pathlib.Path(path).read_text())
|
||
except Exception:
|
||
return default if default is not None else {}
|
||
|
||
|
||
def now_iso():
|
||
"""返回 UTC ISO 8601 时间字符串(末尾 Z)"""
|
||
return datetime.datetime.now(datetime.timezone.utc).isoformat().replace('+00:00', 'Z')
|
||
|
||
|
||
def today_str(fmt='%Y%m%d'):
|
||
"""返回今天日期字符串,默认 YYYYMMDD"""
|
||
return datetime.date.today().strftime(fmt)
|
||
|
||
|
||
def safe_name(s: str) -> bool:
|
||
"""检查名称是否只含安全字符(字母、数字、下划线、连字符、中文)"""
|
||
import re
|
||
return bool(re.match(r'^[a-zA-Z0-9_\-\u4e00-\u9fff]+$', s))
|
||
|
||
|
||
def validate_url(url: str, allowed_schemes=('https',), allowed_domains=None) -> bool:
|
||
"""校验 URL 合法性,防 SSRF"""
|
||
from urllib.parse import urlparse
|
||
try:
|
||
parsed = urlparse(url)
|
||
if parsed.scheme not in allowed_schemes:
|
||
return False
|
||
if allowed_domains and parsed.hostname not in allowed_domains:
|
||
return False
|
||
if not parsed.hostname:
|
||
return False
|
||
# 禁止内网地址
|
||
import ipaddress
|
||
try:
|
||
ip = ipaddress.ip_address(parsed.hostname)
|
||
if ip.is_private or ip.is_loopback or ip.is_reserved:
|
||
return False
|
||
except ValueError:
|
||
pass # hostname 不是 IP,放行
|
||
return True
|
||
except Exception:
|
||
return False
|