mirror of
https://github.com/RaisFast/raisfast.git
synced 2026-09-24 08:02:36 +00:00
1071 lines
46 KiB
SQL
1071 lines
46 KiB
SQL
-- ============================================================
|
||
-- raisfast 完整数据库 Schema — MySQL(含多租户支持)
|
||
-- 由所有 migration 文件合并而成,用于新部署一键初始化
|
||
-- 生成日期:2026-05-07
|
||
--
|
||
-- MySQL 注意事项:
|
||
-- - 不支持 WHERE 条件的部分索引,已移除
|
||
-- - BOOLEAN 实际为 TINYINT(1)
|
||
-- ============================================================
|
||
|
||
-- ── 平台基础层(永不禁用) ──────────────────────────────────
|
||
|
||
-- 租户表
|
||
CREATE TABLE IF NOT EXISTS tenants (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
name VARCHAR(255) NOT NULL,
|
||
domain VARCHAR(255) UNIQUE,
|
||
config JSON NOT NULL,
|
||
status VARCHAR(50) NOT NULL DEFAULT 'active',
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 默认租户
|
||
INSERT IGNORE INTO tenants (document_id, name, domain, config, status, created_at, updated_at) VALUES
|
||
('default', 'Default', NULL, '{}', 'active', NOW(), NOW());
|
||
|
||
-- 用户
|
||
CREATE TABLE IF NOT EXISTS users (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
username VARCHAR(255) UNIQUE NOT NULL,
|
||
role VARCHAR(50) NOT NULL DEFAULT 'reader',
|
||
avatar VARCHAR(500),
|
||
bio TEXT,
|
||
website VARCHAR(500),
|
||
status VARCHAR(50) NOT NULL DEFAULT 'active',
|
||
registered_via VARCHAR(100) NOT NULL,
|
||
display_name VARCHAR(100),
|
||
slug VARCHAR(100) UNIQUE,
|
||
locale VARCHAR(10),
|
||
social_links JSON,
|
||
metadata JSON,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_users_username ON users(username);
|
||
CREATE UNIQUE INDEX idx_users_slug ON users(slug);
|
||
CREATE INDEX idx_users_tenant ON users(tenant_id);
|
||
|
||
-- 用户凭据
|
||
CREATE TABLE IF NOT EXISTS user_credentials (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
user_id BIGINT NOT NULL,
|
||
auth_type VARCHAR(100) NOT NULL,
|
||
identifier VARCHAR(500) NOT NULL,
|
||
credential_data TEXT NOT NULL,
|
||
verified BOOLEAN NOT NULL DEFAULT FALSE,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
UNIQUE KEY uq_credential_type_id (auth_type, identifier),
|
||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_user_credentials_user ON user_credentials(user_id);
|
||
CREATE INDEX idx_user_credentials_type_id ON user_credentials(auth_type, identifier);
|
||
CREATE INDEX idx_user_credentials_type ON user_credentials(auth_type);
|
||
|
||
-- OAuth 账号绑定
|
||
CREATE TABLE IF NOT EXISTS oauth_accounts (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
user_id BIGINT NOT NULL,
|
||
provider VARCHAR(50) NOT NULL,
|
||
provider_user_id VARCHAR(255) NOT NULL,
|
||
email VARCHAR(255),
|
||
display_name VARCHAR(255),
|
||
avatar_url VARCHAR(500),
|
||
access_token VARCHAR(1024),
|
||
refresh_token VARCHAR(1024),
|
||
token_expires_at DATETIME(3),
|
||
profile TEXT,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
UNIQUE KEY uq_oauth_provider (provider, provider_user_id),
|
||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_oauth_accounts_user ON oauth_accounts(user_id);
|
||
CREATE INDEX idx_oauth_accounts_provider ON oauth_accounts(provider, provider_user_id);
|
||
|
||
-- OAuth 短期 state 存储(PKCE)
|
||
CREATE TABLE IF NOT EXISTS oauth_states (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
provider VARCHAR(50) NOT NULL,
|
||
code_verifier VARCHAR(255) NOT NULL,
|
||
user_id BIGINT,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
expires_at DATETIME(3) NOT NULL
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_oauth_states_expires ON oauth_states(expires_at);
|
||
|
||
-- 币种配置
|
||
CREATE TABLE IF NOT EXISTS currencies (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
code VARCHAR(10) NOT NULL UNIQUE,
|
||
name VARCHAR(255) NOT NULL,
|
||
decimals INT NOT NULL DEFAULT 0,
|
||
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
||
version INT NOT NULL DEFAULT 1,
|
||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
CONSTRAINT chk_currencies_code CHECK (code = UPPER(code) AND CHAR_LENGTH(code) BETWEEN 1 AND 10),
|
||
CONSTRAINT chk_currencies_decimals CHECK (decimals BETWEEN 0 AND 18)
|
||
);
|
||
|
||
CREATE UNIQUE INDEX idx_currencies_code ON currencies(code);
|
||
|
||
CREATE TABLE IF NOT EXISTS wallets (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
user_id BIGINT NOT NULL,
|
||
currency VARCHAR(50) NOT NULL,
|
||
balance BIGINT NOT NULL DEFAULT 0 CHECK(balance >= 0),
|
||
version BIGINT NOT NULL DEFAULT 1,
|
||
status VARCHAR(50) NOT NULL DEFAULT 'active',
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
UNIQUE KEY uq_wallets_user_currency (user_id, currency),
|
||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_wallets_user ON wallets(user_id);
|
||
CREATE INDEX idx_wallets_currency ON wallets(currency);
|
||
CREATE INDEX idx_wallets_tenant ON wallets(tenant_id);
|
||
|
||
CREATE TABLE IF NOT EXISTS wallet_transactions (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
wallet_id BIGINT NOT NULL,
|
||
user_id BIGINT NOT NULL,
|
||
entry_type VARCHAR(10) NOT NULL,
|
||
amount BIGINT NOT NULL CHECK(amount > 0),
|
||
balance_after BIGINT NOT NULL CHECK(balance_after >= 0),
|
||
tx_type VARCHAR(50) NOT NULL,
|
||
currency VARCHAR(50) NOT NULL,
|
||
transaction_no VARCHAR(255) NOT NULL UNIQUE,
|
||
related_tx_id BIGINT,
|
||
reference_type VARCHAR(100),
|
||
reference_id VARCHAR(255),
|
||
counterparty_wallet_id BIGINT,
|
||
metadata JSON,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
FOREIGN KEY (wallet_id) REFERENCES wallets(id),
|
||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_wallet_tx_wallet ON wallet_transactions(wallet_id);
|
||
CREATE INDEX idx_wallet_tx_user ON wallet_transactions(user_id, created_at DESC);
|
||
CREATE INDEX idx_wallet_tx_transaction_no ON wallet_transactions(transaction_no);
|
||
CREATE INDEX idx_wallet_tx_tx_type ON wallet_transactions(tx_type);
|
||
CREATE INDEX idx_wallet_tx_reference ON wallet_transactions(reference_type, reference_id);
|
||
CREATE INDEX idx_wallet_tx_created ON wallet_transactions(created_at);
|
||
CREATE INDEX idx_wallet_transactions_tenant ON wallet_transactions(tenant_id);
|
||
|
||
-- Refresh Tokens
|
||
CREATE TABLE IF NOT EXISTS refresh_tokens (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
user_id BIGINT NOT NULL,
|
||
token VARCHAR(500) UNIQUE NOT NULL,
|
||
expires_at DATETIME(3) NOT NULL,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_refresh_tokens_user ON refresh_tokens(user_id);
|
||
CREATE INDEX idx_refresh_tokens_token ON refresh_tokens(token);
|
||
CREATE INDEX idx_refresh_tokens_expires_at ON refresh_tokens(expires_at);
|
||
|
||
-- 站点配置
|
||
CREATE TABLE IF NOT EXISTS options (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
`option_key` VARCHAR(255) NOT NULL,
|
||
value TEXT NOT NULL,
|
||
`type` VARCHAR(50) NOT NULL DEFAULT 'text',
|
||
group_name VARCHAR(100) NOT NULL DEFAULT 'general',
|
||
label VARCHAR(255) NOT NULL DEFAULT '',
|
||
description TEXT,
|
||
validation JSON,
|
||
is_public BOOLEAN NOT NULL DEFAULT FALSE,
|
||
autoload BOOLEAN NOT NULL DEFAULT TRUE,
|
||
sort_order INT NOT NULL DEFAULT 0,
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
UNIQUE KEY uq_options_option_key (`option_key`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_options_tenant_option_key ON options(tenant_id, `option_key`);
|
||
|
||
-- RBAC 角色
|
||
CREATE TABLE IF NOT EXISTS roles (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
name VARCHAR(100) NOT NULL UNIQUE,
|
||
description TEXT,
|
||
is_system BOOLEAN NOT NULL DEFAULT FALSE,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_roles_tenant ON roles(tenant_id);
|
||
|
||
-- RBAC 权限
|
||
CREATE TABLE IF NOT EXISTS permissions (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
role_id BIGINT NOT NULL,
|
||
action VARCHAR(255) NOT NULL,
|
||
subject VARCHAR(255) NOT NULL,
|
||
fields JSON,
|
||
conditions JSON,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
FOREIGN KEY (role_id) REFERENCES roles(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE UNIQUE INDEX idx_permissions_role_action_subject
|
||
ON permissions(role_id, action, subject);
|
||
CREATE INDEX idx_permissions_tenant ON permissions(tenant_id);
|
||
|
||
-- 审计日志
|
||
CREATE TABLE IF NOT EXISTS audit_log (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
actor_id BIGINT,
|
||
actor_role VARCHAR(50),
|
||
action VARCHAR(255) NOT NULL,
|
||
subject VARCHAR(255) NOT NULL,
|
||
subject_id VARCHAR(36),
|
||
detail TEXT,
|
||
ip_address VARCHAR(45),
|
||
user_agent TEXT,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_audit_log_action ON audit_log(action);
|
||
CREATE INDEX idx_audit_log_actor ON audit_log(actor_id);
|
||
CREATE INDEX idx_audit_log_created ON audit_log(created_at);
|
||
CREATE INDEX idx_audit_log_tenant ON audit_log(tenant_id);
|
||
|
||
-- API Token
|
||
CREATE TABLE IF NOT EXISTS api_tokens (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
user_id BIGINT NOT NULL,
|
||
name VARCHAR(255) NOT NULL,
|
||
token_hash VARCHAR(255) UNIQUE NOT NULL,
|
||
token_prefix VARCHAR(50) NOT NULL,
|
||
scopes JSON NOT NULL,
|
||
last_used_at DATETIME(3),
|
||
expires_at DATETIME(3),
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_api_tokens_user_id ON api_tokens(user_id);
|
||
CREATE INDEX idx_api_tokens_token_hash ON api_tokens(token_hash);
|
||
|
||
-- Webhook 订阅
|
||
CREATE TABLE IF NOT EXISTS webhook_subscriptions (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
url VARCHAR(1024) NOT NULL,
|
||
secret VARCHAR(255) NOT NULL,
|
||
events JSON NOT NULL,
|
||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||
description TEXT,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_webhook_subscriptions_enabled ON webhook_subscriptions(enabled);
|
||
CREATE INDEX idx_webhook_subscriptions_tenant ON webhook_subscriptions(tenant_id);
|
||
|
||
-- 插件 KV 存储
|
||
CREATE TABLE IF NOT EXISTS plugin_storage (
|
||
plugin_id VARCHAR(100) NOT NULL,
|
||
`storage_key` VARCHAR(255) NOT NULL,
|
||
value TEXT NOT NULL,
|
||
expires_at DATETIME(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
PRIMARY KEY (plugin_id, `storage_key`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_plugin_storage_plugin ON plugin_storage(plugin_id);
|
||
|
||
-- 内容版本历史
|
||
CREATE TABLE IF NOT EXISTS content_revisions (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
content_type VARCHAR(100) NOT NULL,
|
||
record_id TEXT NOT NULL,
|
||
revision_number INT NOT NULL,
|
||
snapshot TEXT NOT NULL,
|
||
created_by BIGINT,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
UNIQUE KEY uq_revision (content_type, record_id, revision_number)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_revisions_ct_record
|
||
ON content_revisions(content_type, record_id);
|
||
CREATE INDEX idx_revisions_ct_record_rev
|
||
ON content_revisions(content_type, record_id, revision_number DESC);
|
||
|
||
-- 密码重置令牌
|
||
CREATE TABLE IF NOT EXISTS password_reset_tokens (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
user_id BIGINT NOT NULL,
|
||
token VARCHAR(255) NOT NULL UNIQUE,
|
||
expires_at DATETIME(3) NOT NULL,
|
||
used_at DATETIME(3),
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_password_reset_tokens_token ON password_reset_tokens(token);
|
||
CREATE INDEX idx_password_reset_tokens_user_id ON password_reset_tokens(user_id);
|
||
CREATE INDEX idx_password_reset_tokens_expires_at ON password_reset_tokens(expires_at);
|
||
|
||
-- 短信验证码
|
||
CREATE TABLE IF NOT EXISTS sms_codes (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
phone VARCHAR(50) NOT NULL,
|
||
code VARCHAR(20) NOT NULL,
|
||
purpose VARCHAR(50) NOT NULL,
|
||
expires_at DATETIME(3) NOT NULL,
|
||
verified_at DATETIME(3),
|
||
attempts INT NOT NULL DEFAULT 0,
|
||
ip_address VARCHAR(45),
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_sms_codes_phone ON sms_codes(phone);
|
||
CREATE INDEX idx_sms_codes_expires ON sms_codes(expires_at);
|
||
|
||
-- 邮箱验证令牌
|
||
CREATE TABLE IF NOT EXISTS email_verification_tokens (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
user_id BIGINT NOT NULL,
|
||
token VARCHAR(255) NOT NULL UNIQUE,
|
||
email VARCHAR(255) NOT NULL,
|
||
expires_at DATETIME(3) NOT NULL,
|
||
verified_at DATETIME(3),
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_email_verification_tokens_token ON email_verification_tokens(token);
|
||
CREATE INDEX idx_email_verification_tokens_user_id ON email_verification_tokens(user_id);
|
||
CREATE INDEX idx_email_verification_tokens_expires ON email_verification_tokens(expires_at);
|
||
|
||
-- 后台任务队列
|
||
CREATE TABLE IF NOT EXISTS jobs (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
job_type VARCHAR(100) NOT NULL,
|
||
payload TEXT NOT NULL,
|
||
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
||
attempts INT NOT NULL DEFAULT 0,
|
||
max_attempts INT NOT NULL DEFAULT 3,
|
||
run_after DATETIME(3),
|
||
error TEXT,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_jobs_status ON jobs(status);
|
||
CREATE INDEX idx_jobs_run_after ON jobs(run_after);
|
||
CREATE INDEX idx_jobs_type ON jobs(job_type);
|
||
|
||
-- 定时任务调度
|
||
CREATE TABLE IF NOT EXISTS cron_schedules (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
label VARCHAR(255) NOT NULL,
|
||
job_type VARCHAR(100) NOT NULL,
|
||
payload TEXT,
|
||
cron_expr VARCHAR(100) NOT NULL,
|
||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||
last_run_at DATETIME(3),
|
||
next_run_at DATETIME(3) NOT NULL,
|
||
plugin_id VARCHAR(100),
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_cron_enabled ON cron_schedules(enabled);
|
||
CREATE INDEX idx_cron_next_run ON cron_schedules(next_run_at);
|
||
CREATE INDEX idx_cron_plugin ON cron_schedules(plugin_id);
|
||
|
||
-- Cron 执行历史
|
||
CREATE TABLE IF NOT EXISTS cron_execution_log (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
schedule_id BIGINT NOT NULL,
|
||
job_type VARCHAR(100) NOT NULL,
|
||
label VARCHAR(255) NOT NULL,
|
||
status VARCHAR(50) NOT NULL DEFAULT 'running',
|
||
duration_ms INT,
|
||
error TEXT,
|
||
started_at DATETIME(3) NOT NULL,
|
||
finished_at DATETIME(3)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_cron_log_schedule ON cron_execution_log(schedule_id);
|
||
CREATE INDEX idx_cron_log_status ON cron_execution_log(status);
|
||
CREATE INDEX idx_cron_log_started ON cron_execution_log(started_at);
|
||
|
||
-- ── 内置模块:Blog(BUILTIN_BLOG=true) ──────────────────
|
||
|
||
-- 分类
|
||
CREATE TABLE IF NOT EXISTS categories (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
name VARCHAR(255) UNIQUE NOT NULL,
|
||
slug VARCHAR(255) UNIQUE NOT NULL,
|
||
description TEXT,
|
||
parent_id BIGINT,
|
||
sort_order INT NOT NULL DEFAULT 0,
|
||
created_by BIGINT,
|
||
updated_by BIGINT,
|
||
cover_image VARCHAR(500),
|
||
meta_title VARCHAR(255),
|
||
meta_description VARCHAR(500),
|
||
og_title VARCHAR(255),
|
||
og_description VARCHAR(500),
|
||
og_image VARCHAR(500),
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
FOREIGN KEY (parent_id) REFERENCES categories(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_categories_tenant ON categories(tenant_id);
|
||
|
||
-- 标签
|
||
CREATE TABLE IF NOT EXISTS tags (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
name VARCHAR(255) UNIQUE NOT NULL,
|
||
slug VARCHAR(255) UNIQUE NOT NULL,
|
||
created_by BIGINT,
|
||
updated_by BIGINT,
|
||
description TEXT,
|
||
cover_image VARCHAR(500),
|
||
meta_title VARCHAR(255),
|
||
meta_description VARCHAR(500),
|
||
og_title VARCHAR(255),
|
||
og_description VARCHAR(500),
|
||
og_image VARCHAR(500),
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_tags_tenant ON tags(tenant_id);
|
||
|
||
-- 文章
|
||
CREATE TABLE IF NOT EXISTS posts (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
title VARCHAR(500) NOT NULL,
|
||
slug VARCHAR(255) UNIQUE NOT NULL,
|
||
content LONGTEXT NOT NULL,
|
||
excerpt TEXT,
|
||
cover_image VARCHAR(500),
|
||
status VARCHAR(50) NOT NULL DEFAULT 'draft',
|
||
created_by BIGINT NOT NULL,
|
||
updated_by BIGINT,
|
||
category_id BIGINT,
|
||
view_count INT NOT NULL DEFAULT 0,
|
||
is_pinned BOOLEAN NOT NULL DEFAULT FALSE,
|
||
password VARCHAR(255),
|
||
comment_status VARCHAR(20) NOT NULL DEFAULT 'open',
|
||
format VARCHAR(20) NOT NULL DEFAULT 'standard',
|
||
template VARCHAR(100) NOT NULL DEFAULT 'default',
|
||
meta_title VARCHAR(255),
|
||
meta_description VARCHAR(500),
|
||
og_title VARCHAR(255),
|
||
og_description VARCHAR(500),
|
||
og_image VARCHAR(500),
|
||
canonical_url VARCHAR(1024),
|
||
reading_time INTEGER NOT NULL DEFAULT 0,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
published_at DATETIME(3),
|
||
FOREIGN KEY (created_by) REFERENCES users(id),
|
||
FOREIGN KEY (category_id) REFERENCES categories(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_posts_slug ON posts(slug);
|
||
CREATE INDEX idx_posts_status ON posts(status);
|
||
CREATE INDEX idx_posts_author ON posts(created_by);
|
||
CREATE INDEX idx_posts_category ON posts(category_id);
|
||
CREATE INDEX idx_posts_created ON posts(created_at);
|
||
CREATE INDEX idx_posts_status_created
|
||
ON posts(status, is_pinned DESC, created_at DESC);
|
||
CREATE INDEX idx_posts_status_category
|
||
ON posts(status, category_id);
|
||
CREATE INDEX idx_posts_status_author
|
||
ON posts(status, created_by);
|
||
CREATE INDEX idx_posts_tenant ON posts(tenant_id);
|
||
|
||
-- 文章-标签(多对多)
|
||
CREATE TABLE IF NOT EXISTS posts_tags (
|
||
post_id BIGINT NOT NULL,
|
||
tag_id BIGINT NOT NULL,
|
||
PRIMARY KEY (post_id, tag_id),
|
||
FOREIGN KEY (post_id) REFERENCES posts(id),
|
||
FOREIGN KEY (tag_id) REFERENCES tags(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_posts_tags_tag_id ON posts_tags(tag_id);
|
||
|
||
-- 评论
|
||
CREATE TABLE IF NOT EXISTS comments (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
post_id BIGINT NOT NULL,
|
||
created_by BIGINT,
|
||
updated_by BIGINT,
|
||
nickname VARCHAR(100),
|
||
email VARCHAR(255),
|
||
content TEXT NOT NULL,
|
||
parent_id BIGINT,
|
||
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
||
author_ip VARCHAR(45),
|
||
author_url VARCHAR(500),
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
FOREIGN KEY (post_id) REFERENCES posts(id),
|
||
FOREIGN KEY (created_by) REFERENCES users(id),
|
||
FOREIGN KEY (parent_id) REFERENCES comments(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_comments_post ON comments(post_id);
|
||
CREATE INDEX idx_comments_status ON comments(status);
|
||
CREATE INDEX idx_comments_post_status
|
||
ON comments(post_id, status);
|
||
CREATE INDEX idx_comments_parent_id
|
||
ON comments(parent_id);
|
||
CREATE INDEX idx_comments_tenant ON comments(tenant_id);
|
||
|
||
-- ── 内置模块:Pages(BUILTIN_PAGES=true) ────────────────
|
||
|
||
CREATE TABLE IF NOT EXISTS pages (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
title VARCHAR(500) NOT NULL,
|
||
slug VARCHAR(255) NOT NULL UNIQUE,
|
||
content LONGTEXT,
|
||
blocks JSON,
|
||
meta_title VARCHAR(255),
|
||
meta_description VARCHAR(500),
|
||
og_image VARCHAR(500),
|
||
template VARCHAR(100) NOT NULL DEFAULT 'default',
|
||
parent_id BIGINT,
|
||
sort_order INT NOT NULL DEFAULT 0,
|
||
status VARCHAR(50) NOT NULL DEFAULT 'draft',
|
||
created_by BIGINT NOT NULL,
|
||
updated_by BIGINT,
|
||
cover_image VARCHAR(500),
|
||
published_at DATETIME(3),
|
||
password VARCHAR(255),
|
||
comment_status VARCHAR(20) NOT NULL DEFAULT 'closed',
|
||
og_title VARCHAR(255),
|
||
og_description VARCHAR(500),
|
||
canonical_url VARCHAR(1024),
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
FOREIGN KEY (created_by) REFERENCES users(id),
|
||
FOREIGN KEY (parent_id) REFERENCES pages(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_pages_slug ON pages(slug);
|
||
CREATE INDEX idx_pages_status ON pages(status);
|
||
CREATE INDEX idx_pages_parent ON pages(parent_id);
|
||
CREATE INDEX idx_pages_author ON pages(created_by);
|
||
CREATE INDEX idx_pages_tenant_slug ON pages(tenant_id, slug);
|
||
CREATE INDEX idx_pages_tenant_status ON pages(tenant_id, status);
|
||
|
||
CREATE TABLE IF NOT EXISTS reusable_blocks (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
name VARCHAR(255) NOT NULL,
|
||
block_type VARCHAR(100) NOT NULL,
|
||
content LONGTEXT NOT NULL,
|
||
description TEXT,
|
||
created_by BIGINT,
|
||
updated_by BIGINT,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_reusable_blocks_tenant ON reusable_blocks(tenant_id);
|
||
|
||
-- ── 内置模块:Media(BUILTIN_MEDIA=true) ────────────────
|
||
|
||
CREATE TABLE IF NOT EXISTS media (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
user_id BIGINT NOT NULL,
|
||
filename VARCHAR(255) NOT NULL,
|
||
filepath VARCHAR(500) NOT NULL,
|
||
mimetype VARCHAR(100) NOT NULL,
|
||
size BIGINT NOT NULL,
|
||
width INT,
|
||
height INT,
|
||
title VARCHAR(255),
|
||
alt_text VARCHAR(255),
|
||
caption TEXT,
|
||
description TEXT,
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_media_user_created
|
||
ON media(user_id, created_at DESC);
|
||
CREATE INDEX idx_media_tenant ON media(tenant_id);
|
||
|
||
-- ── 内置模块:Workflow(BUILTIN_WORKFLOW=true) ──────────
|
||
|
||
CREATE TABLE IF NOT EXISTS workflow_definitions (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
name VARCHAR(255) NOT NULL,
|
||
description TEXT,
|
||
steps JSON NOT NULL,
|
||
initial_step VARCHAR(100) NOT NULL,
|
||
version INT NOT NULL DEFAULT 1,
|
||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE TABLE IF NOT EXISTS workflow_instances (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
definition_id BIGINT NOT NULL,
|
||
status VARCHAR(50) NOT NULL DEFAULT 'running',
|
||
current_step VARCHAR(100),
|
||
context JSON NOT NULL,
|
||
triggered_by BIGINT,
|
||
started_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
completed_at DATETIME(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
FOREIGN KEY (definition_id) REFERENCES workflow_definitions(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_wf_instances_definition ON workflow_instances(definition_id);
|
||
CREATE INDEX idx_wf_instances_status ON workflow_instances(status);
|
||
|
||
CREATE TABLE IF NOT EXISTS workflow_step_logs (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
instance_id BIGINT NOT NULL,
|
||
step_id VARCHAR(100) NOT NULL,
|
||
step_name VARCHAR(255) NOT NULL,
|
||
status VARCHAR(50) NOT NULL DEFAULT 'running',
|
||
input LONGTEXT,
|
||
output LONGTEXT,
|
||
error TEXT,
|
||
started_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
completed_at DATETIME(3),
|
||
FOREIGN KEY (instance_id) REFERENCES workflow_instances(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_wf_step_logs_instance ON workflow_step_logs(instance_id);
|
||
|
||
-- Products
|
||
CREATE TABLE IF NOT EXISTS products (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
category_id BIGINT,
|
||
title VARCHAR(500) NOT NULL,
|
||
description TEXT,
|
||
cover_url VARCHAR(500),
|
||
product_type VARCHAR(50) NOT NULL DEFAULT 'custom',
|
||
fulfillment_type VARCHAR(50) NOT NULL DEFAULT 'digital',
|
||
delivery_hook VARCHAR(255),
|
||
weight INT,
|
||
shipping_template_id BIGINT,
|
||
price BIGINT NOT NULL CHECK(price >= 0),
|
||
currency VARCHAR(50) NOT NULL DEFAULT 'USD',
|
||
status VARCHAR(50) NOT NULL DEFAULT 'draft',
|
||
attributes JSON,
|
||
sort_order INT NOT NULL DEFAULT 0,
|
||
version INT NOT NULL DEFAULT 1,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
slug VARCHAR(255) UNIQUE,
|
||
content LONGTEXT,
|
||
image_ids JSON,
|
||
original_price BIGINT,
|
||
specs JSON,
|
||
unit VARCHAR(50) NOT NULL DEFAULT 'piece',
|
||
min_purchase INT NOT NULL DEFAULT 1,
|
||
max_purchase INT,
|
||
total_sales INT NOT NULL DEFAULT 0,
|
||
virtual_sales INT NOT NULL DEFAULT 0,
|
||
meta_title VARCHAR(255),
|
||
meta_description VARCHAR(500),
|
||
published_at DATETIME(3),
|
||
stock INT NOT NULL DEFAULT 0,
|
||
cost_price BIGINT,
|
||
sale_price BIGINT,
|
||
has_variants BOOLEAN NOT NULL DEFAULT FALSE,
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
FOREIGN KEY (category_id) REFERENCES categories(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_products_status ON products(status);
|
||
CREATE INDEX idx_products_type ON products(product_type);
|
||
CREATE INDEX idx_products_slug ON products(slug);
|
||
CREATE INDEX idx_products_tenant ON products(tenant_id);
|
||
|
||
-- Product Variants
|
||
CREATE TABLE IF NOT EXISTS product_variants (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
product_id BIGINT NOT NULL,
|
||
sku VARCHAR(100) UNIQUE,
|
||
title VARCHAR(500) NOT NULL,
|
||
price BIGINT NOT NULL CHECK(price >= 0),
|
||
original_price BIGINT,
|
||
stock INT NOT NULL DEFAULT 0,
|
||
attributes JSON,
|
||
sort_order INT NOT NULL DEFAULT 0,
|
||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
FOREIGN KEY (product_id) REFERENCES products(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_product_variants_product ON product_variants(product_id);
|
||
CREATE INDEX idx_product_variants_sku ON product_variants(sku);
|
||
CREATE INDEX idx_product_variants_tenant ON product_variants(tenant_id);
|
||
|
||
-- User Addresses
|
||
CREATE TABLE IF NOT EXISTS user_addresses (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
user_id BIGINT NOT NULL,
|
||
label VARCHAR(100) NOT NULL DEFAULT '',
|
||
recipient_name VARCHAR(200) NOT NULL,
|
||
phone VARCHAR(50) NOT NULL,
|
||
country VARCHAR(10) NOT NULL DEFAULT 'CN',
|
||
province VARCHAR(100) NOT NULL DEFAULT '',
|
||
city VARCHAR(100) NOT NULL DEFAULT '',
|
||
district VARCHAR(100) NOT NULL DEFAULT '',
|
||
address_line1 TEXT NOT NULL,
|
||
address_line2 TEXT,
|
||
postal_code VARCHAR(20),
|
||
is_default BOOLEAN NOT NULL DEFAULT FALSE,
|
||
address_type VARCHAR(20) NOT NULL DEFAULT 'shipping',
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_user_addresses_user ON user_addresses(user_id);
|
||
CREATE INDEX idx_user_addresses_tenant ON user_addresses(tenant_id);
|
||
|
||
-- Orders
|
||
CREATE TABLE IF NOT EXISTS orders (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
user_id BIGINT NOT NULL,
|
||
order_no VARCHAR(255) NOT NULL UNIQUE,
|
||
subtotal BIGINT NOT NULL DEFAULT 0,
|
||
discount_amount BIGINT NOT NULL DEFAULT 0,
|
||
shipping_amount BIGINT NOT NULL DEFAULT 0,
|
||
total_amount BIGINT NOT NULL CHECK(total_amount >= 0),
|
||
currency VARCHAR(50) NOT NULL DEFAULT 'USD',
|
||
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
||
buyer_name VARCHAR(255),
|
||
buyer_phone VARCHAR(50),
|
||
buyer_email VARCHAR(255),
|
||
shipping_address TEXT,
|
||
tracking_no VARCHAR(255),
|
||
carrier VARCHAR(100),
|
||
remark TEXT,
|
||
admin_remark TEXT,
|
||
delivery_data JSON,
|
||
tax_amount BIGINT NOT NULL DEFAULT 0,
|
||
coupon_id BIGINT,
|
||
shipping_address_id BIGINT,
|
||
billing_address_id BIGINT,
|
||
paid_at DATETIME(3),
|
||
completed_at DATETIME(3),
|
||
cancelled_at DATETIME(3),
|
||
refunding_at DATETIME(3),
|
||
refunded_at DATETIME(3),
|
||
expired_at DATETIME(3),
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_orders_user ON orders(user_id);
|
||
CREATE INDEX idx_orders_status ON orders(status);
|
||
CREATE INDEX idx_orders_order_no ON orders(order_no);
|
||
CREATE INDEX idx_orders_tenant ON orders(tenant_id);
|
||
|
||
-- Order Items
|
||
CREATE TABLE IF NOT EXISTS order_items (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
order_id BIGINT NOT NULL,
|
||
product_id BIGINT,
|
||
variant_id BIGINT,
|
||
title VARCHAR(500) NOT NULL,
|
||
description TEXT,
|
||
sku VARCHAR(100),
|
||
unit_price BIGINT NOT NULL CHECK(unit_price >= 0),
|
||
quantity INT NOT NULL CHECK(quantity > 0),
|
||
subtotal BIGINT NOT NULL,
|
||
tax_amount BIGINT NOT NULL DEFAULT 0,
|
||
cover_url VARCHAR(500),
|
||
attributes JSON,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
FOREIGN KEY (order_id) REFERENCES orders(id),
|
||
FOREIGN KEY (product_id) REFERENCES products(id),
|
||
FOREIGN KEY (variant_id) REFERENCES product_variants(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_order_items_order ON order_items(order_id);
|
||
CREATE INDEX idx_order_items_tenant ON order_items(tenant_id);
|
||
|
||
CREATE TABLE IF NOT EXISTS cart_items (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
user_id BIGINT NOT NULL,
|
||
product_id BIGINT NOT NULL,
|
||
variant_id BIGINT,
|
||
quantity INT NOT NULL DEFAULT 1,
|
||
attributes JSON,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
UNIQUE KEY uq_cart_user_product_variant (user_id, product_id, variant_id),
|
||
FOREIGN KEY (user_id) REFERENCES users(id),
|
||
FOREIGN KEY (product_id) REFERENCES products(id),
|
||
FOREIGN KEY (variant_id) REFERENCES product_variants(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_cart_items_tenant ON cart_items(tenant_id);
|
||
|
||
CREATE TABLE IF NOT EXISTS payment_channels (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
provider VARCHAR(50) NOT NULL,
|
||
name VARCHAR(200) NOT NULL,
|
||
is_live BOOLEAN NOT NULL DEFAULT FALSE,
|
||
credentials TEXT NOT NULL,
|
||
webhook_secret TEXT,
|
||
settings JSON,
|
||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||
sort_order INT NOT NULL DEFAULT 0,
|
||
version INT NOT NULL DEFAULT 1,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
UNIQUE KEY uq_channel_provider_name (provider, name)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_payment_channels_provider ON payment_channels(provider);
|
||
CREATE INDEX idx_payment_channels_active ON payment_channels(is_active);
|
||
CREATE INDEX idx_payment_channels_tenant ON payment_channels(tenant_id);
|
||
|
||
-- Payment Orders
|
||
CREATE TABLE IF NOT EXISTS payment_orders (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
user_id BIGINT NOT NULL,
|
||
order_id VARCHAR(36),
|
||
title VARCHAR(500) NOT NULL,
|
||
amount BIGINT NOT NULL,
|
||
currency VARCHAR(10) NOT NULL DEFAULT 'USD',
|
||
channel_id BIGINT NOT NULL,
|
||
provider VARCHAR(50) NOT NULL,
|
||
provider_order_id VARCHAR(200),
|
||
provider_method VARCHAR(50),
|
||
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
||
reference_type VARCHAR(50),
|
||
reference_id VARCHAR(200),
|
||
return_url VARCHAR(500),
|
||
idempotency_key VARCHAR(200) NOT NULL UNIQUE,
|
||
version INT NOT NULL DEFAULT 1,
|
||
provider_data JSON,
|
||
client_ip VARCHAR(45),
|
||
client_language VARCHAR(50),
|
||
client_country VARCHAR(2),
|
||
client_user_agent VARCHAR(512),
|
||
channel_selected_by VARCHAR(20),
|
||
metadata JSON,
|
||
paid_at DATETIME(3),
|
||
cancelled_at DATETIME(3),
|
||
expired_at DATETIME(3),
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
FOREIGN KEY (user_id) REFERENCES users(id),
|
||
FOREIGN KEY (channel_id) REFERENCES payment_channels(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_payment_orders_user ON payment_orders(user_id);
|
||
CREATE INDEX idx_payment_orders_status ON payment_orders(status);
|
||
CREATE INDEX idx_payment_orders_provider ON payment_orders(provider_order_id);
|
||
CREATE INDEX idx_payment_orders_order_id ON payment_orders(order_id);
|
||
CREATE INDEX idx_payment_orders_tenant ON payment_orders(tenant_id);
|
||
|
||
-- Payment Transactions
|
||
CREATE TABLE IF NOT EXISTS payment_transactions (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
payment_order_id BIGINT NOT NULL,
|
||
order_id VARCHAR(36),
|
||
user_id BIGINT NOT NULL,
|
||
tx_type VARCHAR(50) NOT NULL,
|
||
amount BIGINT NOT NULL,
|
||
currency VARCHAR(10) NOT NULL,
|
||
provider_tx_id VARCHAR(200) NOT NULL UNIQUE,
|
||
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
||
raw_payload JSON,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
FOREIGN KEY (payment_order_id) REFERENCES payment_orders(id),
|
||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_payment_tx_order ON payment_transactions(payment_order_id);
|
||
CREATE INDEX idx_payment_tx_order_id ON payment_transactions(order_id);
|
||
CREATE INDEX idx_payment_transactions_tenant ON payment_transactions(tenant_id);
|
||
|
||
-- Payment Refunds
|
||
CREATE TABLE IF NOT EXISTS payment_refunds (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
payment_order_id BIGINT NOT NULL,
|
||
order_id VARCHAR(36),
|
||
user_id BIGINT NOT NULL,
|
||
amount BIGINT NOT NULL,
|
||
currency VARCHAR(10) NOT NULL,
|
||
reason VARCHAR(200),
|
||
provider_refund_id VARCHAR(200),
|
||
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
||
payment_tx_id BIGINT,
|
||
metadata JSON,
|
||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
FOREIGN KEY (payment_order_id) REFERENCES payment_orders(id),
|
||
FOREIGN KEY (user_id) REFERENCES users(id),
|
||
FOREIGN KEY (payment_tx_id) REFERENCES payment_transactions(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
CREATE INDEX idx_payment_refunds_order ON payment_refunds(payment_order_id);
|
||
CREATE INDEX idx_payment_refunds_order_id ON payment_refunds(order_id);
|
||
CREATE INDEX idx_payment_refunds_tenant ON payment_refunds(tenant_id);
|
||
|
||
-- Wallet Outbox (ensures wallet operations are never lost)
|
||
CREATE TABLE IF NOT EXISTS wallet_outbox (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
document_id VARCHAR(36) NOT NULL UNIQUE,
|
||
tenant_id VARCHAR(36) NOT NULL DEFAULT 'default',
|
||
user_id BIGINT NOT NULL,
|
||
currency VARCHAR(10) NOT NULL,
|
||
amount BIGINT NOT NULL,
|
||
entry_type VARCHAR(20) NOT NULL,
|
||
tx_type VARCHAR(20) NOT NULL,
|
||
transaction_no VARCHAR(100) NOT NULL,
|
||
reference_type VARCHAR(30),
|
||
reference_id VARCHAR(100),
|
||
metadata TEXT,
|
||
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
||
attempts INT NOT NULL DEFAULT 0,
|
||
max_attempts INT NOT NULL DEFAULT 5,
|
||
last_error TEXT,
|
||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
INDEX idx_wallet_outbox_status (status),
|
||
INDEX idx_wallet_outbox_transaction_no (transaction_no),
|
||
INDEX idx_wallet_outbox_tenant (tenant_id)
|
||
);
|
||
|
||
-- ============================================================
|
||
-- 预置数据
|
||
-- ============================================================
|
||
|
||
-- 系统角色
|
||
INSERT IGNORE INTO roles (document_id, tenant_id, name, description, is_system, created_at, updated_at) VALUES
|
||
('role-admin', 'default', 'admin', '超级管理员', TRUE, NOW(), NOW()),
|
||
('role-editor', 'default', 'editor', '编辑', FALSE, NOW(), NOW()),
|
||
('role-author', 'default', 'author', '作者', FALSE, NOW(), NOW()),
|
||
('role-reader', 'default', 'reader', '读者', TRUE, NOW(), NOW());
|
||
|
||
-- admin 全局权限
|
||
INSERT IGNORE INTO permissions (document_id, tenant_id, role_id, action, subject, fields, conditions, created_at) VALUES
|
||
('perm-admin-all', 'default', (SELECT id FROM roles WHERE document_id = 'role-admin'), '*', '*', '["*"]', NULL, NOW());
|
||
|
||
-- editor 权限
|
||
INSERT IGNORE INTO permissions (document_id, tenant_id, role_id, action, subject, fields, conditions, created_at) VALUES
|
||
('perm-editor-ct-create', 'default', (SELECT id FROM roles WHERE document_id = 'role-editor'), 'content-type::*.*', 'content-type::*', '["*"]', NULL, NOW());
|
||
|
||
-- author 权限
|
||
INSERT IGNORE INTO permissions (document_id, tenant_id, role_id, action, subject, fields, conditions, created_at) VALUES
|
||
('perm-author-post-create', 'default', (SELECT id FROM roles WHERE document_id = 'role-author'), 'content-type::post.create', 'content-type::post', '["*"]', NULL, NOW()),
|
||
('perm-author-post-read', 'default', (SELECT id FROM roles WHERE document_id = 'role-author'), 'content-type::post.read', 'content-type::post', '["*"]', NULL, NOW()),
|
||
('perm-author-post-update', 'default', (SELECT id FROM roles WHERE document_id = 'role-author'), 'content-type::post.update', 'content-type::post', '["*"]', '{"author_id":"$user.id"}', NOW()),
|
||
('perm-author-post-delete', 'default', (SELECT id FROM roles WHERE document_id = 'role-author'), 'content-type::post.delete', 'content-type::post', '["*"]', '{"author_id":"$user.id"}', NOW());
|
||
|
||
-- reader 权限
|
||
INSERT IGNORE INTO permissions (document_id, tenant_id, role_id, action, subject, fields, conditions, created_at) VALUES
|
||
('perm-reader-post-read', 'default', (SELECT id FROM roles WHERE document_id = 'role-reader'), 'content-type::post.read', 'content-type::post', '["title","slug","content","excerpt","status"]', NULL, NOW()),
|
||
('perm-reader-comment-create', 'default', (SELECT id FROM roles WHERE document_id = 'role-reader'), 'content-type::comment.create', 'content-type::comment', '["content","nickname","email"]', NULL, NOW());
|
||
|
||
-- 站点配置
|
||
INSERT IGNORE INTO options (document_id, tenant_id, `option_key`, value, `type`, group_name, label, description, validation, is_public, autoload, sort_order, updated_at) VALUES
|
||
('opt-site-title', 'default', 'site_title', '"My Blog"', 'text', 'general', '站点标题', '显示在浏览器标题栏和页面头部', '{"max_length":100}', TRUE, TRUE, 1, NOW()),
|
||
('opt-site-desc', 'default', 'site_description', '""', 'text', 'general', '站点描述', '简短描述站点用途', '{"max_length":500}', TRUE, TRUE, 2, NOW()),
|
||
('opt-site-url', 'default', 'site_url', '""', 'url', 'general', '站点 URL', '如 https://example.com', NULL, TRUE, TRUE, 3, NOW()),
|
||
('opt-admin-email', 'default', 'admin_email', '""', 'email', 'general', '管理员邮箱', NULL, NULL, FALSE, TRUE, 4, NOW()),
|
||
('opt-timezone', 'default', 'timezone', '"UTC"', 'select', 'general', '时区', NULL, '{"values":["UTC","Asia/Shanghai","Asia/Tokyo","US/Eastern","US/Pacific","Europe/London","Europe/Berlin"]}', TRUE, TRUE, 5, NOW()),
|
||
('opt-date-fmt', 'default', 'date_format', '"%Y-%m-%d"', 'select', 'general', '日期格式', NULL, '{"values":["%Y-%m-%d","%d/%m/%Y","%m/%d/%Y","%Y年%m月%d日"]}', TRUE, TRUE, 6, NOW()),
|
||
('opt-per-page', 'default', 'posts_per_page', '10', 'integer', 'reading', '每页文章数', NULL, '{"min":1,"max":100}', TRUE, TRUE, 10, NOW()),
|
||
('opt-rss-items', 'default', 'rss_items', '20', 'integer', 'reading', 'RSS 条目数', NULL, '{"min":1,"max":100}', TRUE, TRUE, 11, NOW()),
|
||
('opt-permalink', 'default', 'permalink_structure', '"/:year/:month/:slug"', 'select', 'reading', 'URL 结构', NULL, '{"values":["/:year/:month/:slug","/:slug","/posts/:slug"]}', TRUE, TRUE, 12, NOW()),
|
||
('opt-comment-mod', 'default', 'comment_moderation', 'true', 'boolean', 'discussion', '评论需审核', '开启后新评论需管理员审批', NULL, FALSE, TRUE, 20, NOW()),
|
||
('opt-comment-order', 'default', 'comment_order', '"asc"', 'select', 'discussion', '评论排序', NULL, '{"values":["asc","desc"]}', TRUE, TRUE, 21, NOW()),
|
||
('opt-default-role', 'default', 'default_role', '"reader"', 'select', 'discussion', '新用户默认角色', NULL, '{"values":["reader","author"]}', FALSE, TRUE, 22, NOW()),
|
||
('opt-theme', 'default', 'theme', '"default"', 'select', 'appearance', '当前主题', NULL, '{"values":["default","corporate","minimal","warm"]}', TRUE, TRUE, 30, NOW()),
|
||
('opt-maintenance', 'default', 'maintenance_mode', 'false', 'boolean', 'appearance', '维护模式', '开启后前台显示维护页面', NULL, TRUE, TRUE, 31, NOW());
|