修复所有bug并添加新功能
- 修复添加账号按钮无反应问题
- 添加账号备注字段(可选)
- 添加账号设置按钮(修改密码/备注)
- 修复用户反馈���能
- 添加定时任务执行日志
- 修复容器重启后账号加载问题
- 修复所有JavaScript语法错误
- 优化账号加载机制(4层保障)
🤖 Generated with Claude Code
This commit is contained in:
676
database.py
676
database.py
@@ -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()]
|
||||
|
||||
Reference in New Issue
Block a user