feat: 新增自动下载 API 和设置页面 UI
This commit is contained in:
@@ -6,6 +6,19 @@ import json
|
||||
from datetime import datetime
|
||||
import glob
|
||||
import time
|
||||
import asyncio
|
||||
import threading
|
||||
import logging
|
||||
|
||||
from config import Config
|
||||
from automation.uploader import import_excel_file, cleanup_download
|
||||
from automation.scheduler import init_scheduler, get_scheduler_status, shutdown_scheduler
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['UPLOAD_FOLDER'] = 'uploads'
|
||||
@@ -186,6 +199,154 @@ def cleanup_files():
|
||||
except Exception as e:
|
||||
return jsonify({'error': f'清理文件失败: {str(e)}'}), 500
|
||||
|
||||
# ============ 自动化相关路由 ============
|
||||
|
||||
# 全局下载任务状态
|
||||
download_status = {
|
||||
'running': False,
|
||||
'message': '',
|
||||
'last_run': None,
|
||||
'last_file': None
|
||||
}
|
||||
|
||||
|
||||
@app.route('/settings')
|
||||
def settings_page():
|
||||
"""设置页面"""
|
||||
return render_template('settings.html')
|
||||
|
||||
|
||||
@app.route('/api/settings', methods=['GET'])
|
||||
def get_settings():
|
||||
"""获取配置"""
|
||||
try:
|
||||
return jsonify({'success': True, 'data': Config.get_all_config()})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/settings', methods=['POST'])
|
||||
def save_settings():
|
||||
"""保存配置"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
|
||||
# 保存凭据
|
||||
if 'secsion' in data:
|
||||
secsion = data['secsion']
|
||||
username = secsion.get('username', '').strip()
|
||||
password = secsion.get('password', '').strip()
|
||||
shop_id = secsion.get('shop_id', '').strip()
|
||||
if username and password and password != '******':
|
||||
Config.save_secsion_credentials(username, password)
|
||||
if shop_id is not None:
|
||||
Config.save_shop_id(shop_id)
|
||||
|
||||
# 保存调度配置
|
||||
if 'scheduler' in data:
|
||||
sched = data['scheduler']
|
||||
Config.save_schedule_config(
|
||||
enabled=sched.get('enabled', True),
|
||||
hour=sched.get('hour', 1),
|
||||
minute=sched.get('minute', 0)
|
||||
)
|
||||
|
||||
return jsonify({'success': True, 'message': '配置已保存'})
|
||||
except Exception as e:
|
||||
return jsonify({'error': f'保存配置失败: {str(e)}'}), 500
|
||||
|
||||
|
||||
@app.route('/api/auto-download', methods=['POST'])
|
||||
def auto_download():
|
||||
"""触发自动下载"""
|
||||
global download_status
|
||||
|
||||
if download_status['running']:
|
||||
return jsonify({'error': '已有下载任务正在执行,请稍候'}), 409
|
||||
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
start_date = data.get('start_date')
|
||||
end_date = data.get('end_date', start_date)
|
||||
|
||||
if not start_date:
|
||||
from datetime import timedelta
|
||||
yesterday = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')
|
||||
start_date = yesterday
|
||||
end_date = yesterday
|
||||
|
||||
# 检查凭据
|
||||
creds = Config.get_secsion_credentials()
|
||||
if not creds:
|
||||
return jsonify({'error': '未配置 secsion.com 登录凭据,请先在设置页面配置'}), 400
|
||||
|
||||
username, password = creds
|
||||
|
||||
# 在后台线程执行下载
|
||||
def run_download():
|
||||
global download_status
|
||||
download_status['running'] = True
|
||||
download_status['message'] = f'正在下载 {start_date} ~ {end_date} 的数据...'
|
||||
|
||||
try:
|
||||
from automation.secsion import SecsionDownloader
|
||||
|
||||
shop_id = Config.get_shop_id()
|
||||
downloader = SecsionDownloader(username, password, download_dir='downloads', shop_id=shop_id)
|
||||
file_path = asyncio.run(downloader.download_report(start_date, end_date))
|
||||
|
||||
if file_path:
|
||||
imported_name = import_excel_file(file_path, upload_dir='uploads')
|
||||
cleanup_download(file_path)
|
||||
|
||||
if imported_name:
|
||||
download_status['message'] = f'下载完成: {imported_name}'
|
||||
download_status['last_file'] = imported_name
|
||||
logger.info(f"自动下载并导入成功: {imported_name}")
|
||||
else:
|
||||
download_status['message'] = '下载成功但导入失败'
|
||||
else:
|
||||
download_status['message'] = '下载失败:未获取到文件'
|
||||
|
||||
except Exception as e:
|
||||
download_status['message'] = f'下载异常: {str(e)}'
|
||||
logger.error(f"自动下载异常: {e}", exc_info=True)
|
||||
finally:
|
||||
download_status['running'] = False
|
||||
download_status['last_run'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
thread = threading.Thread(target=run_download, daemon=True)
|
||||
thread.start()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'已开始下载 {start_date} ~ {end_date} 的数据'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'error': f'启动下载失败: {str(e)}'}), 500
|
||||
|
||||
|
||||
@app.route('/api/auto-download/status', methods=['GET'])
|
||||
def get_download_status():
|
||||
"""获取下载任务状态"""
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'status': download_status
|
||||
})
|
||||
|
||||
|
||||
@app.route('/api/scheduler/status', methods=['GET'])
|
||||
def get_scheduler():
|
||||
"""获取定时任务状态"""
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'status': get_scheduler_status()
|
||||
})
|
||||
|
||||
|
||||
# ============ 数据处理函数 ============
|
||||
|
||||
def find_header_row(filepath):
|
||||
"""查找表头所在的行索引"""
|
||||
try:
|
||||
@@ -417,4 +578,11 @@ if __name__ == '__main__':
|
||||
port = int(os.environ.get('PORT', 5000))
|
||||
# 生产环境建议关闭 debug
|
||||
debug_mode = os.environ.get('FLASK_DEBUG', 'True').lower() == 'true'
|
||||
|
||||
# 初始化定时任务调度器
|
||||
try:
|
||||
init_scheduler(app)
|
||||
except Exception as e:
|
||||
logger.warning(f"定时任务调度器初始化失败: {e}")
|
||||
|
||||
app.run(debug=debug_mode, host='0.0.0.0', port=port)
|
||||
Reference in New Issue
Block a user