增加推销模式

This commit is contained in:
cyh666666
2026-03-06 17:30:50 +08:00
parent 3b0e397714
commit 7b5801a72b
39 changed files with 9831 additions and 34 deletions

View File

@@ -0,0 +1,356 @@
-- =====================================================
-- 积分过期提醒功能 - 数据库脚本
-- 创建日期: 2026-03-06
-- =====================================================
-- =====================================================
-- 一、更新现有表结构
-- =====================================================
-- 为积分记录表添加过期时间字段(如果不存在)
ALTER TABLE ml_point_records ADD COLUMN IF NOT EXISTS expires_at TIMESTAMPTZ;
ALTER TABLE ml_point_records ADD COLUMN IF NOT EXISTS is_expired BOOLEAN DEFAULT FALSE;
ALTER TABLE ml_point_records ADD COLUMN IF NOT EXISTS expired_at TIMESTAMPTZ;
-- 为积分记录表添加余额字段(记录变动后余额)
ALTER TABLE ml_point_records ADD COLUMN IF NOT EXISTS balance_after INT;
-- 添加索引
CREATE INDEX IF NOT EXISTS idx_point_records_expires_at ON ml_point_records(expires_at);
CREATE INDEX IF NOT EXISTS idx_point_records_is_expired ON ml_point_records(is_expired);
-- 更新用户积分表,添加即将过期积分字段
ALTER TABLE ml_user_points ADD COLUMN IF NOT EXISTS expiring_points INT DEFAULT 0;
ALTER TABLE ml_user_points ADD COLUMN IF NOT EXISTS expiring_date DATE;
COMMENT ON COLUMN ml_point_records.expires_at IS '积分过期时间NULL表示永不过期';
COMMENT ON COLUMN ml_point_records.is_expired IS '是否已过期';
COMMENT ON COLUMN ml_point_records.expired_at IS '实际过期时间';
COMMENT ON COLUMN ml_user_points.expiring_points IS '即将过期积分30天内';
COMMENT ON COLUMN ml_user_points.expiring_date IS '最近过期日期';
-- =====================================================
-- 二、创建积分过期通知表
-- =====================================================
CREATE TABLE IF NOT EXISTS ml_point_expiry_notifications (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID NOT NULL REFERENCES auth.users(id),
points_expiring INT NOT NULL,
expiry_date DATE NOT NULL,
notification_type VARCHAR(20) NOT NULL, -- '7days', '3days', '1day'
is_sent BOOLEAN DEFAULT FALSE,
sent_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_expiry_notifications_user_id ON ml_point_expiry_notifications(user_id);
CREATE INDEX IF NOT EXISTS idx_expiry_notifications_is_sent ON ml_point_expiry_notifications(is_sent);
COMMENT ON TABLE ml_point_expiry_notifications IS '积分过期通知记录表';
-- =====================================================
-- 三、创建积分过期配置表
-- =====================================================
CREATE TABLE IF NOT EXISTS ml_point_expiry_config (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
config_key VARCHAR(50) NOT NULL UNIQUE,
config_value JSONB NOT NULL,
description TEXT,
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- 初始化配置
INSERT INTO ml_point_expiry_config (config_key, config_value, description) VALUES
('expiry_period', '{"days": 365}', '积分有效期NULL或0表示永不过期'),
('notification_days', '{"days": [7, 3, 1]}', '过期前多少天发送提醒'),
('enabled', '{"value": true}', '是否启用积分过期功能')
ON CONFLICT (config_key) DO NOTHING;
COMMENT ON TABLE ml_point_expiry_config IS '积分过期配置表';
-- =====================================================
-- 四、创建定时任务函数
-- =====================================================
-- 1. 为新获取的积分设置过期时间
CREATE OR REPLACE FUNCTION set_points_expiry()
RETURNS void AS $$
DECLARE
expiry_days INT;
enabled BOOLEAN;
BEGIN
-- 检查是否启用过期功能
SELECT (config_value->>'value')::boolean INTO enabled
FROM ml_point_expiry_config
WHERE config_key = 'enabled';
IF NOT enabled THEN
RETURN;
END IF;
-- 获取过期天数
SELECT (config_value->>'days')::int INTO expiry_days
FROM ml_point_expiry_config
WHERE config_key = 'expiry_period';
-- 为未设置过期时间且未过期的积分记录设置过期时间
-- 只处理正积分(获取的积分)
UPDATE ml_point_records
SET expires_at = created_at + (expiry_days || ' days')::interval
WHERE expires_at IS NULL
AND is_expired = FALSE
AND points > 0;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 2. 处理过期积分
CREATE OR REPLACE FUNCTION process_expired_points()
RETURNS void AS $$
DECLARE
expired_record RECORD;
user_points_rec RECORD;
BEGIN
-- 查找所有已过期但未标记的积分记录
FOR expired_record IN
SELECT id, user_id, points, expires_at
FROM ml_point_records
WHERE expires_at IS NOT NULL
AND expires_at < NOW()
AND is_expired = FALSE
AND points > 0
LOOP
-- 标记为已过期
UPDATE ml_point_records
SET is_expired = TRUE, expired_at = NOW()
WHERE id = expired_record.id;
-- 扣减用户积分
UPDATE ml_user_points
SET points = GREATEST(0, points - expired_record.points),
updated_at = NOW()
WHERE user_id = expired_record.user_id;
-- 记录过期扣减
INSERT INTO ml_point_records (user_id, points, type, description, created_at)
VALUES (
expired_record.user_id,
-expired_record.points,
'expire',
'积分过期自动扣除',
NOW()
);
END LOOP;
-- 更新用户即将过期积分统计
FOR user_points_rec IN SELECT user_id FROM ml_user_points LOOP
UPDATE ml_user_points up
SET expiring_points = (
SELECT COALESCE(SUM(points), 0)
FROM ml_point_records
WHERE user_id = up.user_id
AND points > 0
AND is_expired = FALSE
AND expires_at IS NOT NULL
AND expires_at < NOW() + INTERVAL '30 days'
),
expiring_date = (
SELECT MIN(expires_at)::date
FROM ml_point_records
WHERE user_id = up.user_id
AND points > 0
AND is_expired = FALSE
AND expires_at IS NOT NULL
AND expires_at < NOW() + INTERVAL '30 days'
)
WHERE up.user_id = user_points_rec.user_id;
END LOOP;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 3. 生成过期提醒通知
CREATE OR REPLACE FUNCTION generate_expiry_notifications()
RETURNS void AS $$
DECLARE
notification_days INT[];
days_before INT;
expiring_record RECORD;
BEGIN
-- 获取提醒天数配置
SELECT ARRAY(SELECT jsonb_array_elements_text(config_value->'days')::int)
INTO notification_days
FROM ml_point_expiry_config
WHERE config_key = 'notification_days';
-- 遍历每个提醒天数
FOREACH days_before IN ARRAY notification_days LOOP
-- 查找即将过期的积分
FOR expiring_record IN
SELECT
user_id,
SUM(points) as total_points,
MIN(expires_at)::date as expiry_date
FROM ml_point_records
WHERE points > 0
AND is_expired = FALSE
AND expires_at IS NOT NULL
AND expires_at::date = (CURRENT_DATE + days_before)
GROUP BY user_id
LOOP
-- 检查是否已发送过该类型通知
IF NOT EXISTS (
SELECT 1 FROM ml_point_expiry_notifications
WHERE user_id = expiring_record.user_id
AND expiry_date = expiring_record.expiry_date
AND notification_type = days_before || 'days'
) THEN
-- 插入通知记录
INSERT INTO ml_point_expiry_notifications
(user_id, points_expiring, expiry_date, notification_type)
VALUES (
expiring_record.user_id,
expiring_record.total_points,
expiring_record.expiry_date,
days_before || 'days'
);
END IF;
END LOOP;
END LOOP;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 4. 获取用户即将过期积分(供前端调用)
CREATE OR REPLACE FUNCTION get_user_expiring_points(p_user_id UUID)
RETURNS TABLE(
expiring_points INT,
expiring_date DATE,
expiring_details JSONB
) AS $$
BEGIN
RETURN QUERY
SELECT
COALESCE(SUM(pr.points), 0)::INT as expiring_points,
MIN(pr.expires_at)::date as expiring_date,
jsonb_agg(
jsonb_build_object(
'points', pr.points,
'expires_at', pr.expires_at,
'description', pr.description,
'created_at', pr.created_at
)
) as expiring_details
FROM ml_point_records pr
WHERE pr.user_id = p_user_id
AND pr.points > 0
AND pr.is_expired = FALSE
AND pr.expires_at IS NOT NULL
AND pr.expires_at < NOW() + INTERVAL '30 days';
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 5. 综合定时任务(每天执行一次)
CREATE OR REPLACE FUNCTION daily_points_maintenance()
RETURNS void AS $$
BEGIN
-- 设置新积分的过期时间
PERFORM set_points_expiry();
-- 处理已过期积分
PERFORM process_expired_points();
-- 生成过期提醒
PERFORM generate_expiry_notifications();
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- =====================================================
-- 五、配置 pg_cron 定时任务(如果可用)
-- =====================================================
-- 注意:需要先在 Supabase 中启用 pg_cron 扩展
-- 可以在 Supabase Dashboard -> Database -> Extensions 中启用
-- 每天凌晨2点执行积分维护任务
-- SELECT cron.schedule(
-- 'daily-points-maintenance',
-- '0 2 * * *',
-- 'SELECT daily_points_maintenance()'
-- );
-- 每小时检查并发送过期提醒(可选)
-- SELECT cron.schedule(
-- 'hourly-expiry-notification-check',
-- '0 * * * *',
-- 'SELECT generate_expiry_notifications()'
-- );
-- =====================================================
-- 六、RLS 策略
-- =====================================================
ALTER TABLE ml_point_expiry_notifications ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view own expiry notifications"
ON ml_point_expiry_notifications FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
-- =====================================================
-- 七、创建视图方便查询
-- =====================================================
-- 用户积分概览视图
CREATE OR REPLACE VIEW ml_user_points_overview AS
SELECT
up.user_id,
up.points as current_points,
up.total_earned,
COALESCE(up.expiring_points, 0) as expiring_points,
up.expiring_date,
(SELECT COUNT(*) FROM ml_signin_records WHERE user_id = up.user_id) as total_signin_days,
(SELECT MAX(continuous_days) FROM ml_signin_records WHERE user_id = up.user_id) as max_continuous_days
FROM ml_user_points up;
-- =====================================================
-- 八、测试数据(可选)
-- =====================================================
-- 插入一些即将过期的测试积分记录
-- INSERT INTO ml_point_records (user_id, points, type, description, expires_at, created_at)
-- VALUES
-- ('YOUR_USER_ID', 50, 'signin', '测试即将过期积分', NOW() + INTERVAL '5 days', NOW() - INTERVAL '360 days'),
-- ('YOUR_USER_ID', 30, 'shopping', '测试购物积分', NOW() + INTERVAL '10 days', NOW() - INTERVAL '355 days');
-- =====================================================
-- 九、使用说明
-- =====================================================
/*
使用方法:
1. 手动执行定时任务:
SELECT daily_points_maintenance();
2. 查询用户即将过期积分:
SELECT * FROM get_user_expiring_points('用户ID');
3. 修改积分有效期配置:
UPDATE ml_point_expiry_config
SET config_value = '{"days": 180}', updated_at = NOW()
WHERE config_key = 'expiry_period';
4. 禁用积分过期功能:
UPDATE ml_point_expiry_config
SET config_value = '{"value": false}', updated_at = NOW()
WHERE config_key = 'enabled';
5. 修改提醒天数:
UPDATE ml_point_expiry_config
SET config_value = '{"days": [14, 7, 3, 1]}', updated_at = NOW()
WHERE config_key = 'notification_days';
注意:
- 如果 Supabase 项目启用了 pg_cron可以取消注释定时任务配置
- 如果没有 pg_cron需要通过外部定时任务如 Supabase Edge Functions调用 daily_points_maintenance()
*/