Files
akmon/doc_mall/database/mall_seo_security.sql
2026-01-20 08:04:15 +08:00

667 lines
22 KiB
PL/PgSQL

-- =====================================================================================
-- 商城系统 SEO 优化和安全策略脚本
-- 用途: 为商城系统添加 SEO 优化函数和 RLS 安全策略
-- 前置条件: 需要先执行 mall_migration.sql
-- =====================================================================================
-- =====================================================================================
-- 1. SEO 优化相关函数
-- =====================================================================================
-- 根据 cid 获取商品信息 (SEO 友好)
CREATE OR REPLACE FUNCTION public.get_product_by_cid(p_cid INTEGER)
RETURNS TABLE (
id UUID,
cid INTEGER,
name VARCHAR,
slug VARCHAR,
description TEXT,
main_image_url TEXT,
base_price DECIMAL,
rating_avg DECIMAL,
sale_count INTEGER,
category_name VARCHAR,
brand_name VARCHAR,
shop_name VARCHAR
) AS $$
BEGIN
RETURN QUERY
SELECT
p.id,
p.cid,
p.name,
p.slug,
p.description,
p.main_image_url,
p.base_price,
p.rating_avg,
p.sale_count,
c.name as category_name,
b.name as brand_name,
s.shop_name
FROM public.ml_products p
LEFT JOIN public.ml_categories c ON p.category_id = c.id
LEFT JOIN public.ml_brands b ON p.brand_id = b.id
LEFT JOIN public.ml_shops s ON p.merchant_id = s.merchant_id
WHERE p.cid = p_cid AND p.status = 1;
END;
$$ LANGUAGE plpgsql;
-- 根据 cid 获取分类信息 (SEO 友好)
CREATE OR REPLACE FUNCTION public.get_category_by_cid(p_cid INTEGER)
RETURNS TABLE (
id UUID,
cid INTEGER,
name VARCHAR,
slug VARCHAR,
description TEXT,
icon_url TEXT,
path TEXT[]
) AS $$
BEGIN
RETURN QUERY
SELECT
c.id,
c.cid,
c.name,
c.slug,
c.description,
c.icon_url,
c.path
FROM public.ml_categories c
WHERE c.cid = p_cid AND c.is_active = TRUE;
END;
$$ LANGUAGE plpgsql;
-- 根据 cid 获取品牌信息 (SEO 友好)
CREATE OR REPLACE FUNCTION public.get_brand_by_cid(p_cid INTEGER)
RETURNS TABLE (
id UUID,
cid INTEGER,
name VARCHAR,
logo_url TEXT,
description TEXT
) AS $$
BEGIN
RETURN QUERY
SELECT
b.id,
b.cid,
b.name,
b.logo_url,
b.description
FROM public.ml_brands b
WHERE b.cid = p_cid AND b.is_active = TRUE;
END;
$$ LANGUAGE plpgsql;
-- 根据 cid 获取店铺信息 (SEO 友好)
CREATE OR REPLACE FUNCTION public.get_shop_by_cid(p_cid INTEGER)
RETURNS TABLE (
id UUID,
cid INTEGER,
shop_name VARCHAR,
description TEXT,
shop_logo TEXT,
rating_avg DECIMAL,
product_count INTEGER
) AS $$
BEGIN
RETURN QUERY
SELECT
s.id,
s.cid,
s.shop_name,
s.description,
s.shop_logo,
s.rating_avg,
s.product_count
FROM public.ml_shops s
WHERE s.cid = p_cid AND s.status = 1;
END;
$$ LANGUAGE plpgsql;
-- 生成 SEO 友好的 URL 路径
CREATE OR REPLACE FUNCTION public.generate_seo_url(
p_type VARCHAR, -- 'product', 'category', 'brand', 'shop'
p_cid INTEGER,
p_slug VARCHAR DEFAULT NULL
)
RETURNS TEXT AS $$
DECLARE
url_path TEXT;
BEGIN
CASE p_type
WHEN 'product' THEN
url_path := '/product/' || p_cid;
IF p_slug IS NOT NULL THEN
url_path := url_path || '/' || p_slug;
END IF;
WHEN 'category' THEN
url_path := '/category/' || p_cid;
IF p_slug IS NOT NULL THEN
url_path := url_path || '/' || p_slug;
END IF;
WHEN 'brand' THEN
url_path := '/brand/' || p_cid;
IF p_slug IS NOT NULL THEN
url_path := url_path || '/' || p_slug;
END IF;
WHEN 'shop' THEN
url_path := '/shop/' || p_cid;
IF p_slug IS NOT NULL THEN
url_path := url_path || '/' || p_slug;
END IF;
ELSE
url_path := '/' || p_type || '/' || p_cid;
END CASE;
RETURN url_path;
END;
$$ LANGUAGE plpgsql;
-- 批量更新 slug 字段(用于现有数据)
CREATE OR REPLACE FUNCTION public.update_seo_slugs()
RETURNS VOID AS $$
BEGIN
-- 更新商品 slug
UPDATE public.ml_products
SET slug = LOWER(REGEXP_REPLACE(name, '[^a-zA-Z0-9\u4e00-\u9fa5]+', '-', 'g'))
WHERE slug IS NULL OR slug = '';
-- 更新分类 slug
UPDATE public.ml_categories
SET slug = LOWER(REGEXP_REPLACE(name, '[^a-zA-Z0-9\u4e00-\u9fa5]+', '-', 'g'))
WHERE slug IS NULL OR slug = '';
RAISE NOTICE 'SEO slugs updated successfully';
END;
$$ LANGUAGE plpgsql;
-- =====================================================================================
-- 2. 商业逻辑函数
-- =====================================================================================
-- 计算购物车总金额
CREATE OR REPLACE FUNCTION public.calculate_cart_total(p_user_id UUID)
RETURNS DECIMAL AS $$
DECLARE
total_amount DECIMAL := 0;
BEGIN
SELECT COALESCE(SUM(
CASE
WHEN s.id IS NOT NULL THEN s.price * c.quantity
ELSE p.base_price * c.quantity
END
), 0) INTO total_amount
FROM public.ml_shopping_cart c
LEFT JOIN public.ml_product_skus s ON c.sku_id = s.id
LEFT JOIN public.ml_products p ON c.product_id = p.id
WHERE c.user_id = p_user_id
AND c.selected = TRUE
AND p.status = 1
AND (s.id IS NULL OR s.status = 1);
RETURN total_amount;
END;
$$ LANGUAGE plpgsql;
-- 获取商品可用库存
CREATE OR REPLACE FUNCTION public.get_product_available_stock(p_product_id UUID, p_sku_id UUID DEFAULT NULL)
RETURNS INTEGER AS $$
DECLARE
stock_count INTEGER := 0;
BEGIN
IF p_sku_id IS NOT NULL THEN
-- 获取特定SKU库存
SELECT COALESCE(stock, 0) INTO stock_count
FROM public.ml_product_skus
WHERE id = p_sku_id AND product_id = p_product_id AND status = 1;
ELSE
-- 获取商品总库存
SELECT COALESCE(available_stock, 0) INTO stock_count
FROM public.ml_products
WHERE id = p_product_id AND status = 1;
END IF;
RETURN stock_count;
END;
$$ LANGUAGE plpgsql;
-- 商品库存更新触发器函数
CREATE OR REPLACE FUNCTION public.update_product_stock()
RETURNS TRIGGER AS $$
BEGIN
-- 更新商品总库存
IF TG_OP = 'DELETE' THEN
UPDATE public.ml_products
SET
total_stock = (
SELECT COALESCE(SUM(stock), 0)
FROM public.ml_product_skus
WHERE product_id = OLD.product_id AND status = 1
),
available_stock = (
SELECT COALESCE(SUM(stock), 0)
FROM public.ml_product_skus
WHERE product_id = OLD.product_id AND status = 1
)
WHERE id = OLD.product_id;
RETURN OLD;
ELSE
UPDATE public.ml_products
SET
total_stock = (
SELECT COALESCE(SUM(stock), 0)
FROM public.ml_product_skus
WHERE product_id = NEW.product_id AND status = 1
),
available_stock = (
SELECT COALESCE(SUM(stock), 0)
FROM public.ml_product_skus
WHERE product_id = NEW.product_id AND status = 1
)
WHERE id = NEW.product_id;
RETURN NEW;
END IF;
END;
$$ LANGUAGE plpgsql;
-- 订单状态变更时的处理
CREATE OR REPLACE FUNCTION public.handle_order_status_change()
RETURNS TRIGGER AS $$
BEGIN
-- 如果订单状态变为已付款
IF NEW.order_status = 2 AND (OLD.order_status IS NULL OR OLD.order_status = 1) THEN
NEW.paid_at = NOW();
END IF;
-- 如果订单状态变为已发货
IF NEW.order_status = 3 AND OLD.order_status = 2 THEN
NEW.shipped_at = NOW();
END IF;
-- 如果订单状态变为已完成
IF NEW.order_status = 4 AND OLD.order_status = 3 THEN
NEW.delivered_at = NOW();
NEW.completed_at = NOW();
-- 更新商品销量
UPDATE public.ml_products
SET sale_count = sale_count + (
SELECT SUM(quantity)
FROM public.ml_order_items
WHERE order_id = NEW.id
)
WHERE id IN (
SELECT product_id
FROM public.ml_order_items
WHERE order_id = NEW.id
);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 创建库存更新触发器
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'trigger_ml_update_product_stock') THEN
CREATE TRIGGER trigger_ml_update_product_stock
AFTER INSERT OR UPDATE OR DELETE ON public.ml_product_skus
FOR EACH ROW EXECUTE FUNCTION public.update_product_stock();
END IF;
END $$;
-- 创建订单状态变更触发器
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'trigger_ml_order_status_change') THEN
CREATE TRIGGER trigger_ml_order_status_change
BEFORE UPDATE ON public.ml_orders
FOR EACH ROW EXECUTE FUNCTION public.handle_order_status_change();
END IF;
END $$;
-- =====================================================================================
-- 3. 创建详细视图
-- =====================================================================================
-- 商品详情视图
CREATE OR REPLACE VIEW public.ml_products_detail_view AS
SELECT
p.*,
c.cid as category_cid,
c.name as category_name,
c.path as category_path,
b.cid as brand_cid,
b.name as brand_name,
s.cid as shop_cid,
s.shop_name,
u.username as merchant_name,
CASE
WHEN p.status = 1 THEN '上架'
WHEN p.status = 2 THEN '下架'
WHEN p.status = 3 THEN '草稿'
WHEN p.status = 4 THEN '删除'
ELSE '未知'
END as status_name
FROM public.ml_products p
LEFT JOIN public.ml_categories c ON p.category_id = c.id
LEFT JOIN public.ml_brands b ON p.brand_id = b.id
LEFT JOIN public.ml_shops s ON p.merchant_id = s.merchant_id
LEFT JOIN public.ak_users u ON p.merchant_id = u.id;
COMMENT ON VIEW public.ml_products_detail_view IS '商品详情视图';
-- 订单详情视图
CREATE OR REPLACE VIEW public.ml_orders_detail_view AS
SELECT
o.*,
u.username as customer_name,
u.phone as customer_phone,
m.username as merchant_name,
s.shop_name,
CASE
WHEN o.order_status = 1 THEN '待付款'
WHEN o.order_status = 2 THEN '待发货'
WHEN o.order_status = 3 THEN '待收货'
WHEN o.order_status = 4 THEN '已完成'
WHEN o.order_status = 5 THEN '已取消'
WHEN o.order_status = 6 THEN '退款中'
WHEN o.order_status = 7 THEN '已退款'
ELSE '未知'
END as order_status_name,
CASE
WHEN o.payment_status = 1 THEN '未付款'
WHEN o.payment_status = 2 THEN '已付款'
WHEN o.payment_status = 3 THEN '部分退款'
WHEN o.payment_status = 4 THEN '全额退款'
ELSE '未知'
END as payment_status_name
FROM public.ml_orders o
LEFT JOIN public.ak_users u ON o.user_id = u.id
LEFT JOIN public.ak_users m ON o.merchant_id = m.id
LEFT JOIN public.ml_shops s ON o.merchant_id = s.merchant_id;
COMMENT ON VIEW public.ml_orders_detail_view IS '订单详情视图';
-- =====================================================================================
-- 4. RLS (Row Level Security) 策略
-- =====================================================================================
-- 启用 RLS
ALTER TABLE public.ml_user_profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.ml_user_addresses ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.ml_shopping_cart ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.ml_user_favorites ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.ml_browse_history ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.ml_user_coupons ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.ml_orders ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.ml_products ENABLE ROW LEVEL SECURITY;
-- 用户档案策略:用户只能访问自己的数据
DO $$
BEGIN
-- 删除可能存在的策略
DROP POLICY IF EXISTS ml_user_profiles_select_policy ON public.ml_user_profiles;
DROP POLICY IF EXISTS ml_user_profiles_insert_policy ON public.ml_user_profiles;
DROP POLICY IF EXISTS ml_user_profiles_update_policy ON public.ml_user_profiles;
DROP POLICY IF EXISTS ml_user_profiles_delete_policy ON public.ml_user_profiles;
-- 创建新策略
CREATE POLICY ml_user_profiles_select_policy ON public.ml_user_profiles
FOR SELECT USING (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
CREATE POLICY ml_user_profiles_insert_policy ON public.ml_user_profiles
FOR INSERT WITH CHECK (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
CREATE POLICY ml_user_profiles_update_policy ON public.ml_user_profiles
FOR UPDATE USING (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
CREATE POLICY ml_user_profiles_delete_policy ON public.ml_user_profiles
FOR DELETE USING (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
END $$;
-- 用户地址策略
DO $$
BEGIN
DROP POLICY IF EXISTS ml_user_addresses_select_policy ON public.ml_user_addresses;
DROP POLICY IF EXISTS ml_user_addresses_insert_policy ON public.ml_user_addresses;
DROP POLICY IF EXISTS ml_user_addresses_update_policy ON public.ml_user_addresses;
DROP POLICY IF EXISTS ml_user_addresses_delete_policy ON public.ml_user_addresses;
CREATE POLICY ml_user_addresses_select_policy ON public.ml_user_addresses
FOR SELECT USING (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
CREATE POLICY ml_user_addresses_insert_policy ON public.ml_user_addresses
FOR INSERT WITH CHECK (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
CREATE POLICY ml_user_addresses_update_policy ON public.ml_user_addresses
FOR UPDATE USING (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
CREATE POLICY ml_user_addresses_delete_policy ON public.ml_user_addresses
FOR DELETE USING (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
END $$;
-- 购物车策略
DO $$
BEGIN
DROP POLICY IF EXISTS ml_shopping_cart_select_policy ON public.ml_shopping_cart;
DROP POLICY IF EXISTS ml_shopping_cart_insert_policy ON public.ml_shopping_cart;
DROP POLICY IF EXISTS ml_shopping_cart_update_policy ON public.ml_shopping_cart;
DROP POLICY IF EXISTS ml_shopping_cart_delete_policy ON public.ml_shopping_cart;
CREATE POLICY ml_shopping_cart_select_policy ON public.ml_shopping_cart
FOR SELECT USING (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
CREATE POLICY ml_shopping_cart_insert_policy ON public.ml_shopping_cart
FOR INSERT WITH CHECK (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
CREATE POLICY ml_shopping_cart_update_policy ON public.ml_shopping_cart
FOR UPDATE USING (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
CREATE POLICY ml_shopping_cart_delete_policy ON public.ml_shopping_cart
FOR DELETE USING (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
END $$;
-- 订单策略:用户可以查看自己的订单,商家可以查看自己店铺的订单
DO $$
BEGIN
DROP POLICY IF EXISTS ml_orders_select_policy ON public.ml_orders;
DROP POLICY IF EXISTS ml_orders_insert_policy ON public.ml_orders;
DROP POLICY IF EXISTS ml_orders_update_policy ON public.ml_orders;
DROP POLICY IF EXISTS ml_orders_delete_policy ON public.ml_orders;
CREATE POLICY ml_orders_select_policy ON public.ml_orders
FOR SELECT USING (
auth.uid() IN (
SELECT auth_id FROM public.ak_users WHERE id IN (user_id, merchant_id)
)
);
CREATE POLICY ml_orders_insert_policy ON public.ml_orders
FOR INSERT WITH CHECK (
auth.uid() IN (
SELECT auth_id FROM public.ak_users WHERE id IN (user_id, merchant_id)
)
);
CREATE POLICY ml_orders_update_policy ON public.ml_orders
FOR UPDATE USING (
auth.uid() IN (
SELECT auth_id FROM public.ak_users WHERE id IN (user_id, merchant_id)
)
);
CREATE POLICY ml_orders_delete_policy ON public.ml_orders
FOR DELETE USING (
auth.uid() IN (
SELECT auth_id FROM public.ak_users WHERE id IN (user_id, merchant_id)
)
);
END $$;
-- 商品策略:所有人可以查看上架商品,商家只能管理自己的商品
DO $$
BEGIN
DROP POLICY IF EXISTS ml_products_select_policy ON public.ml_products;
DROP POLICY IF EXISTS ml_products_insert_policy ON public.ml_products;
DROP POLICY IF EXISTS ml_products_update_policy ON public.ml_products;
DROP POLICY IF EXISTS ml_products_delete_policy ON public.ml_products;
CREATE POLICY ml_products_select_policy ON public.ml_products
FOR SELECT USING (status = 1);
CREATE POLICY ml_products_insert_policy ON public.ml_products
FOR INSERT WITH CHECK (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = merchant_id)
);
CREATE POLICY ml_products_update_policy ON public.ml_products
FOR UPDATE USING (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = merchant_id)
);
CREATE POLICY ml_products_delete_policy ON public.ml_products
FOR DELETE USING (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = merchant_id)
);
END $$;
-- 收藏策略
DO $$
BEGIN
DROP POLICY IF EXISTS ml_user_favorites_select_policy ON public.ml_user_favorites;
DROP POLICY IF EXISTS ml_user_favorites_insert_policy ON public.ml_user_favorites;
DROP POLICY IF EXISTS ml_user_favorites_update_policy ON public.ml_user_favorites;
DROP POLICY IF EXISTS ml_user_favorites_delete_policy ON public.ml_user_favorites;
CREATE POLICY ml_user_favorites_select_policy ON public.ml_user_favorites
FOR SELECT USING (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
CREATE POLICY ml_user_favorites_insert_policy ON public.ml_user_favorites
FOR INSERT WITH CHECK (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
CREATE POLICY ml_user_favorites_update_policy ON public.ml_user_favorites
FOR UPDATE USING (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
CREATE POLICY ml_user_favorites_delete_policy ON public.ml_user_favorites
FOR DELETE USING (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
END $$;
-- 浏览历史策略
DO $$
BEGIN
DROP POLICY IF EXISTS ml_browse_history_select_policy ON public.ml_browse_history;
DROP POLICY IF EXISTS ml_browse_history_insert_policy ON public.ml_browse_history;
DROP POLICY IF EXISTS ml_browse_history_update_policy ON public.ml_browse_history;
DROP POLICY IF EXISTS ml_browse_history_delete_policy ON public.ml_browse_history;
CREATE POLICY ml_browse_history_select_policy ON public.ml_browse_history
FOR SELECT USING (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
CREATE POLICY ml_browse_history_insert_policy ON public.ml_browse_history
FOR INSERT WITH CHECK (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
CREATE POLICY ml_browse_history_update_policy ON public.ml_browse_history
FOR UPDATE USING (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
CREATE POLICY ml_browse_history_delete_policy ON public.ml_browse_history
FOR DELETE USING (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
END $$;
-- 优惠券策略
DO $$
BEGIN
DROP POLICY IF EXISTS ml_user_coupons_select_policy ON public.ml_user_coupons;
DROP POLICY IF EXISTS ml_user_coupons_insert_policy ON public.ml_user_coupons;
DROP POLICY IF EXISTS ml_user_coupons_update_policy ON public.ml_user_coupons;
DROP POLICY IF EXISTS ml_user_coupons_delete_policy ON public.ml_user_coupons;
CREATE POLICY ml_user_coupons_select_policy ON public.ml_user_coupons
FOR SELECT USING (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
CREATE POLICY ml_user_coupons_insert_policy ON public.ml_user_coupons
FOR INSERT WITH CHECK (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
CREATE POLICY ml_user_coupons_update_policy ON public.ml_user_coupons
FOR UPDATE USING (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
CREATE POLICY ml_user_coupons_delete_policy ON public.ml_user_coupons
FOR DELETE USING (
auth.uid() = (SELECT auth_id FROM public.ak_users WHERE id = user_id)
);
END $$;
-- =====================================================================================
-- 5. 完成提示
-- =====================================================================================
DO $$
BEGIN
RAISE NOTICE '=======================================================';
RAISE NOTICE 'SEO 优化和安全策略配置完成!';
RAISE NOTICE '=======================================================';
RAISE NOTICE '已创建 SEO 函数: 6 个';
RAISE NOTICE '已创建业务函数: 4 个';
RAISE NOTICE '已创建详细视图: 2 个';
RAISE NOTICE '已配置 RLS 策略: 8 个表';
RAISE NOTICE '已创建库存和订单触发器';
RAISE NOTICE '=======================================================';
RAISE NOTICE '功能说明:';
RAISE NOTICE '- SEO 友好的 URL 生成';
RAISE NOTICE '- CID 基础的数据查询';
RAISE NOTICE '- 自动库存管理';
RAISE NOTICE '- 订单状态自动更新';
RAISE NOTICE '- 用户数据安全隔离';
RAISE NOTICE '=======================================================';
END $$;