-- ===================================================================================== -- 商城系统 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 $$;