-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathmain.py
More file actions
208 lines (166 loc) · 7.16 KB
/
Copy pathmain.py
File metadata and controls
208 lines (166 loc) · 7.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
import json
import logging
import os
import random
import time
from datetime import datetime
from functools import wraps
import requests
from cozepy import Coze, TokenAuth, COZE_CN_BASE_URL
from dotenv import load_dotenv
from utils import save_file, generate_random_filename, is_file_exists, get_jinshan, get_yyymmdd_date
# --- 新增:日志和敏感信息处理函数 ---
def mask_sensitive_data(data: str, visible_chars: int = 1) -> str:
"""
对敏感字符串进行脱敏处理,只显示前几位和后几位。
例如: "735e8a9b1c2d" -> "735e***1c2d"
"""
if not data or len(data) <= visible_chars * 2:
return "***"
return f"{data[:visible_chars]}****{data[-visible_chars:]}"
def setup_logging():
"""
配置日志系统,将日志同时输出到控制台和按日期归档的文件中。
每次运行都会创建一个新的日志文件。
"""
# 1. 创建日志目录结构: logs/YYYY-MM-DD/
log_dir_base = "logs"
today_str = datetime.now().strftime("%Y-%m-%d")
log_dir = os.path.join(log_dir_base, today_str)
os.makedirs(log_dir, exist_ok=True)
# 2. 创建唯一的日志文件名: app_HHMMSS_<random>.log
timestamp = datetime.now().strftime("%H%M%S")
random_suffix = random.randint(1000, 9999)
log_filename = f"app_{timestamp}_{random_suffix}.log"
log_filepath = os.path.join(log_dir, log_filename)
# 3. 配置logger
_logger = logging.getLogger("CozeWorkflowRunner")
_logger.setLevel(logging.INFO) # 设置日志级别
# 避免重复添加Handler
if _logger.handlers:
return _logger
# 创建格式化器
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
# 4. 创建并配置文件处理器
file_handler = logging.FileHandler(log_filepath, encoding='utf-8')
file_handler.setFormatter(formatter)
_logger.addHandler(file_handler)
# 5. 创建并配置控制台处理器
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
_logger.addHandler(console_handler)
_logger.info(f"日志系统初始化完成,日志文件保存在: {log_filepath}")
return _logger
# --- 原有代码(保持不变) ---
fail_sum = 0
is_today_jinshan = False
# 重试装饰器
def retry_with_exponential_backoff(max_attempts=5, initial_delay=1, max_delay=180, exponential_base=2, jitter=True):
"""
带有指数退避和抖动的重试装饰器
参数:
max_attempts: 最大重试次数
initial_delay: 初始延迟时间(秒)
max_delay: 最大延迟时间(秒)
exponential_base: 指数基数
jitter: 是否添加随机抖动避免惊群效应
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except Exception as err:
attempts += 1
global fail_sum
fail_sum += 1
if attempts == max_attempts:
# 使用logger.error记录最终失败
logger.error(f"操作失败,已达到最大重试次数 {max_attempts},最后一次异常: {str(err)}")
raise
# 计算指数退避延迟
delay = initial_delay * (exponential_base ** (attempts - 1))
delay = min(delay, max_delay)
if jitter:
delay = delay * (0.5 + random.random())
# 使用logger.warning记录重试信息
logger.warning(f"操作失败: {str(err)},{delay:.2f}秒后进行第{attempts + 1}次重试...")
time.sleep(delay)
return None
return wrapper
return decorator
# --- 主要逻辑函数 ---
def main():
"""主执行函数"""
# initialize client
load_dotenv()
coze_api_token = os.getenv("COZE_API_TOKEN")
coze_api_base = COZE_CN_BASE_URL
coze = Coze(auth=TokenAuth(coze_api_token), base_url=coze_api_base)
@retry_with_exponential_backoff(max_attempts=5, initial_delay=15)
def create_workflow_run(_workflow_id, _parameters=None):
global is_today_jinshan
if is_today_jinshan:
_parameters = None
is_today_jinshan = True
"""创建工作流运行,带有重试机制"""
return coze.workflows.runs.create(workflow_id=_workflow_id, parameters=_parameters)
try:
# 获取工作流ID并创建运行实例,最多重试3次
workflow_id = os.getenv("WORKFLOW_ID")
if not workflow_id:
raise ValueError("环境变量中未找到WORKFLOW_ID")
# 对敏感信息进行脱敏
masked_workflow_id = mask_sensitive_data(workflow_id)
logger.info(f"开始执行工作流,ID: {masked_workflow_id}")
yyymmdd = get_yyymmdd_date()
parameters = None
global is_today_jinshan
is_today_jinshan = is_file_exists(f"jinshan_data/{yyymmdd}")
if not is_today_jinshan:
jinshan = get_jinshan()
save_file(f"jinshan_data/{yyymmdd}", jinshan)
parameters = {"input": [jinshan['note']]}
ct = create_workflow_run(workflow_id, parameters)
# 对调试URL中的敏感信息进行过滤
debug_url = ct.debug_url
sanitized_debug_url = debug_url.replace(workflow_id, masked_workflow_id)
logger.info(f"工作流运行调试URL: {sanitized_debug_url}")
# 处理返回结果
ct_data = json.loads(ct.data)
output = ct_data["output"]
output0 = json.loads(ct_data["output0"])
resp = requests.delete(url=f"https://lsk.icu/aiblog/api/blog/{output0['filename']}")
if resp.status_code != 200:
logger.warning(f"删除博客失败,状态码: {resp.status_code},响应: {resp.text}")
else:
logger.info(resp.json()["message"])
# 保存结果到文件
file_name = generate_random_filename(extension="md")
save_file(f"content/posts/TrialRun/{file_name}", output)
logger.info(f"成功保存结果到文件: {file_name}")
except Exception as e:
# 使用logger.exception记录完整的错误堆栈
logger.exception(f"脚本执行失败: {str(e)}")
finally:
global fail_sum
if fail_sum == 0:
return
logger.info(f"当前失败次数: {fail_sum}")
resp = requests.get(url=f"https://lsk.icu/aiblog/api/blog/latest/{fail_sum}")
if resp.status_code != 200:
logger.warning(f"获取最新博客失败,状态码: {resp.status_code},响应: {resp.text}")
if resp.json()["files"]:
for file in resp.json()["files"]:
file_name = generate_random_filename(extension="md")
save_file(f"content/posts/TrialRun/{file_name}", file["content"])
logger.info(f"成功保存文件: {file_name}")
else:
logger.info(resp.json()["message"])
if __name__ == "__main__":
# 在脚本开始时初始化日志
logger = setup_logging()
# 执行主函数
main()