修复所有bug并添加新功能

- 修复添加账号按钮无反应问题
- 添加账号备注字段(可选)
- 添加账号设置按钮(修改密码/备注)
- 修复用户反馈���能
- 添加定时任务执行日志
- 修复容器重启后账号加载问题
- 修复所有JavaScript语法错误
- 优化账号加载机制(4层保障)

🤖 Generated with Claude Code
This commit is contained in:
Yu Yon
2025-12-10 11:19:16 +08:00
parent 0fd7137cea
commit b5344cd55e
67 changed files with 38235 additions and 3271 deletions

View File

@@ -25,12 +25,16 @@ from password_utils import (
is_sha256_hash,
verify_password_sha256
)
from app_config import get_config
# 数据库文件路径
DB_FILE = "data/app_data.db"
# 获取配置
config = get_config()
# 数据库文件路径 - 从配置读取,避免硬编码
DB_FILE = config.DB_FILE
# 数据库版本 (用于迁移管理)
DB_VERSION = 2
DB_VERSION = 5
def hash_password(password):
@@ -40,7 +44,7 @@ def hash_password(password):
def init_database():
"""初始化数据库表结构"""
db_pool.init_pool(DB_FILE, pool_size=5)
db_pool.init_pool(DB_FILE, pool_size=config.DB_POOL_SIZE)
with db_pool.get_db() as conn:
cursor = conn.cursor()
@@ -98,6 +102,7 @@ def init_database():
id INTEGER PRIMARY KEY CHECK (id = 1),
max_concurrent_global INTEGER DEFAULT 2,
max_concurrent_per_account INTEGER DEFAULT 1,
max_screenshot_concurrent INTEGER DEFAULT 3,
schedule_enabled INTEGER DEFAULT 0,
schedule_time TEXT DEFAULT '02:00',
schedule_browse_type TEXT DEFAULT '应读',
@@ -124,6 +129,7 @@ def init_database():
error_message TEXT,
duration INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
source TEXT DEFAULT 'manual',
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
)
''')
@@ -150,6 +156,42 @@ def init_database():
)
''')
# Bug反馈表
cursor.execute('''
CREATE TABLE IF NOT EXISTS bug_feedbacks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
username TEXT NOT NULL,
title TEXT NOT NULL,
description TEXT NOT NULL,
contact TEXT,
status TEXT DEFAULT 'pending',
admin_reply TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
replied_at TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
)
''')
# 用户定时任务表
cursor.execute('''
CREATE TABLE IF NOT EXISTS user_schedules (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
name TEXT DEFAULT '我的定时任务',
enabled INTEGER DEFAULT 0,
schedule_time TEXT NOT NULL DEFAULT '08:00',
weekdays TEXT NOT NULL DEFAULT '1,2,3,4,5',
browse_type TEXT NOT NULL DEFAULT '应读',
enable_screenshot INTEGER DEFAULT 1,
account_ids TEXT,
last_run_at TIMESTAMP,
next_run_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
)
''')
# ========== 创建索引 ==========
# 用户表索引
cursor.execute('CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)')
@@ -170,12 +212,22 @@ def init_database():
cursor.execute('CREATE INDEX IF NOT EXISTS idx_password_reset_status ON password_reset_requests(status)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_password_reset_user_id ON password_reset_requests(user_id)')
# Bug反馈表索引
cursor.execute('CREATE INDEX IF NOT EXISTS idx_bug_feedbacks_user_id ON bug_feedbacks(user_id)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_bug_feedbacks_status ON bug_feedbacks(status)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_bug_feedbacks_created_at ON bug_feedbacks(created_at)')
# 用户定时任务表索引
cursor.execute('CREATE INDEX IF NOT EXISTS idx_user_schedules_user_id ON user_schedules(user_id)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_user_schedules_enabled ON user_schedules(enabled)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_user_schedules_next_run ON user_schedules(next_run_at)')
# 初始化VIP配置
try:
cursor.execute('INSERT INTO vip_config (id, default_vip_days) VALUES (1, 0)')
conn.commit()
print("✓ 已创建VIP配置(默认不赠送)")
except sqlite3.IntegrityError:
# VIP配置已存在忽略
pass
# 初始化系统配置
@@ -189,6 +241,7 @@ def init_database():
conn.commit()
print("✓ 已创建系统配置(默认并发2,定时任务关闭)")
except sqlite3.IntegrityError:
# 系统配置已存在,忽略
pass
# 初始化数据库版本
@@ -197,6 +250,7 @@ def init_database():
conn.commit()
print(f"✓ 数据库版本: {DB_VERSION}")
except sqlite3.IntegrityError:
# 数据库版本记录已存在,忽略
pass
conn.commit()
@@ -205,6 +259,9 @@ def init_database():
# 执行数据迁移
migrate_database()
# 确保存在默认管理员
ensure_default_admin()
def migrate_database():
"""数据库迁移 - 自动检测并应用必要的迁移"""
@@ -227,6 +284,19 @@ def migrate_database():
_migrate_to_v2(conn)
current_version = 2
if current_version < 3:
_migrate_to_v3(conn)
current_version = 3
if current_version < 4:
_migrate_to_v4(conn)
current_version = 4
if current_version < 5:
_migrate_to_v5(conn)
current_version = 5
# 更新版本号
cursor.execute('UPDATE db_version SET version = ?, updated_at = CURRENT_TIMESTAMP WHERE id = 1',
(DB_VERSION,))
@@ -248,6 +318,9 @@ def _migrate_to_v1(conn):
cursor.execute('ALTER TABLE system_config ADD COLUMN schedule_weekdays TEXT DEFAULT "1,2,3,4,5,6,7"')
print(" ✓ 添加 schedule_weekdays 字段")
if 'max_screenshot_concurrent' not in columns:
cursor.execute('ALTER TABLE system_config ADD COLUMN max_screenshot_concurrent INTEGER DEFAULT 3')
print(" ✓ 添加 max_screenshot_concurrent 字段")
if 'max_concurrent_per_account' not in columns:
cursor.execute('ALTER TABLE system_config ADD COLUMN max_concurrent_per_account INTEGER DEFAULT 1')
print(" ✓ 添加 max_concurrent_per_account 字段")
@@ -289,8 +362,124 @@ def _migrate_to_v2(conn):
conn.commit()
def _migrate_to_v3(conn):
"""迁移到版本3 - 添加账号状态和登录失败计数字段"""
cursor = conn.cursor()
cursor.execute("PRAGMA table_info(accounts)")
columns = [col[1] for col in cursor.fetchall()]
if 'status' not in columns:
cursor.execute('ALTER TABLE accounts ADD COLUMN status TEXT DEFAULT "active"')
print(" ✓ 添加 accounts.status 字段 (账号状态)")
if 'login_fail_count' not in columns:
cursor.execute('ALTER TABLE accounts ADD COLUMN login_fail_count INTEGER DEFAULT 0')
print(" ✓ 添加 accounts.login_fail_count 字段 (登录失败计数)")
if 'last_login_error' not in columns:
cursor.execute('ALTER TABLE accounts ADD COLUMN last_login_error TEXT')
print(" ✓ 添加 accounts.last_login_error 字段 (最后登录错误)")
conn.commit()
def _migrate_to_v4(conn):
"""迁移到版本4 - 添加任务来源字段"""
cursor = conn.cursor()
cursor.execute("PRAGMA table_info(task_logs)")
columns = [col[1] for col in cursor.fetchall()]
if 'source' not in columns:
cursor.execute('ALTER TABLE task_logs ADD COLUMN source TEXT DEFAULT "manual"')
print(" ✓ 添加 task_logs.source 字段 (任务来源: manual/scheduled/immediate)")
def _migrate_to_v5(conn):
"""迁移到版本5 - 添加用户定时任务表"""
cursor = conn.cursor()
# 检查user_schedules表是否存在
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='user_schedules'")
if not cursor.fetchone():
cursor.execute('''
CREATE TABLE IF NOT EXISTS user_schedules (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
name TEXT DEFAULT '我的定时任务',
enabled INTEGER DEFAULT 0,
schedule_time TEXT NOT NULL DEFAULT '08:00',
weekdays TEXT NOT NULL DEFAULT '1,2,3,4,5',
browse_type TEXT NOT NULL DEFAULT '应读',
enable_screenshot INTEGER DEFAULT 1,
account_ids TEXT,
last_run_at TIMESTAMP,
next_run_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
)
''')
print(" ✓ 创建 user_schedules 表 (用户定时任务)")
# 定时任务执行日志表
cursor.execute('''
CREATE TABLE IF NOT EXISTS schedule_execution_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
schedule_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
schedule_name TEXT,
execute_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
total_accounts INTEGER DEFAULT 0,
success_accounts INTEGER DEFAULT 0,
failed_accounts INTEGER DEFAULT 0,
total_items INTEGER DEFAULT 0,
total_attachments INTEGER DEFAULT 0,
total_screenshots INTEGER DEFAULT 0,
duration_seconds INTEGER DEFAULT 0,
status TEXT DEFAULT 'running',
error_message TEXT,
FOREIGN KEY (schedule_id) REFERENCES user_schedules (id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
)
''')
print(" ✓ 创建 schedule_execution_logs 表 (定时任务执行日志)")
# 创建索引
cursor.execute('CREATE INDEX IF NOT EXISTS idx_user_schedules_user_id ON user_schedules(user_id)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_user_schedules_enabled ON user_schedules(enabled)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_user_schedules_next_run ON user_schedules(next_run_at)')
print(" ✓ 创建 user_schedules 表索引")
conn.commit()
# ==================== 管理员相关 ====================
def ensure_default_admin():
"""确保存在默认管理员账号 admin/admin"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
# 检查是否已存在管理员
cursor.execute('SELECT COUNT(*) as count FROM admins')
result = cursor.fetchone()
if result['count'] == 0:
# 创建默认管理员 admin/admin
default_password_hash = hash_password_bcrypt('admin')
cursor.execute(
'INSERT INTO admins (username, password_hash) VALUES (?, ?)',
('admin', default_password_hash)
)
conn.commit()
print("✓ 已创建默认管理员账号 (admin/admin)")
return True
return False
def verify_admin(username, password):
"""验证管理员登录 - 自动从SHA256升级到bcrypt"""
with db_pool.get_db() as conn:
@@ -405,7 +594,9 @@ def extend_user_vip(user_id, days):
if expire_time < now:
expire_time = now
new_expire = (expire_time + timedelta(days=days)).strftime('%Y-%m-%d %H:%M:%S')
except:
except (ValueError, AttributeError) as e:
# VIP过期时间格式错误使用当前时间
print(f"解析VIP过期时间失败: {e}, 使用当前时间")
new_expire = (datetime.now(cst_tz) + timedelta(days=days)).strftime('%Y-%m-%d %H:%M:%S')
else:
new_expire = (datetime.now(cst_tz) + timedelta(days=days)).strftime('%Y-%m-%d %H:%M:%S')
@@ -436,7 +627,8 @@ def is_user_vip(user_id):
expire_time_naive = datetime.strptime(user['vip_expire_time'], '%Y-%m-%d %H:%M:%S')
expire_time = cst_tz.localize(expire_time_naive)
return datetime.now(cst_tz) < expire_time
except:
except (ValueError, AttributeError) as e:
print(f"检查VIP状态失败 (user_id={user_id}): {e}")
return False
@@ -646,6 +838,70 @@ def delete_account(account_id):
return cursor.rowcount > 0
def increment_account_login_fail(account_id, error_message):
"""增加账号登录失败次数如果达到3次则暂停账号"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
# 获取当前失败次数
cursor.execute('SELECT login_fail_count FROM accounts WHERE id = ?', (account_id,))
row = cursor.fetchone()
if not row:
return False
fail_count = (row['login_fail_count'] or 0) + 1
# 更新失败次数和错误信息
if fail_count >= 3:
# 达到3次暂停账号
cursor.execute('''
UPDATE accounts
SET login_fail_count = ?,
last_login_error = ?,
status = 'suspended'
WHERE id = ?
''', (fail_count, error_message, account_id))
conn.commit()
return True # 返回True表示账号已被暂停
else:
# 未达到3次只更新计数
cursor.execute('''
UPDATE accounts
SET login_fail_count = ?,
last_login_error = ?
WHERE id = ?
''', (fail_count, error_message, account_id))
conn.commit()
return False # 返回False表示未暂停
def reset_account_login_status(account_id):
"""重置账号登录状态(修改密码后调用)"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute('''
UPDATE accounts
SET login_fail_count = 0,
last_login_error = NULL,
status = 'active'
WHERE id = ?
''', (account_id,))
conn.commit()
return cursor.rowcount > 0
def get_account_status(account_id):
"""获取账号状态信息"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute('''
SELECT status, login_fail_count, last_login_error
FROM accounts
WHERE id = ?
''', (account_id,))
return cursor.fetchone()
def delete_user_accounts(user_id):
"""删除用户的所有账号"""
with db_pool.get_db() as conn:
@@ -715,6 +971,7 @@ def get_system_config():
return {
'max_concurrent_global': 2,
'max_concurrent_per_account': 1,
'max_screenshot_concurrent': 3,
'schedule_enabled': 0,
'schedule_time': '02:00',
'schedule_browse_type': '应读',
@@ -728,7 +985,7 @@ def get_system_config():
def update_system_config(max_concurrent=None, schedule_enabled=None, schedule_time=None,
schedule_browse_type=None, schedule_weekdays=None,
max_concurrent_per_account=None, proxy_enabled=None,
max_concurrent_per_account=None, max_screenshot_concurrent=None, proxy_enabled=None,
proxy_api_url=None, proxy_expire_minutes=None):
"""更新系统配置"""
with db_pool.get_db() as conn:
@@ -785,8 +1042,12 @@ def update_system_config(max_concurrent=None, schedule_enabled=None, schedule_ti
# ==================== 任务日志管理 ====================
def create_task_log(user_id, account_id, username, browse_type, status,
total_items=0, total_attachments=0, error_message='', duration=None):
"""创建任务日志记录"""
total_items=0, total_attachments=0, error_message='', duration=None, source='manual'):
"""创建任务日志记录
Args:
source: 任务来源 - 'manual'(手动执行), 'scheduled'(定时任务), 'immediate'(立即执行)
"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cst_tz = pytz.timezone("Asia/Shanghai")
@@ -795,43 +1056,77 @@ def create_task_log(user_id, account_id, username, browse_type, status,
cursor.execute('''
INSERT INTO task_logs (
user_id, account_id, username, browse_type, status,
total_items, total_attachments, error_message, duration, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
total_items, total_attachments, error_message, duration, created_at, source
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (user_id, account_id, username, browse_type, status,
total_items, total_attachments, error_message, duration, cst_time))
total_items, total_attachments, error_message, duration, cst_time, source))
conn.commit()
return cursor.lastrowid
def get_task_logs(limit=100, offset=0, date_filter=None, status_filter=None):
"""获取任务日志列表"""
def get_task_logs(limit=100, offset=0, date_filter=None, status_filter=None,
source_filter=None, user_id_filter=None, account_filter=None):
"""获取任务日志列表(支持分页和多种筛选)"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
sql = '''
# 构建WHERE条件
where_clauses = ["1=1"]
params = []
if date_filter:
where_clauses.append("date(tl.created_at) = ?")
params.append(date_filter)
if status_filter:
where_clauses.append("tl.status = ?")
params.append(status_filter)
if source_filter:
where_clauses.append("tl.source = ?")
params.append(source_filter)
if user_id_filter:
where_clauses.append("tl.user_id = ?")
params.append(user_id_filter)
if account_filter:
where_clauses.append("tl.username LIKE ?")
params.append(f"%{account_filter}%")
where_sql = " AND ".join(where_clauses)
# 获取总数
count_sql = f'''
SELECT COUNT(*) as total
FROM task_logs tl
LEFT JOIN users u ON tl.user_id = u.id
WHERE {where_sql}
'''
cursor.execute(count_sql, params)
total = cursor.fetchone()['total']
# 获取分页数据
data_sql = f'''
SELECT
tl.*,
u.username as user_username
FROM task_logs tl
LEFT JOIN users u ON tl.user_id = u.id
WHERE 1=1
WHERE {where_sql}
ORDER BY tl.created_at DESC
LIMIT ? OFFSET ?
'''
params = []
if date_filter:
sql += " AND date(tl.created_at) = ?"
params.append(date_filter)
if status_filter:
sql += " AND tl.status = ?"
params.append(status_filter)
sql += " ORDER BY tl.created_at DESC LIMIT ? OFFSET ?"
params.extend([limit, offset])
cursor.execute(sql, params)
return [dict(row) for row in cursor.fetchall()]
cursor.execute(data_sql, params)
logs = [dict(row) for row in cursor.fetchall()]
return {
'logs': logs,
'total': total
}
def get_task_stats(date_filter=None):
@@ -1064,3 +1359,326 @@ def clean_old_operation_logs(days=30):
except Exception as e:
print(f"清理旧操作日志失败: {e}")
return 0
# ==================== Bug反馈管理 ====================
def create_bug_feedback(user_id, username, title, description, contact=''):
"""创建Bug反馈"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cst_tz = pytz.timezone("Asia/Shanghai")
cst_time = datetime.now(cst_tz).strftime("%Y-%m-%d %H:%M:%S")
cursor.execute('''
INSERT INTO bug_feedbacks (user_id, username, title, description, contact, created_at)
VALUES (?, ?, ?, ?, ?, ?)
''', (user_id, username, title, description, contact, cst_time))
conn.commit()
return cursor.lastrowid
def get_bug_feedbacks(limit=100, offset=0, status_filter=None):
"""获取Bug反馈列表管理员用"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
sql = 'SELECT * FROM bug_feedbacks WHERE 1=1'
params = []
if status_filter:
sql += ' AND status = ?'
params.append(status_filter)
sql += ' ORDER BY created_at DESC LIMIT ? OFFSET ?'
params.extend([limit, offset])
cursor.execute(sql, params)
return [dict(row) for row in cursor.fetchall()]
def get_user_feedbacks(user_id, limit=50):
"""获取用户自己的反馈列表"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute('''
SELECT * FROM bug_feedbacks
WHERE user_id = ?
ORDER BY created_at DESC
LIMIT ?
''', (user_id, limit))
return [dict(row) for row in cursor.fetchall()]
def get_feedback_by_id(feedback_id):
"""根据ID获取反馈详情"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute('SELECT * FROM bug_feedbacks WHERE id = ?', (feedback_id,))
row = cursor.fetchone()
return dict(row) if row else None
def reply_feedback(feedback_id, admin_reply):
"""管理员回复反馈"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cst_tz = pytz.timezone("Asia/Shanghai")
cst_time = datetime.now(cst_tz).strftime("%Y-%m-%d %H:%M:%S")
cursor.execute('''
UPDATE bug_feedbacks
SET admin_reply = ?, status = 'replied', replied_at = ?
WHERE id = ?
''', (admin_reply, cst_time, feedback_id))
conn.commit()
return cursor.rowcount > 0
def close_feedback(feedback_id):
"""关闭反馈"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute('''
UPDATE bug_feedbacks
SET status = 'closed'
WHERE id = ?
''', (feedback_id,))
conn.commit()
return cursor.rowcount > 0
def delete_feedback(feedback_id):
"""删除反馈"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute('DELETE FROM bug_feedbacks WHERE id = ?', (feedback_id,))
conn.commit()
return cursor.rowcount > 0
def get_feedback_stats():
"""获取反馈统计"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute('''
SELECT
COUNT(*) as total,
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending,
SUM(CASE WHEN status = 'replied' THEN 1 ELSE 0 END) as replied,
SUM(CASE WHEN status = 'closed' THEN 1 ELSE 0 END) as closed
FROM bug_feedbacks
''')
row = cursor.fetchone()
return dict(row) if row else {'total': 0, 'pending': 0, 'replied': 0, 'closed': 0}
# ==================== 用户定时任务管理 ====================
def get_user_schedules(user_id):
"""获取用户的所有定时任务"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute('''
SELECT * FROM user_schedules
WHERE user_id = ?
ORDER BY created_at DESC
''', (user_id,))
return [dict(row) for row in cursor.fetchall()]
def get_schedule_by_id(schedule_id):
"""根据ID获取定时任务"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute('SELECT * FROM user_schedules WHERE id = ?', (schedule_id,))
row = cursor.fetchone()
return dict(row) if row else None
def create_user_schedule(user_id, name='我的定时任务', schedule_time='08:00',
weekdays='1,2,3,4,5', browse_type='应读',
enable_screenshot=1, account_ids=None):
"""创建用户定时任务"""
import json
with db_pool.get_db() as conn:
cursor = conn.cursor()
cst_tz = pytz.timezone("Asia/Shanghai")
cst_time = datetime.now(cst_tz).strftime("%Y-%m-%d %H:%M:%S")
account_ids_str = json.dumps(account_ids) if account_ids else '[]'
cursor.execute('''
INSERT INTO user_schedules (
user_id, name, enabled, schedule_time, weekdays,
browse_type, enable_screenshot, account_ids, created_at, updated_at
) VALUES (?, ?, 0, ?, ?, ?, ?, ?, ?, ?)
''', (user_id, name, schedule_time, weekdays, browse_type,
enable_screenshot, account_ids_str, cst_time, cst_time))
conn.commit()
return cursor.lastrowid
def update_user_schedule(schedule_id, **kwargs):
"""更新用户定时任务"""
import json
with db_pool.get_db() as conn:
cursor = conn.cursor()
cst_tz = pytz.timezone("Asia/Shanghai")
updates = []
params = []
allowed_fields = ['name', 'enabled', 'schedule_time', 'weekdays',
'browse_type', 'enable_screenshot', 'account_ids']
for field in allowed_fields:
if field in kwargs:
value = kwargs[field]
if field == 'account_ids' and isinstance(value, list):
value = json.dumps(value)
updates.append(f'{field} = ?')
params.append(value)
if not updates:
return False
updates.append('updated_at = ?')
params.append(datetime.now(cst_tz).strftime("%Y-%m-%d %H:%M:%S"))
params.append(schedule_id)
sql = f"UPDATE user_schedules SET {', '.join(updates)} WHERE id = ?"
cursor.execute(sql, params)
conn.commit()
return cursor.rowcount > 0
def delete_user_schedule(schedule_id):
"""删除用户定时任务"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute('DELETE FROM user_schedules WHERE id = ?', (schedule_id,))
conn.commit()
return cursor.rowcount > 0
def toggle_user_schedule(schedule_id, enabled):
"""启用/禁用用户定时任务"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cst_tz = pytz.timezone("Asia/Shanghai")
cst_time = datetime.now(cst_tz).strftime("%Y-%m-%d %H:%M:%S")
cursor.execute('''
UPDATE user_schedules
SET enabled = ?, updated_at = ?
WHERE id = ?
''', (1 if enabled else 0, cst_time, schedule_id))
conn.commit()
return cursor.rowcount > 0
def get_enabled_user_schedules():
"""获取所有启用的用户定时任务"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute('''
SELECT us.*, u.username as user_username
FROM user_schedules us
JOIN users u ON us.user_id = u.id
WHERE us.enabled = 1
ORDER BY us.schedule_time
''')
return [dict(row) for row in cursor.fetchall()]
def update_schedule_last_run(schedule_id):
"""更新定时任务最后运行时间"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cst_tz = pytz.timezone("Asia/Shanghai")
cst_time = datetime.now(cst_tz).strftime("%Y-%m-%d %H:%M:%S")
cursor.execute('''
UPDATE user_schedules
SET last_run_at = ?, updated_at = ?
WHERE id = ?
''', (cst_time, cst_time, schedule_id))
conn.commit()
return cursor.rowcount > 0
# ==================== 定时任务执行日志 ====================
def create_schedule_execution_log(schedule_id, user_id, schedule_name):
"""创建定时任务执行日志"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cst_tz = pytz.timezone("Asia/Shanghai")
execute_time = datetime.now(cst_tz).strftime("%Y-%m-%d %H:%M:%S")
cursor.execute('''
INSERT INTO schedule_execution_logs (
schedule_id, user_id, schedule_name, execute_time, status
) VALUES (?, ?, ?, ?, 'running')
''', (schedule_id, user_id, schedule_name, execute_time))
conn.commit()
return cursor.lastrowid
def update_schedule_execution_log(log_id, **kwargs):
"""更新定时任务执行日志"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
updates = []
params = []
allowed_fields = ['total_accounts', 'success_accounts', 'failed_accounts',
'total_items', 'total_attachments', 'total_screenshots',
'duration_seconds', 'status', 'error_message']
for field in allowed_fields:
if field in kwargs:
updates.append(f'{field} = ?')
params.append(kwargs[field])
if not updates:
return False
params.append(log_id)
sql = f"UPDATE schedule_execution_logs SET {', '.join(updates)} WHERE id = ?"
cursor.execute(sql, params)
conn.commit()
return cursor.rowcount > 0
def get_schedule_execution_logs(schedule_id, limit=10):
"""获取定时任务执行日志"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute('''
SELECT * FROM schedule_execution_logs
WHERE schedule_id = ?
ORDER BY execute_time DESC
LIMIT ?
''', (schedule_id, limit))
return [dict(row) for row in cursor.fetchall()]
def get_user_all_schedule_logs(user_id, limit=50):
"""获取用户所有定时任务的执行日志"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute('''
SELECT * FROM schedule_execution_logs
WHERE user_id = ?
ORDER BY execute_time DESC
LIMIT ?
''', (user_id, limit))
return [dict(row) for row in cursor.fetchall()]