feat: 新增 automation 自动化模块和配置管理
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
"""
|
||||
定时调度模块
|
||||
使用 APScheduler 实现每日自动下载前一天的销售数据
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
scheduler = None
|
||||
|
||||
|
||||
def auto_download_job():
|
||||
"""
|
||||
定时任务:自动下载前一天的销售数据
|
||||
每日凌晨 1 点执行
|
||||
"""
|
||||
logger.info("========== 定时任务触发:自动下载前一天数据 ==========")
|
||||
|
||||
try:
|
||||
from config import Config
|
||||
from automation.secsion import SecsionDownloader
|
||||
from automation.uploader import import_excel_file, cleanup_download
|
||||
|
||||
# 获取凭据
|
||||
creds = Config.get_secsion_credentials()
|
||||
if not creds:
|
||||
logger.error("未配置 secsion.com 登录凭据,跳过自动下载")
|
||||
return
|
||||
|
||||
username, password = creds
|
||||
|
||||
# 计算前一天日期
|
||||
yesterday = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')
|
||||
logger.info(f"下载日期: {yesterday}")
|
||||
|
||||
# 获取店铺 ID
|
||||
shop_id = Config.get_shop_id()
|
||||
|
||||
# 执行下载
|
||||
downloader = SecsionDownloader(username, password, download_dir='downloads', shop_id=shop_id)
|
||||
file_path = asyncio.run(downloader.download_report(yesterday, yesterday))
|
||||
|
||||
if not file_path:
|
||||
logger.error("自动下载失败:未获取到文件")
|
||||
return
|
||||
|
||||
# 导入到 SaleShow
|
||||
imported_name = import_excel_file(file_path, upload_dir='uploads')
|
||||
if imported_name:
|
||||
logger.info(f"自动导入成功: {imported_name}")
|
||||
else:
|
||||
logger.error("自动导入失败")
|
||||
|
||||
# 清理下载的临时文件
|
||||
cleanup_download(file_path)
|
||||
|
||||
logger.info("========== 定时任务完成 ==========")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"定时任务执行异常: {e}", exc_info=True)
|
||||
|
||||
|
||||
def init_scheduler(app=None):
|
||||
"""
|
||||
初始化定时调度器
|
||||
|
||||
Args:
|
||||
app: Flask app 实例(可选,用于获取配置)
|
||||
|
||||
Returns:
|
||||
BackgroundScheduler: 调度器实例
|
||||
"""
|
||||
global scheduler
|
||||
|
||||
try:
|
||||
from config import Config
|
||||
schedule_config = Config.get_schedule_config()
|
||||
except Exception:
|
||||
schedule_config = {'enabled': True, 'hour': 1, 'minute': 0}
|
||||
|
||||
if not schedule_config.get('enabled', True):
|
||||
logger.info("定时任务已禁用")
|
||||
return None
|
||||
|
||||
hour = schedule_config.get('hour', 1)
|
||||
minute = schedule_config.get('minute', 0)
|
||||
|
||||
scheduler = BackgroundScheduler()
|
||||
scheduler.add_job(
|
||||
func=auto_download_job,
|
||||
trigger=CronTrigger(hour=hour, minute=minute),
|
||||
id='daily_download',
|
||||
name='每日自动下载销售数据',
|
||||
replace_existing=True,
|
||||
misfire_grace_time=3600 # 允许 1 小时的延迟执行
|
||||
)
|
||||
scheduler.start()
|
||||
|
||||
logger.info(f"定时任务已启动:每日 {hour:02d}:{minute:02d} 自动下载前一天数据")
|
||||
return scheduler
|
||||
|
||||
|
||||
def get_scheduler_status():
|
||||
"""获取调度器状态"""
|
||||
if scheduler is None:
|
||||
return {'running': False, 'jobs': []}
|
||||
|
||||
jobs = []
|
||||
for job in scheduler.get_jobs():
|
||||
jobs.append({
|
||||
'id': job.id,
|
||||
'name': job.name,
|
||||
'next_run': job.next_run_time.strftime('%Y-%m-%d %H:%M:%S') if job.next_run_time else None
|
||||
})
|
||||
|
||||
return {
|
||||
'running': scheduler.running,
|
||||
'jobs': jobs
|
||||
}
|
||||
|
||||
|
||||
def shutdown_scheduler():
|
||||
"""关闭调度器"""
|
||||
global scheduler
|
||||
if scheduler and scheduler.running:
|
||||
scheduler.shutdown(wait=False)
|
||||
logger.info("定时任务已关闭")
|
||||
Reference in New Issue
Block a user