feat(commerce): 新增打赏任务中心与签到发奖
backend-ci / test (push) Failing after 4m53s

This commit is contained in:
Zhou Weidong committed 2026-11-09 10:32:53 +08:00
1 parent 2fee329002
commit 64eadf1446
52 files changed
+2590 -4

No files matched your search

+237
View File
@@ -0,0 +1,237 @@
-- Author: fruit.com.cn
-- 奖励中心:任务配置、签到、进度、发奖、活动与后台菜单(PostgreSQL,幂等)。
CREATE TABLE IF NOT EXISTS reward_task (
id BIGINT PRIMARY KEY,
code VARCHAR(64) NOT NULL,
section_code VARCHAR(32) NOT NULL DEFAULT 'NEWBIE',
type VARCHAR(32) NOT NULL,
verify_mode VARCHAR(16) NOT NULL DEFAULT 'SERVER',
target_value BIGINT NOT NULL DEFAULT 1,
reward_coin BIGINT NOT NULL DEFAULT 0,
cycle_type VARCHAR(16) NOT NULL DEFAULT 'ONCE',
cycle_limit INT NOT NULL DEFAULT 1,
action_type VARCHAR(32) NOT NULL DEFAULT 'NONE',
action_url VARCHAR(512) NOT NULL DEFAULT '',
icon_url VARCHAR(512) NOT NULL DEFAULT '',
title_id VARCHAR(128) NOT NULL DEFAULT '',
title_en VARCHAR(128) NOT NULL DEFAULT '',
title_zh VARCHAR(128) NOT NULL DEFAULT '',
subtitle_id VARCHAR(255) NOT NULL DEFAULT '',
subtitle_en VARCHAR(255) NOT NULL DEFAULT '',
subtitle_zh VARCHAR(255) NOT NULL DEFAULT '',
cta_id VARCHAR(64) NOT NULL DEFAULT '',
cta_en VARCHAR(64) NOT NULL DEFAULT '',
cta_zh VARCHAR(64) NOT NULL DEFAULT '',
section_title_id VARCHAR(64) NOT NULL DEFAULT '',
section_title_en VARCHAR(64) NOT NULL DEFAULT '',
section_title_zh VARCHAR(64) NOT NULL DEFAULT '',
config_json TEXT NOT NULL DEFAULT '',
sort INT NOT NULL DEFAULT 0,
status SMALLINT NOT NULL DEFAULT 0,
start_at TIMESTAMP(3),
end_at TIMESTAMP(3),
version INT NOT NULL DEFAULT 1,
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_reward_task_code ON reward_task (code) WHERE deleted = 0;
CREATE INDEX IF NOT EXISTS idx_reward_task_active
ON reward_task (status, section_code, sort, start_at, end_at) WHERE deleted = 0;
COMMENT ON TABLE reward_task IS '奖励任务配置;文案、奖励、周期、动作和上下线均由后台维护';
CREATE TABLE IF NOT EXISTS reward_task_progress (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
task_id BIGINT NOT NULL,
cycle_key VARCHAR(32) NOT NULL,
progress_value BIGINT NOT NULL DEFAULT 0,
target_value BIGINT NOT NULL DEFAULT 1,
status VARCHAR(16) NOT NULL DEFAULT 'IN_PROGRESS',
claim_seq INT NOT NULL DEFAULT 0,
completed_at TIMESTAMP(3),
claimed_at TIMESTAMP(3),
last_event_at TIMESTAMP(3),
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_reward_progress_cycle
ON reward_task_progress (user_id, task_id, cycle_key);
CREATE INDEX IF NOT EXISTS idx_reward_progress_user
ON reward_task_progress (user_id, status, updated_at DESC);
COMMENT ON TABLE reward_task_progress IS '用户在任务周期内的服务端进度与领取状态';
CREATE TABLE IF NOT EXISTS reward_checkin_rule_day (
id BIGINT PRIMARY KEY,
task_id BIGINT NOT NULL,
day_index INT NOT NULL,
reward_coin 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_reward_checkin_rule_day
ON reward_checkin_rule_day (task_id, day_index);
COMMENT ON TABLE reward_checkin_rule_day IS '签到任务每日奖励规则,修改后立即由奖励页读取';
CREATE TABLE IF NOT EXISTS reward_checkin_record (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
task_id BIGINT NOT NULL,
checkin_date DATE NOT NULL,
day_index INT NOT NULL,
streak INT NOT NULL DEFAULT 1,
reward_coin BIGINT NOT NULL DEFAULT 0,
grant_id BIGINT NOT NULL DEFAULT 0,
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX IF NOT EXISTS uk_reward_checkin_date
ON reward_checkin_record (user_id, task_id, checkin_date);
CREATE INDEX IF NOT EXISTS idx_reward_checkin_user
ON reward_checkin_record (user_id, task_id, checkin_date DESC);
COMMENT ON TABLE reward_checkin_record IS '每日签到事实;用户、任务、日期唯一,防止重复签到';
CREATE TABLE IF NOT EXISTS reward_grant (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
task_id BIGINT NOT NULL,
cycle_key VARCHAR(32) NOT NULL,
claim_seq INT NOT NULL DEFAULT 1,
amount BIGINT NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'PENDING',
idempotent_key VARCHAR(64) NOT NULL,
coin_ledger_id BIGINT NOT NULL DEFAULT 0,
source VARCHAR(16) NOT NULL DEFAULT 'APP',
operator_id BIGINT NOT NULL DEFAULT 0,
failure_reason VARCHAR(255) NOT NULL DEFAULT '',
granted_at TIMESTAMP(3),
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_reward_grant_cycle
ON reward_grant (user_id, task_id, cycle_key, claim_seq);
CREATE UNIQUE INDEX IF NOT EXISTS uk_reward_grant_idem ON reward_grant (idempotent_key);
CREATE INDEX IF NOT EXISTS idx_reward_grant_user ON reward_grant (user_id, created_at DESC);
COMMENT ON TABLE reward_grant IS '奖励发放单;必须关联 coin_ledger,支持幂等与审计';
CREATE TABLE IF NOT EXISTS reward_activity (
id BIGINT PRIMARY KEY,
code VARCHAR(64) NOT NULL,
cover_url VARCHAR(512) NOT NULL DEFAULT '',
action_type VARCHAR(32) NOT NULL DEFAULT 'NONE',
action_url VARCHAR(512) NOT NULL DEFAULT '',
title_id VARCHAR(128) NOT NULL DEFAULT '',
title_en VARCHAR(128) NOT NULL DEFAULT '',
title_zh VARCHAR(128) NOT NULL DEFAULT '',
summary_id VARCHAR(255) NOT NULL DEFAULT '',
summary_en VARCHAR(255) NOT NULL DEFAULT '',
summary_zh VARCHAR(255) NOT NULL DEFAULT '',
cta_id VARCHAR(64) NOT NULL DEFAULT '',
cta_en VARCHAR(64) NOT NULL DEFAULT '',
cta_zh VARCHAR(64) NOT NULL DEFAULT '',
sort INT NOT NULL DEFAULT 0,
status SMALLINT NOT NULL DEFAULT 0,
start_at TIMESTAMP(3),
end_at TIMESTAMP(3),
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_reward_activity_code
ON reward_activity (code) WHERE deleted = 0;
CREATE INDEX IF NOT EXISTS idx_reward_activity_active
ON reward_activity (status, sort, start_at, end_at) WHERE deleted = 0;
COMMENT ON TABLE reward_activity IS '奖励中心活动卡片;一期只负责展示与跳转';
CREATE TABLE IF NOT EXISTS reward_event_log (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
event_type VARCHAR(32) NOT NULL,
source_id VARCHAR(128) NOT NULL DEFAULT '',
event_value BIGINT NOT NULL DEFAULT 0,
idempotent_key VARCHAR(128) NOT NULL,
occurred_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX IF NOT EXISTS uk_reward_event_idem ON reward_event_log (idempotent_key);
CREATE INDEX IF NOT EXISTS idx_reward_event_user ON reward_event_log (user_id, occurred_at DESC);
COMMENT ON TABLE reward_event_log IS '服务端奖励事件去重日志,不接受客户端直接写入';
DROP TRIGGER IF EXISTS trg_reward_task_updated_at ON reward_task;
CREATE TRIGGER trg_reward_task_updated_at BEFORE UPDATE ON reward_task
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
DROP TRIGGER IF EXISTS trg_reward_task_progress_updated_at ON reward_task_progress;
CREATE TRIGGER trg_reward_task_progress_updated_at BEFORE UPDATE ON reward_task_progress
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
DROP TRIGGER IF EXISTS trg_reward_checkin_rule_day_updated_at ON reward_checkin_rule_day;
CREATE TRIGGER trg_reward_checkin_rule_day_updated_at BEFORE UPDATE ON reward_checkin_rule_day
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
DROP TRIGGER IF EXISTS trg_reward_grant_updated_at ON reward_grant;
CREATE TRIGGER trg_reward_grant_updated_at BEFORE UPDATE ON reward_grant
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
DROP TRIGGER IF EXISTS trg_reward_activity_updated_at ON reward_activity;
CREATE TRIGGER trg_reward_activity_updated_at BEFORE UPDATE ON reward_activity
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
INSERT INTO reward_task (
id, code, section_code, type, verify_mode, target_value, reward_coin, cycle_type,
action_type, title_id, title_en, title_zh, subtitle_id, subtitle_en, subtitle_zh,
cta_id, cta_en, cta_zh, section_title_id, section_title_en, section_title_zh, sort, status
) VALUES
(202611050001, 'daily_check_in', 'CHECK_IN', 'DAILY_CHECK_IN', 'SERVER', 1, 35, 'DAILY',
'NONE', 'Check-in harian', 'Daily check-in', '每日签到',
'Check-in setiap hari untuk mendapatkan koin', 'Check in every day to earn coins', '每日签到领取金币',
'Check-in', 'Check in', '签到', 'Hadiah check-in', 'Check-in rewards', '签到奖励', 10, 1),
(202611050002, 'watch_5_minutes', 'DAILY', 'WATCH_DURATION', 'SERVER', 300, 20, 'DAILY',
'OPEN_HOME', 'Tonton selama 5 menit', 'Watch for 5 minutes', '观看 5 分钟',
'Waktu menonton valid dihitung oleh server', 'Valid watch time is counted by the server', '有效观看时长由服务端统计',
'Mulai menonton', 'Watch now', '去观看', 'Tugas harian', 'Daily tasks', '每日任务', 20, 1),
(202611050003, 'watch_3_episodes', 'DAILY', 'WATCH_EPISODES', 'SERVER', 3, 25, 'DAILY',
'OPEN_HOME', 'Tonton 3 episode', 'Watch 3 episodes', '观看 3 集',
'Episode yang berbeda dihitung satu kali', 'Each distinct episode counts once', '不同分集只计一次',
'Mulai menonton', 'Watch now', '去观看', 'Tugas harian', 'Daily tasks', '每日任务', 30, 1),
(202611050004, 'complete_profile', 'NEWBIE', 'COMPLETE_PROFILE', 'SERVER', 1, 50, 'ONCE',
'OPEN_PROFILE', 'Lengkapi profil', 'Complete your profile', '完善个人资料',
'Tambahkan nama panggilan dan foto profil', 'Add a nickname and profile photo', '填写昵称并上传头像',
'Lengkapi', 'Complete', '去完成', 'Tugas pengguna baru', 'New user tasks', '新手任务', 40, 1)
ON CONFLICT DO NOTHING;
INSERT INTO reward_checkin_rule_day (id, task_id, day_index, reward_coin) VALUES
(202611050101, 202611050001, 1, 35),
(202611050102, 202611050001, 2, 35),
(202611050103, 202611050001, 3, 35),
(202611050104, 202611050001, 4, 35),
(202611050105, 202611050001, 5, 35),
(202611050106, 202611050001, 6, 55),
(202611050107, 202611050001, 7, 55)
ON CONFLICT DO NOTHING;
INSERT INTO sys_permission (id, code, name, group_name, type, sort) VALUES
(1170, 'reward:list', '查看奖励任务', '订单与权益', 'BUTTON', 170),
(1171, 'reward:update', '维护奖励任务', '订单与权益', 'BUTTON', 171),
(1172, 'reward:checkin', '配置签到规则', '订单与权益', 'BUTTON', 172),
(1173, 'reward:grant', '查看与补发奖励', '订单与权益', 'BUTTON', 173)
ON CONFLICT (code) DO UPDATE SET
name = EXCLUDED.name, group_name = EXCLUDED.group_name, type = EXCLUDED.type, sort = EXCLUDED.sort;
INSERT INTO sys_role_permission (id, role_id, permission_id)
SELECT (11700000 + r.id * 100 + p.id % 100), r.id, p.id
FROM sys_role r
CROSS JOIN sys_permission p
WHERE r.code = 'super_admin' AND p.code IN ('reward:list', 'reward:update', 'reward:checkin', 'reward:grant')
ON CONFLICT (role_id, permission_id) DO NOTHING;
INSERT INTO sys_menu (id, parent_id, title, route_path, route_name, component, redirect, icon,
permission_code, type, always_show, affix, sort) VALUES
(35, 22, '奖励任务', 'rewards', 'commerce-rewards',
'views/commerce/RewardTaskManagementView', '', '', 'reward:list', 'MENU', 0, 0, 3)
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,
permission_code = EXCLUDED.permission_code, type = EXCLUDED.type, sort = EXCLUDED.sort;
INSERT INTO sys_role_menu (id, role_id, menu_id)
SELECT (350000 + r.id), r.id, 35
FROM sys_role r
WHERE r.code = 'super_admin'
ON CONFLICT (role_id, menu_id) DO NOTHING;
+6
View File
@@ -1045,3 +1045,9 @@ CREATE INDEX IF NOT EXISTS idx_app_event_log_time ON app_event_log (event_time D
CREATE INDEX IF NOT EXISTS idx_app_event_log_user ON app_event_log (user_id);
CREATE INDEX IF NOT EXISTS idx_app_event_log_name_time ON app_event_log (event_name, event_time DESC);
COMMENT ON TABLE app_event_log IS '客户端埋点事件原始落库(匿名上报,供后续分析与漏斗)';
-- ============================================================
-- 十一、奖励中心(任务配置、签到、进度、发奖与活动)
-- ============================================================
-- 完整幂等建表、索引、触发器、种子任务与后台菜单见:
-- patch-2026-11-05-reward-center.sql。部署时执行该迁移,避免旧环境漏建奖励表。