354 lines
17 KiB
SQL
354 lines
17 KiB
SQL
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);
|