feat: 初始化短剧服务端(管理后台 API + 客户端 API)

Spring Boot 3.2 / MyBatis-Plus / PostgreSQL,7 个 Maven 模块;含认证鉴权、内容与媒资管理、操作日志,以及客户端 /app/** 接口。
This commit is contained in:
Vicky committed 2026-10-14 18:02:47 +08:00
commit a4aa749cc6
152 files changed
+8027

No files matched your search

+191
View File
@@ -0,0 +1,191 @@
-- ReelShort 短剧服务 · 初始化数据(PostgreSQL 18,可重复执行)
-- 演示账号:admin / admin123(其余种子账号密码相同)
--
-- MySQL 的 ON DUPLICATE KEY UPDATE 在这里换成 ON CONFLICT (id) DO UPDATE;
-- INSERT IGNORE 换成 ON CONFLICT DO NOTHING。
-- 末尾会把自增序列对齐到已有最大 ID,避免后续插入主键冲突。
-- ---------- 角色分类 ----------
INSERT INTO sys_role_category (id, name, description, sort) VALUES
(1, '系统管理员', '负责账号、权限和菜单配置', 1),
(2, '内容管理', '负责短剧与剧集内容维护', 2),
(3, '数据分析', '负责查看运营数据', 3)
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, description = EXCLUDED.description, sort = EXCLUDED.sort;
-- ---------- 角色 ----------
INSERT INTO sys_role (id, name, code, category_id, level, description, status, is_builtin) VALUES
(1, '超级管理员', 'super_admin', 1, 1, '拥有系统全部管理权限', 1, 1),
(2, '内容运营', 'content_operator', 2, 2, '管理短剧、剧集及封面素材', 1, 1),
(3, '审核专员', 'content_reviewer', 2, 3, '审核短剧内容与发布状态', 1, 1),
(4, '数据分析师', 'data_analyst', 3, 4, '查看内容数据与运营报表', 1, 1)
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, code = EXCLUDED.code,
description = EXCLUDED.description, category_id = EXCLUDED.category_id, level = EXCLUDED.level;
-- ---------- 权限点 ----------
INSERT INTO sys_permission (id, code, name, group_name, type, sort) VALUES
(1, 'drama:list', '查看短剧列表', '短剧管理', 'BUTTON', 1),
(2, 'drama:create', '创建短剧', '短剧管理', 'BUTTON', 2),
(3, 'drama:update', '编辑短剧信息', '短剧管理', 'BUTTON', 3),
(4, 'drama:delete', '删除短剧', '短剧管理', 'BUTTON', 4),
(5, 'episode:list', '查看剧集列表', '剧集管理', 'BUTTON', 5),
(6, 'episode:upload', '上传剧集视频', '剧集管理', 'BUTTON', 6),
(7, 'episode:sort', '管理剧集排序', '剧集管理', 'BUTTON', 7),
(8, 'cover:upload', '上传封面素材', '封面管理', 'BUTTON', 8),
(9, 'drama:review', '审核并发布', '审核与系统', 'BUTTON', 9),
(10, 'stat:view', '查看数据报表', '审核与系统', 'BUTTON', 10),
(11, 'system:role', '角色与权限配置', '后台管理', 'BUTTON', 11),
(12, 'system:admin-user', '管理员用户列表', '后台管理', 'BUTTON', 12),
(13, 'system:menu', '菜单管理', '后台管理', 'BUTTON', 13),
(14, 'system:role-category', '角色分类管理', '后台管理', 'BUTTON', 14),
(15, 'banner:list', '查看轮播图', '运营编排', 'BUTTON', 15),
(16, 'banner:create', '新建轮播图', '运营编排', 'BUTTON', 16),
(17, 'banner:update', '编辑轮播图', '运营编排', 'BUTTON', 17),
(18, 'banner:delete', '删除轮播图', '运营编排', 'BUTTON', 18),
(19, 'log:list', '查看操作日志', '后台管理', 'BUTTON', 19)
ON CONFLICT (id) DO UPDATE SET code = EXCLUDED.code, name = EXCLUDED.name,
group_name = EXCLUDED.group_name, sort = EXCLUDED.sort;
-- ---------- 菜单 ----------
INSERT INTO sys_menu (id, parent_id, title, route_path, route_name, component, redirect, icon,
permission_code, type, always_show, affix, sort) VALUES
(1, 0, '工作台', '/dashboard', 'Dashboard', '#', '/dashboard/index', 'ant-design:dashboard-filled', 'drama:list', 'CATALOG', 0, 0, 1),
(2, 1, '工作台', 'index', 'dashboard', 'views/common/DashboardView', '', 'ant-design:dashboard-filled', 'drama:list', 'MENU', 0, 1, 1),
(3, 0, '内容管理', '/content', 'Content', '#', '/content/dramas', 'ep:video-camera', 'drama:list', 'CATALOG', 1, 0, 2),
(4, 3, '短剧管理', 'dramas', 'dramas', 'views/contentManagement/DramaManagementView', '', '', 'drama:list', 'MENU', 0, 0, 1),
(5, 3, '剧集管理', 'episodes', 'episodes', 'views/contentManagement/EpisodeManagementView', '', '', 'episode:list', 'MENU', 0, 0, 2),
(12, 3, '轮播图管理', 'banners', 'banners', 'views/contentManagement/BannerManagementView', '', '', 'banner:list', 'MENU', 0, 0, 3),
(13, 3, '类型管理', 'categories', 'categories', 'views/contentManagement/CategoryManagementView','', '', 'drama:list', 'MENU', 0, 0, 4),
(6, 0, '后台管理', '/system', 'System', '#', '/system/access', 'ep:setting', 'system:role', 'CATALOG', 1, 0, 3),
(7, 6, '角色权限配置', 'access', 'access', 'views/permission/RoleManagementView', '', '', 'system:role', 'MENU', 0, 0, 1),
(8, 6, '管理员用户列表','admin-users','admin-users', 'views/userManagement/AdminUsersView', '', '', 'system:admin-user','MENU', 0, 0, 2),
(9, 6, '菜单管理', 'menus', 'menus', 'views/permission/MenuManagementView', '', '', 'system:menu', 'MENU', 0, 0, 3),
(10, 6, '角色分类', 'role-categories','role-categories','views/permission/RoleCategoriesView', '', '', 'system:role-category','MENU', 0, 0, 4),
(11, 6, '系统设置', 'settings', 'settings', 'views/systemManagement/SettingsView', '', '', 'system:role', 'MENU', 0, 0, 5)
ON CONFLICT (id) DO UPDATE SET parent_id = EXCLUDED.parent_id, title = EXCLUDED.title,
route_path = EXCLUDED.route_path, route_name = EXCLUDED.route_name, component = EXCLUDED.component,
redirect = EXCLUDED.redirect, icon = EXCLUDED.icon, permission_code = EXCLUDED.permission_code,
type = EXCLUDED.type, always_show = EXCLUDED.always_show, affix = EXCLUDED.affix, sort = EXCLUDED.sort;
-- 操作日志菜单(挂在后台管理下,仅超级管理员可见)
INSERT INTO sys_menu (id, parent_id, title, route_path, route_name, component, redirect, icon,
permission_code, type, always_show, affix, sort) VALUES
(14, 6, '操作日志', 'operation-logs', 'operation-logs', 'views/systemManagement/OperationLogView', '', '', 'log:list', 'MENU', 0, 0, 6)
ON CONFLICT (id) DO UPDATE SET parent_id = EXCLUDED.parent_id, title = EXCLUDED.title,
route_path = EXCLUDED.route_path, route_name = EXCLUDED.route_name, component = EXCLUDED.component,
redirect = EXCLUDED.redirect, icon = EXCLUDED.icon, permission_code = EXCLUDED.permission_code,
type = EXCLUDED.type, always_show = EXCLUDED.always_show, affix = EXCLUDED.affix, sort = EXCLUDED.sort;
-- ---------- 角色-权限 ----------
INSERT INTO sys_role_permission (role_id, permission_id)
SELECT r.id, p.id FROM sys_role r CROSS JOIN sys_permission p WHERE r.code = 'super_admin'
ON CONFLICT DO NOTHING;
INSERT INTO sys_role_permission (role_id, permission_id)
SELECT r.id, p.id FROM sys_role r CROSS JOIN sys_permission p
WHERE r.code = 'content_operator'
AND p.code IN ('drama:list','drama:create','drama:update','drama:delete',
'episode:list','episode:upload','episode:sort','cover:upload',
'banner:list','banner:create','banner:update','banner:delete')
ON CONFLICT DO NOTHING;
INSERT INTO sys_role_permission (role_id, permission_id)
SELECT r.id, p.id FROM sys_role r CROSS JOIN sys_permission p
WHERE r.code = 'content_reviewer'
AND p.code IN ('drama:list','episode:list','drama:review','stat:view','banner:list')
ON CONFLICT DO NOTHING;
INSERT INTO sys_role_permission (role_id, permission_id)
SELECT r.id, p.id FROM sys_role r CROSS JOIN sys_permission p
WHERE r.code = 'data_analyst'
AND p.code IN ('drama:list','stat:view','banner:list')
ON CONFLICT DO NOTHING;
-- ---------- 角色-菜单 ----------
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT r.id, m.id FROM sys_role r CROSS JOIN sys_menu m WHERE r.code = 'super_admin'
ON CONFLICT DO NOTHING;
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT r.id, m.id FROM sys_role r CROSS JOIN sys_menu m
WHERE r.code IN ('content_operator','content_reviewer','data_analyst') AND m.id IN (1,2,3,4,5,12,13)
ON CONFLICT DO NOTHING;
-- ---------- 管理员(密码均为 admin123) ----------
INSERT INTO sys_admin_user (id, username, password, real_name, mobile, email, status, ip_whitelist,
two_factor, last_login_at, created_at) VALUES
(1001, 'admin', '$2a$10$cknSDsmYgEADwwv0HdwzUOPqiz8D6mO/OOWMm3QEx.zmJW4iQqLcq', '系统管理员', '', '', 1, '', 0, NULL, '2026-09-01 09:00:00'),
(1002, 'lin.xiaoman', '$2a$10$cknSDsmYgEADwwv0HdwzUOPqiz8D6mO/OOWMm3QEx.zmJW4iQqLcq', '林小满', '', '', 1, '', 1, '2026-10-12 09:41:22', '2026-09-18 09:30:00'),
(1003, 'content.editor01', '$2a$10$cknSDsmYgEADwwv0HdwzUOPqiz8D6mO/OOWMm3QEx.zmJW4iQqLcq', '陈一诺', '', '', 1, '', 0, '2026-10-11 16:20:15', '2026-09-21 10:15:00'),
(1004, 'content.editor02', '$2a$10$cknSDsmYgEADwwv0HdwzUOPqiz8D6mO/OOWMm3QEx.zmJW4iQqLcq', '周子瑜', '', '', 1, '', 0, '2026-10-11 13:34:10', '2026-09-25 14:20:00'),
(1005, 'content.reviewer01', '$2a$10$cknSDsmYgEADwwv0HdwzUOPqiz8D6mO/OOWMm3QEx.zmJW4iQqLcq', '林晓彤', '', '', 1, '', 1, '2026-10-10 11:20:08', '2026-10-02 11:05:00'),
(1006, 'data.analyst01', '$2a$10$cknSDsmYgEADwwv0HdwzUOPqiz8D6mO/OOWMm3QEx.zmJW4iQqLcq', '程予安', '', '', 1, '', 0, '2026-10-09 14:10:02', '2026-10-05 13:02:00')
ON CONFLICT (id) DO UPDATE SET username = EXCLUDED.username, real_name = EXCLUDED.real_name,
status = EXCLUDED.status, two_factor = EXCLUDED.two_factor, last_login_at = EXCLUDED.last_login_at;
INSERT INTO sys_admin_user_role (user_id, role_id) VALUES
(1001, 1), (1002, 1), (1003, 2), (1004, 2), (1005, 3), (1006, 4)
ON CONFLICT DO NOTHING;
-- ---------- 系统配置 ----------
INSERT INTO sys_config (config_key, config_value, remark) VALUES
('workspace.name', 'ReelShort Studio', '工作空间名称'),
('content.default-status', 'draft', '内容默认状态')
ON CONFLICT (config_key) DO UPDATE SET config_value = EXCLUDED.config_value, remark = EXCLUDED.remark;
-- ---------- 短剧类型 ----------
INSERT INTO drama_category (id, name, sort, status) VALUES
(1, '都市情感', 1, 1),
(2, '重生逆袭', 2, 1),
(3, '甜宠爱情', 3, 1),
(4, '悬疑复仇', 4, 1)
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, sort = EXCLUDED.sort, status = EXCLUDED.status;
-- ---------- 短剧 ----------
INSERT INTO drama (id, code, title, category_id, genre, synopsis, total_episodes, free_episodes,
unlock_coin, is_hot, status, published_at, view_count, sort, created_at) VALUES
(1, 'D20250612001', '替嫁新娘:总裁的掌心宠', 1, '都市情感',
'一场意外替嫁,让她成为冷面总裁的契约新娘。', 68, 3, 60, 1, 'published', '2025-06-12 10:00:00', 2400000, 1, '2025-06-01 09:00:00'),
(2, 'D20250618002', '重生之我在豪门当千金', 2, '重生逆袭',
'重活一世,她要拿回属于自己的一切。', 42, 3, 60, 0, 'reviewing', NULL, 0, 2, '2025-06-05 09:00:00'),
(3, 'D20250529003', '心动信号:请和我恋爱', 3, '甜宠爱情',
'恋爱观察室里,心动正在悄悄发生。', 56, 3, 50, 0, 'published', '2025-05-29 10:00:00', 1800000, 3, '2025-05-20 09:00:00'),
(4, 'D20250620004', '她从深渊中走来', 4, '悬疑复仇',
'真相只有一个,而她亲手揭开迷雾。', 24, 3, 80, 0, 'draft', NULL, 0, 4, '2025-06-15 09:00:00'),
(5, 'D20250521005', '闪婚后,傅先生每天都在撒糖', 1, '都市情感',
'一纸婚约,开启了甜蜜又心动的同居生活。', 72, 3, 60, 1, 'published', '2025-05-21 10:00:00', 3100000, 5, '2025-05-10 09:00:00')
ON CONFLICT (id) DO UPDATE SET code = EXCLUDED.code, title = EXCLUDED.title, genre = EXCLUDED.genre,
synopsis = EXCLUDED.synopsis, category_id = EXCLUDED.category_id, status = EXCLUDED.status,
total_episodes = EXCLUDED.total_episodes, free_episodes = EXCLUDED.free_episodes,
unlock_coin = EXCLUDED.unlock_coin, is_hot = EXCLUDED.is_hot, view_count = EXCLUDED.view_count;
-- ---------- 剧集(示例:短剧 1) ----------
INSERT INTO drama_episode (id, drama_id, ep_no, title, duration_sec, is_free, unlock_coin, status, play_count) VALUES
(1, 1, 1, '命运的相遇', 138, 1, 0, 'published', 48201),
(2, 1, 2, '意外的婚约', 116, 1, 0, 'published', 42817),
(3, 1, 3, '心动的开始', 124, 1, 0, 'published', 39506),
(4, 1, 4, '他的秘密', 151, 0, 60, 'draft', 0)
ON CONFLICT (id) DO UPDATE SET drama_id = EXCLUDED.drama_id, ep_no = EXCLUDED.ep_no,
title = EXCLUDED.title, duration_sec = EXCLUDED.duration_sec, is_free = EXCLUDED.is_free,
unlock_coin = EXCLUDED.unlock_coin, status = EXCLUDED.status, play_count = EXCLUDED.play_count;
-- ---------- 对齐自增序列 ----------
-- 上面用了显式 ID,但 IDENTITY 序列不会因此前移;不对齐的话,后续插入会从 1 开始而撞主键。
DO $$
DECLARE
t text;
seq text;
maxid bigint;
BEGIN
FOREACH t IN ARRAY ARRAY[
'sys_admin_user','sys_role_category','sys_role','sys_admin_user_role','sys_permission',
'sys_menu','sys_role_permission','sys_role_menu','sys_config','sys_operation_log',
'drama_category','drama','drama_episode','drama_review_record','content_banner',
'media_asset','media_upload_task'
] LOOP
seq := pg_get_serial_sequence(t, 'id');
EXECUTE format('SELECT COALESCE(MAX(id), 0) FROM %I', t) INTO maxid;
IF seq IS NOT NULL AND maxid > 0 THEN
PERFORM setval(seq, maxid);
END IF;
END LOOP;
END $$;
+320
View File
@@ -0,0 +1,320 @@
-- ReelShort 短剧服务 · 建表脚本(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 '轮播图';
-- ========== 媒资域 ==========
-- 本地存储版本:文件落在 reelshort.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;
$$;