Files
FruitServer/sql/schema.sql
T

323 lines
15 KiB
PL/PgSQL
Raw 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.
-- Author: fruit.com.cn
-- FruitShorts 短剧服务 · 建表脚本(PostgreSQL 18)
--
-- 与 MySQL 版本的对应关系:
-- AUTO_INCREMENT -> GENERATED BY DEFAULT AS IDENTITY
-- TINYINT -> SMALLINT
-- DATETIME(3) -> TIMESTAMP(3)
-- ON UPDATE CURRENT_TIMESTAMP -> set_updated_at() 触发器
-- 表内 KEY / UNIQUE KEY -> 单独的 CREATE INDEX / UNIQUE 约束
-- 列注释 -> 行内注释 + 表注释(COMMENT ON TABLE)
-- ========== 系统域 ==========
CREATE TABLE IF NOT EXISTS sys_admin_user (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
username VARCHAR(64) NOT NULL, -- 登录名
password VARCHAR(100) NOT NULL, -- BCrypt 哈希
real_name VARCHAR(64) NOT NULL DEFAULT '',
mobile VARCHAR(20) NOT NULL DEFAULT '',
email VARCHAR(128) NOT NULL DEFAULT '',
avatar VARCHAR(255) NOT NULL DEFAULT '',
status SMALLINT NOT NULL DEFAULT 1, -- 1 启用 0 停用
ip_whitelist VARCHAR(255) NOT NULL DEFAULT '',-- 空 = 不限
two_factor SMALLINT NOT NULL DEFAULT 0,
last_login_at TIMESTAMP(3),
last_login_ip VARCHAR(64) NOT NULL DEFAULT '',
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted SMALLINT NOT NULL DEFAULT 0
);
CREATE UNIQUE INDEX IF NOT EXISTS uk_sys_admin_user_username ON sys_admin_user (username);
COMMENT ON TABLE sys_admin_user IS '管理员';
CREATE TABLE IF NOT EXISTS sys_role_category (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(64) NOT NULL,
description VARCHAR(255) NOT NULL DEFAULT '',
sort INT NOT NULL DEFAULT 0,
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted SMALLINT NOT NULL DEFAULT 0
);
CREATE UNIQUE INDEX IF NOT EXISTS uk_sys_role_category_name ON sys_role_category (name);
COMMENT ON TABLE sys_role_category IS '角色分类';
CREATE TABLE IF NOT EXISTS sys_role (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(64) NOT NULL,
code VARCHAR(64) NOT NULL, -- 如 super_admin
category_id BIGINT NOT NULL DEFAULT 0,
level INT NOT NULL DEFAULT 9, -- 越小权限越高
description VARCHAR(255) NOT NULL DEFAULT '',
status SMALLINT NOT NULL DEFAULT 1,
is_builtin SMALLINT NOT NULL DEFAULT 0, -- 内置角色不可删除
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted SMALLINT NOT NULL DEFAULT 0
);
CREATE UNIQUE INDEX IF NOT EXISTS uk_sys_role_code ON sys_role (code);
COMMENT ON TABLE sys_role IS '角色';
CREATE TABLE IF NOT EXISTS sys_admin_user_role (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS uk_sys_admin_user_role ON sys_admin_user_role (user_id, role_id);
CREATE INDEX IF NOT EXISTS idx_sys_admin_user_role_role ON sys_admin_user_role (role_id);
COMMENT ON TABLE sys_admin_user_role IS '管理员-角色';
CREATE TABLE IF NOT EXISTS sys_permission (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
code VARCHAR(128) NOT NULL, -- 如 drama:create
name VARCHAR(64) NOT NULL,
group_name VARCHAR(64) NOT NULL DEFAULT '',
type VARCHAR(16) NOT NULL DEFAULT 'BUTTON',
sort INT NOT NULL DEFAULT 0
);
CREATE UNIQUE INDEX IF NOT EXISTS uk_sys_permission_code ON sys_permission (code);
COMMENT ON TABLE sys_permission IS '权限点';
CREATE TABLE IF NOT EXISTS sys_menu (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
parent_id BIGINT NOT NULL DEFAULT 0,
title VARCHAR(64) NOT NULL,
route_path VARCHAR(128) NOT NULL DEFAULT '',
route_name VARCHAR(64) NOT NULL DEFAULT '',
component VARCHAR(255) NOT NULL DEFAULT '',
redirect VARCHAR(128) NOT NULL DEFAULT '',
icon VARCHAR(64) NOT NULL DEFAULT '',
permission_code VARCHAR(128) NOT NULL DEFAULT '',
type VARCHAR(16) NOT NULL DEFAULT 'MENU', -- CATALOG/MENU/BUTTON
always_show SMALLINT NOT NULL DEFAULT 0,
affix SMALLINT NOT NULL DEFAULT 0,
sort INT NOT NULL DEFAULT 0,
visible SMALLINT NOT NULL DEFAULT 1,
status SMALLINT NOT NULL DEFAULT 1,
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_sys_menu_parent ON sys_menu (parent_id);
COMMENT ON TABLE sys_menu IS '菜单';
CREATE TABLE IF NOT EXISTS sys_role_permission (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
role_id BIGINT NOT NULL,
permission_id BIGINT NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS uk_sys_role_permission ON sys_role_permission (role_id, permission_id);
COMMENT ON TABLE sys_role_permission IS '角色-权限';
CREATE TABLE IF NOT EXISTS sys_role_menu (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
role_id BIGINT NOT NULL,
menu_id BIGINT NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS uk_sys_role_menu ON sys_role_menu (role_id, menu_id);
COMMENT ON TABLE sys_role_menu IS '角色-菜单';
CREATE TABLE IF NOT EXISTS sys_config (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
config_key VARCHAR(128) NOT NULL,
config_value VARCHAR(1024) NOT NULL DEFAULT '',
remark VARCHAR(255) NOT NULL DEFAULT ''
);
CREATE UNIQUE INDEX IF NOT EXISTS uk_sys_config_key ON sys_config (config_key);
COMMENT ON TABLE sys_config IS '系统配置';
CREATE TABLE IF NOT EXISTS sys_operation_log (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
admin_user_id BIGINT NOT NULL DEFAULT 0,
username VARCHAR(64) NOT NULL DEFAULT '',
module VARCHAR(64) NOT NULL DEFAULT '',
action VARCHAR(64) NOT NULL DEFAULT '',
request_method VARCHAR(10) NOT NULL DEFAULT '',
request_url VARCHAR(512) NOT NULL DEFAULT '',
request_params TEXT, -- 查询串
request_body TEXT, -- 请求体(已脱敏、截断)
response_body TEXT, -- 响应体(截断)
status VARCHAR(16) NOT NULL DEFAULT 'SUCCESS', -- SUCCESS/FAIL
error_msg VARCHAR(500) NOT NULL DEFAULT '',
duration_ms INT NOT NULL DEFAULT 0,
ip VARCHAR(64) NOT NULL DEFAULT '',
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_sys_operation_log_admin_time ON sys_operation_log (admin_user_id, created_at);
COMMENT ON TABLE sys_operation_log IS '操作日志';
-- ========== 内容域 ==========
CREATE TABLE IF NOT EXISTS drama_category (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(64) NOT NULL,
sort INT NOT NULL DEFAULT 0,
status SMALLINT NOT NULL DEFAULT 1
);
CREATE UNIQUE INDEX IF NOT EXISTS uk_drama_category_name ON drama_category (name);
COMMENT ON TABLE drama_category IS '短剧类型';
CREATE TABLE IF NOT EXISTS drama (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
code VARCHAR(64) NOT NULL DEFAULT '',
title VARCHAR(128) NOT NULL,
subtitle VARCHAR(128) NOT NULL DEFAULT '',
category_id BIGINT NOT NULL DEFAULT 0,
genre VARCHAR(64) NOT NULL DEFAULT '',
synopsis VARCHAR(1000) NOT NULL DEFAULT '',
cover_url VARCHAR(512) NOT NULL DEFAULT '',
horizontal_cover_url VARCHAR(512) NOT NULL DEFAULT '',
language VARCHAR(16) NOT NULL DEFAULT 'zh-CN',
total_episodes INT NOT NULL DEFAULT 0,
free_episodes INT NOT NULL DEFAULT 3,
unlock_coin INT NOT NULL DEFAULT 0,
is_hot SMALLINT NOT NULL DEFAULT 0, -- 是否热门,供首页热门位使用
-- draft/reviewing/approved/published/offline
status VARCHAR(16) NOT NULL DEFAULT 'draft',
publish_at TIMESTAMP(3),
published_at TIMESTAMP(3),
view_count BIGINT NOT NULL DEFAULT 0,
play_count BIGINT NOT NULL DEFAULT 0,
favorite_count BIGINT NOT NULL DEFAULT 0,
like_count BIGINT NOT NULL DEFAULT 0,
sort INT NOT NULL DEFAULT 0,
create_by BIGINT NOT NULL DEFAULT 0,
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted SMALLINT NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_drama_status_publish ON drama (status, publish_at);
CREATE INDEX IF NOT EXISTS idx_drama_category ON drama (category_id);
CREATE INDEX IF NOT EXISTS idx_drama_updated ON drama (updated_at);
-- C 端目录:按状态取热门 / 按分类取列表
CREATE INDEX IF NOT EXISTS idx_drama_status_hot ON drama (status, is_hot, sort DESC);
CREATE INDEX IF NOT EXISTS idx_drama_status_category ON drama (status, category_id, sort DESC);
COMMENT ON TABLE drama IS '短剧';
CREATE TABLE IF NOT EXISTS drama_episode (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
drama_id BIGINT NOT NULL,
ep_no INT NOT NULL,
title VARCHAR(128) NOT NULL DEFAULT '',
duration_sec INT NOT NULL DEFAULT 0,
cover_url VARCHAR(512) NOT NULL DEFAULT '',
video_asset_id BIGINT NOT NULL DEFAULT 0,
video_url VARCHAR(512) NOT NULL DEFAULT '',
is_free SMALLINT NOT NULL DEFAULT 0,
unlock_coin INT NOT NULL DEFAULT 0,
status VARCHAR(16) NOT NULL DEFAULT 'draft',
release_at TIMESTAMP(3),
play_count BIGINT NOT NULL DEFAULT 0,
sort INT NOT NULL DEFAULT 0,
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted SMALLINT NOT NULL DEFAULT 0
);
CREATE UNIQUE INDEX IF NOT EXISTS uk_drama_episode_no ON drama_episode (drama_id, ep_no);
CREATE INDEX IF NOT EXISTS idx_drama_episode_status ON drama_episode (drama_id, status);
COMMENT ON TABLE drama_episode IS '剧集';
CREATE TABLE IF NOT EXISTS drama_review_record (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
biz_type VARCHAR(16) NOT NULL, -- DRAMA/EPISODE
biz_id BIGINT NOT NULL,
action VARCHAR(16) NOT NULL, -- SUBMIT/APPROVE/REJECT/PUBLISH/OFFLINE
reason VARCHAR(500) NOT NULL DEFAULT '',
operator_id BIGINT NOT NULL DEFAULT 0,
operator_name VARCHAR(64) NOT NULL DEFAULT '',
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_drama_review_biz ON drama_review_record (biz_type, biz_id, created_at);
COMMENT ON TABLE drama_review_record IS '审核记录';
CREATE TABLE IF NOT EXISTS content_banner (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
image_url VARCHAR(512) NOT NULL DEFAULT '',
slot VARCHAR(32) NOT NULL DEFAULT 'HOME_TOP', -- 展示位置:HOME_TOP/DISCOVER
link_type VARCHAR(16) NOT NULL DEFAULT 'NONE', -- NONE/DRAMA/URL
link_value VARCHAR(512) NOT NULL DEFAULT '',
sort INT NOT NULL DEFAULT 0,
status SMALLINT NOT NULL DEFAULT 1,
create_by BIGINT NOT NULL DEFAULT 0,
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted SMALLINT NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_content_banner_slot ON content_banner (slot, status, sort);
COMMENT ON TABLE content_banner IS '轮播图';
-- ========== 媒资域 ==========
-- 本地存储版本:文件落在 fruitshorts.media.root-dir 下,通过 /files/** 访问
CREATE TABLE IF NOT EXISTS media_asset (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
biz_type VARCHAR(32) NOT NULL DEFAULT 'EPISODE',
file_name VARCHAR(255) NOT NULL DEFAULT '',
object_key VARCHAR(512) NOT NULL DEFAULT '',
source_url VARCHAR(512) NOT NULL DEFAULT '',
size_bytes BIGINT NOT NULL DEFAULT 0,
md5 VARCHAR(64) NOT NULL DEFAULT '',
duration_sec INT NOT NULL DEFAULT 0,
width INT NOT NULL DEFAULT 0,
height INT NOT NULL DEFAULT 0,
-- uploading/uploaded/transcoding/ready/failed
status VARCHAR(16) NOT NULL DEFAULT 'uploading',
hls_url VARCHAR(512) NOT NULL DEFAULT '',
mp4_url VARCHAR(512) NOT NULL DEFAULT '',
poster_url VARCHAR(512) NOT NULL DEFAULT '',
create_by BIGINT NOT NULL DEFAULT 0,
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted SMALLINT NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_media_asset_status ON media_asset (status);
CREATE INDEX IF NOT EXISTS idx_media_asset_biz_type ON media_asset (biz_type);
COMMENT ON TABLE media_asset IS '媒资';
CREATE TABLE IF NOT EXISTS media_upload_task (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
upload_id VARCHAR(64) NOT NULL,
biz_type VARCHAR(32) NOT NULL DEFAULT 'EPISODE',
file_name VARCHAR(255) NOT NULL DEFAULT '',
file_size BIGINT NOT NULL DEFAULT 0,
chunk_size INT NOT NULL DEFAULT 0,
total_chunks INT NOT NULL DEFAULT 0,
received_chunks INT NOT NULL DEFAULT 0,
object_key VARCHAR(512) NOT NULL DEFAULT '',
status VARCHAR(16) NOT NULL DEFAULT 'uploading',
asset_id BIGINT NOT NULL DEFAULT 0,
create_by BIGINT NOT NULL DEFAULT 0,
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX IF NOT EXISTS uk_media_upload_task ON media_upload_task (upload_id);
COMMENT ON TABLE media_upload_task IS '分片上传任务';
-- ========== updated_at 维护 ==========
-- PostgreSQL 没有 ON UPDATE CURRENT_TIMESTAMP,用触发器统一维护
CREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DO $$
DECLARE
t text;
BEGIN
FOREACH t IN ARRAY ARRAY[
'sys_admin_user', 'sys_role_category', 'sys_role', 'sys_menu',
'drama', 'drama_episode', 'content_banner', 'media_asset', 'media_upload_task'
] LOOP
EXECUTE format('DROP TRIGGER IF EXISTS trg_%s_updated_at ON %I', t, t);
EXECUTE format(
'CREATE TRIGGER trg_%s_updated_at BEFORE UPDATE ON %I FOR EACH ROW EXECUTE FUNCTION set_updated_at()',
t, t
);
END LOOP;
END;
$$;