增加推销模式
This commit is contained in:
356
doc_mall/consumer/sql/points_expiry_functions.sql
Normal file
356
doc_mall/consumer/sql/points_expiry_functions.sql
Normal 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()
|
||||
*/
|
||||
Reference in New Issue
Block a user