From b4114c93eeaaa1f6199347319a451289264563d1 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Sat, 13 Jun 2026 01:28:01 +0800 Subject: [PATCH] feat(db): add MySQL migrations 000001-000007 covering all 12 tables + seed [tsk_paiqu21eZIuw] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 000001: users + devices(账户 / 设备,含 argon2id pw_hash、dp_uuid) - 000002: plans + subscriptions(套餐口径来自 design/CLAUDE.md §7) - 000003: code_batches + codes(SHA-256 code_hash,明文不入库) - 000004: usage_daily + audit_log(最小数据原则 + JSON meta) - 000005: providers + nodes(providers 先建,nodes FK 引用) - 000006: node_events(9 值 ENUM)+ directory_version(CHECK id=1,MySQL ≥ 8.0.16) - 000007: plans 三行 seed + directory_version(1,1),ON DUPLICATE KEY UPDATE 保可重入 所有表 ENGINE=InnoDB DEFAULT CHARSET=utf8mb4,时间列 DATETIME(6), 主键 BIGINT UNSIGNED AUTO_INCREMENT + 对外 uuid CHAR(36) UNIQUE。 每对 down 文件逆序 DROP(子表先于父表)。 Co-Authored-By: Claude Sonnet 4.6 --- .../migrations/000001_users_devices.down.sql | 3 ++ server/migrations/000001_users_devices.up.sql | 22 +++++++++++++ .../000002_plans_subscriptions.down.sql | 3 ++ .../000002_plans_subscriptions.up.sql | 20 +++++++++++ server/migrations/000003_codes.down.sql | 3 ++ server/migrations/000003_codes.up.sql | 22 +++++++++++++ server/migrations/000004_usage_audit.down.sql | 3 ++ server/migrations/000004_usage_audit.up.sql | 21 ++++++++++++ .../000005_providers_nodes.down.sql | 3 ++ .../migrations/000005_providers_nodes.up.sql | 33 +++++++++++++++++++ .../000006_node_events_dirver.down.sql | 3 ++ .../000006_node_events_dirver.up.sql | 19 +++++++++++ server/migrations/000007_seed.down.sql | 3 ++ server/migrations/000007_seed.up.sql | 19 +++++++++++ 14 files changed, 177 insertions(+) create mode 100644 server/migrations/000001_users_devices.down.sql create mode 100644 server/migrations/000001_users_devices.up.sql create mode 100644 server/migrations/000002_plans_subscriptions.down.sql create mode 100644 server/migrations/000002_plans_subscriptions.up.sql create mode 100644 server/migrations/000003_codes.down.sql create mode 100644 server/migrations/000003_codes.up.sql create mode 100644 server/migrations/000004_usage_audit.down.sql create mode 100644 server/migrations/000004_usage_audit.up.sql create mode 100644 server/migrations/000005_providers_nodes.down.sql create mode 100644 server/migrations/000005_providers_nodes.up.sql create mode 100644 server/migrations/000006_node_events_dirver.down.sql create mode 100644 server/migrations/000006_node_events_dirver.up.sql create mode 100644 server/migrations/000007_seed.down.sql create mode 100644 server/migrations/000007_seed.up.sql diff --git a/server/migrations/000001_users_devices.down.sql b/server/migrations/000001_users_devices.down.sql new file mode 100644 index 0000000..68ec3ac --- /dev/null +++ b/server/migrations/000001_users_devices.down.sql @@ -0,0 +1,3 @@ +-- 逆序删除:子表先于父表 +DROP TABLE IF EXISTS devices; +DROP TABLE IF EXISTS users; diff --git a/server/migrations/000001_users_devices.up.sql b/server/migrations/000001_users_devices.up.sql new file mode 100644 index 0000000..f575bec --- /dev/null +++ b/server/migrations/000001_users_devices.up.sql @@ -0,0 +1,22 @@ +-- 账户与设备 +CREATE TABLE users ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + uuid CHAR(36) NOT NULL UNIQUE, + email VARCHAR(255) NOT NULL UNIQUE, + pw_hash VARCHAR(255) NOT NULL, -- argon2id + dp_uuid CHAR(36) NOT NULL, -- 数据面凭证 UUID(可轮换,节点侧只见它) + status ENUM('active','banned') NOT NULL DEFAULT 'active', + created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE devices ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + uuid CHAR(36) NOT NULL UNIQUE, + user_id BIGINT UNSIGNED NOT NULL, + name VARCHAR(64) NOT NULL, + platform ENUM('ios','android','windows','macos') NOT NULL, + last_seen DATETIME(6) NULL, + created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + FOREIGN KEY (user_id) REFERENCES users(id), + INDEX idx_user (user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/server/migrations/000002_plans_subscriptions.down.sql b/server/migrations/000002_plans_subscriptions.down.sql new file mode 100644 index 0000000..695c2fa --- /dev/null +++ b/server/migrations/000002_plans_subscriptions.down.sql @@ -0,0 +1,3 @@ +-- 逆序删除:子表先于父表 +DROP TABLE IF EXISTS subscriptions; +DROP TABLE IF EXISTS plans; diff --git a/server/migrations/000002_plans_subscriptions.up.sql b/server/migrations/000002_plans_subscriptions.up.sql new file mode 100644 index 0000000..f754ff1 --- /dev/null +++ b/server/migrations/000002_plans_subscriptions.up.sql @@ -0,0 +1,20 @@ +-- 套餐与订阅(数字口径 = design/CLAUDE.md §7) +CREATE TABLE plans ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + code ENUM('free','pro','team') NOT NULL UNIQUE, + max_devices INT NOT NULL, -- free 1 / pro 5 / team 10 + daily_minutes INT NULL, -- free 10;NULL = 不限 + ad_gate BOOLEAN NOT NULL DEFAULT FALSE -- free 每日看广告解锁 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE subscriptions ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT UNSIGNED NOT NULL, + plan_id BIGINT UNSIGNED NOT NULL, + expires_at DATETIME(6) NOT NULL, + source ENUM('trial','code') NOT NULL, -- 注册自动 7 天试用 source='trial' + created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (plan_id) REFERENCES plans(id), + INDEX idx_user_exp (user_id, expires_at) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/server/migrations/000003_codes.down.sql b/server/migrations/000003_codes.down.sql new file mode 100644 index 0000000..2c97e97 --- /dev/null +++ b/server/migrations/000003_codes.down.sql @@ -0,0 +1,3 @@ +-- 逆序删除:子表先于父表 +DROP TABLE IF EXISTS codes; +DROP TABLE IF EXISTS code_batches; diff --git a/server/migrations/000003_codes.up.sql b/server/migrations/000003_codes.up.sql new file mode 100644 index 0000000..84499da --- /dev/null +++ b/server/migrations/000003_codes.up.sql @@ -0,0 +1,22 @@ +-- 激活码(App 内无支付,资金流外部化) +CREATE TABLE code_batches ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + channel ENUM('store','tg','line','manual') NOT NULL, + created_by VARCHAR(64) NOT NULL, + note VARCHAR(255) NULL, + created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE codes ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + code_hash CHAR(64) NOT NULL UNIQUE, -- SHA-256;明文只出现一次 + plan_id BIGINT UNSIGNED NOT NULL, + duration_days INT NOT NULL, + batch_id BIGINT UNSIGNED NOT NULL, + status ENUM('unused','redeemed','void') NOT NULL DEFAULT 'unused', + redeemed_by BIGINT UNSIGNED NULL, + redeemed_at DATETIME(6) NULL, + FOREIGN KEY (plan_id) REFERENCES plans(id), + FOREIGN KEY (batch_id) REFERENCES code_batches(id), + INDEX idx_status (status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/server/migrations/000004_usage_audit.down.sql b/server/migrations/000004_usage_audit.down.sql new file mode 100644 index 0000000..5c1d8cf --- /dev/null +++ b/server/migrations/000004_usage_audit.down.sql @@ -0,0 +1,3 @@ +-- 逆序删除 +DROP TABLE IF EXISTS audit_log; +DROP TABLE IF EXISTS usage_daily; diff --git a/server/migrations/000004_usage_audit.up.sql b/server/migrations/000004_usage_audit.up.sql new file mode 100644 index 0000000..8cced33 --- /dev/null +++ b/server/migrations/000004_usage_audit.up.sql @@ -0,0 +1,21 @@ +-- 用量(最小数据原则:仅字节/分钟,无目的地) +CREATE TABLE usage_daily ( + user_id BIGINT UNSIGNED NOT NULL, + date DATE NOT NULL, + bytes_up BIGINT UNSIGNED NOT NULL DEFAULT 0, + bytes_down BIGINT UNSIGNED NOT NULL DEFAULT 0, + minutes_used INT NOT NULL DEFAULT 0, + ad_unlocked_at DATETIME(6) NULL, -- 免费版当日激励视频解锁时刻 + PRIMARY KEY (user_id, date) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 审计(兑换/封禁/节点操作必记) +CREATE TABLE audit_log ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + actor VARCHAR(64) NOT NULL, + action VARCHAR(64) NOT NULL, + target VARCHAR(128) NOT NULL, + meta JSON NULL, + at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + INDEX idx_at (at) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/server/migrations/000005_providers_nodes.down.sql b/server/migrations/000005_providers_nodes.down.sql new file mode 100644 index 0000000..d53c7bc --- /dev/null +++ b/server/migrations/000005_providers_nodes.down.sql @@ -0,0 +1,3 @@ +-- 逆序删除:nodes 先于 providers(子表先于父表) +DROP TABLE IF EXISTS nodes; +DROP TABLE IF EXISTS providers; diff --git a/server/migrations/000005_providers_nodes.up.sql b/server/migrations/000005_providers_nodes.up.sql new file mode 100644 index 0000000..f8a3caa --- /dev/null +++ b/server/migrations/000005_providers_nodes.up.sql @@ -0,0 +1,33 @@ +-- VPS 厂商池(先建,nodes 有 FK 引用) +CREATE TABLE providers ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(64) NOT NULL, + api_kind VARCHAR(32) NOT NULL, -- 开通自动化所用 API 适配器 + regions JSON NOT NULL, + pool ENUM('consumable','premium') NOT NULL, + enabled BOOLEAN NOT NULL DEFAULT TRUE, + note VARCHAR(255) NULL -- 凭证不入库,存独立 secrets 管理 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 节点:蓝本 nodes 表 + 拓扑/生命周期扩展字段 +CREATE TABLE nodes ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + uuid CHAR(36) NOT NULL UNIQUE, + region VARCHAR(8) NOT NULL, -- HK / JP / SG / US… + name_zh VARCHAR(64) NOT NULL, + name_en VARCHAR(64) NOT NULL, + role ENUM('entry','relay','exit') NOT NULL DEFAULT 'entry', + tier ENUM('free','pro') NOT NULL, -- 消耗品池 / 精品池 + endpoint VARCHAR(255) NOT NULL, -- ip:port(REALITY) + hy2_port INT NULL, -- Hysteria2 端口跳跃区间起点 + reality_pbk VARCHAR(64) NOT NULL, + reality_sni VARCHAR(128) NOT NULL, -- 伪装目标 SNI + provider_id BIGINT UNSIGNED NOT NULL, + tags JSON NULL, + status ENUM('provisioning','probing','up','draining','down','destroyed') + NOT NULL DEFAULT 'provisioning', -- 生命周期状态机 + weight INT NOT NULL DEFAULT 100, + created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + FOREIGN KEY (provider_id) REFERENCES providers(id), + INDEX idx_status_tier (status, tier) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/server/migrations/000006_node_events_dirver.down.sql b/server/migrations/000006_node_events_dirver.down.sql new file mode 100644 index 0000000..c9ca62e --- /dev/null +++ b/server/migrations/000006_node_events_dirver.down.sql @@ -0,0 +1,3 @@ +-- 逆序删除:directory_version 先,再 node_events +DROP TABLE IF EXISTS directory_version; +DROP TABLE IF EXISTS node_events; diff --git a/server/migrations/000006_node_events_dirver.up.sql b/server/migrations/000006_node_events_dirver.up.sql new file mode 100644 index 0000000..0806b23 --- /dev/null +++ b/server/migrations/000006_node_events_dirver.up.sql @@ -0,0 +1,19 @@ +-- 节点事件:状态迁移与判封依据(探针明细在 Redis,结论落库) +CREATE TABLE node_events ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + node_id BIGINT UNSIGNED NOT NULL, + event ENUM('provisioned','probe_pass','probe_fail','marked_up','draining', + 'blocked_suspect','blocked_confirmed','replaced','destroyed') NOT NULL, + detail JSON NULL, -- 探针命中率、判封依据等 + at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + FOREIGN KEY (node_id) REFERENCES nodes(id), + INDEX idx_node_at (node_id, at) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 目录版本:nodes 任何变更 bump,客户端 if_version 灰度 +-- CHECK (id = 1) 需要 MySQL >= 8.0.16 +CREATE TABLE directory_version ( + id TINYINT PRIMARY KEY DEFAULT 1, + version BIGINT UNSIGNED NOT NULL, + CHECK (id = 1) +) ENGINE=InnoDB; diff --git a/server/migrations/000007_seed.down.sql b/server/migrations/000007_seed.down.sql new file mode 100644 index 0000000..64b6019 --- /dev/null +++ b/server/migrations/000007_seed.down.sql @@ -0,0 +1,3 @@ +-- 删除 seed 行(逆序:directory_version 先,plans 后) +DELETE FROM directory_version WHERE id = 1; +DELETE FROM plans WHERE code IN ('free', 'pro', 'team'); diff --git a/server/migrations/000007_seed.up.sql b/server/migrations/000007_seed.up.sql new file mode 100644 index 0000000..2634b3c --- /dev/null +++ b/server/migrations/000007_seed.up.sql @@ -0,0 +1,19 @@ +-- plans seed(口径来源:design/CLAUDE.md §7,价格不入表) +-- free: 1 设备,10 分钟/天,每日广告解锁 +-- pro: 5 设备,不限时长,无广告门槛 +-- team: 10 设备,不限时长,无广告门槛 +INSERT INTO plans (code, max_devices, daily_minutes, ad_gate) +VALUES + ('free', 1, 10, TRUE), + ('pro', 5, NULL, FALSE), + ('team', 10, NULL, FALSE) +ON DUPLICATE KEY UPDATE + max_devices = VALUES(max_devices), + daily_minutes = VALUES(daily_minutes), + ad_gate = VALUES(ad_gate); + +-- directory_version 单例初始化 +INSERT INTO directory_version (id, version) +VALUES (1, 1) +ON DUPLICATE KEY UPDATE + version = VALUES(version);