Files
2026-09-30 16:11:51 +08:00

354 lines
17 KiB
SQL
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
PRAGMA foreign_keys = ON;
-- ============ 用户 / 会话 / 设置 ============
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'admin', -- admin | viewer
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS sessions (
id TEXT PRIMARY KEY, -- 32 字节随机 base64url
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
csrf_token TEXT NOT NULL,
ip_addr TEXT,
user_agent TEXT,
expires_at TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
-- ============ 服务器节点 ============
CREATE TABLE IF NOT EXISTS nodes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
is_local INTEGER NOT NULL DEFAULT 0, -- 1 = Master 本机
addr TEXT,
public_host TEXT, -- 对外域名/IP,用于生成订阅
api_token TEXT NOT NULL,
enabled INTEGER NOT NULL DEFAULT 1,
meta TEXT, -- JSON:地区、运营商、备注
version TEXT,
last_heartbeat_at TEXT,
last_load TEXT, -- JSON:{cpu,mem,conns}
online_state TEXT NOT NULL DEFAULT 'unknown', -- unknown | online | offline(离线告警用)
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- ============ 分组 / 标签 / 输出模板 ============
CREATE TABLE IF NOT EXISTS entry_groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
color TEXT NOT NULL DEFAULT '#3b82f6',
description TEXT,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
color TEXT NOT NULL DEFAULT '#64748b',
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS sub_templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
description TEXT,
format TEXT NOT NULL, -- clash | singbox
content TEXT NOT NULL, -- Clash=YAML,Sing-box=JSON,含 @ 标记
is_builtin INTEGER NOT NULL DEFAULT 0, -- 内置模板不可删除/修改,可复制
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- ============ 导入记录(必须先于条目表创建) ============
CREATE TABLE IF NOT EXISTS import_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
source_url TEXT,
source_type TEXT NOT NULL, -- clash | singbox | base64 | uri_list | paste
target_node_id INTEGER REFERENCES nodes(id) ON DELETE SET NULL,
parsed_count INTEGER NOT NULL DEFAULT 0,
imported_count INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- ============ 三类条目 ============
CREATE TABLE IF NOT EXISTS proxy_rules ( -- 落地代理
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id INTEGER NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
name TEXT NOT NULL,
listen_addr TEXT, -- 本地监听,可空(纯导入条目无需监听)
upstream_type TEXT NOT NULL, -- http | https | socks5 | ss | vmess | vless | trojan | hysteria2 | tuic ...
upstream_addr TEXT NOT NULL, -- host:port
auth_user TEXT,
auth_pass TEXT,
export_host TEXT, -- 订阅导出地址;空则用 upstream 的 host
export_port INTEGER,
extra TEXT, -- JSON:原始节点完整配置(Clash 风格字典)
source_import_id INTEGER REFERENCES import_records(id) ON DELETE SET NULL,
group_id INTEGER REFERENCES entry_groups(id) ON DELETE SET NULL,
expires_at TEXT, -- UTC;NULL = 不过期
enabled INTEGER NOT NULL DEFAULT 1,
disabled_reason TEXT, -- manual | expired | quota
remark TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS port_forwards ( -- 端口转发
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id INTEGER NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
name TEXT NOT NULL,
listen_ip TEXT NOT NULL DEFAULT '0.0.0.0',
listen_port INTEGER NOT NULL,
target_ip TEXT NOT NULL,
target_port INTEGER NOT NULL,
protocol TEXT NOT NULL DEFAULT 'tcp', -- tcp | udp | both
export_host TEXT,
export_port INTEGER,
extra TEXT,
source_import_id INTEGER REFERENCES import_records(id) ON DELETE SET NULL,
group_id INTEGER REFERENCES entry_groups(id) ON DELETE SET NULL,
expires_at TEXT,
enabled INTEGER NOT NULL DEFAULT 1,
disabled_reason TEXT,
remark TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE(node_id, listen_ip, listen_port, protocol)
);
CREATE TABLE IF NOT EXISTS tunnels ( -- 中转隧道
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id INTEGER NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
name TEXT NOT NULL,
tunnel_type TEXT NOT NULL, -- tcp | ws | wss | reverse
local_addr TEXT NOT NULL,
remote_addr TEXT NOT NULL,
token TEXT,
export_host TEXT,
export_port INTEGER,
extra TEXT,
source_import_id INTEGER REFERENCES import_records(id) ON DELETE SET NULL,
group_id INTEGER REFERENCES entry_groups(id) ON DELETE SET NULL,
expires_at TEXT,
enabled INTEGER NOT NULL DEFAULT 1,
disabled_reason TEXT,
remark TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- ============ 多态关联表(删除条目时由应用层同事务清理) ============
CREATE TABLE IF NOT EXISTS entry_tags (
entry_type TEXT NOT NULL, -- proxy | forward | tunnel
entry_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
PRIMARY KEY (entry_type, entry_id, tag_id)
);
CREATE TABLE IF NOT EXISTS entry_quotas (
id INTEGER PRIMARY KEY AUTOINCREMENT,
entry_type TEXT NOT NULL,
entry_id INTEGER NOT NULL,
quota_bytes INTEGER NOT NULL, -- 0 = 无限制(等同于删除配额)
used_bytes INTEGER NOT NULL DEFAULT 0,
period TEXT NOT NULL DEFAULT 'total', -- total | monthly
reset_day INTEGER NOT NULL DEFAULT 1, -- monthly 时每月几号重置,1~28
period_start TEXT NOT NULL DEFAULT (datetime('now')),
warn_level INTEGER NOT NULL DEFAULT 0, -- 本周期已发出的最高预警百分比
status TEXT NOT NULL DEFAULT 'ok', -- ok | exceeded
exceeded_at TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE (entry_type, entry_id)
);
-- ============ 日志 / 统计 / 监控 ============
CREATE TABLE IF NOT EXISTS audit_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
actor_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
actor_name TEXT, -- 系统动作为 'system'
action TEXT NOT NULL,
resource_type TEXT,
resource_id INTEGER,
node_id INTEGER,
detail TEXT, -- JSON
ip_addr TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS access_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id INTEGER NOT NULL,
rule_type TEXT NOT NULL, -- proxy | forward | tunnel
rule_id INTEGER NOT NULL,
client_ip TEXT,
started_at TEXT NOT NULL,
ended_at TEXT,
bytes_up INTEGER NOT NULL DEFAULT 0,
bytes_down INTEGER NOT NULL DEFAULT 0,
status TEXT
);
CREATE TABLE IF NOT EXISTS stats_hourly (
id INTEGER PRIMARY KEY AUTOINCREMENT,
stat_time TEXT NOT NULL, -- 2026-09-30T14
node_id INTEGER NOT NULL,
rule_type TEXT NOT NULL,
rule_id INTEGER NOT NULL,
total_connections INTEGER NOT NULL DEFAULT 0,
total_bytes_up INTEGER NOT NULL DEFAULT 0,
total_bytes_down INTEGER NOT NULL DEFAULT 0,
UNIQUE(stat_time, node_id, rule_type, rule_id)
);
CREATE TABLE IF NOT EXISTS stats_daily (
id INTEGER PRIMARY KEY AUTOINCREMENT,
stat_date TEXT NOT NULL, -- 2026-09-30
node_id INTEGER NOT NULL,
rule_type TEXT NOT NULL,
rule_id INTEGER NOT NULL,
total_connections INTEGER NOT NULL DEFAULT 0,
total_bytes_up INTEGER NOT NULL DEFAULT 0,
total_bytes_down INTEGER NOT NULL DEFAULT 0,
UNIQUE(stat_date, node_id, rule_type, rule_id)
);
CREATE TABLE IF NOT EXISTS metrics_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id INTEGER NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
collected_at TEXT NOT NULL,
cpu REAL, mem REAL, conns INTEGER, net_up_bps INTEGER, net_down_bps INTEGER
);
-- ============ 订阅 ============
CREATE TABLE IF NOT EXISTS subscriptions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
token TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
description TEXT,
scope TEXT NOT NULL DEFAULT 'custom', -- single | custom | all | rule
default_format TEXT NOT NULL DEFAULT 'clash', -- clash | singbox | base64 | json
template_id INTEGER REFERENCES sub_templates(id) ON DELETE SET NULL,
enabled INTEGER NOT NULL DEFAULT 1,
disabled_reason TEXT, -- manual | expired
expires_at TEXT, -- NULL = 永久;UTC
max_ips INTEGER, -- 24h 内不同来源 IP 数上限,NULL = 不限
access_count INTEGER NOT NULL DEFAULT 0,
last_access_at TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS subscription_entries ( -- 手工勾选的条目(single / custom,及 rule 的例外项)
id INTEGER PRIMARY KEY AUTOINCREMENT,
subscription_id INTEGER NOT NULL REFERENCES subscriptions(id) ON DELETE CASCADE,
entry_type TEXT NOT NULL,
entry_id INTEGER NOT NULL,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE(subscription_id, entry_type, entry_id)
);
CREATE TABLE IF NOT EXISTS subscription_rules ( -- 动态规则
id INTEGER PRIMARY KEY AUTOINCREMENT,
subscription_id INTEGER NOT NULL REFERENCES subscriptions(id) ON DELETE CASCADE,
kind TEXT NOT NULL, -- group | tag | node | type
value TEXT NOT NULL, -- group/tag 存 id;node 存 node_id;type 存 proxy|forward|tunnel
mode TEXT NOT NULL DEFAULT 'include', -- include | exclude
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS subscription_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
subscription_id INTEGER NOT NULL REFERENCES subscriptions(id) ON DELETE CASCADE,
client_ip TEXT,
user_agent TEXT,
format TEXT,
result TEXT NOT NULL, -- ok | expired | disabled | ip_limit
accessed_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- ============ 告警 ============
CREATE TABLE IF NOT EXISTS alert_channels (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
kind TEXT NOT NULL, -- webhook | telegram | email
config TEXT NOT NULL, -- JSON,见第 14 章
enabled INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS alert_rules (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
event_type TEXT NOT NULL, -- 见 14.3 事件清单
params TEXT NOT NULL DEFAULT '{}', -- JSON 阈值参数
channel_ids TEXT NOT NULL DEFAULT '[]', -- JSON 数组
cooldown_secs INTEGER NOT NULL DEFAULT 3600,
enabled INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS alert_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
rule_id INTEGER REFERENCES alert_rules(id) ON DELETE SET NULL,
event_type TEXT NOT NULL,
dedup_key TEXT NOT NULL,
severity TEXT NOT NULL DEFAULT 'warning', -- info | warning | critical
title TEXT NOT NULL,
body TEXT NOT NULL,
resource_type TEXT,
resource_id INTEGER,
status TEXT NOT NULL DEFAULT 'pending', -- pending | sent | failed
attempts INTEGER NOT NULL DEFAULT 0,
next_retry_at TEXT,
last_error TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
sent_at TEXT
);
-- ============ 索引 ============
CREATE INDEX IF NOT EXISTS idx_sessions_exp ON sessions(expires_at);
CREATE INDEX IF NOT EXISTS idx_proxy_node ON proxy_rules(node_id);
CREATE INDEX IF NOT EXISTS idx_proxy_import ON proxy_rules(source_import_id);
CREATE INDEX IF NOT EXISTS idx_proxy_group ON proxy_rules(group_id);
CREATE INDEX IF NOT EXISTS idx_proxy_exp ON proxy_rules(expires_at) WHERE expires_at IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_forward_node ON port_forwards(node_id);
CREATE INDEX IF NOT EXISTS idx_forward_import ON port_forwards(source_import_id);
CREATE INDEX IF NOT EXISTS idx_forward_group ON port_forwards(group_id);
CREATE INDEX IF NOT EXISTS idx_forward_exp ON port_forwards(expires_at) WHERE expires_at IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_tunnel_node ON tunnels(node_id);
CREATE INDEX IF NOT EXISTS idx_tunnel_import ON tunnels(source_import_id);
CREATE INDEX IF NOT EXISTS idx_tunnel_group ON tunnels(group_id);
CREATE INDEX IF NOT EXISTS idx_tunnel_exp ON tunnels(expires_at) WHERE expires_at IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_entry_tags_tag ON entry_tags(tag_id);
CREATE INDEX IF NOT EXISTS idx_audit_created ON audit_logs(created_at);
CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_logs(action);
CREATE INDEX IF NOT EXISTS idx_access_rule ON access_logs(rule_type, rule_id, started_at);
CREATE INDEX IF NOT EXISTS idx_access_node_time ON access_logs(node_id, started_at);
CREATE INDEX IF NOT EXISTS idx_stats_h_time ON stats_hourly(stat_time);
CREATE INDEX IF NOT EXISTS idx_stats_d_date ON stats_daily(stat_date);
CREATE INDEX IF NOT EXISTS idx_metrics_node ON metrics_snapshots(node_id, collected_at);
CREATE INDEX IF NOT EXISTS idx_sub_token ON subscriptions(token);
CREATE INDEX IF NOT EXISTS idx_sub_entries_sub ON subscription_entries(subscription_id);
CREATE INDEX IF NOT EXISTS idx_sub_rules_sub ON subscription_rules(subscription_id);
CREATE INDEX IF NOT EXISTS idx_sub_logs_sub ON subscription_logs(subscription_id, accessed_at);
CREATE INDEX IF NOT EXISTS idx_alert_ev_status ON alert_events(status, next_retry_at);
CREATE INDEX IF NOT EXISTS idx_alert_ev_dedup ON alert_events(rule_id, dedup_key, created_at);