Files
FruitServer/sql/data-phase1.sql
T
Vicky 8b8ebf944f feat: 07 创作者中心批 3(分片上传挂载 + 分集收费与试看)
为什么:创作者传完片子得把产物挂到分集上才能播,而这条链路之前有两个洞:
uploadId 是"凭证",谁拿到都能往别人的会话里塞分片 / 合并;产物挂载也没有归属校验,
拿到别人的 assetId 就能把自己的剧指向别人的素材。定价这边则缺创作者入口与试看秒数配置。

- 上传复用现有 /admin/media/upload/{init,chunk,complete},不重写;新增 GET /admin/media/upload/{uploadId}
  做断点续传回显(receivedChunks/totalChunks/percent/status,完成后带 url)
- 上传会话归属校验:chunk / complete / 进度查询都要求会话发起人,别人拿到 uploadId 也操作不了(403)
- 新增 attach 接口:产物必须① 是本次登录的人上传的(media_asset.create_by)② bizType=EPISODE 且状态 ready/uploaded
  ③ 分集属于自己这部剧且作品可编辑;挂载时把产物时长补到分集上
- 收费配置复用 PriceService:新增创作者侧 GET/PUT /admin/creator-center/dramas/{id}/prices;
  PriceSaveReqVO/PriceRespVO 补 previewSeconds(落在已有的 drama_episode.preview_seconds,不新造一套)
- 定价沿用"可编辑状态"口径:draft/offline 可改,已上线要先申请下线;dramaId 以路径为准,别人的 episodeId 被忽略
- 权限点 creator-center:upload / creator-center:price:update;创作者角色同时获得 cover:upload / episode:upload(复用上传接口)

实测:未登录请求付费集返回 {"accessType":"COIN_REQUIRED","unlockCoin":50,"previewSeconds":15},
免费集直接拿到签名播放地址 —— 创作者改的价格确实走到了 C 端付费判定。
2026-10-15 14:12:10 +08:00

216 lines
13 KiB
SQL
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.
-- FruitShorts 短剧服务 · 第一阶段增量数据(PostgreSQL 18)
--
-- 只放「代码已经实现、需要权限点才能调通」的数据。
-- 菜单(sys_menu)暂时不加:前端页面还没做,加了会出现指向不存在组件的动态路由,
-- 等 fruitshorts-web 的 App 用户管理页落地后再补菜单与角色菜单绑定。
--
-- 幂等:可重复执行。
-- 先对齐 IDENTITY 序列:历史种子数据用了显式 ID,序列没有前移,
-- 不对齐的话下面的插入会从 1 开始而撞主键。
DO $$
DECLARE
t text;
seq text;
maxid bigint;
BEGIN
FOREACH t IN ARRAY ARRAY['sys_permission','sys_menu','sys_role_permission','sys_role_menu',
'product','app_user','app_user_credential','orders','wallet'] LOOP
seq := pg_get_serial_sequence(t, 'id');
IF seq IS NULL THEN
CONTINUE;
END IF;
EXECUTE format('SELECT COALESCE(MAX(id), 0) FROM %I', t) INTO maxid;
IF maxid > 0 THEN
PERFORM setval(seq, maxid);
END IF;
END LOOP;
END $$;
INSERT INTO sys_permission (code, name, group_name, type, sort) VALUES
('app-user:list', '查看 App 用户', '用户管理', 'BUTTON', 101),
('app-user:update', '处置 App 用户', '用户管理', 'BUTTON', 102)
ON CONFLICT (code) DO NOTHING;
INSERT INTO sys_permission (code, name, group_name, type, sort) VALUES
('download:list', '查看下载授权', '用户管理', 'BUTTON', 103),
('download:revoke', '吊销下载授权', '用户管理', 'BUTTON', 104)
ON CONFLICT (code) DO NOTHING;
INSERT INTO sys_permission (code, name, group_name, type, sort) VALUES
('comment:list', '查看评论', '内容治理', 'BUTTON', 111),
('comment:audit', '审核评论', '内容治理', 'BUTTON', 112),
('report:list', '查看举报', '内容治理', 'BUTTON', 113),
('report:handle', '处理举报', '内容治理', 'BUTTON', 114),
('sensitive-word:list', '查看敏感词', '内容治理', 'BUTTON', 115),
('sensitive-word:update','维护敏感词', '内容治理', 'BUTTON', 116)
ON CONFLICT (code) DO NOTHING;
INSERT INTO sys_permission (code, name, group_name, type, sort) VALUES
('order:list', '查看订单', '订单与权益', 'BUTTON', 121),
('order:update', '补发/退款', '订单与权益', 'BUTTON', 122),
('entitlement:list', '查看权益', '订单与权益', 'BUTTON', 123),
('entitlement:update', '补发/回收权益', '订单与权益', 'BUTTON', 124),
('wallet:update', '人工补币', '订单与权益', 'BUTTON', 125),
('reconcile:run', '手动对账', '订单与权益', 'BUTTON', 126)
ON CONFLICT (code) DO NOTHING;
INSERT INTO sys_permission (code, name, group_name, type, sort) VALUES
('creator:list', '查看创作者', '创作者管理', 'BUTTON', 131),
('creator:audit', '创作者入驻审核','创作者管理', 'BUTTON', 132),
('submission:list', '查看内容提交', '创作者管理', 'BUTTON', 133),
('submission:audit', '内容提交审核', '创作者管理', 'BUTTON', 134),
('price:update', '配置分集价格', '内容管理', 'BUTTON', 135)
ON CONFLICT (code) DO NOTHING;
-- 看板权限:菜单在 3-4 补页面时一起加,避免菜单指向不存在的组件
INSERT INTO sys_permission (code, name, group_name, type, sort) VALUES
('dashboard:view', '查看运营大盘', '数据看板', 'BUTTON', 141)
ON CONFLICT (code) 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 = 'super_admin'
AND p.code IN ('app-user:list', 'app-user:update', 'download:list', 'download:revoke',
'comment:list', 'comment:audit', 'report:list', 'report:handle',
'sensitive-word:list', 'sensitive-word:update',
'order:list', 'order:update', 'entitlement:list', 'entitlement:update',
'wallet:update', 'reconcile:run',
'creator:list', 'creator:audit', 'submission:list', 'submission:audit',
'price:update', 'dashboard:view')
ON CONFLICT DO NOTHING;
-- ---------- 菜单:创作者管理 ----------
-- component 指向 src/views/creator/ 下的页面,前端按这个字符串动态加载。
INSERT INTO sys_menu (id, parent_id, title, route_path, route_name, component, redirect, icon,
permission_code, type, always_show, affix, sort) VALUES
(17, 0, '创作者管理', '/creator', 'CreatorCenter', '#', '/creator/creators', 'ep:user-filled', 'creator:list', 'CATALOG', 1, 0, 5),
(18, 17, '创作者入驻', 'creators', 'creators', 'views/creator/CreatorAuditView', '', '', 'creator:list', 'MENU', 0, 0, 1),
(19, 17, '内容审核', 'submissions', 'submissions', 'views/creator/SubmissionAuditView', '', '', 'submission:list', 'MENU', 0, 0, 2)
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_menu (role_id, menu_id)
SELECT r.id, m.id
FROM sys_role r
CROSS JOIN sys_menu m
WHERE r.code = 'super_admin'
AND m.id IN (17, 18, 19)
ON CONFLICT DO NOTHING;
-- 第一阶段新增的白名单数据(当前阶段都是免费内容,先把演示商品占位好)
INSERT INTO product (code, name, type, platform, store_product_id, coin_amount, bonus_coin,
price_amount, currency, region_codes, sort, status)
SELECT 'coin_100', '100 金币', 'COIN_PACK', 'ALL', '', 100, 0, 0.99, 'USD', 'ID', 1, 1
WHERE NOT EXISTS (SELECT 1 FROM product WHERE code = 'coin_100');
INSERT INTO product (code, name, type, platform, store_product_id, coin_amount, bonus_coin,
price_amount, currency, region_codes, sort, status)
SELECT 'coin_550', '550 金币', 'COIN_PACK', 'ALL', '', 500, 50, 4.99, 'USD', 'ID', 2, 1
WHERE NOT EXISTS (SELECT 1 FROM product WHERE code = 'coin_550');
INSERT INTO product (code, name, type, platform, store_product_id, coin_amount, bonus_coin,
price_amount, currency, region_codes, sort, status)
SELECT 'coin_1200', '1200 金币', 'COIN_PACK', 'ALL', '', 1000, 200, 9.99, 'USD', 'ID', 3, 1
WHERE NOT EXISTS (SELECT 1 FROM product WHERE code = 'coin_1200');
-- ---------- 菜单:用户管理 ----------
-- C 端用户与后台管理员是两套账号体系,单独挂一个一级目录,避免和管理员列表混淆。
-- component 指向 src/views/userManagement/AppUsersView.vue,前端按这个字符串动态加载。
INSERT INTO sys_menu (id, parent_id, title, route_path, route_name, component, redirect, icon,
permission_code, type, always_show, affix, sort) VALUES
(15, 0, '用户管理', '/user', 'UserCenter', '#', '/user/app-users', 'ep:user', 'app-user:list', 'CATALOG', 1, 0, 4),
(16, 15, 'App 用户', 'app-users', 'app-users', 'views/userManagement/AppUsersView', '', '', 'app-user:list', 'MENU', 0, 0, 1)
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_menu (role_id, menu_id)
SELECT r.id, m.id
FROM sys_role r
CROSS JOIN sys_menu m
WHERE r.code = 'super_admin'
AND m.id IN (15, 16)
ON CONFLICT DO NOTHING;
-- ---------- 菜单:内容治理 / 订单与权益 / 数据看板 ----------
-- 这三条菜单拖到 3-4 页面做出来才加:菜单先落库而组件不存在的话,
-- 前端动态路由会指向空组件、侧边栏直接报错(3-3 时就是这么刻意留空的)。
-- component 指向 src/views/governance|commerce|dashboard/ 下的页面,前端按字符串动态加载。
INSERT INTO sys_menu (id, parent_id, title, route_path, route_name, component, redirect, icon,
permission_code, type, always_show, affix, sort) VALUES
(20, 0, '内容治理', '/governance', 'Governance', '#', '/governance/content', 'ep:chat-dot-round', 'comment:list', 'CATALOG', 1, 0, 6),
(21, 20, '评论与举报', 'content', 'governance-content', 'views/governance/ContentGovernanceView', '', '', 'comment:list', 'MENU', 0, 0, 1),
(22, 0, '订单与权益', '/commerce', 'Commerce', '#', '/commerce/orders', 'ep:coin', 'order:list', 'CATALOG', 1, 0, 7),
(23, 22, '订单管理', 'orders', 'commerce-orders', 'views/commerce/OrderManagementView', '', '', 'order:list', 'MENU', 0, 0, 1),
(24, 0, '数据看板', '/board', 'BoardCenter', '#', '/board/overview', 'ep:data-line', 'dashboard:view', 'CATALOG', 1, 0, 8),
(25, 24, '运营大盘', 'overview', 'board-overview', 'views/dashboard/BusinessDashboardView', '', '', 'dashboard:view', 'MENU', 0, 0, 1)
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_menu (role_id, menu_id)
SELECT r.id, m.id
FROM sys_role r
CROSS JOIN sys_menu m
WHERE r.code = 'super_admin'
AND m.id IN (20, 21, 22, 23, 24, 25)
ON CONFLICT DO NOTHING;
-- ---------- 07 创作者中心:角色、权限点、授权 ----------
-- 创作者第一阶段直接登后台(复用 sys_admin_user + sys_role + sys_menu),
-- 这里只落"身份与权限";菜单等页面做出来再挂 —— 菜单先入库会让前端动态路由指向不存在的组件。
INSERT INTO sys_role_category (id, name, description, sort) VALUES
(4, '创作者', '外部内容创作者,只能管理自己的作品与数据', 4)
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
(5, '创作者', 'creator', 4, 5, '只能查看与编辑自己的作品与数据', 1, 0)
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, code = EXCLUDED.code,
description = EXCLUDED.description, category_id = EXCLUDED.category_id, level = EXCLUDED.level;
-- 权限点按批补:批 1 只有「进创作者中心」这一个,后面各批用到什么再幂等追加
INSERT INTO sys_permission (code, name, group_name, type, sort) VALUES
('creator-center:view', '访问创作者中心', '创作者中心', 'BUTTON', 150),
('creator-center:drama:edit', '创作者中心-编辑作品', '创作者中心', 'BUTTON', 151),
('creator-center:upload', '创作者中心-上传与挂载', '创作者中心', 'BUTTON', 152),
('creator-center:price:update', '创作者中心-配置分集价格', '创作者中心', 'BUTTON', 153)
ON CONFLICT (code) 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 = 'creator'
AND p.code IN ('creator-center:view', 'creator-center:drama:edit',
'creator-center:upload', 'creator-center:price:update')
ON CONFLICT DO NOTHING;
-- 超管也留一份,方便排查创作者视角的问题(超管没有绑定创作者主体,进 creator-center 仍会被挡)
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'
AND p.code IN ('creator-center:view', 'creator-center:drama:edit',
'creator-center:upload', 'creator-center:price:update')
ON CONFLICT DO NOTHING;
-- 媒资分片上传是复用后台已有的 /admin/media/upload/*,所以创作者角色要用现成的上传权限点,
-- 不再造一套 creator-center 的上传接口(上传会话归属与产物归属另在服务层校验)。
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 = 'creator'
AND p.code IN ('cover:upload', 'episode:upload')
ON CONFLICT DO NOTHING;