9 changed files with 616 additions and 2 deletions
@ -0,0 +1,63 @@
|
||||
# Python |
||||
*.pyc |
||||
__pycache__/ |
||||
*.pyo |
||||
*.pyd |
||||
.pytest_cache/ |
||||
.tox/ |
||||
.venv/ |
||||
venv/ |
||||
ENV/ |
||||
env/ |
||||
dist/ |
||||
build/ |
||||
*.egg-info/ |
||||
*.egg |
||||
.mypy_cache/ |
||||
.dmypy.json |
||||
dmypy.json |
||||
.pyre/ |
||||
.pytype/ |
||||
*.so |
||||
|
||||
# Editor directories and files |
||||
.vscode/ |
||||
.idea/ |
||||
*.swp |
||||
*.swo |
||||
*~ |
||||
.project |
||||
.classpath |
||||
.c9/ |
||||
*.launch |
||||
.settings/ |
||||
*.sublime-workspace |
||||
|
||||
# OS generated files |
||||
.DS_Store |
||||
.DS_Store? |
||||
._* |
||||
.Spotlight-V100 |
||||
.Trashes |
||||
ehthumbs.db |
||||
Thumbs.db |
||||
|
||||
# Logs |
||||
logs/ |
||||
*.log |
||||
*.log.* |
||||
log/ |
||||
|
||||
# Runtime data |
||||
pids/ |
||||
*.pid |
||||
*.seed |
||||
*.pid.lock |
||||
|
||||
# Coverage directory used by tools like istanbul |
||||
coverage/ |
||||
*.lcov |
||||
|
||||
# Temporary folders |
||||
tmp/ |
||||
temp/ |
||||
@ -0,0 +1,69 @@
|
||||
# 固件检查器 (Firmware Checker) |
||||
|
||||
一个用于检查iOS固件版本更新的工具,支持定时检查并通过企业微信发送通知。 |
||||
|
||||
## 项目结构 |
||||
|
||||
``` |
||||
firmware_checker/ |
||||
├── firmware_checker/ # 主包目录 |
||||
│ ├── __init__.py # 包入口点 |
||||
│ ├── tasks/ # 任务目录 |
||||
│ │ └── firmware_task.py # 固件检查任务 |
||||
│ └── utils/ # 工具目录 |
||||
│ └── logger.py # 日志工具 |
||||
├── firmware_versions.txt # 本地版本存储文件 |
||||
├── README.md # 项目说明 |
||||
├── requirements.txt # 依赖文件 |
||||
├── Dockerfile # Docker配置 |
||||
└── build.bat # 构建脚本 |
||||
``` |
||||
|
||||
## 功能特性 |
||||
|
||||
- 自动从API获取最新的iOS固件版本 |
||||
- 与本地存储的版本进行比较 |
||||
- 发现新版本时通过企业微信发送通知 |
||||
- 支持定时执行检查任务 |
||||
- 模块化设计,易于扩展其他任务 |
||||
|
||||
## 安装依赖 |
||||
|
||||
```bash |
||||
pip install -r requirements.txt |
||||
``` |
||||
|
||||
## 使用方法 |
||||
|
||||
### 1. 直接运行 |
||||
|
||||
```bash |
||||
python -m firmware_checker |
||||
``` |
||||
|
||||
### 2. 作为模块导入 |
||||
|
||||
```python |
||||
from firmware_checker import check_versions, firmware_main |
||||
|
||||
# 检查版本一次 |
||||
check_versions() |
||||
|
||||
# 启动定时检查服务 |
||||
firmware_main() |
||||
``` |
||||
|
||||
## 环境变量 |
||||
|
||||
| 环境变量 | 说明 | 默认值 | |
||||
|---------|------|-------| |
||||
| WECHAT_WEBHOOK_URL | 企业微信机器人webhook地址 | https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4226c76e-725b-4990-b926-05f16142e513 | |
||||
| CHECK_INTERVAL_MINUTES | 检查间隔(分钟) | 30 | |
||||
|
||||
## 扩展功能 |
||||
|
||||
要添加新任务,只需在`tasks`目录中创建新的任务模块,然后在`__init__.py`中导出相应的函数即可。 |
||||
|
||||
## 许可证 |
||||
|
||||
MIT |
||||
@ -0,0 +1,22 @@
|
||||
"""固件检查器包""" |
||||
import os |
||||
import sys |
||||
|
||||
# 添加包路径到系统路径 |
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) |
||||
|
||||
from firmware_checker.tasks.firmware_task import check_versions, main as firmware_main |
||||
from firmware_checker.utils.logger import setup_logger |
||||
|
||||
# 设置日志 |
||||
logger = setup_logger() |
||||
|
||||
__version__ = '1.0.0' |
||||
__all__ = ['check_versions', 'firmware_main', 'logger'] |
||||
|
||||
def main(): |
||||
"""主函数,调用固件检查任务""" |
||||
firmware_main() |
||||
|
||||
if __name__ == "__main__": |
||||
main() |
||||
@ -0,0 +1,106 @@
|
||||
"""固件检查任务""" |
||||
import os |
||||
import requests |
||||
import json |
||||
from firmware_checker.utils.logger import setup_logger |
||||
|
||||
logger = setup_logger() |
||||
|
||||
def get_firmware_list(): |
||||
"""获取固件列表""" |
||||
# 这里使用模拟数据,实际项目中应该从API获取 |
||||
logger.info("获取固件列表") |
||||
# 模拟固件数据 |
||||
firmware_data = { |
||||
"firmware": [ |
||||
{"version": "26.3", "url": "https://example.com/firmware/26.3.zip"}, |
||||
{"version": "26.2", "url": "https://example.com/firmware/26.2.zip"}, |
||||
{"version": "26.1", "url": "https://example.com/firmware/26.1.zip"} |
||||
] |
||||
} |
||||
return firmware_data |
||||
|
||||
def extract_versions_from_api(data): |
||||
"""从API返回的数据中提取版本信息""" |
||||
logger.info("提取版本信息") |
||||
versions = [] |
||||
if data and "firmware" in data: |
||||
for item in data["firmware"]: |
||||
if "version" in item: |
||||
versions.append(item["version"]) |
||||
return versions |
||||
|
||||
def get_local_versions(): |
||||
"""获取本地记录的版本信息""" |
||||
logger.info("获取本地版本信息") |
||||
file_path = os.path.join(os.getcwd(), "firmware_versions.txt") |
||||
versions = [] |
||||
if os.path.exists(file_path): |
||||
try: |
||||
with open(file_path, "r") as f: |
||||
for line in f: |
||||
version = line.strip() |
||||
if version: |
||||
versions.append(version) |
||||
except Exception as e: |
||||
logger.error(f"读取本地版本文件失败: {e}") |
||||
return versions |
||||
|
||||
def send_wechat_message(message): |
||||
"""发送企业微信消息""" |
||||
webhook_url = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4226c76e-725b-4990-b926-05f16142e513" |
||||
data = { |
||||
"msgtype": "text", |
||||
"text": { |
||||
"content": message |
||||
} |
||||
} |
||||
try: |
||||
response = requests.post(webhook_url, json=data, timeout=10) |
||||
response.raise_for_status() |
||||
logger.info("企业微信消息发送成功") |
||||
except Exception as e: |
||||
logger.error(f"发送企业微信消息失败: {e}") |
||||
|
||||
def check_versions(): |
||||
"""执行固件版本检查""" |
||||
logger.info("开始执行固件版本检查") |
||||
|
||||
# 获取固件列表 |
||||
data = get_firmware_list() |
||||
|
||||
# 提取版本信息 |
||||
api_versions = extract_versions_from_api(data) |
||||
|
||||
# 获取本地版本信息 |
||||
local_versions = get_local_versions() |
||||
|
||||
# 检查是否有新版本 |
||||
new_versions = [v for v in api_versions if v not in local_versions] |
||||
|
||||
if new_versions: |
||||
logger.info(f"发现新版本: {', '.join(new_versions)}") |
||||
# 发送企业微信消息 |
||||
message = f"发现新固件版本: {', '.join(new_versions)}" |
||||
send_wechat_message(message) |
||||
|
||||
# 更新本地版本信息 |
||||
file_path = os.path.join(os.getcwd(), "firmware_versions.txt") |
||||
try: |
||||
with open(file_path, "a") as f: |
||||
for version in new_versions: |
||||
f.write(f"{version}\n") |
||||
logger.info("本地版本信息已更新") |
||||
except Exception as e: |
||||
logger.error(f"更新本地版本信息失败: {e}") |
||||
else: |
||||
logger.info("没有发现新版本") |
||||
|
||||
logger.info("固件版本检查完成") |
||||
|
||||
def main(): |
||||
"""主函数""" |
||||
check_versions() |
||||
|
||||
if __name__ == "__main__": |
||||
main() |
||||
@ -0,0 +1,267 @@
|
||||
"""价格查询任务""" |
||||
import requests |
||||
import json |
||||
import time |
||||
import hashlib |
||||
from firmware_checker.utils.logger import setup_logger |
||||
|
||||
logger = setup_logger() |
||||
|
||||
|
||||
def generate_md5_js_style(data): |
||||
""" |
||||
模拟JavaScript代码的实现: |
||||
a.default["MD5"](data)["toString"](); |
||||
""" |
||||
# 计算MD5(JavaScript中的MD5通常是返回32位小写十六进制字符串) |
||||
md5_hash = hashlib.md5(data.encode('utf-8')).hexdigest() |
||||
|
||||
return md5_hash |
||||
|
||||
|
||||
def javascript_json_stringify(obj): |
||||
""" |
||||
模拟 JavaScript 的 JSON.stringify() 行为 |
||||
特别注意:确保与 JavaScript 的格式完全一致 |
||||
""" |
||||
# 使用紧凑格式(无空格),不转义非ASCII字符 |
||||
return json.dumps(obj, separators=(',', ':'), ensure_ascii=False) |
||||
|
||||
|
||||
def generate_final_sign(first_md5, request_params): |
||||
""" |
||||
模拟JavaScript的最终签名生成: |
||||
u["Lb-Sign"] = a.default["MD5"](d + JSON["stringify"](t))["toString"]() |
||||
|
||||
request_params 应该是一个字典对象,而不是 JSON 字符串 |
||||
""" |
||||
# 将请求参数转换为 JSON 字符串,模拟 JavaScript 的 JSON.stringify() |
||||
params_json = javascript_json_stringify(request_params) |
||||
|
||||
# 拼接第一次的 MD5 和 JSON 字符串 |
||||
combined_str = first_md5 + params_json |
||||
|
||||
# 计算最终的 MD5 |
||||
final_md5 = hashlib.md5(combined_str.encode('utf-8')).hexdigest() |
||||
|
||||
return final_md5 |
||||
|
||||
|
||||
def complete_generate_lb_sign(l_string, timestamp, request_params): |
||||
""" |
||||
完整的 Lb-Sign 生成函数 |
||||
|
||||
参数: |
||||
- l_string: 固定的字符串密钥 |
||||
- timestamp: 时间戳(整数或字符串) |
||||
- request_params: 请求参数字典对象 |
||||
""" |
||||
# 模拟JavaScript的split-reverse-join操作 |
||||
reversed_l = ''.join(reversed(l_string)) |
||||
|
||||
# 拼接时间戳(转换为字符串) |
||||
combined_str = reversed_l + str(timestamp) |
||||
|
||||
# 第一次 MD5 计算 |
||||
first_md5 = generate_md5_js_style(combined_str) |
||||
|
||||
# 最终签名计算 |
||||
final_sign = generate_final_sign(first_md5, request_params) |
||||
|
||||
return { |
||||
"Lb-Timestamp": timestamp, |
||||
"Lb-Sign": final_sign |
||||
} |
||||
|
||||
|
||||
def get_price_data(): |
||||
"""根据curl请求发送HTTP请求获取价格数据""" |
||||
url = 'https://gw.7881.com/goods-service-api/api/goods/list' |
||||
|
||||
# 请求数据(与curl原始请求完全一致) |
||||
data = '{"carrierId":"","extendAttrList":[{"eid":"398615","evs":["联盟"],"selectOption":2,"minCnt":1}],"topExtendAttrList":[],"gameId":"G6211","goodsSortType":1,"groupId":"G6211P001","gtid":"100001","gtId":"100001","maxPrice":"","minPrice":"","pageNum":1,"pageSize":30,"serverId":"G6211P001005","tradePlace":"","tradeType":"","instock":false,"chiledPropertiess":"","order":"","platformId":"","rentalByHourEnd":"","rentalByHourStart":"","optionsSize":0,"curPageData":[],"accountSelfCheck":"","serviceId":"","canBuyCnt":"","publishTitleTemplate":"1","supportReport":"","screenshotService":""}' |
||||
|
||||
try: |
||||
# 将data字符串转换为json对象 |
||||
data_obj = json.loads(data) |
||||
|
||||
# 生成当前时间戳 |
||||
timestamp = int(time.time() * 1000) |
||||
|
||||
# 生成Lb-Sign |
||||
# 从原始代码中获取固定的l字符串 |
||||
l_string = "5c2c5" + "38a3937c6" + "db2d04b" + "ce3d03b" + "be88bl" |
||||
sign_data = complete_generate_lb_sign(l_string, timestamp, data_obj) |
||||
|
||||
# 请求头 |
||||
headers = { |
||||
'Accept': '*/*', |
||||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,zh-TW;q=0.5', |
||||
'Connection': 'keep-alive', |
||||
'Content-Type': 'application/json', |
||||
'Lb-Sign': sign_data["Lb-Sign"], |
||||
'Lb-Timestamp': str(sign_data["Lb-Timestamp"]), |
||||
'Origin': 'https://h5.7881.com', |
||||
'Referer': 'https://h5.7881.com/', |
||||
'Sec-Fetch-Dest': 'empty', |
||||
'Sec-Fetch-Mode': 'cors', |
||||
'Sec-Fetch-Site': 'same-site', |
||||
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1 Edg/143.0.0.0', |
||||
'channelSource': '17', |
||||
'cid': 'undefined', |
||||
'pay-version': '3.0', |
||||
'platform': '7881', |
||||
'userPosition': 'JTdCJTIyZGV2aWNlSWQlMjI6JTIyJTIyLCUyMmRldmljZUlkMSUyMjolMjIlMjIsJTIyZGV2aWNlTmFtZSUyMjolMjIlMjIlN0Q=' |
||||
} |
||||
|
||||
# Cookie(从curl中复制,确保完全一致) |
||||
cookies = { |
||||
'home_page_yxb': '%7B%22gameid%22%3A%22G10%22%2C%22gamename%22%3A%22%E5%9C%B0%E4%B8%8B%E5%9F%8E%E4%B8%8E%E5%8B%87%E5%A3%AB%22%2C%22groupid%22%3A%22G10P001%22%2C%22serverid%22%3A%22G10P001001%22%2C%22groupserver%22%3A%22%E5%B9%BF%E4%B8%9C%E5%8C%BA%2F%E5%B9%BF%E4%B8%9C1%E5%8C%BA%22%2C%22tradeplace%22%3A%22%22%2C%22tradeplacename%22%3A%22%E4%BA%A4%E6%98%93%E6%96%B9%E5%BC%8F%22%2C%22camp%22%3A%22%22%2C%22refresh%22%3A%221%22%7D', |
||||
'AUTHTICKETCHECKVALUE': 'W9Dv9bfHb6N98u25PmnFEpiMLSEW4vAdIWM5sOdJAR32SdhdBoVeWfxs9lVPYKQhRj0RDmE9Wm%2B8%0AC%2Ft3q8M32UkQ9BtVJWOp%2F23zPePwJNJa1Jwn9uYPoDmmGJz7%2BA9lHEcAKXnQ3Z%2FPIlIgJSTQCMdj%0Ab%2BXM9p9Q', |
||||
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1XzE3NDUzOTQwNjMyOTRfOWM1MzE0IiwidXNlcklkIjoxMzE5MDkzMTQsIm5pY2tOYW1lIjoidV8xNzQ1Mzk0MDYzMjk0XzljNTMxNCIsInBob25lIjoiMTg2NzI3ODkzMjkiLCJwd2RVcGRhdGVUaW1lIjoiMjAyNS0wNS0xMyAxMzo1MDozOCIsImxhc3RMb2dpblRpbWUiOiIyMDI1LTEwLTIyIDIxOjUwOjA3IiwicmVtZW1iZXJNZSI6ZmFsc2UsInN5c3RlbVZlcnNpb24iOiIxIiwicGxhdGZvcm0iOiI3ODgxIiwiY2hhbm5lbFNvdXJjZSI6IjEyIiwiZGV2aWNlSWQiOiIxMDIxYWZlMmNjN2Y0NjYyODdmNjZkZmVlMzAwNWMyZiIsImxvZ2luU291cmNlIjoiUEhPTkUiLCJhdXRoIjoiUk9MRV9VU0VSIiwiZXhwIjoxNzY2MzI1MDA3fQ.LlujJ63rGJUUD6fO2jX4hg7CHtbSFajjCVnsI0wOya0', |
||||
'didStr': '1021afe2cc7f466287f66dfee3005c2f', |
||||
'SERVERID': '216', |
||||
'Hm_lvt_6fb35abaf76325a4316e33e23c984e73': '1763837083,1764525733,1765469967,1765643951', |
||||
'PUBLISH_AGREEMENT': '1765643995584', |
||||
'home_page_search': '%7B%22gameid%22%3A%22G6211%22%2C%22goodschannel%22%3A%22100001%22%2C%22groupid%22%3A%22G6211P001%22%2C%22serverid%22%3A%22G6211P001005%22%2C%22camp%22%3Anull%7D' |
||||
} |
||||
|
||||
# 直接使用原始的data字符串作为请求体,与curl保持一致 |
||||
response = requests.post(url, headers=headers, cookies=cookies, data=data, timeout=10) |
||||
response.raise_for_status() # 检查请求是否成功 |
||||
return response.json() |
||||
except Exception as e: |
||||
logger.error(f"获取价格数据失败: {e}") |
||||
return None |
||||
|
||||
|
||||
def extract_price_of_unit(data): |
||||
"""提取结果集中的priceOfUnitForShow字段""" |
||||
if not data: |
||||
return [] |
||||
|
||||
# 打印数据结构,帮助理解 |
||||
# logger.info(f"解析数据结构: {json.dumps(data, indent=2)[:500]}...") |
||||
|
||||
# 尝试不同的数据结构路径 |
||||
price_list = [] |
||||
|
||||
# 路径1: 检查body.results路径(根据curl响应结构) |
||||
if isinstance(data, dict): |
||||
# 检查是否有body字段 |
||||
if 'body' in data: |
||||
body = data['body'] |
||||
# 检查是否有results字段 |
||||
if 'results' in body: |
||||
results_list = body['results'] |
||||
|
||||
for item in results_list: |
||||
if 'priceOfUnitForShow' in item: |
||||
price = item['priceOfUnitForShow'] |
||||
price_list.append(price) |
||||
|
||||
# 路径2: 检查body.goodsList路径 |
||||
if not price_list and isinstance(data, dict) and 'body' in data: |
||||
body = data['body'] |
||||
if 'goodsList' in body: |
||||
goods_list = body['goodsList'] |
||||
logger.info(f"找到goodsList,包含 {len(goods_list)} 个商品") |
||||
|
||||
for item in goods_list: |
||||
if 'priceOfUnitForShow' in item: |
||||
price = item['priceOfUnitForShow'] |
||||
price_list.append(price) |
||||
logger.info(f"找到priceOfUnitForShow: {price}") |
||||
|
||||
# 路径3: 递归查找所有可能的priceOfUnitForShow字段 |
||||
if not price_list: |
||||
# 递归查找函数 |
||||
def find_prices(obj, path=""): |
||||
if isinstance(obj, dict): |
||||
# 检查当前字典是否包含priceOfUnitForShow |
||||
if 'priceOfUnitForShow' in obj: |
||||
price = obj['priceOfUnitForShow'] |
||||
price_list.append(price) |
||||
logger.info(f"在路径 {path} 中找到priceOfUnitForShow: {price}") |
||||
|
||||
# 递归遍历字典的所有值 |
||||
for key, value in obj.items(): |
||||
new_path = f"{path}.{key}" if path else key |
||||
find_prices(value, new_path) |
||||
elif isinstance(obj, list): |
||||
# 遍历列表中的每个元素 |
||||
for i, item in enumerate(obj): |
||||
new_path = f"{path}[{i}]" if path else f"[{i}]" |
||||
find_prices(item, new_path) |
||||
|
||||
# 开始递归查找 |
||||
find_prices(data) |
||||
|
||||
# 按价格排序(假设价格是数字字符串) |
||||
try: |
||||
price_list.sort(key=lambda x: float(x) if x else 0) |
||||
except Exception as e: |
||||
logger.error(f"排序价格失败: {e}") |
||||
|
||||
# 返回前三个价格 |
||||
return price_list[:5] |
||||
|
||||
|
||||
def send_wechat_message(prices): |
||||
"""通过企业微信机器人发送消息""" |
||||
if not prices: |
||||
logger.info("没有价格数据可发送") |
||||
return |
||||
|
||||
# 企业微信机器人Webhook |
||||
webhook_url = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=4226c76e-725b-4990-b926-05f16142e513" |
||||
|
||||
# 构建消息内容 |
||||
message = f"7881铁血4联盟金价查询结果(前5个):\n" |
||||
for i, price in enumerate(prices, 1): |
||||
message += f"{i}. {price}\n" |
||||
|
||||
# 消息体 |
||||
data = { |
||||
"msgtype": "text", |
||||
"text": { |
||||
"content": message |
||||
} |
||||
} |
||||
|
||||
try: |
||||
import requests |
||||
response = requests.post(webhook_url, json=data, timeout=10) |
||||
response.raise_for_status() |
||||
logger.info("企业微信消息发送成功") |
||||
except Exception as e: |
||||
logger.error(f"发送企业微信消息失败: {e}") |
||||
|
||||
|
||||
def check_prices(): |
||||
"""执行价格查询任务""" |
||||
logger.info("开始执行价格查询任务") |
||||
|
||||
# 获取价格数据 |
||||
data = get_price_data() |
||||
|
||||
# 提取价格 |
||||
prices = extract_price_of_unit(data) |
||||
|
||||
if prices: |
||||
logger.info(f"获取到价格数据: {prices}") |
||||
# 发送企业微信消息 |
||||
send_wechat_message(prices) |
||||
else: |
||||
logger.info("未获取到价格数据") |
||||
|
||||
logger.info("价格查询任务完成") |
||||
|
||||
|
||||
def main(): |
||||
"""主函数""" |
||||
check_prices() |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
main() |
||||
@ -0,0 +1,20 @@
|
||||
"""日志工具模块""" |
||||
import logging |
||||
import sys |
||||
|
||||
def setup_logger(): |
||||
"""配置日志系统""" |
||||
logging.basicConfig( |
||||
level=logging.INFO, |
||||
format='%(asctime)s - %(message)s', |
||||
handlers=[ |
||||
logging.StreamHandler(sys.stdout) |
||||
] |
||||
) |
||||
logger = logging.getLogger(__name__) |
||||
|
||||
# 确保日志输出不缓冲 |
||||
for handler in logging.getLogger().handlers: |
||||
handler.flush = sys.stdout.flush |
||||
|
||||
return logger |
||||
@ -0,0 +1,66 @@
|
||||
"""统一任务执行入口""" |
||||
import os |
||||
import time |
||||
import sys |
||||
from firmware_checker.tasks.firmware_task import check_versions as check_firmware_versions |
||||
from firmware_checker.tasks.price_task import check_prices as check_7881_prices |
||||
from firmware_checker.utils.logger import setup_logger |
||||
|
||||
logger = setup_logger() |
||||
|
||||
def run_task1(): |
||||
"""执行第一个任务:固件检查""" |
||||
logger.info("开始执行任务1:固件版本检查") |
||||
check_firmware_versions() |
||||
logger.info("任务1执行完成\n") |
||||
|
||||
def run_task2(): |
||||
"""执行第二个任务:7881价格查询""" |
||||
logger.info("开始执行任务2:7881价格查询") |
||||
check_7881_prices() |
||||
logger.info("任务2执行完成\n") |
||||
|
||||
def run_all_tasks(): |
||||
"""执行所有任务""" |
||||
logger.info("开始执行任务序列") |
||||
logger.info("=" * 50) |
||||
|
||||
# 执行任务1 |
||||
run_task1() |
||||
|
||||
# 执行任务2 |
||||
run_task2() |
||||
|
||||
logger.info("=" * 50) |
||||
logger.info("所有任务执行完成") |
||||
|
||||
def main(): |
||||
"""主函数,支持定时执行任务序列""" |
||||
# 从环境变量读取执行间隔,默认30分钟 |
||||
interval_env = os.environ.get('CHECK_INTERVAL_MINUTES') |
||||
try: |
||||
interval_minutes = int(interval_env) if interval_env else 30 |
||||
except ValueError: |
||||
logger.warning(f"检查间隔参数无效,使用默认值30分钟") |
||||
interval_minutes = 30 |
||||
|
||||
interval_seconds = interval_minutes * 60 |
||||
logger.info(f"任务执行服务启动,间隔: {interval_minutes}分钟,当前时间: {time.strftime('%Y-%m-%d %H:%M:%S')}") |
||||
|
||||
# 首次立即执行一次 |
||||
run_all_tasks() |
||||
|
||||
# 然后定时执行 |
||||
try: |
||||
while True: |
||||
logger.info(f"等待下次执行,{interval_minutes}分钟后...") |
||||
time.sleep(interval_seconds) |
||||
run_all_tasks() |
||||
except KeyboardInterrupt: |
||||
logger.info("服务已停止") |
||||
except Exception as e: |
||||
logger.error(f"服务运行出错: {e}") |
||||
sys.exit(1) |
||||
|
||||
if __name__ == "__main__": |
||||
main() |
||||
Loading…
Reference in new issue