同步更新:重构路由、服务模块,更新前端构建
This commit is contained in:
288
db/schema.py
Normal file
288
db/schema.py
Normal file
@@ -0,0 +1,288 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
|
||||
from db.utils import get_cst_now_str
|
||||
|
||||
|
||||
def ensure_schema(conn) -> None:
|
||||
"""创建当前版本所需的所有表与索引(新库可直接得到完整 schema)。"""
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 管理员表
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS admins (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
# 用户表
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
email TEXT,
|
||||
email_verified INTEGER DEFAULT 0,
|
||||
email_notify_enabled INTEGER DEFAULT 1,
|
||||
status TEXT DEFAULT 'pending',
|
||||
vip_expire_time TIMESTAMP,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
approved_at TIMESTAMP
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
# 账号表(关联用户)
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS accounts (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
remember INTEGER DEFAULT 1,
|
||||
remark TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
# VIP配置表
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS vip_config (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
default_vip_days INTEGER DEFAULT 0,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
# 系统配置表
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS system_config (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
max_concurrent_global INTEGER DEFAULT 2,
|
||||
schedule_enabled INTEGER DEFAULT 0,
|
||||
schedule_time TEXT DEFAULT '02:00',
|
||||
schedule_browse_type TEXT DEFAULT '应读',
|
||||
proxy_enabled INTEGER DEFAULT 0,
|
||||
proxy_api_url TEXT DEFAULT '',
|
||||
proxy_expire_minutes INTEGER DEFAULT 3,
|
||||
max_screenshot_concurrent INTEGER DEFAULT 3,
|
||||
max_concurrent_per_account INTEGER DEFAULT 1,
|
||||
schedule_weekdays TEXT DEFAULT '1,2,3,4,5,6,7',
|
||||
enable_screenshot INTEGER DEFAULT 1,
|
||||
auto_approve_enabled INTEGER DEFAULT 0,
|
||||
auto_approve_hourly_limit INTEGER DEFAULT 10,
|
||||
auto_approve_vip_days INTEGER DEFAULT 7,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
# 任务日志表
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS task_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
account_id TEXT NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
browse_type TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
total_items INTEGER DEFAULT 0,
|
||||
total_attachments INTEGER DEFAULT 0,
|
||||
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
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
# 密码重置申请表
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS password_reset_requests (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
new_password_hash TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
processed_at TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
# 数据库版本表
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS db_version (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
version INTEGER NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
# 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 announcements (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
is_active INTEGER DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
# 公告永久关闭记录表(用户维度)
|
||||
cursor.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS announcement_dismissals (
|
||||
user_id INTEGER NOT NULL,
|
||||
announcement_id INTEGER NOT NULL,
|
||||
dismissed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (user_id, announcement_id),
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (announcement_id) REFERENCES announcements (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,
|
||||
random_delay INTEGER DEFAULT 0,
|
||||
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 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
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
# ========== 创建索引 ==========
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_users_status ON users(status)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_users_vip_expire ON users(vip_expire_time)")
|
||||
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_accounts_user_id ON accounts(user_id)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_accounts_username ON accounts(username)")
|
||||
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_task_logs_user_id ON task_logs(user_id)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_task_logs_status ON task_logs(status)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_task_logs_created_at ON task_logs(created_at)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_task_logs_user_date ON task_logs(user_id, created_at)")
|
||||
|
||||
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)")
|
||||
|
||||
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_announcements_active ON announcements(is_active)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_announcements_created_at ON announcements(created_at)")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_announcement_dismissals_user ON announcement_dismissals(user_id)")
|
||||
|
||||
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)")
|
||||
except sqlite3.IntegrityError:
|
||||
pass
|
||||
|
||||
# 初始化系统配置(幂等)
|
||||
try:
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO system_config (
|
||||
id, max_concurrent_global, max_concurrent_per_account, max_screenshot_concurrent,
|
||||
schedule_enabled, schedule_time, schedule_browse_type, schedule_weekdays,
|
||||
proxy_enabled, proxy_api_url, proxy_expire_minutes, enable_screenshot,
|
||||
auto_approve_enabled, auto_approve_hourly_limit, auto_approve_vip_days
|
||||
) VALUES (1, 2, 1, 3, 0, '02:00', '应读', '1,2,3,4,5,6,7', 0, '', 3, 1, 0, 10, 7)
|
||||
"""
|
||||
)
|
||||
except sqlite3.IntegrityError:
|
||||
pass
|
||||
|
||||
# 确保 db_version 记录存在(默认 0,由迁移统一更新)
|
||||
cursor.execute("INSERT OR IGNORE INTO db_version (id, version, updated_at) VALUES (1, 0, ?)", (get_cst_now_str(),))
|
||||
|
||||
conn.commit()
|
||||
Reference in New Issue
Block a user